Merge "Fix notigication group background color" into sc-dev
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 175fb38..65b2511 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -22,7 +22,7 @@
 
 strings_lint_hook = ${REPO_ROOT}/frameworks/base/tools/stringslint/stringslint_sha.sh ${PREUPLOAD_COMMIT}
 
-hidden_api_txt_checksorted_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
+hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
 
 hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
 
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 23dc720..bc3f131 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -96,6 +96,8 @@
     ],
     api_levels_annotations_enabled: false,
     filter_packages: packages_to_document,
+    defaults_visibility: ["//visibility:private"],
+    visibility: ["//frameworks/base/api"],
 }
 
 /////////////////////////////////////////////////////////////////////
@@ -352,6 +354,8 @@
         tag: ".jar",
         dest: "android-non-updatable.jar",
     },
+    defaults_visibility: ["//visibility:private"],
+    visibility: ["//visibility:private"],
 }
 
 java_library_static {
@@ -405,6 +409,8 @@
     system_modules: "none",
     java_version: "1.8",
     compile_dex: true,
+    defaults_visibility: ["//visibility:private"],
+    visibility: ["//visibility:public"],
 }
 
 java_defaults {
@@ -417,6 +423,7 @@
         tag: ".jar",
         dest: "android.jar",
     },
+    defaults_visibility: ["//frameworks/base/services"],
 }
 
 java_library_static {
@@ -516,6 +523,7 @@
         "metalava-manual",
     ],
     args: priv_apps,
+    visibility: ["//visibility:private"],
 }
 
 java_library_static {
@@ -525,4 +533,5 @@
     srcs: [
         ":hwbinder-stubs-docs",
     ],
+    visibility: ["//visibility:public"],
 }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
index 35cea3e..6c62426 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
@@ -26,6 +26,7 @@
 
 import java.util.Collections;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Provides results for AppSearch batch operations which encompass multiple documents.
@@ -180,7 +181,7 @@
         public Builder<KeyType, ValueType> setSuccess(
                 @NonNull KeyType key, @Nullable ValueType result) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(key);
+            Objects.requireNonNull(key);
             return setResult(key, AppSearchResult.newSuccessfulResult(result));
         }
 
@@ -198,7 +199,7 @@
                 @AppSearchResult.ResultCode int resultCode,
                 @Nullable String errorMessage) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(key);
+            Objects.requireNonNull(key);
             return setResult(key, AppSearchResult.newFailedResult(resultCode, errorMessage));
         }
 
@@ -214,8 +215,8 @@
         public Builder<KeyType, ValueType> setResult(
                 @NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(key);
-            Preconditions.checkNotNull(result);
+            Objects.requireNonNull(key);
+            Objects.requireNonNull(result);
             if (result.isSuccess()) {
                 mSuccesses.put(key, result.getResultValue());
                 mFailures.remove(key);
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index ac91bdb..c85c4c3 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -19,9 +19,9 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.appsearch.exceptions.AppSearchException;
 import android.app.appsearch.util.SchemaMigrationUtil;
 import android.os.Bundle;
-import android.os.ParcelableException;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.ArrayMap;
@@ -274,12 +274,14 @@
             mService.putDocuments(mPackageName, mDatabaseName, documentBundles, mUserId,
                     /*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
                     new IAppSearchBatchResultCallback.Stub() {
+                        @Override
                         public void onResult(AppSearchBatchResult result) {
                             executor.execute(() -> callback.onResult(result));
                         }
 
-                        public void onSystemError(ParcelableException exception) {
-                            executor.execute(() -> callback.onSystemError(exception.getCause()));
+                        @Override
+                        public void onSystemError(AppSearchResult result) {
+                            executor.execute(() -> sendSystemErrorToCallback(result, callback));
                         }
                     });
             mIsMutated = true;
@@ -321,6 +323,7 @@
                     request.getProjectionsInternal(),
                     mUserId,
                     new IAppSearchBatchResultCallback.Stub() {
+                        @Override
                         public void onResult(AppSearchBatchResult result) {
                             executor.execute(() -> {
                                 AppSearchBatchResult.Builder<String, GenericDocument>
@@ -359,8 +362,9 @@
                             });
                         }
 
-                        public void onSystemError(ParcelableException exception) {
-                            executor.execute(() -> callback.onSystemError(exception.getCause()));
+                        @Override
+                        public void onSystemError(AppSearchResult result) {
+                            executor.execute(() -> sendSystemErrorToCallback(result, callback));
                         }
                     });
         } catch (RemoteException e) {
@@ -515,12 +519,14 @@
             mService.removeByUri(mPackageName, mDatabaseName, request.getNamespace(),
                     new ArrayList<>(request.getUris()), mUserId,
                     new IAppSearchBatchResultCallback.Stub() {
+                        @Override
                         public void onResult(AppSearchBatchResult result) {
                             executor.execute(() -> callback.onResult(result));
                         }
 
-                        public void onSystemError(ParcelableException exception) {
-                            executor.execute(() -> callback.onSystemError(exception.getCause()));
+                        @Override
+                        public void onSystemError(AppSearchResult result) {
+                            executor.execute(() -> sendSystemErrorToCallback(result, callback));
                         }
                     });
             mIsMutated = true;
@@ -817,4 +823,21 @@
             }
         });
     }
+
+    /**
+     * Calls {@link BatchResultCallback#onSystemError} with a throwable derived from the given
+     * failed {@link AppSearchResult}.
+     *
+     * <p>The {@link AppSearchResult} generally comes from
+     * {@link IAppSearchBatchResultCallback#onSystemError}.
+     *
+     * <p>This method should be called from the callback executor thread.
+     */
+    private void sendSystemErrorToCallback(
+            @NonNull AppSearchResult<?> failedResult, @NonNull BatchResultCallback<?, ?> callback) {
+        Preconditions.checkArgument(!failedResult.isSuccess());
+        Throwable throwable = new AppSearchException(
+                failedResult.getResultCode(), failedResult.getErrorMessage());
+        callback.onSystemError(throwable);
+    }
 }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java b/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java
index 49049b6..28f8a7a 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java
@@ -36,13 +36,23 @@
     void onResult(@NonNull AppSearchBatchResult<KeyType, ValueType> result);
 
     /**
-     * Called when a system error occurred.
+     * Called when a system error occurs.
      *
-     * @param throwable The cause throwable.
+     * <p>This method is only called the infrastructure is fundamentally broken or unavailable, such
+     * that none of the requests could be started. For example, it will be called if the AppSearch
+     * service unexpectedly fails to initialize and can't be recovered by any means, or if
+     * communicating to the server over Binder fails (e.g. system service crashed or device is
+     * rebooting).
+     *
+     * <p>The error is not expected to be recoverable and there is no specific recommended action
+     * other than displaying a permanent message to the user.
+     *
+     * <p>Normal errors that are caused by invalid inputs or recoverable/retriable situations
+     * are reported associated with the input that caused them via the {@link #onResult} method.
+     *
+     * @param throwable an exception describing the system error
      */
     default void onSystemError(@Nullable Throwable throwable) {
-        if (throwable != null) {
-            throw new RuntimeException(throwable);
-        }
+        throw new RuntimeException("Unrecoverable system error", throwable);
     }
 }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl
index b1bbd18..64b331e 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl
@@ -16,10 +16,10 @@
 package android.app.appsearch;
 
 import android.app.appsearch.AppSearchBatchResult;
-import android.os.ParcelableException;
+import android.app.appsearch.AppSearchResult;
 
 /** {@hide} */
 oneway interface IAppSearchBatchResultCallback {
     void onResult(in AppSearchBatchResult result);
-    void onSystemError(in ParcelableException exception);
-}
\ No newline at end of file
+    void onSystemError(in AppSearchResult result);
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl
index 27729a5..299c9957 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl
@@ -16,9 +16,8 @@
 package android.app.appsearch;
 
 import android.app.appsearch.AppSearchResult;
-import android.os.ParcelableException;
 
 /** {@hide} */
 oneway interface IAppSearchResultCallback {
     void onResult(in AppSearchResult result);
-}
\ No newline at end of file
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index c3f1978..f6f5c98 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -44,7 +44,6 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
-import android.os.ParcelableException;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -55,7 +54,6 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.appsearch.external.localstorage.AppSearchImpl;
@@ -76,7 +74,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
-import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
@@ -95,7 +93,7 @@
     // mutate requests will need to gain write lock and query requests need to gain read lock.
     private static final Executor EXECUTOR = new ThreadPoolExecutor(/*corePoolSize=*/1,
             Runtime.getRuntime().availableProcessors(), /*keepAliveTime*/ 60L, TimeUnit.SECONDS,
-            new SynchronousQueue<Runnable>());
+            new LinkedBlockingQueue<>());
 
     // Cache of unlocked user ids so we don't have to query UserManager service each time. The
     // "locked" suffix refers to the fact that access to the field should be locked; unrelated to
@@ -182,10 +180,10 @@
                 int schemaVersion,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(schemaBundles);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(schemaBundles);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -231,9 +229,9 @@
                 @NonNull String databaseName,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -258,9 +256,9 @@
                 @NonNull String databaseName,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -286,10 +284,10 @@
                 @UserIdInt int userId,
                 @ElapsedRealtimeLong long binderCallStartTimeMillis,
                 @NonNull IAppSearchBatchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(documentBundles);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(documentBundles);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -360,11 +358,11 @@
                 @NonNull Map<String, List<String>> typePropertyPaths,
                 @UserIdInt int userId,
                 @NonNull IAppSearchBatchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(namespace);
-            Preconditions.checkNotNull(uris);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(namespace);
+            Objects.requireNonNull(uris);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -405,11 +403,11 @@
                 @NonNull Bundle searchSpecBundle,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(queryExpression);
-            Preconditions.checkNotNull(searchSpecBundle);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(queryExpression);
+            Objects.requireNonNull(searchSpecBundle);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -440,10 +438,10 @@
                 @NonNull Bundle searchSpecBundle,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(queryExpression);
-            Preconditions.checkNotNull(searchSpecBundle);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(queryExpression);
+            Objects.requireNonNull(searchSpecBundle);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -472,7 +470,7 @@
                 long nextPageToken,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             // TODO(b/162450968) check nextPageToken is being advanced by the same uid as originally
@@ -645,10 +643,10 @@
                 @NonNull List<String> uris,
                 @UserIdInt int userId,
                 @NonNull IAppSearchBatchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(uris);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(uris);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -685,11 +683,11 @@
                 @NonNull Bundle searchSpecBundle,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(queryExpression);
-            Preconditions.checkNotNull(searchSpecBundle);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(queryExpression);
+            Objects.requireNonNull(searchSpecBundle);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -718,9 +716,9 @@
                 @NonNull String databaseName,
                 @UserIdInt int userId,
                 @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(packageName);
-            Preconditions.checkNotNull(databaseName);
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(packageName);
+            Objects.requireNonNull(databaseName);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -758,7 +756,7 @@
 
         @Override
         public void initialize(@UserIdInt int userId, @NonNull IAppSearchResultCallback callback) {
-            Preconditions.checkNotNull(callback);
+            Objects.requireNonNull(callback);
             int callingUid = Binder.getCallingUid();
             int callingUserId = handleIncomingUser(userId, callingUid);
             EXECUTOR.execute(() -> {
@@ -789,7 +787,7 @@
         }
 
         private void verifyCallingPackage(int callingUid, @NonNull String callingPackage) {
-            Preconditions.checkNotNull(callingPackage);
+            Objects.requireNonNull(callingPackage);
             if (mPackageManagerInternal.getPackageUid(
                             callingPackage, /*flags=*/ 0, UserHandle.getUserId(callingUid))
                     != callingUid) {
@@ -837,13 +835,12 @@
         /**
          * Invokes the {@link IAppSearchBatchResultCallback} with an unexpected internal throwable.
          *
-         * <p>The throwable is converted to {@link ParcelableException}.
+         * <p>The throwable is converted to {@link AppSearchResult}.
          */
         private void invokeCallbackOnError(
-                IAppSearchBatchResultCallback callback, Throwable throwable) {
+                @NonNull IAppSearchBatchResultCallback callback, @NonNull Throwable throwable) {
             try {
-                //TODO(b/175067650) verify ParcelableException could propagate throwable correctly.
-                callback.onSystemError(new ParcelableException(throwable));
+                callback.onSystemError(throwableToFailedResult(throwable));
             } catch (RemoteException e) {
                 Log.e(TAG, "Unable to send error to the callback", e);
             }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
index c1b8294..4de52fb 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
@@ -35,7 +35,6 @@
 import android.util.ArraySet;
 import android.util.Log;
 
-import com.android.internal.util.Preconditions;
 import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
 
 import java.util.ArrayList;
@@ -43,6 +42,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -333,9 +333,9 @@
             @NonNull Set<String> schemasNotPlatformSurfaceable,
             @NonNull Map<String, List<PackageIdentifier>> schemasPackageAccessible)
             throws AppSearchException {
-        Preconditions.checkNotNull(prefix);
-        Preconditions.checkNotNull(schemasNotPlatformSurfaceable);
-        Preconditions.checkNotNull(schemasPackageAccessible);
+        Objects.requireNonNull(prefix);
+        Objects.requireNonNull(schemasNotPlatformSurfaceable);
+        Objects.requireNonNull(schemasPackageAccessible);
 
         // Persist the document
         GenericDocument.Builder<?> visibilityDocument =
@@ -383,8 +383,8 @@
     /** Checks whether {@code prefixedSchema} can be searched over by the {@code callerUid}. */
     public boolean isSchemaSearchableByCaller(
             @NonNull String prefix, @NonNull String prefixedSchema, int callerUid) {
-        Preconditions.checkNotNull(prefix);
-        Preconditions.checkNotNull(prefixedSchema);
+        Objects.requireNonNull(prefix);
+        Objects.requireNonNull(prefixedSchema);
 
         // We compare appIds here rather than direct uids because the package's uid may change based
         // on the user that's running.
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
index 1c04d99..731ab35 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
@@ -29,7 +29,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.Preconditions;
 import com.android.server.appsearch.external.localstorage.AppSearchLogger;
 import com.android.server.appsearch.external.localstorage.stats.CallStats;
 import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
@@ -38,6 +37,7 @@
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Random;
 
 /**
@@ -162,15 +162,15 @@
      * Westworld constructor
      */
     public PlatformLogger(@NonNull Context context, int userId, @NonNull Config config) {
-        mContext = Preconditions.checkNotNull(context);
-        mConfig = Preconditions.checkNotNull(config);
+        mContext = Objects.requireNonNull(context);
+        mConfig = Objects.requireNonNull(config);
         mUserId = userId;
     }
 
     /** Logs {@link CallStats}. */
     @Override
     public void logStats(@NonNull CallStats stats) {
-        Preconditions.checkNotNull(stats);
+        Objects.requireNonNull(stats);
         synchronized (mLock) {
             if (shouldLogForTypeLocked(stats.getCallType())) {
                 logStatsImplLocked(stats);
@@ -181,7 +181,7 @@
     /** Logs {@link PutDocumentStats}. */
     @Override
     public void logStats(@NonNull PutDocumentStats stats) {
-        Preconditions.checkNotNull(stats);
+        Objects.requireNonNull(stats);
         synchronized (mLock) {
             if (shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)) {
                 logStatsImplLocked(stats);
@@ -197,7 +197,7 @@
     */
     public int removeCachedUidForPackage(@NonNull String packageName) {
         // TODO(b/173532925) This needs to be called when we get PACKAGE_REMOVED intent
-        Preconditions.checkNotNull(packageName);
+        Objects.requireNonNull(packageName);
         synchronized (mLock) {
             Integer uid = mPackageUidCacheLocked.remove(packageName);
             return uid != null ? uid : Process.INVALID_UID;
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
index f0de496..6193367 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
@@ -40,11 +40,11 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
-import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.SettableFuture;
 
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -81,8 +81,8 @@
 
     private AppSearchSessionShimImpl(
             @NonNull AppSearchSession session, @NonNull ExecutorService executor) {
-        mAppSearchSession = Preconditions.checkNotNull(session);
-        mExecutor = Preconditions.checkNotNull(executor);
+        mAppSearchSession = Objects.requireNonNull(session);
+        mExecutor = Objects.requireNonNull(executor);
     }
 
     @Override
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
index 5042ce0..c35849d 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
@@ -30,11 +30,11 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
-import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.SettableFuture;
 
+import java.util.Objects;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
@@ -69,8 +69,8 @@
 
     private GlobalSearchSessionShimImpl(
             @NonNull GlobalSearchSession session, @NonNull ExecutorService executor) {
-        mGlobalSearchSession = Preconditions.checkNotNull(session);
-        mExecutor = Preconditions.checkNotNull(executor);
+        mGlobalSearchSession = Objects.requireNonNull(session);
+        mExecutor = Objects.requireNonNull(executor);
     }
 
     @NonNull
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java
index 5f26e8c..72078f8 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java
@@ -22,12 +22,12 @@
 import android.app.appsearch.SearchResults;
 import android.app.appsearch.SearchResultsShim;
 
-import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.SettableFuture;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -40,8 +40,8 @@
     private final SearchResults mSearchResults;
 
     SearchResultsShimImpl(@NonNull SearchResults searchResults, @NonNull Executor executor) {
-        mExecutor = Preconditions.checkNotNull(executor);
-        mSearchResults = Preconditions.checkNotNull(searchResults);
+        mExecutor = Objects.requireNonNull(executor);
+        mSearchResults = Objects.requireNonNull(searchResults);
     }
 
     @NonNull
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 31da201..03d9a96 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -1684,17 +1684,13 @@
             }
         }
 
-        if ((flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) {
-            // Do not support windows for idle-until alarms.
-            windowLength = AlarmManager.WINDOW_EXACT;
-        }
-
         // Snap the window to reasonable limits.
         if (windowLength > INTERVAL_DAY) {
             Slog.w(TAG, "Window length " + windowLength
                     + "ms suspiciously long; limiting to 1 day");
             windowLength = INTERVAL_DAY;
-        } else if (windowLength > 0 && windowLength < mConstants.MIN_WINDOW) {
+        } else if (windowLength > 0 && windowLength < mConstants.MIN_WINDOW
+                && (flags & FLAG_PRIORITIZE) == 0) {
             if (CompatChanges.isChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS,
                     callingPackage, UserHandle.getUserHandleForUid(callingUid))) {
                 Slog.w(TAG, "Window length " + windowLength + "ms too short; expanding to "
@@ -1741,7 +1737,7 @@
         final long triggerElapsed = (nominalTrigger > minTrigger) ? nominalTrigger : minTrigger;
 
         final long maxElapsed;
-        if (windowLength == AlarmManager.WINDOW_EXACT) {
+        if (windowLength == 0) {
             maxElapsed = triggerElapsed;
         } else if (windowLength < 0) {
             maxElapsed = maxTriggerTime(nowElapsed, triggerElapsed, interval);
@@ -2145,17 +2141,63 @@
                         + " does not belong to the calling uid " + callingUid);
             }
 
-            final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0;
-            final boolean exact = (windowLength == AlarmManager.WINDOW_EXACT);
+            // Repeating alarms must use PendingIntent, not direct listener
+            if (interval != 0 && directReceiver != null) {
+                throw new IllegalArgumentException("Repeating alarms cannot use AlarmReceivers");
+            }
 
-            // make sure the caller is allowed to use the requested kind of alarm, and also
+            if (workSource != null) {
+                getContext().enforcePermission(
+                        android.Manifest.permission.UPDATE_DEVICE_STATS,
+                        Binder.getCallingPid(), callingUid, "AlarmManager.set");
+            }
+
+            if ((flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+                // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm
+                // manager when to come out of idle mode, which is only for DeviceIdleController.
+                if (callingUid != Process.SYSTEM_UID) {
+                    // TODO (b/169463012): Throw instead of tolerating this mistake.
+                    flags &= ~AlarmManager.FLAG_IDLE_UNTIL;
+                } else {
+                    // Do not support windows for idle-until alarms.
+                    windowLength = 0;
+                }
+            }
+
+            // Remove flags reserved for the service, we will apply those later as appropriate.
+            flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED
+                    | FLAG_ALLOW_WHILE_IDLE_COMPAT);
+
+            // If this alarm is for an alarm clock, then it must be exact and we will
+            // use it to wake early from idle if needed.
+            if (alarmClock != null) {
+                flags |= FLAG_WAKE_FROM_IDLE;
+                windowLength = 0;
+
+            // If the caller is a core system component or on the user's allowlist, and not calling
+            // to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED.
+            // This means we will allow these alarms to go off as normal even while idle, with no
+            // timing restrictions.
+            } else if (workSource == null && (UserHandle.isCore(callingUid)
+                    || UserHandle.isSameApp(callingUid, mSystemUiUid)
+                    || ((mAppStateTracker != null)
+                    && mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) {
+                flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
+                flags &= ~(FLAG_ALLOW_WHILE_IDLE | FLAG_PRIORITIZE);
+            }
+
+            final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0;
+            final boolean exact = (windowLength == 0);
+
+            // Make sure the caller is allowed to use the requested kind of alarm, and also
             // decide what quota and broadcast options to use.
             Bundle idleOptions = null;
             if ((flags & FLAG_PRIORITIZE) != 0) {
                 getContext().enforcePermission(
                         Manifest.permission.SCHEDULE_PRIORITIZED_ALARM,
                         Binder.getCallingPid(), callingUid, "AlarmManager.setPrioritized");
-                flags &= ~(FLAG_ALLOW_WHILE_IDLE | FLAG_ALLOW_WHILE_IDLE_COMPAT);
+                // The API doesn't allow using both together.
+                flags &= ~FLAG_ALLOW_WHILE_IDLE;
             } else if (exact || allowWhileIdle) {
                 final boolean needsPermission;
                 boolean lowerQuota;
@@ -2193,55 +2235,11 @@
                 }
             }
 
-            // Repeating alarms must use PendingIntent, not direct listener
-            if (interval != 0) {
-                if (directReceiver != null) {
-                    throw new IllegalArgumentException(
-                            "Repeating alarms cannot use AlarmReceivers");
-                }
-            }
-
-            if (workSource != null) {
-                getContext().enforcePermission(
-                        android.Manifest.permission.UPDATE_DEVICE_STATS,
-                        Binder.getCallingPid(), callingUid, "AlarmManager.set");
-            }
-
-            // No incoming callers can request either WAKE_FROM_IDLE or
-            // ALLOW_WHILE_IDLE_UNRESTRICTED -- we will apply those later as appropriate.
-            flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);
-
-            // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm
-            // manager when to come out of idle mode, which is only for DeviceIdleController.
-            if (callingUid != Process.SYSTEM_UID) {
-                flags &= ~AlarmManager.FLAG_IDLE_UNTIL;
-            }
-
             // If this is an exact time alarm, then it can't be batched with other alarms.
-            if (windowLength == AlarmManager.WINDOW_EXACT) {
+            if (exact) {
                 flags |= AlarmManager.FLAG_STANDALONE;
             }
 
-            // If this alarm is for an alarm clock, then it must be standalone and we will
-            // use it to wake early from idle if needed.
-            if (alarmClock != null) {
-                flags |= FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
-
-            // If the caller is a core system component or on the user's whitelist, and not calling
-            // to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED.
-            // This means we will allow these alarms to go off as normal even while idle, with no
-            // timing restrictions.
-            } else if (workSource == null && (UserHandle.isCore(callingUid)
-                    || UserHandle.isSameApp(callingUid, mSystemUiUid)
-                    || ((mAppStateTracker != null)
-                        && mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) {
-                flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
-                flags &= ~FLAG_ALLOW_WHILE_IDLE;
-                flags &= ~FLAG_ALLOW_WHILE_IDLE_COMPAT;
-                flags &= ~FLAG_PRIORITIZE;
-                idleOptions = null;
-            }
-
             setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
                     listenerTag, flags, workSource, alarmClock, callingUid, callingPackage,
                     idleOptions);
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java
index c37d2c3..9b1b066 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java
@@ -16,7 +16,6 @@
 
 package com.android.server.alarm;
 
-import static com.android.server.alarm.AlarmManagerService.TAG;
 import static com.android.server.alarm.AlarmManagerService.dumpAlarmList;
 import static com.android.server.alarm.AlarmManagerService.isTimeTickAlarm;
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index d94d638..131783f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.job;
 
+import static com.android.server.job.JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
 import android.annotation.IntDef;
@@ -39,7 +40,9 @@
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Pair;
+import android.util.Pools;
 import android.util.Slog;
+import android.util.SparseArrayMap;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
 import android.util.TimeUtils;
@@ -60,6 +63,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * This class decides, given the various configuration and the system status, which jobs can start
@@ -73,6 +77,12 @@
     private static final String KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS =
             CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms";
     private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000;
+    private static final String KEY_PKG_CONCURRENCY_LIMIT_EJ =
+            CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_ej";
+    private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_EJ = 3;
+    private static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR =
+            CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular";
+    private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = MAX_JOB_CONTEXTS_COUNT / 2;
 
     /**
      * Set of possible execution types that a job can have. The actual type(s) of a job are based
@@ -165,8 +175,6 @@
     private long mLastScreenOnRealtime;
     private long mLastScreenOffRealtime;
 
-    private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
-
     private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON =
             new WorkConfigLimitsPerMemoryTrimLevel(
                     new WorkTypeConfig("screen_on_normal", 11,
@@ -274,11 +282,28 @@
 
     private final WorkCountTracker mWorkCountTracker = new WorkCountTracker();
 
+    private final Pools.Pool<PackageStats> mPkgStatsPool =
+            new Pools.SimplePool<>(MAX_JOB_CONTEXTS_COUNT);
+
+    private final SparseArrayMap<String, PackageStats> mActivePkgStats = new SparseArrayMap<>();
+
     private WorkTypeConfig mWorkTypeConfig = CONFIG_LIMITS_SCREEN_OFF.normal;
 
     /** Wait for this long after screen off before adjusting the job concurrency. */
     private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS;
 
+    /**
+     * The maximum number of expedited jobs a single userId-package can have running simultaneously.
+     * TOP apps are not limited.
+     */
+    private long mPkgConcurrencyLimitEj = DEFAULT_PKG_CONCURRENCY_LIMIT_EJ;
+
+    /**
+     * The maximum number of regular jobs a single userId-package can have running simultaneously.
+     * TOP apps are not limited.
+     */
+    private long mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR;
+
     /** Current memory trim level. */
     private int mLastMemoryTrimLevel;
 
@@ -286,6 +311,9 @@
     private long mNextSystemStateRefreshTime;
     private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000;
 
+    private final Consumer<PackageStats> mPackageStatsStagingCountClearer =
+            PackageStats::resetStagedCount;
+
     private final StatLogger mStatLogger = new StatLogger(new String[]{
             "assignJobsToContexts",
             "refreshSystemState",
@@ -330,6 +358,21 @@
         onInteractiveStateChanged(mPowerManager.isInteractive());
     }
 
+    @GuardedBy("mLock")
+    void onAppRemovedLocked(String pkgName, int uid) {
+        final PackageStats packageStats = mActivePkgStats.get(UserHandle.getUserId(uid), pkgName);
+        if (packageStats != null) {
+            if (packageStats.numRunningEj > 0 || packageStats.numRunningRegular > 0) {
+                // Don't delete the object just yet. We'll remove it in onJobCompleted() when the
+                // jobs officially stop running.
+                Slog.w(TAG,
+                        pkgName + "(" + uid + ") marked as removed before jobs stopped running");
+            } else {
+                mActivePkgStats.delete(UserHandle.getUserId(uid), pkgName);
+            }
+        }
+    }
+
     void onUserRemoved(int userId) {
         mGracePeriodObserver.onUserRemoved(userId);
     }
@@ -557,6 +600,7 @@
             boolean startingJob = false;
             int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
             String preemptReason = null;
+            final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending);
             // TODO(141645789): rewrite this to look at empty contexts first so we don't
             // unnecessarily preempt
             for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) {
@@ -566,7 +610,7 @@
                     final boolean preferredUidOkay = (preferredUid == nextPending.getUid())
                             || (preferredUid == JobServiceContext.NO_PREFERRED_UID);
 
-                    if (preferredUidOkay && workType != WORK_TYPE_NONE) {
+                    if (preferredUidOkay && pkgConcurrencyOkay && workType != WORK_TYPE_NONE) {
                         // This slot is free, and we haven't yet hit the limit on
                         // concurrent jobs...  we can just throw the job in to here.
                         selectedContextId = j;
@@ -579,9 +623,11 @@
                     continue;
                 }
                 if (job.getUid() != nextPending.getUid()) {
-                    // Maybe stop the job if it has had its day in the sun.
+                    // Maybe stop the job if it has had its day in the sun. Don't let a different
+                    // app preempt jobs started for TOP apps though.
                     final String reason = shouldStopJobReason[j];
-                    if (reason != null && mWorkCountTracker.canJobStart(allWorkTypes,
+                    if (job.lastEvaluatedPriority < JobInfo.PRIORITY_TOP_APP
+                            && reason != null && mWorkCountTracker.canJobStart(allWorkTypes,
                             activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) {
                         // Right now, the way the code is set up, we don't need to explicitly
                         // assign the new job to this context since we'll reassign when the
@@ -608,23 +654,27 @@
                     // actually starting a job, so don't set startingJob.
                 }
             }
+            final PackageStats packageStats = getPkgStatsLocked(
+                    nextPending.getSourceUserId(), nextPending.getSourcePackageName());
             if (selectedContextId != -1) {
                 contextIdToJobMap[selectedContextId] = nextPending;
                 slotChanged[selectedContextId] = true;
                 preemptReasonCodeForContext[selectedContextId] = preemptReasonCode;
                 preemptReasonForContext[selectedContextId] = preemptReason;
+                packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob());
             }
             if (startingJob) {
                 // Increase the counters when we're going to start a job.
                 workTypeForContext[selectedContextId] = workType;
                 mWorkCountTracker.stageJob(workType, allWorkTypes);
+                mActivePkgStats.add(
+                        nextPending.getSourceUserId(), nextPending.getSourcePackageName(),
+                        packageStats);
             }
         }
         if (DEBUG) {
             Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
-        }
 
-        if (DEBUG) {
             Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString());
         }
 
@@ -660,6 +710,7 @@
             }
         }
         mWorkCountTracker.resetStagingCount();
+        mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
         noteConcurrency();
     }
 
@@ -702,18 +753,66 @@
     }
 
     @GuardedBy("mLock")
+    @NonNull
+    private PackageStats getPkgStatsLocked(int userId, @NonNull String packageName) {
+        PackageStats packageStats = mActivePkgStats.get(userId, packageName);
+        if (packageStats == null) {
+            packageStats = mPkgStatsPool.acquire();
+            if (packageStats == null) {
+                packageStats = new PackageStats();
+            }
+            packageStats.setPackage(userId, packageName);
+        }
+        return packageStats;
+    }
+
+    @GuardedBy("mLock")
+    private boolean isPkgConcurrencyLimitedLocked(@NonNull JobStatus jobStatus) {
+        if (jobStatus.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
+            // Don't restrict top apps' concurrency. The work type limits will make sure
+            // background jobs have slots to run if the system has resources.
+            return false;
+        }
+        // Use < instead of <= as that gives us a little wiggle room in case a new job comes
+        // along very shortly.
+        if (mService.mPendingJobs.size() + mRunningJobs.size() < mWorkTypeConfig.getMaxTotal()) {
+            // Don't artificially limit a single package if we don't even have enough jobs to use
+            // the maximum number of slots. We'll preempt the job later if we need the slot.
+            return false;
+        }
+        final PackageStats packageStats =
+                mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
+        if (packageStats == null) {
+            // No currently running jobs.
+            return false;
+        }
+        if (jobStatus.shouldTreatAsExpeditedJob()) {
+            return packageStats.numRunningEj + packageStats.numStagedEj < mPkgConcurrencyLimitEj;
+        } else {
+            return packageStats.numRunningRegular + packageStats.numStagedRegular
+                    < mPkgConcurrencyLimitRegular;
+        }
+    }
+
+    @GuardedBy("mLock")
     private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus,
             @WorkType final int workType) {
         final List<StateController> controllers = mService.mControllers;
         for (int ic = 0; ic < controllers.size(); ic++) {
             controllers.get(ic).prepareForExecutionLocked(jobStatus);
         }
+        final PackageStats packageStats =
+                getPkgStatsLocked(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
+        packageStats.adjustStagedCount(false, jobStatus.shouldTreatAsExpeditedJob());
         if (!worker.executeRunnableJob(jobStatus, workType)) {
             Slog.e(TAG, "Error executing " + jobStatus);
             mWorkCountTracker.onStagedJobFailed(workType);
         } else {
             mRunningJobs.add(jobStatus);
             mWorkCountTracker.onJobStarted(workType);
+            packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob());
+            mActivePkgStats.add(
+                    jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), packageStats);
         }
         final List<JobStatus> pendingJobs = mService.mPendingJobs;
         if (pendingJobs.remove(jobStatus)) {
@@ -726,6 +825,18 @@
             @WorkType final int workType) {
         mWorkCountTracker.onJobFinished(workType);
         mRunningJobs.remove(jobStatus);
+        final PackageStats packageStats =
+                mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
+        if (packageStats == null) {
+            Slog.wtf(TAG, "Running job didn't have an active PackageStats object");
+        } else {
+            packageStats.adjustRunningCount(false, jobStatus.startedAsExpeditedJob);
+            if (packageStats.numRunningEj <= 0 && packageStats.numRunningRegular <= 0) {
+                mActivePkgStats.delete(packageStats.userId, packageStats.packageName);
+                mPkgStatsPool.release(packageStats);
+            }
+        }
+
         final List<JobStatus> pendingJobs = mService.mPendingJobs;
         if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) {
             updateCounterConfigLocked();
@@ -746,7 +857,7 @@
                 }
 
                 if (worker.getPreferredUid() != nextPending.getUid()) {
-                    if (backupJob == null) {
+                    if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) {
                         int allWorkTypes = getJobWorkTypes(nextPending);
                         int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
                         if (workAsType != WORK_TYPE_NONE) {
@@ -758,6 +869,13 @@
                     continue;
                 }
 
+                // Only bypass the concurrent limit if we had preempted the job due to a higher
+                // priority job.
+                if (nextPending.lastEvaluatedPriority <= jobStatus.lastEvaluatedPriority
+                        && isPkgConcurrencyLimitedLocked(nextPending)) {
+                    continue;
+                }
+
                 if (highestPriorityJob == null
                         || highestPriorityJob.lastEvaluatedPriority
                         < nextPending.lastEvaluatedPriority) {
@@ -815,6 +933,10 @@
                     continue;
                 }
 
+                if (isPkgConcurrencyLimitedLocked(nextPending)) {
+                    continue;
+                }
+
                 final int allWorkTypes = getJobWorkTypes(nextPending);
                 final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
                 if (workAsType == WORK_TYPE_NONE) {
@@ -979,8 +1101,16 @@
         CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties);
         CONFIG_LIMITS_SCREEN_OFF.low.update(properties);
         CONFIG_LIMITS_SCREEN_OFF.critical.update(properties);
+
+        // Package concurrency limits must in the range [1, MAX_JOB_CONTEXTS_COUNT].
+        mPkgConcurrencyLimitEj = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT,
+                properties.getInt(KEY_PKG_CONCURRENCY_LIMIT_EJ, DEFAULT_PKG_CONCURRENCY_LIMIT_EJ)));
+        mPkgConcurrencyLimitRegular = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT,
+                properties.getInt(
+                        KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR)));
     }
 
+    @GuardedBy("mLock")
     public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
         pw.println("Concurrency:");
 
@@ -989,6 +1119,8 @@
             pw.println("Configuration:");
             pw.increaseIndent();
             pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
+            pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println();
+            pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println();
             pw.println();
             CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
             pw.println();
@@ -1033,6 +1165,12 @@
             pw.println(mLastMemoryTrimLevel);
             pw.println();
 
+            pw.println("Active Package stats:");
+            pw.increaseIndent();
+            mActivePkgStats.forEach(pkgStats -> pkgStats.dumpLocked(pw));
+            pw.decreaseIndent();
+            pw.println();
+
             pw.print("User Grace Period: ");
             pw.println(mGracePeriodObserver.mGracePeriodExpiration);
             pw.println();
@@ -1620,4 +1758,53 @@
             return sb.toString();
         }
     }
+
+    private static class PackageStats {
+        public int userId;
+        public String packageName;
+        public int numRunningEj;
+        public int numRunningRegular;
+        public int numStagedEj;
+        public int numStagedRegular;
+
+        private void setPackage(int userId, @NonNull String packageName) {
+            this.userId = userId;
+            this.packageName = packageName;
+            numRunningEj = numRunningRegular = 0;
+            resetStagedCount();
+        }
+
+        private void resetStagedCount() {
+            numStagedEj = numStagedRegular = 0;
+        }
+
+        private void adjustRunningCount(boolean add, boolean forEj) {
+            if (forEj) {
+                numRunningEj = Math.max(0, numRunningEj + (add ? 1 : -1));
+            } else {
+                numRunningRegular = Math.max(0, numRunningRegular + (add ? 1 : -1));
+            }
+        }
+
+        private void adjustStagedCount(boolean add, boolean forEj) {
+            if (forEj) {
+                numStagedEj = Math.max(0, numStagedEj + (add ? 1 : -1));
+            } else {
+                numStagedRegular = Math.max(0, numStagedRegular + (add ? 1 : -1));
+            }
+        }
+
+        @GuardedBy("mLock")
+        private void dumpLocked(IndentingPrintWriter pw) {
+            pw.print("PackageStats{");
+            pw.print(userId);
+            pw.print("-");
+            pw.print(packageName);
+            pw.print("#runEJ", numRunningEj);
+            pw.print("#runReg", numRunningRegular);
+            pw.print("#stagedEJ", numStagedEj);
+            pw.print("#stagedReg", numStagedRegular);
+            pw.println("}");
+        }
+    }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index d72f565b..1815661 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -816,6 +816,7 @@
                         mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid);
                     }
                     mDebuggableApps.remove(pkgName);
+                    mConcurrencyManager.onAppRemovedLocked(pkgName, pkgUid);
                 }
             } else if (Intent.ACTION_USER_ADDED.equals(action)) {
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
@@ -2233,7 +2234,8 @@
     }
 
     /** Returns true if both the calling and source users for the job are started. */
-    private boolean areUsersStartedLocked(final JobStatus job) {
+    @GuardedBy("mLock")
+    public boolean areUsersStartedLocked(final JobStatus job) {
         boolean sourceStarted = ArrayUtils.contains(mStartedUsers, job.getSourceUserId());
         if (job.getUserId() == job.getSourceUserId()) {
             return sourceStarted;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
index 999c53f..12d9c7f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
@@ -34,6 +34,7 @@
 import android.util.SparseArrayMap;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.job.JobSchedulerService;
 
 import java.util.Objects;
@@ -58,13 +59,28 @@
                 return;
             }
             switch (action) {
+                case Intent.ACTION_PACKAGE_ADDED:
+                    if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                        // Only do this for app updates since new installs won't have any jobs
+                        // scheduled.
+                        final Uri uri = intent.getData();
+                        final String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
+                        if (pkg != null) {
+                            final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                            final int userId = UserHandle.getUserId(pkgUid);
+                            updateComponentStateForPackage(userId, pkg);
+                        }
+                    }
+                    break;
                 case Intent.ACTION_PACKAGE_CHANGED:
                     final Uri uri = intent.getData();
                     final String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
                     final String[] changedComponents = intent.getStringArrayExtra(
                             Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
                     if (pkg != null && changedComponents != null && changedComponents.length > 0) {
-                        updateComponentStateForPackage(pkg);
+                        final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                        final int userId = UserHandle.getUserId(pkgUid);
+                        updateComponentStateForPackage(userId, pkg);
                     }
                     break;
                 case Intent.ACTION_USER_UNLOCKED:
@@ -86,6 +102,7 @@
         super(service);
 
         final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         filter.addDataScheme("package");
         mContext.registerReceiverAsUser(
@@ -99,6 +116,7 @@
     }
 
     @Override
+    @GuardedBy("mLock")
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
         updateComponentEnabledStateLocked(jobStatus);
     }
@@ -108,30 +126,53 @@
             boolean forUpdate) {
     }
 
+    @Override
+    @GuardedBy("mLock")
+    public void onAppRemovedLocked(String packageName, int uid) {
+        clearComponentsForPackageLocked(UserHandle.getUserId(uid), packageName);
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    public void onUserRemovedLocked(int userId) {
+        mServiceInfoCache.delete(userId);
+    }
+
     @Nullable
-    private ServiceInfo getServiceInfo(JobStatus jobStatus) {
+    @GuardedBy("mLock")
+    private ServiceInfo getServiceInfoLocked(JobStatus jobStatus) {
         final ComponentName service = jobStatus.getServiceComponent();
         final int userId = jobStatus.getUserId();
-        ServiceInfo si = mServiceInfoCache.get(userId, service);
-        if (si == null) {
-            try {
-                // createContextAsUser may potentially be expensive
-                // TODO: cache user context or improve ContextImpl implementation if this becomes
-                // a problem
-                si = mContext.createContextAsUser(UserHandle.of(userId), 0)
-                        .getPackageManager()
-                        .getServiceInfo(service, PackageManager.MATCH_DIRECT_BOOT_AUTO);
-            } catch (NameNotFoundException e) {
-                Slog.e(TAG, "Job exists for non-existent package: " + service.getPackageName());
-                return null;
-            }
-            mServiceInfoCache.add(userId, service, si);
+        if (mServiceInfoCache.contains(userId, service)) {
+            // Return whatever is in the cache, even if it's null. When something changes, we
+            // clear the cache.
+            return mServiceInfoCache.get(userId, service);
         }
+
+        ServiceInfo si;
+        try {
+            // createContextAsUser may potentially be expensive
+            // TODO: cache user context or improve ContextImpl implementation if this becomes
+            // a problem
+            si = mContext.createContextAsUser(UserHandle.of(userId), 0)
+                    .getPackageManager()
+                    .getServiceInfo(service, PackageManager.MATCH_DIRECT_BOOT_AUTO);
+        } catch (NameNotFoundException e) {
+            if (mService.areUsersStartedLocked(jobStatus)) {
+                // User is fully unlocked but PM still says the package doesn't exist.
+                Slog.e(TAG, "Job exists for non-existent package: " + service.getPackageName());
+            }
+            // Write null to the cache so we don't keep querying PM.
+            si = null;
+        }
+        mServiceInfoCache.add(userId, service, si);
+
         return si;
     }
 
+    @GuardedBy("mLock")
     private boolean updateComponentEnabledStateLocked(JobStatus jobStatus) {
-        final ServiceInfo service = getServiceInfo(jobStatus);
+        final ServiceInfo service = getServiceInfoLocked(jobStatus);
 
         if (DEBUG && service == null) {
             Slog.v(TAG, jobStatus.toShortString() + " component not present");
@@ -141,20 +182,26 @@
         return !Objects.equals(ogService, service);
     }
 
-    private void updateComponentStateForPackage(final String pkg) {
-        synchronized (mLock) {
-            for (int u = mServiceInfoCache.numMaps() - 1; u >= 0; --u) {
-                final int userId = mServiceInfoCache.keyAt(u);
-
-                for (int c = mServiceInfoCache.numElementsForKey(userId) - 1; c >= 0; --c) {
-                    final ComponentName cn = mServiceInfoCache.keyAt(u, c);
-                    if (cn.getPackageName().equals(pkg)) {
-                        mServiceInfoCache.delete(userId, cn);
-                    }
-                }
+    @GuardedBy("mLock")
+    private void clearComponentsForPackageLocked(final int userId, final String pkg) {
+        final int uIdx = mServiceInfoCache.indexOfKey(userId);
+        for (int c = mServiceInfoCache.numElementsForKey(userId) - 1; c >= 0; --c) {
+            final ComponentName cn = mServiceInfoCache.keyAt(uIdx, c);
+            if (cn.getPackageName().equals(pkg)) {
+                mServiceInfoCache.delete(userId, cn);
             }
-            updateComponentStatesLocked(
-                    jobStatus -> jobStatus.getServiceComponent().getPackageName().equals(pkg));
+        }
+    }
+
+    private void updateComponentStateForPackage(final int userId, final String pkg) {
+        synchronized (mLock) {
+            clearComponentsForPackageLocked(userId, pkg);
+            updateComponentStatesLocked(jobStatus -> {
+                // Using user ID instead of source user ID because the service will run under the
+                // user ID, not source user ID.
+                return jobStatus.getUserId() == userId
+                        && jobStatus.getServiceComponent().getPackageName().equals(pkg);
+            });
         }
     }
 
@@ -169,6 +216,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void updateComponentStatesLocked(@NonNull Predicate<JobStatus> filter) {
         mComponentStateUpdateFunctor.reset();
         mService.getJobStore().forEachJob(filter, mComponentStateUpdateFunctor);
@@ -178,24 +226,40 @@
     }
 
     final class ComponentStateUpdateFunctor implements Consumer<JobStatus> {
+        @GuardedBy("mLock")
         boolean mChanged;
 
         @Override
+        @GuardedBy("mLock")
         public void accept(JobStatus jobStatus) {
             mChanged |= updateComponentEnabledStateLocked(jobStatus);
         }
 
+        @GuardedBy("mLock")
         private void reset() {
             mChanged = false;
         }
     }
 
     @Override
+    @GuardedBy("mLock")
     public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
-
+        for (int u = 0; u < mServiceInfoCache.numMaps(); ++u) {
+            final int userId = mServiceInfoCache.keyAt(u);
+            for (int p = 0; p < mServiceInfoCache.numElementsForKey(userId); ++p) {
+                final ComponentName componentName = mServiceInfoCache.keyAt(u, p);
+                pw.print(userId);
+                pw.print("-");
+                pw.print(componentName);
+                pw.print(": ");
+                pw.print(mServiceInfoCache.valueAt(u, p));
+                pw.println();
+            }
+        }
     }
 
     @Override
+    @GuardedBy("mLock")
     public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
             Predicate<JobStatus> predicate) {
 
diff --git a/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt
index bebf019..b7d7ed8 100644
--- a/apex/media/framework/api/current.txt
+++ b/apex/media/framework/api/current.txt
@@ -10,7 +10,6 @@
     method @NonNull public java.util.List<java.lang.String> getUnsupportedVideoMimeTypes();
     method public boolean isFormatSpecified(@NonNull String);
     method public boolean isHdrTypeSupported(@NonNull String);
-    method public boolean isSlowMotionSupported();
     method public boolean isVideoMimeTypeSupported(@NonNull String);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.ApplicationMediaCapabilities> CREATOR;
diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
index 3f30d3e..97fa0ec 100644
--- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
+++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
@@ -242,7 +242,7 @@
                 }
             };
 
-    /*
+    /**
      * Query the video codec mime types supported by the application.
      * @return List of supported video codec mime types. The list will be empty if there are none.
      */
@@ -251,7 +251,7 @@
         return new ArrayList<>(mSupportedVideoMimeTypes);
     }
 
-    /*
+    /**
      * Query the video codec mime types that are not supported by the application.
      * @return List of unsupported video codec mime types. The list will be empty if there are none.
      */
@@ -260,7 +260,7 @@
         return new ArrayList<>(mUnsupportedVideoMimeTypes);
     }
 
-    /*
+    /**
      * Query all hdr types that are supported by the application.
      * @return List of supported hdr types. The list will be empty if there are none.
      */
@@ -269,7 +269,7 @@
         return new ArrayList<>(mSupportedHdrTypes);
     }
 
-    /*
+    /**
      * Query all hdr types that are not supported by the application.
      * @return List of unsupported hdr types. The list will be empty if there are none.
      */
@@ -278,7 +278,7 @@
         return new ArrayList<>(mUnsupportedHdrTypes);
     }
 
-    /*
+    /**
      * Whether handling of slow-motion video is supported
      * @hide
      */
diff --git a/core/api/current.txt b/core/api/current.txt
index 3cd2494..0420714 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -25218,7 +25218,6 @@
     method public float getPlaybackSpeed();
     method public long getPosition();
     method public int getState();
-    method public boolean isActive();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L
     field public static final long ACTION_PAUSE = 2L; // 0x2L
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 2be8c9a..b653410 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -161,6 +161,10 @@
     method public void onVolumeChanged(@NonNull android.media.session.MediaSession.Token, int);
   }
 
+  public final class PlaybackState implements android.os.Parcelable {
+    method public boolean isActiveState();
+  }
+
 }
 
 package android.net {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e956dee..5b978e5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2321,7 +2321,7 @@
     field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
     field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
     field public static final String TETHERING_SERVICE = "tethering";
-    field public static final String TRANSLATION_MANAGER_SERVICE = "transformer";
+    field public static final String TRANSLATION_MANAGER_SERVICE = "translation";
     field public static final String UI_TRANSLATION_SERVICE = "ui_translation";
     field public static final String VR_SERVICE = "vrmanager";
     field public static final String WIFI_NL80211_SERVICE = "wifinl80211";
@@ -7298,10 +7298,6 @@
 
 package android.net {
 
-  public class DnsResolverServiceManager {
-    method @NonNull @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public static android.os.IBinder getService(@NonNull android.content.Context);
-  }
-
   public class EthernetManager {
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public android.net.EthernetManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.TetheredInterfaceCallback);
   }
@@ -7861,7 +7857,7 @@
     method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String);
     method @Nullable public static android.net.wifi.nl80211.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]);
     method @Deprecated public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback);
-    method public boolean registerCountryCodeChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangeListener);
+    method public boolean registerCountryCodeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangedListener);
     method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SendMgmtFrameCallback);
     method public void setOnServiceDeadCallback(@NonNull Runnable);
     method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback);
@@ -7874,7 +7870,7 @@
     method public boolean tearDownClientInterface(@NonNull String);
     method public boolean tearDownInterfaces();
     method public boolean tearDownSoftApInterface(@NonNull String);
-    method public void unregisterCountryCodeChangeListener(@NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangeListener);
+    method public void unregisterCountryCodeChangedListener(@NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangedListener);
     field public static final String SCANNING_PARAM_ENABLE_6GHZ_RNR = "android.net.wifi.nl80211.SCANNING_PARAM_ENABLE_6GHZ_RNR";
     field public static final int SCAN_TYPE_PNO_SCAN = 1; // 0x1
     field public static final int SCAN_TYPE_SINGLE_SCAN = 0; // 0x0
@@ -7885,8 +7881,8 @@
     field public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1; // 0x1
   }
 
-  public static interface WifiNl80211Manager.CountryCodeChangeListener {
-    method public void onChanged(@NonNull String);
+  public static interface WifiNl80211Manager.CountryCodeChangedListener {
+    method public void onCountryCodeChanged(@NonNull String);
   }
 
   public static class WifiNl80211Manager.OemSecurityType {
@@ -7944,16 +7940,16 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
-    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback);
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
     method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
-    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnStateCallback(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback);
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
     field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
   }
 
-  public static interface NfcAdapter.ControllerAlwaysOnStateCallback {
-    method public void onStateChanged(boolean);
+  public static interface NfcAdapter.ControllerAlwaysOnListener {
+    method public void onControllerAlwaysOnChanged(boolean);
   }
 
   public static interface NfcAdapter.NfcUnlockHandler {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0541934..e449728 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -161,6 +161,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void moveTaskToRootTask(int, int, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void removeRootTasksInWindowingModes(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void removeRootTasksWithActivityTypes(@NonNull int[]);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean removeTask(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void resizeTask(int, android.graphics.Rect);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void startSystemLockTaskMode(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void stopSystemLockTaskMode();
@@ -1574,6 +1575,10 @@
     method public static void setServiceForTest(@Nullable android.os.IBinder);
   }
 
+  public class NetworkWatchlistManager {
+    method @Nullable public byte[] getWatchlistConfigHash();
+  }
+
   public class TrafficStats {
     method public static long getLoopbackRxBytes();
     method public static long getLoopbackRxPackets();
@@ -2009,6 +2014,7 @@
 
   public final class PermissionManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData();
+    method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(boolean);
     method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource);
   }
 
diff --git a/core/java/android/annotation/RequiresPermission.java b/core/java/android/annotation/RequiresPermission.java
index 1d89e31..303ab41 100644
--- a/core/java/android/annotation/RequiresPermission.java
+++ b/core/java/android/annotation/RequiresPermission.java
@@ -20,7 +20,7 @@
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.METHOD;
 import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
@@ -76,7 +76,7 @@
  *
  * @hide
  */
-@Retention(SOURCE)
+@Retention(CLASS)
 @Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
 public @interface RequiresPermission {
     /**
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 627017c..28d6fbb 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -469,7 +469,8 @@
         }
     }
 
-    /** @hide */
+    /** Removes task by a given taskId */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
     public boolean removeTask(int taskId) {
         try {
             return getService().removeTask(taskId);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index df9530f..1cb46b1 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -6475,7 +6475,7 @@
                     historicalDiscreteAccesses.add(other.mDiscreteAccesses.get(i++));
                 }
             }
-            mDiscreteAccesses = historicalDiscreteAccesses;
+            mDiscreteAccesses = deduplicateDiscreteEvents(historicalDiscreteAccesses);
         }
 
         private void increaseAccessCount(@UidState int uidState, @OpFlags int flags,
@@ -6996,7 +6996,7 @@
             }
             result.add(entry);
         }
-        return result;
+        return deduplicateDiscreteEvents(result);
     }
 
     /**
@@ -9819,4 +9819,52 @@
             }
         }
     }
+
+    private static List<AttributedOpEntry> deduplicateDiscreteEvents(List<AttributedOpEntry> list) {
+        int n = list.size();
+        int i = 0;
+        for (int j = 0, k = 0; j < n; i++, j = k) {
+            long currentAccessTime = list.get(j).getLastAccessTime(OP_FLAGS_ALL);
+            k = j + 1;
+            while(k < n && list.get(k).getLastAccessTime(OP_FLAGS_ALL) == currentAccessTime) {
+                k++;
+            }
+            list.set(i, mergeAttributedOpEntries(list.subList(j, k)));
+        }
+        for (; i < n; i++) {
+            list.remove(list.size() - 1);
+        }
+        return list;
+    }
+
+    private static AttributedOpEntry mergeAttributedOpEntries(List<AttributedOpEntry> opEntries) {
+        if (opEntries.size() == 1) {
+            return opEntries.get(0);
+        }
+        LongSparseArray<AppOpsManager.NoteOpEvent> accessEvents = new LongSparseArray<>();
+        LongSparseArray<AppOpsManager.NoteOpEvent> rejectEvents = new LongSparseArray<>();
+        int opCount = opEntries.size();
+        for (int i = 0; i < opCount; i++) {
+            AttributedOpEntry a = opEntries.get(i);
+            ArraySet<Long> keys = a.collectKeys();
+            final int keyCount = keys.size();
+            for (int k = 0; k < keyCount; k++) {
+                final long key = keys.valueAt(k);
+
+                final int uidState = extractUidStateFromKey(key);
+                final int flags = extractFlagsFromKey(key);
+
+                NoteOpEvent access = a.getLastAccessEvent(uidState, uidState, flags);
+                NoteOpEvent reject = a.getLastRejectEvent(uidState, uidState, flags);
+
+                if (access != null) {
+                    accessEvents.append(key, access);
+                }
+                if (reject != null) {
+                    rejectEvents.append(key, reject);
+                }
+            }
+        }
+        return new AttributedOpEntry(opEntries.get(0).mOp, false, accessEvents, rejectEvents);
+    }
 }
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index dfc105a..8574678 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -614,7 +614,7 @@
      * tombstone traces will be returned for
      * {@link #REASON_CRASH_NATIVE}, with an InputStream containing a protobuf with
      * <a href="https://android.googlesource.com/platform/system/core/+/refs/heads/master/debuggerd/proto/tombstone.proto">this schema</a>.
-     * Note thatbecause these traces are kept in a separate global circular buffer, crashes may be
+     * Note that because these traces are kept in a separate global circular buffer, crashes may be
      * overwritten by newer crashes (including from other applications), so this may still return
      * null.
      *
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index e83557c..4f7c684 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -190,4 +190,18 @@
      * Called from SystemUI when it shows the AoD UI.
      */
     oneway void setInAmbientMode(boolean inAmbientMode, long animationDuration);
+
+    /**
+     * Called from SystemUI when the device is waking up.
+     *
+     * @hide
+     */
+    oneway void notifyWakingUp(int x, int y, in Bundle extras);
+
+    /**
+     * Called from SystemUI when the device is going to sleep.
+     *
+     * @hide
+     */
+    void notifyGoingToSleep(int x, int y, in Bundle extras);
 }
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 4cf3a80..ca08683 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -53,7 +53,6 @@
 import android.os.UserHandle;
 import android.util.AndroidException;
 import android.util.ArraySet;
-import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.os.IResultReceiver;
@@ -371,19 +370,9 @@
                 "Cannot set both FLAG_IMMUTABLE and FLAG_MUTABLE for PendingIntent");
         }
 
-        // TODO(b/178092897) Remove the below instrumentation check and enforce
-        // the explicit mutability requirement for apps under instrumentation.
-        ActivityThread thread = ActivityThread.currentActivityThread();
-        Instrumentation mInstrumentation = thread.getInstrumentation();
-
         if (Compatibility.isChangeEnabled(PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED)
                 && !flagImmutableSet && !flagMutableSet) {
-
-            if (mInstrumentation.isInstrumenting()) {
-                Log.e(TAG, msg);
-            } else {
                 throw new IllegalArgumentException(msg);
-            }
         }
     }
 
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 6a71c92..8d332ab 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -190,6 +190,30 @@
     public static final String COMMAND_DROP = "android.home.drop";
 
     /**
+     * Command for {@link #sendWallpaperCommand}: reported by System UI when the device is waking
+     * up. The x and y arguments are a location (possibly very roughly) corresponding to the action
+     * that caused the device to wake up. For example, if the power button was pressed, this will be
+     * the location on the screen nearest the power button.
+     *
+     * If the location is unknown or not applicable, x and y will be -1.
+     *
+     * @hide
+     */
+    public static final String COMMAND_WAKING_UP = "android.wallpaper.wakingup";
+
+    /**
+     * Command for {@link #sendWallpaperCommand}: reported by System UI when the device is going to
+     * sleep. The x and y arguments are a location (possibly very roughly) corresponding to the
+     * action that caused the device to go to sleep. For example, if the power button was pressed,
+     * this will be the location on the screen nearest the power button.
+     *
+     * If the location is unknown or not applicable, x and y will be -1.
+     *
+     * @hide
+     */
+    public static final String COMMAND_GOING_TO_SLEEP = "android.wallpaper.goingtosleep";
+
+    /**
      * Command for {@link #sendWallpaperCommand}: reported when the wallpaper that was already
      * set is re-applied by the user.
      * @hide
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index e93138b..759597c 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -28,6 +28,7 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 import static com.android.internal.widget.LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
 import static com.android.internal.widget.PasswordValidationError.CONTAINS_INVALID_CHARACTERS;
 import static com.android.internal.widget.PasswordValidationError.CONTAINS_SEQUENCE;
@@ -74,11 +75,8 @@
     // consider it a complex PIN/password.
     public static final int MAX_ALLOWED_SEQUENCE = 3;
 
-    // One of CREDENTIAL_TYPE_NONE, CREDENTIAL_TYPE_PATTERN or CREDENTIAL_TYPE_PASSWORD.
-    // Note that this class still uses CREDENTIAL_TYPE_PASSWORD to represent both numeric PIN
-    // and alphabetic password. This is OK as long as this definition is only used internally,
-    // and the value never gets mixed up with credential types from other parts of the framework.
-    // TODO: fix this (ideally after we move logic to PasswordPolicy)
+    // One of CREDENTIAL_TYPE_NONE, CREDENTIAL_TYPE_PATTERN, CREDENTIAL_TYPE_PIN or
+    // CREDENTIAL_TYPE_PASSWORD.
     public @CredentialType int credType;
     // Fields below only make sense when credType is PASSWORD.
     public int length = 0;
@@ -192,13 +190,15 @@
     /**
      * Returns the {@code PasswordMetrics} for a given credential.
      *
-     * If the credential is a pin or a password, equivalent to {@link #computeForPassword(byte[])}.
-     * {@code credential} cannot be null when {@code type} is
+     * If the credential is a pin or a password, equivalent to
+     * {@link #computeForPasswordOrPin(byte[], boolean)}. {@code credential} cannot be null
+     * when {@code type} is
      * {@link com.android.internal.widget.LockPatternUtils#CREDENTIAL_TYPE_PASSWORD}.
      */
     public static PasswordMetrics computeForCredential(LockscreenCredential credential) {
         if (credential.isPassword() || credential.isPin()) {
-            return PasswordMetrics.computeForPassword(credential.getCredential());
+            return PasswordMetrics.computeForPasswordOrPin(credential.getCredential(),
+                    credential.isPin());
         } else if (credential.isPattern())  {
             return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
         } else if (credential.isNone()) {
@@ -209,9 +209,9 @@
     }
 
     /**
-     * Returns the {@code PasswordMetrics} for a given password
+     * Returns the {@code PasswordMetrics} for a given password or pin
      */
-    public static PasswordMetrics computeForPassword(@NonNull byte[] password) {
+    public static PasswordMetrics computeForPasswordOrPin(byte[] password, boolean isPin) {
         // Analyse the characters used
         int letters = 0;
         int upperCase = 0;
@@ -245,8 +245,9 @@
             }
         }
 
+        final int credType = isPin ? CREDENTIAL_TYPE_PIN : CREDENTIAL_TYPE_PASSWORD;
         final int seqLength = maxLengthSequence(password);
-        return new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD, length, letters, upperCase, lowerCase,
+        return new PasswordMetrics(credType, length, letters, upperCase, lowerCase,
                 numeric, symbols, nonLetter, nonNumeric, seqLength);
     }
 
@@ -353,7 +354,7 @@
      */
     public void maxWith(PasswordMetrics other) {
         credType = Math.max(credType, other.credType);
-        if (credType != CREDENTIAL_TYPE_PASSWORD) {
+        if (credType != CREDENTIAL_TYPE_PASSWORD && credType != CREDENTIAL_TYPE_PIN) {
             return;
         }
         length = Math.max(length, other.length);
@@ -408,7 +409,7 @@
 
             @Override
             boolean allowsCredType(int credType) {
-                return credType == CREDENTIAL_TYPE_PASSWORD;
+                return credType == CREDENTIAL_TYPE_PASSWORD || credType == CREDENTIAL_TYPE_PIN;
             }
         },
         BUCKET_MEDIUM(PASSWORD_COMPLEXITY_MEDIUM) {
@@ -424,7 +425,7 @@
 
             @Override
             boolean allowsCredType(int credType) {
-                return credType == CREDENTIAL_TYPE_PASSWORD;
+                return credType == CREDENTIAL_TYPE_PASSWORD || credType == CREDENTIAL_TYPE_PIN;
             }
         },
         BUCKET_LOW(PASSWORD_COMPLEXITY_LOW) {
@@ -489,7 +490,7 @@
         if (!bucket.allowsCredType(credType)) {
             return false;
         }
-        if (credType != CREDENTIAL_TYPE_PASSWORD) {
+        if (credType != CREDENTIAL_TYPE_PASSWORD && credType != CREDENTIAL_TYPE_PIN) {
             return true;
         }
         return (bucket.canHaveSequence() || seqLength <= MAX_ALLOWED_SEQUENCE)
@@ -529,7 +530,7 @@
                     new PasswordValidationError(CONTAINS_INVALID_CHARACTERS, 0));
         }
 
-        final PasswordMetrics enteredMetrics = computeForPassword(password);
+        final PasswordMetrics enteredMetrics = computeForPasswordOrPin(password, isPin);
         return validatePasswordMetrics(adminMetrics, minComplexity, isPin, enteredMetrics);
     }
 
@@ -555,8 +556,8 @@
                 || !bucket.allowsCredType(actualMetrics.credType)) {
             return Collections.singletonList(new PasswordValidationError(WEAK_CREDENTIAL_TYPE, 0));
         }
-        // TODO: this needs to be modified if CREDENTIAL_TYPE_PIN is added.
-        if (actualMetrics.credType != CREDENTIAL_TYPE_PASSWORD) {
+        if (actualMetrics.credType != CREDENTIAL_TYPE_PASSWORD
+                && actualMetrics.credType != CREDENTIAL_TYPE_PIN) {
             return Collections.emptyList(); // Nothing to check for pattern or none.
         }
 
diff --git a/core/java/android/app/admin/PasswordPolicy.java b/core/java/android/app/admin/PasswordPolicy.java
index 13f11ad..0544a36 100644
--- a/core/java/android/app/admin/PasswordPolicy.java
+++ b/core/java/android/app/admin/PasswordPolicy.java
@@ -20,6 +20,7 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
@@ -27,6 +28,7 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 
 /**
  * {@hide}
@@ -58,14 +60,20 @@
         } else if (quality == PASSWORD_QUALITY_BIOMETRIC_WEAK
                 || quality == PASSWORD_QUALITY_SOMETHING) {
             return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
-        } // quality is NUMERIC or stronger.
+        } else if (quality == PASSWORD_QUALITY_NUMERIC
+                || quality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
+            PasswordMetrics result = new PasswordMetrics(CREDENTIAL_TYPE_PIN);
+            result.length = length;
+            if (quality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
+                result.seqLength = PasswordMetrics.MAX_ALLOWED_SEQUENCE;
+            }
+            return result;
+        } // quality is ALPHABETIC or stronger.
 
         PasswordMetrics result = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
         result.length = length;
 
-        if (quality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
-            result.seqLength = PasswordMetrics.MAX_ALLOWED_SEQUENCE;
-        } else if (quality == PASSWORD_QUALITY_ALPHABETIC) {
+        if (quality == PASSWORD_QUALITY_ALPHABETIC) {
             result.nonNumeric = 1;
         } else if (quality == PASSWORD_QUALITY_ALPHANUMERIC) {
             result.numeric = 1;
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index a72877e..fe81df0 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -320,6 +320,15 @@
     }
 
     /**
+     * Set the host's interaction handler.
+     *
+     * @hide
+     */
+    public void setInteractionHandler(InteractionHandler interactionHandler) {
+        mInteractionHandler = interactionHandler;
+    }
+
+    /**
      * Gets a list of all the appWidgetIds that are bound to the current host
      */
     public int[] getAppWidgetIds() {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 318913f..a88c9ed 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4705,10 +4705,9 @@
      * @hide
      * @see #getSystemService(String)
      */
-    // TODO(b/176208267): change it back to translation before S release.
     @SystemApi
     @SuppressLint("ServiceName")
-    public static final String TRANSLATION_MANAGER_SERVICE = "transformer";
+    public static final String TRANSLATION_MANAGER_SERVICE = "translation";
 
     /**
      * Official published name of the translation service which supports ui translation function.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 5ff1124..86a8a9d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -8923,6 +8923,8 @@
         private final @ParseFlags int mFlags;
         private AssetManager mCachedAssetManager;
 
+        private ApkAssets mBaseApkAssets;
+
         DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) {
             mBaseCodePath = pkg.baseCodePath;
             mSplitCodePaths = pkg.splitCodePaths;
@@ -8953,9 +8955,11 @@
             ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null
                     ? mSplitCodePaths.length : 0) + 1];
 
+            mBaseApkAssets = loadApkAssets(mBaseCodePath, mFlags);
+
             // Load the base.
             int splitIdx = 0;
-            apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags);
+            apkAssets[splitIdx++] = mBaseApkAssets;
 
             // Load any splits.
             if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
@@ -8982,6 +8986,11 @@
         public void close() throws Exception {
             IoUtils.closeQuietly(mCachedAssetManager);
         }
+
+        @Override
+        public ApkAssets getBaseApkAssets() {
+            return mBaseApkAssets;
+        }
     }
 
     /**
@@ -9085,5 +9094,10 @@
                 IoUtils.closeQuietly(assets);
             }
         }
+
+        @Override
+        public ApkAssets getBaseApkAssets() {
+            return mCachedSplitApks[0][0];
+        }
     }
 }
diff --git a/core/java/android/content/pm/PackagePartitions.java b/core/java/android/content/pm/PackagePartitions.java
index 98a20f7..52ee4de 100644
--- a/core/java/android/content/pm/PackagePartitions.java
+++ b/core/java/android/content/pm/PackagePartitions.java
@@ -47,7 +47,7 @@
     public static final int PARTITION_PRODUCT = 4;
     public static final int PARTITION_SYSTEM_EXT = 5;
 
-    @IntDef(flag = true, prefix = { "PARTITION_" }, value = {
+    @IntDef(prefix = { "PARTITION_" }, value = {
         PARTITION_SYSTEM,
         PARTITION_VENDOR,
         PARTITION_ODM,
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index a1ffc0c..0fc6b2b 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -383,10 +383,9 @@
         }
 
         try {
-            final AssetManager assets = assetLoader.getBaseAssetManager();
             final File baseApk = new File(lite.getBaseApkPath());
             final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
-                    lite.getPath(), assets, flags);
+                    lite.getPath(), assetLoader, flags);
             if (result.isError()) {
                 return input.error(result);
             }
@@ -442,7 +441,7 @@
             final ParseResult<ParsingPackage> result = parseBaseApk(input,
                     apkFile,
                     apkFile.getCanonicalPath(),
-                    assetLoader.getBaseAssetManager(), flags);
+                    assetLoader, flags);
             if (result.isError()) {
                 return input.error(result);
             }
@@ -458,7 +457,8 @@
     }
 
     private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
-            String codePath, AssetManager assets, int flags) {
+            String codePath, SplitAssetLoader assetLoader, int flags)
+            throws PackageParserException {
         final String apkPath = apkFile.getAbsolutePath();
 
         String volumeUuid = null;
@@ -469,6 +469,7 @@
 
         if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
 
+        final AssetManager assets = assetLoader.getBaseAssetManager();
         final int cookie = assets.findCookieForPath(apkPath);
         if (cookie == 0) {
             return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST,
@@ -500,12 +501,19 @@
                 }
             }
 
-            ApkAssets apkAssets = assets.getApkAssets()[0];
-            if (apkAssets.definesOverlayable()) {
+            ApkAssets apkAssets = assetLoader.getBaseApkAssets();
+            boolean definesOverlayable = false;
+            try {
+                definesOverlayable = apkAssets.definesOverlayable();
+            } catch (IOException ignored) {
+                // Will fail if there's no packages in the ApkAssets, which can be treated as false
+            }
+
+            if (definesOverlayable) {
                 SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers();
                 int size = packageNames.size();
                 for (int index = 0; index < size; index++) {
-                    String packageName = packageNames.get(index);
+                    String packageName = packageNames.valueAt(index);
                     Map<String, String> overlayableToActor = assets.getOverlayableMap(packageName);
                     if (overlayableToActor != null && !overlayableToActor.isEmpty()) {
                         for (String overlayable : overlayableToActor.keySet()) {
@@ -2799,7 +2807,15 @@
         }
     }
 
+    @SuppressWarnings("AndroidFrameworkCompatChange")
     private void convertSplitPermissions(ParsingPackage pkg) {
+        // STOPSHIP(b/183905675): REMOVE THIS TERRIBLE, HORRIBLE, NO GOOD, VERY BAD HACK
+        if ("com.android.chrome".equals(pkg.getPackageName())
+                && pkg.getVersionCode() <= 445500399
+                && pkg.getTargetSdkVersion() > Build.VERSION_CODES.R) {
+            pkg.setTargetSdkVersion(Build.VERSION_CODES.R);
+        }
+
         final int listSize = mSplitPermissionInfos.size();
         for (int is = 0; is < listSize; is++) {
             final PermissionManager.SplitPermissionInfo spi = mSplitPermissionInfos.get(is);
diff --git a/core/java/android/content/pm/parsing/component/ParsedAttribution.java b/core/java/android/content/pm/parsing/component/ParsedAttribution.java
index 4ec2e73..3a4aae1 100644
--- a/core/java/android/content/pm/parsing/component/ParsedAttribution.java
+++ b/core/java/android/content/pm/parsing/component/ParsedAttribution.java
@@ -40,7 +40,7 @@
     public static final int MAX_ATTRIBUTION_TAG_LEN = 50;
 
     /** Maximum amount of attributions per package */
-    private static final int MAX_NUM_ATTRIBUTIONS = 10000;
+    private static final int MAX_NUM_ATTRIBUTIONS = 1000;
 
     /** Tag of the attribution */
     public final @NonNull String tag;
@@ -100,7 +100,7 @@
 
 
 
-    // Code below generated by codegen v1.0.23.
+    // Code below generated by codegen v1.0.22.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -215,8 +215,8 @@
     };
 
     @DataClass.Generated(
-            time = 1618351459610L,
-            codegenVersion = "1.0.23",
+            time = 1607463855175L,
+            codegenVersion = "1.0.22",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttribution.java",
             inputSignatures = "public static final  int MAX_ATTRIBUTION_TAG_LEN\nprivate static final  int MAX_NUM_ATTRIBUTIONS\npublic final @android.annotation.NonNull java.lang.String tag\npublic final @android.annotation.StringRes int label\npublic final @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\npublic static  boolean isCombinationValid(java.util.List<android.content.pm.parsing.component.ParsedAttribution>)\nclass ParsedAttribution extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=false)")
     @Deprecated
diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
index f3caf60..c1a8396 100644
--- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
+++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -43,6 +43,8 @@
     private final @ParseFlags int mFlags;
     private AssetManager mCachedAssetManager;
 
+    private ApkAssets mBaseApkAssets;
+
     public DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) {
         mBaseApkPath = pkg.getBaseApkPath();
         mSplitApkPaths = pkg.getSplitApkPaths();
@@ -76,7 +78,7 @@
 
         // Load the base.
         int splitIdx = 0;
-        apkAssets[splitIdx++] = loadApkAssets(mBaseApkPath, mFlags);
+        apkAssets[splitIdx++] = mBaseApkAssets = loadApkAssets(mBaseApkPath, mFlags);
 
         // Load any splits.
         if (!ArrayUtils.isEmpty(mSplitApkPaths)) {
@@ -100,6 +102,11 @@
     }
 
     @Override
+    public ApkAssets getBaseApkAssets() {
+        return mBaseApkAssets;
+    }
+
+    @Override
     public void close() throws Exception {
         IoUtils.closeQuietly(mCachedAssetManager);
     }
diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
index 523ca40..e5c2158 100644
--- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -128,6 +128,11 @@
     }
 
     @Override
+    public ApkAssets getBaseApkAssets() {
+        return mCachedSplitApks[0][0];
+    }
+
+    @Override
     public void close() throws Exception {
         for (AssetManager assets : mCachedAssetManagers) {
             IoUtils.closeQuietly(assets);
diff --git a/core/java/android/content/pm/split/SplitAssetLoader.java b/core/java/android/content/pm/split/SplitAssetLoader.java
index 108fb95..7584e15f 100644
--- a/core/java/android/content/pm/split/SplitAssetLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetLoader.java
@@ -16,6 +16,7 @@
 package android.content.pm.split;
 
 import android.content.pm.PackageParser;
+import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 
 /**
@@ -27,4 +28,6 @@
 public interface SplitAssetLoader extends AutoCloseable {
     AssetManager getBaseAssetManager() throws PackageParser.PackageParserException;
     AssetManager getSplitAssetManager(int splitIdx) throws PackageParser.PackageParserException;
+
+    ApkAssets getBaseApkAssets();
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 4ee5383..f03da7c 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -718,7 +718,7 @@
         public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
                 @NonNull EditorInfo editorInfo, boolean restarting,
                 @NonNull IBinder startInputToken) {
-            mPrivOps.reportStartInput(startInputToken);
+            mPrivOps.reportStartInputAsync(startInputToken);
 
             if (restarting) {
                 restartInput(inputConnection, editorInfo);
diff --git a/core/java/android/net/DnsResolverServiceManager.java b/core/java/android/net/DnsResolverServiceManager.java
deleted file mode 100644
index 1597322..0000000
--- a/core/java/android/net/DnsResolverServiceManager.java
+++ /dev/null
@@ -1,63 +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 android.net;
-
-import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
-import android.content.Context;
-import android.os.IBinder;
-import android.os.ServiceManager;
-
-import java.util.Objects;
-
-/**
- * Provides a way to obtain the DnsResolver binder objects.
- *
- * @hide
- */
-@SystemApi
-public class DnsResolverServiceManager {
-    /**
-     * Name to retrieve a {@link android.net.IDnsResolver} IBinder.
-     */
-    private static final String DNS_RESOLVER_SERVICE = "dnsresolver";
-
-    private DnsResolverServiceManager() {}
-
-    /**
-     * Get an {@link IBinder} representing the DnsResolver stable AIDL interface
-     *
-     * @param context the context for permission check.
-     * @return {@link android.net.IDnsResolver} IBinder.
-     */
-    @NonNull
-    @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
-    public static IBinder getService(@NonNull final Context context) {
-        Objects.requireNonNull(context);
-        context.enforceCallingOrSelfPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
-                "DnsResolverServiceManager");
-        try {
-            return ServiceManager.getServiceOrThrow(DNS_RESOLVER_SERVICE);
-        } catch (ServiceManager.ServiceNotFoundException e) {
-            // Catch ServiceManager#ServiceNotFoundException and rethrow IllegalStateException
-            // because ServiceManager#ServiceNotFoundException is @hide so that it can't be listed
-            // on the system api. Thus, rethrow IllegalStateException if dns resolver service cannot
-            // be found.
-            throw new IllegalStateException("Cannot find dns resolver service.");
-        }
-    }
-}
diff --git a/core/java/android/net/NetworkWatchlistManager.java b/core/java/android/net/NetworkWatchlistManager.java
index 8f6510e..da01dcb 100644
--- a/core/java/android/net/NetworkWatchlistManager.java
+++ b/core/java/android/net/NetworkWatchlistManager.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -31,6 +32,7 @@
  * Class that manage network watchlist in system.
  * @hide
  */
+@TestApi
 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 @SystemService(Context.NETWORK_WATCHLIST_SERVICE)
 public class NetworkWatchlistManager {
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index d5cc01a..cb9a3e4 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -24,7 +24,7 @@
 import android.nfc.TechListParcel;
 import android.nfc.IAppCallback;
 import android.nfc.INfcAdapterExtras;
-import android.nfc.INfcControllerAlwaysOnStateCallback;
+import android.nfc.INfcControllerAlwaysOnListener;
 import android.nfc.INfcTag;
 import android.nfc.INfcCardEmulation;
 import android.nfc.INfcFCardEmulation;
@@ -76,6 +76,6 @@
     boolean setControllerAlwaysOn(boolean value);
     boolean isControllerAlwaysOn();
     boolean isControllerAlwaysOnSupported();
-    void registerControllerAlwaysOnStateCallback(in INfcControllerAlwaysOnStateCallback callback);
-    void unregisterControllerAlwaysOnStateCallback(in INfcControllerAlwaysOnStateCallback callback);
+    void registerControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
+    void unregisterControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
 }
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl b/core/java/android/nfc/INfcControllerAlwaysOnListener.aidl
similarity index 87%
rename from core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
rename to core/java/android/nfc/INfcControllerAlwaysOnListener.aidl
index 1e4fdd7..1bb7680 100644
--- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
+++ b/core/java/android/nfc/INfcControllerAlwaysOnListener.aidl
@@ -19,11 +19,11 @@
 /**
  * @hide
  */
-oneway interface INfcControllerAlwaysOnStateCallback {
+oneway interface INfcControllerAlwaysOnListener {
   /**
    * Called whenever the controller always on state changes
    *
    * @param isEnabled true if the state is enabled, false otherwise
    */
-  void onControllerAlwaysOnStateChanged(boolean isEnabled);
+  void onControllerAlwaysOnChanged(boolean isEnabled);
 }
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index bbf802c..64c1211 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -67,7 +67,7 @@
 public final class NfcAdapter {
     static final String TAG = "NFC";
 
-    private final NfcControllerAlwaysOnStateListener mControllerAlwaysOnStateListener;
+    private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener;
 
     /**
      * Intent to start an activity when a tag with NDEF payload is discovered.
@@ -418,19 +418,19 @@
     }
 
     /**
-     * A callback to be invoked when NFC controller always on state changes.
-     * <p>Register your {@code ControllerAlwaysOnStateCallback} implementation with {@link
-     * NfcAdapter#registerControllerAlwaysOnStateCallback} and disable it with {@link
-     * NfcAdapter#unregisterControllerAlwaysOnStateCallback}.
-     * @see #registerControllerAlwaysOnStateCallback
+     * A listener to be invoked when NFC controller always on state changes.
+     * <p>Register your {@code ControllerAlwaysOnListener} implementation with {@link
+     * NfcAdapter#registerControllerAlwaysOnListener} and disable it with {@link
+     * NfcAdapter#unregisterControllerAlwaysOnListener}.
+     * @see #registerControllerAlwaysOnListener
      * @hide
      */
     @SystemApi
-    public interface ControllerAlwaysOnStateCallback {
+    public interface ControllerAlwaysOnListener {
         /**
          * Called on NFC controller always on state changes
          */
-        void onStateChanged(boolean isEnabled);
+        void onControllerAlwaysOnChanged(boolean isEnabled);
     }
 
     /**
@@ -748,7 +748,7 @@
         mNfcUnlockHandlers = new HashMap<NfcUnlockHandler, INfcUnlockHandler>();
         mTagRemovedListener = null;
         mLock = new Object();
-        mControllerAlwaysOnStateListener = new NfcControllerAlwaysOnStateListener(getService());
+        mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService());
     }
 
     /**
@@ -2246,12 +2246,12 @@
      * <p>This API is for the NFCC internal state management. It allows to discriminate
      * the controller function from the NFC function by keeping the NFC controller on without
      * any NFC RF enabled if necessary.
-     * <p>This call is asynchronous. Register a callback {@link #ControllerAlwaysOnStateCallback}
-     * by {@link #registerControllerAlwaysOnStateCallback} to find out when the operation is
+     * <p>This call is asynchronous. Register a listener {@link #ControllerAlwaysOnListener}
+     * by {@link #registerControllerAlwaysOnListener} to find out when the operation is
      * complete.
      * <p>If this returns true, then either NFCC always on state has been set based on the value,
-     * or a {@link ControllerAlwaysOnStateCallback#onStateChanged(boolean)} will be invoked to
-     * indicate the state change.
+     * or a {@link ControllerAlwaysOnListener#onControllerAlwaysOnChanged(boolean)} will be invoked
+     * to indicate the state change.
      * If this returns false, then there is some problem that prevents an attempt to turn NFCC
      * always on.
      * @param value if true the NFCC will be kept on (with no RF enabled if NFC adapter is
@@ -2344,37 +2344,37 @@
     }
 
     /**
-     * Register a {@link ControllerAlwaysOnStateCallback} to listen for NFC controller always on
+     * Register a {@link ControllerAlwaysOnListener} to listen for NFC controller always on
      * state changes
-     * <p>The provided callback will be invoked by the given {@link Executor}.
+     * <p>The provided listener will be invoked by the given {@link Executor}.
      *
-     * @param executor an {@link Executor} to execute given callback
-     * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
+     * @param executor an {@link Executor} to execute given listener
+     * @param listener user implementation of the {@link ControllerAlwaysOnListener}
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
-    public void registerControllerAlwaysOnStateCallback(
+    public void registerControllerAlwaysOnListener(
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull ControllerAlwaysOnStateCallback callback) {
-        mControllerAlwaysOnStateListener.register(executor, callback);
+            @NonNull ControllerAlwaysOnListener listener) {
+        mControllerAlwaysOnListener.register(executor, listener);
     }
 
     /**
-     * Unregister the specified {@link ControllerAlwaysOnStateCallback}
-     * <p>The same {@link ControllerAlwaysOnStateCallback} object used when calling
-     * {@link #registerControllerAlwaysOnStateCallback(Executor, ControllerAlwaysOnStateCallback)}
+     * Unregister the specified {@link ControllerAlwaysOnListener}
+     * <p>The same {@link ControllerAlwaysOnListener} object used when calling
+     * {@link #registerControllerAlwaysOnListener(Executor, ControllerAlwaysOnListener)}
      * must be used.
      *
-     * <p>Callbacks are automatically unregistered when application process goes away
+     * <p>Listeners are automatically unregistered when application process goes away
      *
-     * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
+     * @param listener user implementation of the {@link ControllerAlwaysOnListener}
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
-    public void unregisterControllerAlwaysOnStateCallback(
-            @NonNull ControllerAlwaysOnStateCallback callback) {
-        mControllerAlwaysOnStateListener.unregister(callback);
+    public void unregisterControllerAlwaysOnListener(
+            @NonNull ControllerAlwaysOnListener listener) {
+        mControllerAlwaysOnListener.unregister(listener);
     }
 }
diff --git a/core/java/android/nfc/NfcControllerAlwaysOnListener.java b/core/java/android/nfc/NfcControllerAlwaysOnListener.java
new file mode 100644
index 0000000..96707bb
--- /dev/null
+++ b/core/java/android/nfc/NfcControllerAlwaysOnListener.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 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 android.nfc;
+
+import android.annotation.NonNull;
+import android.nfc.NfcAdapter.ControllerAlwaysOnListener;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class NfcControllerAlwaysOnListener extends INfcControllerAlwaysOnListener.Stub {
+    private static final String TAG = NfcControllerAlwaysOnListener.class.getSimpleName();
+
+    private final INfcAdapter mAdapter;
+
+    private final Map<ControllerAlwaysOnListener, Executor> mListenerMap = new HashMap<>();
+
+    private boolean mCurrentState = false;
+    private boolean mIsRegistered = false;
+
+    public NfcControllerAlwaysOnListener(@NonNull INfcAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    /**
+     * Register a {@link ControllerAlwaysOnListener} with this
+     * {@link NfcControllerAlwaysOnListener}
+     *
+     * @param executor an {@link Executor} to execute given listener
+     * @param listener user implementation of the {@link ControllerAlwaysOnListener}
+     */
+    public void register(@NonNull Executor executor,
+            @NonNull ControllerAlwaysOnListener listener) {
+        synchronized (this) {
+            if (mListenerMap.containsKey(listener)) {
+                return;
+            }
+
+            mListenerMap.put(listener, executor);
+            if (!mIsRegistered) {
+                try {
+                    mAdapter.registerControllerAlwaysOnListener(this);
+                    mIsRegistered = true;
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to register");
+                }
+            }
+        }
+    }
+
+    /**
+     * Unregister the specified {@link ControllerAlwaysOnListener}
+     *
+     * @param listener user implementation of the {@link ControllerAlwaysOnListener}
+     */
+    public void unregister(@NonNull ControllerAlwaysOnListener listener) {
+        synchronized (this) {
+            if (!mListenerMap.containsKey(listener)) {
+                return;
+            }
+
+            mListenerMap.remove(listener);
+
+            if (mListenerMap.isEmpty() && mIsRegistered) {
+                try {
+                    mAdapter.unregisterControllerAlwaysOnListener(this);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to unregister");
+                }
+                mIsRegistered = false;
+            }
+        }
+    }
+
+    private void sendCurrentState(@NonNull ControllerAlwaysOnListener listener) {
+        synchronized (this) {
+            Executor executor = mListenerMap.get(listener);
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                executor.execute(() -> listener.onControllerAlwaysOnChanged(
+                        mCurrentState));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    @Override
+    public void onControllerAlwaysOnChanged(boolean isEnabled) {
+        synchronized (this) {
+            mCurrentState = isEnabled;
+            for (ControllerAlwaysOnListener cb : mListenerMap.keySet()) {
+                sendCurrentState(cb);
+            }
+        }
+    }
+}
+
diff --git a/core/java/android/nfc/NfcControllerAlwaysOnStateListener.java b/core/java/android/nfc/NfcControllerAlwaysOnStateListener.java
deleted file mode 100644
index 69a9ec7..0000000
--- a/core/java/android/nfc/NfcControllerAlwaysOnStateListener.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 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 android.nfc;
-
-import android.annotation.NonNull;
-import android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/**
- * @hide
- */
-public class NfcControllerAlwaysOnStateListener extends INfcControllerAlwaysOnStateCallback.Stub {
-    private static final String TAG = "NfcControllerAlwaysOnStateListener";
-
-    private final INfcAdapter mAdapter;
-
-    private final Map<ControllerAlwaysOnStateCallback, Executor> mCallbackMap = new HashMap<>();
-
-    private boolean mCurrentState = false;
-    private boolean mIsRegistered = false;
-
-    public NfcControllerAlwaysOnStateListener(@NonNull INfcAdapter adapter) {
-        mAdapter = adapter;
-    }
-
-    /**
-     * Register a {@link ControllerAlwaysOnStateCallback} with this
-     * {@link NfcControllerAlwaysOnStateListener}
-     *
-     * @param executor an {@link Executor} to execute given callback
-     * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
-     */
-    public void register(@NonNull Executor executor,
-            @NonNull ControllerAlwaysOnStateCallback callback) {
-        synchronized (this) {
-            if (mCallbackMap.containsKey(callback)) {
-                return;
-            }
-
-            mCallbackMap.put(callback, executor);
-            if (!mIsRegistered) {
-                try {
-                    mAdapter.registerControllerAlwaysOnStateCallback(this);
-                    mIsRegistered = true;
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Failed to register ControllerAlwaysOnStateListener");
-                }
-            }
-        }
-    }
-
-    /**
-     * Unregister the specified {@link ControllerAlwaysOnStateCallback}
-     *
-     * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
-     */
-    public void unregister(@NonNull ControllerAlwaysOnStateCallback callback) {
-        synchronized (this) {
-            if (!mCallbackMap.containsKey(callback)) {
-                return;
-            }
-
-            mCallbackMap.remove(callback);
-
-            if (mCallbackMap.isEmpty() && mIsRegistered) {
-                try {
-                    mAdapter.unregisterControllerAlwaysOnStateCallback(this);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Failed to unregister ControllerAlwaysOnStateListener");
-                }
-                mIsRegistered = false;
-            }
-        }
-    }
-
-    private void sendCurrentState(@NonNull ControllerAlwaysOnStateCallback callback) {
-        synchronized (this) {
-            Executor executor = mCallbackMap.get(callback);
-
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                executor.execute(() -> callback.onStateChanged(
-                        mCurrentState));
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-    }
-
-    @Override
-    public void onControllerAlwaysOnStateChanged(boolean isEnabled) {
-        synchronized (this) {
-            mCurrentState = isEnabled;
-            for (ControllerAlwaysOnStateCallback cb : mCallbackMap.keySet()) {
-                sendCurrentState(cb);
-            }
-        }
-    }
-}
-
diff --git a/core/java/android/nfc/TEST_MAPPING b/core/java/android/nfc/TEST_MAPPING
new file mode 100644
index 0000000..71ad687
--- /dev/null
+++ b/core/java/android/nfc/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "NfcManagerTests"
+    }
+  ]
+}
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 16d041a..d026e95 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -34,6 +34,9 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Java proxy for a native IBinder object.
@@ -262,27 +265,45 @@
                 Log.e(Binder.TAG, "RemoteException while disabling app freezer");
             }
 
-            for (WeakReference<BinderProxy> weakRef : proxiesToQuery) {
-                BinderProxy bp = weakRef.get();
-                String key;
-                if (bp == null) {
-                    key = "<cleared weak-ref>";
-                } else {
-                    try {
-                        key = bp.getInterfaceDescriptor();
-                        if ((key == null || key.isEmpty()) && !bp.isBinderAlive()) {
-                            key = "<proxy to dead node>";
+            // We run the dump on a separate thread, because there are known cases where
+            // a process overrides getInterfaceDescriptor() and somehow blocks on it, causing
+            // the calling thread (usually AMS) to hit the watchdog.
+            // Do the dumping on a separate thread instead, and give up after a while.
+            ExecutorService executorService = Executors.newSingleThreadExecutor();
+            executorService.submit(() -> {
+                for (WeakReference<BinderProxy> weakRef : proxiesToQuery) {
+                    BinderProxy bp = weakRef.get();
+                    String key;
+                    if (bp == null) {
+                        key = "<cleared weak-ref>";
+                    } else {
+                        try {
+                            key = bp.getInterfaceDescriptor();
+                            if ((key == null || key.isEmpty()) && !bp.isBinderAlive()) {
+                                key = "<proxy to dead node>";
+                            }
+                        } catch (Throwable t) {
+                            key = "<exception during getDescriptor>";
                         }
-                    } catch (Throwable t) {
-                        key = "<exception during getDescriptor>";
+                    }
+                    Integer i = counts.get(key);
+                    if (i == null) {
+                        counts.put(key, 1);
+                    } else {
+                        counts.put(key, i + 1);
                     }
                 }
-                Integer i = counts.get(key);
-                if (i == null) {
-                    counts.put(key, 1);
-                } else {
-                    counts.put(key, i + 1);
+            });
+
+            try {
+                executorService.shutdown();
+                boolean dumpDone = executorService.awaitTermination(20, TimeUnit.SECONDS);
+                if (!dumpDone) {
+                    Log.e(Binder.TAG, "Failed to complete binder proxy dump,"
+                            + " dumping what we have so far.");
                 }
+            } catch (InterruptedException e) {
+                // Ignore
             }
             try {
                 ActivityManager.getService().enableAppFreezer(true);
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 1a40f06..17c90d6 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -886,6 +886,24 @@
     }
 
     /**
+     * @param micMuted whether to consider the microphone muted when retrieving audio ops
+     * @return A list of permission groups currently or recently used by all apps by all users in
+     * the current profile group.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
+    public List<PermGroupUsage> getIndicatorAppOpUsageData(boolean micMuted) {
+        // Lazily initialize the usage helper
+        if (mUsageHelper == null) {
+            mUsageHelper = new PermissionUsageHelper(mContext);
+        }
+        return mUsageHelper.getOpUsageData(micMuted);
+    }
+
+    /**
      * Determine if a package should be shown in indicators. Only a select few roles, and the
      * system app itself, are hidden. These values are updated at most every 15 seconds.
      * @hide
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index d89c29a..87fb611 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -37,6 +37,7 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
+import android.graphics.BLASTBufferQueue;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
@@ -208,8 +209,8 @@
         int mCurHeight;
         float mZoom = 0f;
         int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-        int mWindowPrivateFlags =
-                WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS;
+        int mWindowPrivateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS
+                | WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
         int mCurWindowFlags = mWindowFlags;
         int mCurWindowPrivateFlags = mWindowPrivateFlags;
         Rect mPreviewSurfacePosition;
@@ -253,6 +254,7 @@
         private int mDisplayState;
 
         SurfaceControl mSurfaceControl = new SurfaceControl();
+        BLASTBufferQueue mBlastBufferQueue;
 
         final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() {
             {
@@ -974,7 +976,14 @@
                             View.VISIBLE, 0, -1, mWinFrames, mMergedConfiguration, mSurfaceControl,
                             mInsetsState, mTempControls, mSurfaceSize);
                     if (mSurfaceControl.isValid()) {
-                        mSurfaceHolder.mSurface.copyFrom(mSurfaceControl);
+                        Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x,
+                                mSurfaceSize.y, mFormat);
+                        // If blastSurface == null that means it hasn't changed since the last
+                        // time we called. In this situation, avoid calling transferFrom as we
+                        // would then inc the generation ID and cause EGL resources to be recreated.
+                        if (blastSurface != null) {
+                            mSurfaceHolder.mSurface.transferFrom(blastSurface);
+                        }
                     }
                     if (!mLastSurfaceSize.equals(mSurfaceSize)) {
                         mLastSurfaceSize.set(mSurfaceSize.x, mSurfaceSize.y);
@@ -1455,13 +1464,12 @@
                 return;
             }
             Surface surface = mSurfaceHolder.getSurface();
-            boolean widthIsLarger =
-                    mSurfaceControl.getWidth() > mSurfaceControl.getHeight();
-            int smaller = widthIsLarger ? mSurfaceControl.getWidth()
-                    : mSurfaceControl.getHeight();
+            boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y;
+            int smaller = widthIsLarger ? mSurfaceSize.x
+                    : mSurfaceSize.y;
             float ratio = (float) MIN_BITMAP_SCREENSHOT_WIDTH / (float) smaller;
-            int width = (int) (ratio * mSurfaceControl.getWidth());
-            int height = (int) (ratio * mSurfaceControl.getHeight());
+            int width = (int) (ratio * mSurfaceSize.x);
+            int height = (int) (ratio * mSurfaceSize.y);
             if (width <= 0 || height <= 0) {
                 Log.e(TAG, "wrong width and height values of bitmap " + width + " " + height);
                 return;
@@ -1842,6 +1850,21 @@
             public void onDisplayAdded(int displayId) {
             }
         };
+
+        private Surface getOrCreateBLASTSurface(int width, int height, int format) {
+            Surface ret = null;
+            if (mBlastBufferQueue == null) {
+                mBlastBufferQueue = new BLASTBufferQueue("Wallpaper", mSurfaceControl, width,
+                        height, format);
+                // We only return the Surface the first time, as otherwise
+                // it hasn't changed and there is no need to update.
+                ret = mBlastBufferQueue.createSurface();
+            } else {
+                mBlastBufferQueue.update(mSurfaceControl, width, height, format);
+            }
+
+            return ret;
+        }
     }
 
     private boolean isValid(RectF area) {
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 340fa40..4b18c5a 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -796,13 +796,14 @@
     /**
      * Notify {@link PhysicalChannelConfig} has changed for a specific subscription.
      *
+     * @param slotIndex for which physical channel configs changed.
      * @param subId the subId
      * @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel.
      */
-    public void notifyPhysicalChannelConfigForSubscriber(
-            int subId, List<PhysicalChannelConfig> configs) {
+    public void notifyPhysicalChannelConfigForSubscriber(int slotIndex, int subId,
+            List<PhysicalChannelConfig> configs) {
         try {
-            sRegistry.notifyPhysicalChannelConfigForSubscriber(subId, configs);
+            sRegistry.notifyPhysicalChannelConfigForSubscriber(slotIndex, subId, configs);
         } catch (RemoteException ex) {
             // system server crash
         }
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 0a3963d..be172f7 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -693,71 +693,79 @@
         ThreadedRenderer.setFPSDivisor(divisor);
     }
 
+    private void traceMessage(String msg) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, msg);
+        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+    }
+
     void doFrame(long frameTimeNanos, int frame,
             DisplayEventReceiver.VsyncEventData vsyncEventData) {
         final long startNanos;
         final long frameIntervalNanos = vsyncEventData.frameInterval;
-        synchronized (mLock) {
-            if (!mFrameScheduled) {
-                return; // no work to do
-            }
-
-            if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
-                mDebugPrintNextFrameTimeDelta = false;
-                Log.d(TAG, "Frame time delta: "
-                        + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
-            }
-
-            long intendedFrameTimeNanos = frameTimeNanos;
-            startNanos = System.nanoTime();
-            final long jitterNanos = startNanos - frameTimeNanos;
-            if (jitterNanos >= frameIntervalNanos) {
-                final long skippedFrames = jitterNanos / frameIntervalNanos;
-                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
-                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
-                            + "The application may be doing too much work on its main thread.");
-                }
-                final long lastFrameOffset = jitterNanos % frameIntervalNanos;
-                if (DEBUG_JANK) {
-                    Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
-                            + "which is more than the frame interval of "
-                            + (frameIntervalNanos * 0.000001f) + " ms!  "
-                            + "Skipping " + skippedFrames + " frames and setting frame "
-                            + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
-                }
-                frameTimeNanos = startNanos - lastFrameOffset;
-            }
-
-            if (frameTimeNanos < mLastFrameTimeNanos) {
-                if (DEBUG_JANK) {
-                    Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
-                            + "previously skipped frame.  Waiting for next vsync.");
-                }
-                scheduleVsyncLocked();
-                return;
-            }
-
-            if (mFPSDivisor > 1) {
-                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
-                if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
-                    scheduleVsyncLocked();
-                    return;
-                }
-            }
-
-            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
-                    vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
-            mFrameScheduled = false;
-            mLastFrameTimeNanos = frameTimeNanos;
-            mLastFrameIntervalNanos = frameIntervalNanos;
-            mLastVsyncEventData = vsyncEventData;
-        }
-
         try {
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                 Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                         "Choreographer#doFrame " + vsyncEventData.id);
             }
+            synchronized (mLock) {
+                if (!mFrameScheduled) {
+                    traceMessage("Frame not scheduled");
+                    return; // no work to do
+                }
+
+                if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
+                    mDebugPrintNextFrameTimeDelta = false;
+                    Log.d(TAG, "Frame time delta: "
+                            + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
+                }
+
+                long intendedFrameTimeNanos = frameTimeNanos;
+                startNanos = System.nanoTime();
+                final long jitterNanos = startNanos - frameTimeNanos;
+                if (jitterNanos >= frameIntervalNanos) {
+                    final long skippedFrames = jitterNanos / frameIntervalNanos;
+                    if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
+                        Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
+                                + "The application may be doing too much work on its main thread.");
+                    }
+                    final long lastFrameOffset = jitterNanos % frameIntervalNanos;
+                    if (DEBUG_JANK) {
+                        Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+                                + "which is more than the frame interval of "
+                                + (frameIntervalNanos * 0.000001f) + " ms!  "
+                                + "Skipping " + skippedFrames + " frames and setting frame "
+                                + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
+                    }
+                    frameTimeNanos = startNanos - lastFrameOffset;
+                }
+
+                if (frameTimeNanos < mLastFrameTimeNanos) {
+                    if (DEBUG_JANK) {
+                        Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
+                                + "previously skipped frame.  Waiting for next vsync.");
+                    }
+                    traceMessage("Frame time goes backward");
+                    scheduleVsyncLocked();
+                    return;
+                }
+
+                if (mFPSDivisor > 1) {
+                    long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
+                    if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
+                        traceMessage("Frame skipped due to FPSDivisor");
+                        scheduleVsyncLocked();
+                        return;
+                    }
+                }
+
+                mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
+                        vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
+                mFrameScheduled = false;
+                mLastFrameTimeNanos = frameTimeNanos;
+                mLastFrameIntervalNanos = frameIntervalNanos;
+                mLastVsyncEventData = vsyncEventData;
+            }
+
             AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
 
             mFrameInfo.markInputHandlingStart();
@@ -870,7 +878,12 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void scheduleVsyncLocked() {
-        mDisplayEventReceiver.scheduleVsync();
+        try {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
+            mDisplayEventReceiver.scheduleVsync();
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
     }
 
     private boolean isRunningOnLooperThreadLocked() {
@@ -967,32 +980,40 @@
         @Override
         public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
                 VsyncEventData vsyncEventData) {
-            // Post the vsync event to the Handler.
-            // The idea is to prevent incoming vsync events from completely starving
-            // the message queue.  If there are no messages in the queue with timestamps
-            // earlier than the frame time, then the vsync event will be processed immediately.
-            // Otherwise, messages that predate the vsync event will be handled first.
-            long now = System.nanoTime();
-            if (timestampNanos > now) {
-                Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
-                        + " ms in the future!  Check that graphics HAL is generating vsync "
-                        + "timestamps using the correct timebase.");
-                timestampNanos = now;
-            }
+            try {
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+                            "Choreographer#onVsync " + vsyncEventData.id);
+                }
+                // Post the vsync event to the Handler.
+                // The idea is to prevent incoming vsync events from completely starving
+                // the message queue.  If there are no messages in the queue with timestamps
+                // earlier than the frame time, then the vsync event will be processed immediately.
+                // Otherwise, messages that predate the vsync event will be handled first.
+                long now = System.nanoTime();
+                if (timestampNanos > now) {
+                    Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+                            + " ms in the future!  Check that graphics HAL is generating vsync "
+                            + "timestamps using the correct timebase.");
+                    timestampNanos = now;
+                }
 
-            if (mHavePendingVsync) {
-                Log.w(TAG, "Already have a pending vsync event.  There should only be "
-                        + "one at a time.");
-            } else {
-                mHavePendingVsync = true;
-            }
+                if (mHavePendingVsync) {
+                    Log.w(TAG, "Already have a pending vsync event.  There should only be "
+                            + "one at a time.");
+                } else {
+                    mHavePendingVsync = true;
+                }
 
-            mTimestampNanos = timestampNanos;
-            mFrame = frame;
-            mLastVsyncEventData = vsyncEventData;
-            Message msg = Message.obtain(mHandler, this);
-            msg.setAsynchronous(true);
-            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
+                mTimestampNanos = timestampNanos;
+                mFrame = frame;
+                mLastVsyncEventData = vsyncEventData;
+                Message msg = Message.obtain(mHandler, this);
+                msg.setAsynchronous(true);
+                mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
         }
 
         @Override
diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java
index a6d786e..5fcb011 100644
--- a/core/java/android/view/ScrollCaptureConnection.java
+++ b/core/java/android/view/ScrollCaptureConnection.java
@@ -185,7 +185,8 @@
             }
             Log.w(TAG, "close(): capture session still active! Ending now.");
             // -> UiThread
-            mUiThread.execute(() -> mLocal.onScrollCaptureEnd(() -> { /* ignore */ }));
+            final ScrollCaptureCallback callback = mLocal;
+            mUiThread.execute(() -> callback.onScrollCaptureEnd(() -> { /* ignore */ }));
             mActive = false;
         }
         mActive = false;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 21f75d4..2c81e89 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -35,6 +35,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
 import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
@@ -188,6 +189,10 @@
             IBinder displayToken, int mode);
     private static native void nativeReparent(long transactionObj, long nativeObject,
             long newParentNativeObject);
+    private static native void nativeSetBuffer(long transactionObj, long nativeObject,
+            GraphicBuffer buffer);
+    private static native void nativeSetColorSpace(long transactionObj, long nativeObject,
+            int colorSpace);
 
     private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
 
@@ -3362,6 +3367,31 @@
             return this;
         }
 
+        /**
+         * Set a buffer for a SurfaceControl. This can only be used for SurfaceControls that were
+         * created as type {@link #FX_SURFACE_BLAST}
+         *
+         * @hide
+         */
+        public Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) {
+            checkPreconditions(sc);
+            nativeSetBuffer(mNativeObject, sc.mNativeObject, buffer);
+            return this;
+        }
+
+        /**
+         * Set the color space for the SurfaceControl. The supported color spaces are SRGB
+         * and Display P3, other color spaces will be treated as SRGB. This can only be used for
+         * SurfaceControls that were created as type {@link #FX_SURFACE_BLAST}
+         *
+         * @hide
+         */
+        public Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) {
+            checkPreconditions(sc);
+            nativeSetColorSpace(mNativeObject, sc.mNativeObject, colorSpace.getId());
+            return this;
+        }
+
          /**
          * Merge the other transaction into this transaction, clearing the
          * other transaction as if it had been applied.
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 2b96a14..7bdf5cf 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -30,6 +30,7 @@
 import android.graphics.BlendMode;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.HardwareRenderer;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
@@ -221,8 +222,35 @@
 
     private int mPendingReportDraws;
 
-    private SurfaceControl.Transaction mRtTransaction = new SurfaceControl.Transaction();
-    private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+    /**
+     * Transaction that should be used from the render thread. This transaction is only thread safe
+     * with other calls directly from the render thread.
+     */
+    private final SurfaceControl.Transaction mRtTransaction = new SurfaceControl.Transaction();
+
+    /**
+     * Transaction that should be used whe
+     * {@link HardwareRenderer.FrameDrawingCallback#onFrameDraw} is invoked. All
+     * frame callbacks can use the same transaction since they will be thread safe
+     */
+    private final SurfaceControl.Transaction mFrameCallbackTransaction =
+            new SurfaceControl.Transaction();
+
+    /**
+     * Transaction that should be used for
+     * {@link RenderNode.PositionUpdateListener#positionChanged(long, int, int, int, int)}
+     * The callback is invoked from a thread pool so it's not thread safe with other render thread
+     * transactions. Keep the transactions for position changed callbacks on its own transaction.
+     */
+    private final SurfaceControl.Transaction mPositionChangedTransaction =
+            new SurfaceControl.Transaction();
+
+    /**
+     * A temporary transaction holder that should only be used when applying right away. There
+     * should be no assumption about thread safety for this transaction.
+     */
+    private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+
     private int mParentSurfaceSequenceId;
 
     private RemoteAccessibilityController mRemoteAccessibilityController =
@@ -432,7 +460,6 @@
                  * This gets called on a RenderThread worker thread, so members accessed here must
                  * be protected by a lock.
                  */
-                final boolean useBLAST = useBLASTSync(viewRoot);
                 viewRoot.registerRtFrameCallback(frame -> {
                     try {
                         synchronized (mSurfaceControlLock) {
@@ -456,8 +483,9 @@
                                 Log.d(TAG, System.identityHashCode(this)
                                         + " updateSurfaceAlpha RT: set alpha=" + alpha);
                             }
-                            mRtTransaction.setAlpha(mSurfaceControl, alpha);
-                            applyRtTransaction(frame);
+
+                            mFrameCallbackTransaction.setAlpha(mSurfaceControl, alpha);
+                            applyOrMergeTransaction(mFrameCallbackTransaction, frame);
                         }
                         // It's possible that mSurfaceControl is released in the UI thread before
                         // the transaction completes. If that happens, an exception is thrown, which
@@ -806,7 +834,6 @@
          * This gets called on a RenderThread worker thread, so members accessed here must
          * be protected by a lock.
          */
-        final boolean useBLAST = useBLASTSync(viewRoot);
         viewRoot.registerRtFrameCallback(frame -> {
             try {
                 synchronized (mSurfaceControlLock) {
@@ -814,8 +841,8 @@
                         return;
                     }
 
-                    updateRelativeZ(mRtTransaction);
-                    applyRtTransaction(frame);
+                    updateRelativeZ(mFrameCallbackTransaction);
+                    applyOrMergeTransaction(mFrameCallbackTransaction, frame);
                 }
                 // It's possible that mSurfaceControl is released in the UI thread before
                 // the transaction completes. If that happens, an exception is thrown, which
@@ -1380,22 +1407,21 @@
         return mRTLastReportedPosition;
     }
 
-    private void setParentSpaceRectangle(Rect position, long frameNumber) {
+    private void setParentSpaceRectangle(Rect position, long frameNumber, Transaction t) {
         final ViewRootImpl viewRoot = getViewRootImpl();
-        applySurfaceTransforms(mSurfaceControl, mRtTransaction, position);
-        applyChildSurfaceTransaction_renderWorker(mRtTransaction, viewRoot.mSurface, frameNumber);
-        applyRtTransaction(frameNumber);
+        applySurfaceTransforms(mSurfaceControl, t, position);
+        applyChildSurfaceTransaction_renderWorker(t, viewRoot.mSurface, frameNumber);
+        applyOrMergeTransaction(t, frameNumber);
     }
 
-    private void applyRtTransaction(long frameNumber) {
+    private void applyOrMergeTransaction(Transaction t, long frameNumber) {
         final ViewRootImpl viewRoot = getViewRootImpl();
         boolean useBLAST = viewRoot != null && useBLASTSync(viewRoot);
         if (useBLAST) {
             // If we are using BLAST, merge the transaction with the viewroot buffer transaction.
-            viewRoot.mergeWithNextTransaction(mRtTransaction, frameNumber);
-            return;
+            viewRoot.mergeWithNextTransaction(t, frameNumber);
         } else {
-            mRtTransaction.apply();
+            t.apply();
         }
     }
 
@@ -1436,7 +1462,8 @@
                             left, top, right, bottom));
                 }
                 mRTLastReportedPosition.set(left, top, right, bottom);
-                setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
+                setParentSpaceRectangle(mRTLastReportedPosition, frameNumber,
+                        mPositionChangedTransaction);
                 // Now overwrite mRTLastReportedPosition with our values
             } catch (Exception ex) {
                 Log.e(TAG, "Exception from repositionChild", ex);
@@ -1448,7 +1475,7 @@
                 float bottom, float vecX, float vecY, float maxStretch) {
             mRtTransaction.setStretchEffect(mSurfaceControl, left, top, right, bottom, vecX, vecY,
                     maxStretch);
-            applyRtTransaction(frameNumber);
+            applyOrMergeTransaction(mRtTransaction, frameNumber);
         }
 
         @Override
@@ -1468,14 +1495,12 @@
              * need to hold the lock here.
              */
             synchronized (mSurfaceControlLock) {
-                final ViewRootImpl viewRoot = getViewRootImpl();
-
                 mRtTransaction.hide(mSurfaceControl);
                 if (mRtReleaseSurfaces) {
                     mRtReleaseSurfaces = false;
                     releaseSurfaces(mRtTransaction);
                 }
-                applyRtTransaction(frameNumber);
+                applyOrMergeTransaction(mRtTransaction, frameNumber);
                 mRtHandlingPositionUpdates = false;
             }
         }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 76eb882..be8e519 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -9309,11 +9309,11 @@
      * Handles an inbound request for scroll capture from the system. A search will be
      * dispatched through the view tree to locate scrolling content.
      * <p>
-     * A call to {@link IScrollCaptureCallbacks#onScrollCaptureResponse(ScrollCaptureResponse)}
-     * will follow.
+     * A call to
+     * {@link IScrollCaptureResponseListener#onScrollCaptureResponse} will follow.
      *
      * @param listener to receive responses
-     * @see ScrollCaptureTargetSelector
+     * @see ScrollCaptureSearchResults
      */
     public void handleScrollCaptureRequest(@NonNull IScrollCaptureResponseListener listener) {
         ScrollCaptureSearchResults results =
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 616910a..d6292ca 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -581,7 +581,7 @@
      */
     public void reportPerceptible(IBinder windowToken, boolean perceptible) {
         try {
-            mService.reportPerceptible(windowToken, perceptible);
+            mService.reportPerceptibleAsync(windowToken, perceptible);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index 20e520e..4365966 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -30,7 +30,7 @@
  */
 oneway interface IInputMethodPrivilegedOperations {
     void setImeWindowStatusAsync(int vis, int backDisposition);
-    void reportStartInput(in IBinder startInputToken, in IVoidResultCallback resultCallback);
+    void reportStartInputAsync(in IBinder startInputToken);
     void createInputContentUriToken(in Uri contentUri, in String packageName,
             in IIInputContentUriTokenResultCallback resultCallback);
     void reportFullscreenMode(boolean fullscreen, in IVoidResultCallback resultCallback);
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 1000914..555488d 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -123,21 +123,18 @@
     }
 
     /**
-     * Calls {@link IInputMethodPrivilegedOperations#reportStartInput(IBinder,
-     * IVoidResultCallback)}.
+     * Calls {@link IInputMethodPrivilegedOperations#reportStartInputAsync(IBinder)}.
      *
      * @param startInputToken {@link IBinder} token to distinguish startInput session
      */
     @AnyThread
-    public void reportStartInput(IBinder startInputToken) {
+    public void reportStartInputAsync(IBinder startInputToken) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
             return;
         }
         try {
-            final Completable.Void value = Completable.createVoid();
-            ops.reportStartInput(startInputToken, ResultCallbacks.of(value));
-            Completable.getResult(value);
+            ops.reportStartInputAsync(startInputToken);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index e438d39..a0a0f32 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -92,7 +92,7 @@
     void notifyRegistrationFailed(int slotIndex, int subId, in CellIdentity cellIdentity,
             String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
     void notifyBarringInfoChanged(int slotIndex, int subId, in BarringInfo barringInfo);
-    void notifyPhysicalChannelConfigForSubscriber(in int subId,
+    void notifyPhysicalChannelConfigForSubscriber(in int phoneId, in int subId,
             in List<PhysicalChannelConfig> configs);
     void notifyDataEnabled(in int phoneId, int subId, boolean enabled, int reason);
     void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType);
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index fd13c26b..93cd4e9 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -85,7 +85,7 @@
     oneway void reportActivityView(in IInputMethodClient parentClient, int childDisplayId,
             in float[] matrixValues, in IVoidResultCallback resultCallback);
 
-    oneway void reportPerceptible(in IBinder windowToken, boolean perceptible);
+    oneway void reportPerceptibleAsync(in IBinder windowToken, boolean perceptible);
     /** Remove the IME surface. Requires INTERNAL_SYSTEM_WINDOW permission. */
     oneway void removeImeSurface(in IVoidResultCallback resultCallback);
     /** Remove the IME surface. Requires passing the currently focused window. */
diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
index a41511b..8aa2d57 100644
--- a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
+++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
@@ -17,6 +17,7 @@
 package com.android.internal.view;
 
 import android.annotation.UiThread;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.graphics.HardwareRenderer;
@@ -26,6 +27,7 @@
 import android.graphics.RectF;
 import android.graphics.RenderNode;
 import android.os.CancellationSignal;
+import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Display.ColorMode;
@@ -53,11 +55,14 @@
 
     private static final String TAG = "ScrollCaptureViewSupport";
 
-    private static final boolean WAIT_FOR_ANIMATION = true;
+    private static final String SETTING_CAPTURE_DELAY = "screenshot.scroll_capture_delay";
+    private static final long SETTING_CAPTURE_DELAY_DEFAULT = 60L; // millis
 
     private final WeakReference<V> mWeakView;
     private final ScrollCaptureViewHelper<V> mViewHelper;
     private final ViewRenderer mRenderer;
+    private final long mPostScrollDelayMillis;
+
     private boolean mStarted;
     private boolean mEnded;
 
@@ -66,6 +71,10 @@
         mRenderer = new ViewRenderer();
         // TODO(b/177649144): provide access to color space from android.media.Image
         mViewHelper = viewHelper;
+        Context context = containingView.getContext();
+        ContentResolver contentResolver = context.getContentResolver();
+        mPostScrollDelayMillis = Settings.Global.getLong(contentResolver,
+                SETTING_CAPTURE_DELAY, SETTING_CAPTURE_DELAY_DEFAULT);
     }
 
     /** Based on ViewRootImpl#updateColorModeIfNeeded */
@@ -120,37 +129,41 @@
     public final void onScrollCaptureImageRequest(ScrollCaptureSession session,
             CancellationSignal signal, Rect requestRect, Consumer<Rect> onComplete) {
         if (signal.isCanceled()) {
+            Log.w(TAG, "onScrollCaptureImageRequest: cancelled!");
             return;
         }
+
         V view = mWeakView.get();
         if (view == null || !view.isVisibleToUser()) {
             // Signal to the controller that we have a problem and can't continue.
             onComplete.accept(new Rect());
             return;
         }
+
         // Ask the view to scroll as needed to bring this area into view.
         ScrollResult scrollResult = mViewHelper.onScrollRequested(view, session.getScrollBounds(),
                 requestRect);
+
         if (scrollResult.availableArea.isEmpty()) {
             onComplete.accept(scrollResult.availableArea);
             return;
         }
-        view.invalidate(); // don't wait for vsync
 
         // For image capture, shift back by scrollDelta to arrive at the location within the view
         // where the requested content will be drawn
         Rect viewCaptureArea = new Rect(scrollResult.availableArea);
         viewCaptureArea.offset(0, -scrollResult.scrollDelta);
 
-        if (WAIT_FOR_ANIMATION) {
-            view.postOnAnimation(() ->  {
+        Runnable captureAction = () -> {
+            if (signal.isCanceled()) {
+                Log.w(TAG, "onScrollCaptureImageRequest: cancelled! skipping render.");
+            } else {
                 mRenderer.renderView(view, viewCaptureArea);
                 onComplete.accept(new Rect(scrollResult.availableArea));
-            });
-        } else {
-            mRenderer.renderView(view, viewCaptureArea);
-            onComplete.accept(new Rect(scrollResult.availableArea));
-        }
+            }
+        };
+
+        view.postOnAnimationDelayed(captureAction, mPostScrollDelayMillis);
     }
 
     @Override
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index ffba628..4194acb 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -30,6 +30,7 @@
 #include <android/hardware/display/IDeviceProductInfoConstants.h>
 #include <android/os/IInputConstants.h>
 #include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/android_graphics_GraphicBuffer.h>
 #include <android_runtime/android_hardware_HardwareBuffer.h>
 #include <android_runtime/android_view_Surface.h>
 #include <android_runtime/android_view_SurfaceSession.h>
@@ -253,6 +254,15 @@
     }
 }
 
+constexpr ui::Dataspace fromNamedColorSpaceValueToDataspace(const jint colorSpace) {
+    switch (colorSpace) {
+        case JNamedColorSpace::DISPLAY_P3:
+            return ui::Dataspace::DISPLAY_P3;
+        default:
+            return ui::Dataspace::V0_SRGB;
+    }
+}
+
 constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) {
     switch (colorMode) {
         case ui::ColorMode::DISPLAY_P3:
@@ -553,6 +563,23 @@
     transaction->setGeometry(ctrl, source, dst, orientation);
 }
 
+static void nativeSetBuffer(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+                            jobject bufferObject) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    sp<GraphicBuffer> buffer(
+            android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, bufferObject));
+    transaction->setBuffer(ctrl, buffer);
+}
+
+static void nativeSetColorSpace(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+                                jint colorSpace) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    ui::Dataspace dataspace = fromNamedColorSpaceValueToDataspace(colorSpace);
+    transaction->setDataspace(ctrl, dataspace);
+}
+
 static void nativeSetBlurRegions(JNIEnv* env, jclass clazz, jlong transactionObj,
                                  jlong nativeObject, jobjectArray regions, jint regionsLength) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -1877,6 +1904,10 @@
             (void*)nativeGetDisplayedContentSample },
     {"nativeSetGeometry", "(JJLandroid/graphics/Rect;Landroid/graphics/Rect;J)V",
             (void*)nativeSetGeometry },
+    {"nativeSetBuffer", "(JJLandroid/graphics/GraphicBuffer;)V",
+            (void*)nativeSetBuffer },
+    {"nativeSetColorSpace", "(JJI)V",
+            (void*)nativeSetColorSpace },
     {"nativeSyncInputWindows", "(J)V",
             (void*)nativeSyncInputWindows },
     {"nativeGetDisplayBrightnessSupport", "(Landroid/os/IBinder;)Z",
diff --git a/core/proto/android/internal/OWNERS b/core/proto/android/internal/OWNERS
new file mode 100644
index 0000000..24e24c2
--- /dev/null
+++ b/core/proto/android/internal/OWNERS
@@ -0,0 +1,2 @@
+# Binder
+per-file binder_latency.proto = file:/core/java/com/android/internal/os/BINDER_OWNERS
\ No newline at end of file
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index a7127ad..b157146 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -308,6 +308,7 @@
     optional float minimize_amount = 27;
     optional bool created_by_organizer = 28;
     optional string affinity = 29;
+    optional bool has_child_pip_activity = 30;
 }
 
 /* represents ActivityRecordProto */
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 419f142..f24d663 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4700,6 +4700,11 @@
      -->
     <color name="config_letterboxBackgroundColor">#000</color>
 
+    <!-- Horizonal position of a center of the letterboxed app window.
+        0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
+        or > 1, it is ignored and central positionis used (0.5). -->
+    <item name="config_letterboxHorizontalPositionMultiplier" format="float" type="dimen">0.5</item>
+
     <!-- If true, hide the display cutout with display area -->
     <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d69c7f8..5715fab 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4186,6 +4186,7 @@
   <java-symbol type="dimen" name="config_letterboxBackgroundWallaperDarkScrimAlpha" />
   <java-symbol type="integer" name="config_letterboxBackgroundType" />
   <java-symbol type="color" name="config_letterboxBackgroundColor" />
+  <java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
 
   <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
 
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index fb0dd46..b2b9ab3 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -93,8 +93,8 @@
 
     @Test
     public void testComputeForPassword_metrics() {
-        final PasswordMetrics metrics =
-                PasswordMetrics.computeForPassword("6B~0z1Z3*8A".getBytes());
+        final PasswordMetrics metrics = PasswordMetrics.computeForPasswordOrPin(
+                "6B~0z1Z3*8A".getBytes(), /* isPin */ false);
         assertEquals(11, metrics.length);
         assertEquals(4, metrics.letters);
         assertEquals(3, metrics.upperCase);
@@ -133,61 +133,71 @@
     @Test
     public void testDetermineComplexity_lowNumeric() {
         assertEquals(PASSWORD_COMPLEXITY_LOW,
-                PasswordMetrics.computeForPassword("1234".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("1234".getBytes(),
+                        /* isPin */true).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_lowNumericComplex() {
         assertEquals(PASSWORD_COMPLEXITY_LOW,
-                PasswordMetrics.computeForPassword("124".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("124".getBytes(),
+                        /* isPin */ true).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_lowAlphabetic() {
         assertEquals(PASSWORD_COMPLEXITY_LOW,
-                PasswordMetrics.computeForPassword("a!".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("a!".getBytes(),
+                        /* isPin */ false).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_lowAlphanumeric() {
         assertEquals(PASSWORD_COMPLEXITY_LOW,
-                PasswordMetrics.computeForPassword("a!1".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("a!1".getBytes(),
+                        /* isPin */ false).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_mediumNumericComplex() {
         assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
-                PasswordMetrics.computeForPassword("1238".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("1238".getBytes(),
+                        /* isPin */ true).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_mediumAlphabetic() {
         assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
-                PasswordMetrics.computeForPassword("ab!c".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("ab!c".getBytes(),
+                        /* isPin */ false).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_mediumAlphanumeric() {
         assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
-                PasswordMetrics.computeForPassword("ab!1".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("ab!1".getBytes(),
+                        /* isPin */ false).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_highNumericComplex() {
         assertEquals(PASSWORD_COMPLEXITY_HIGH,
-                PasswordMetrics.computeForPassword("12389647!".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("12389647!".getBytes(),
+                        /* isPin */ true).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_highAlphabetic() {
         assertEquals(PASSWORD_COMPLEXITY_HIGH,
-                PasswordMetrics.computeForPassword("alphabetic!".getBytes()).determineComplexity());
+                PasswordMetrics.computeForPasswordOrPin("alphabetic!".getBytes(),
+                        /* isPin */ false).determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_highAlphanumeric() {
-        assertEquals(PASSWORD_COMPLEXITY_HIGH, PasswordMetrics.computeForPassword(
-                "alphanumeric123!".getBytes()).determineComplexity());
+        assertEquals(PASSWORD_COMPLEXITY_HIGH,
+                PasswordMetrics.computeForPasswordOrPin("alphanumeric123!".getBytes(),
+                        /* isPin */ false).determineComplexity());
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java b/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java
index e951054..f1be173 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java
@@ -28,6 +28,7 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 
 import static org.junit.Assert.assertEquals;
 
@@ -80,7 +81,7 @@
     public void testGetMinMetrics_numeric() {
         PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC);
         PasswordMetrics minMetrics = policy.getMinMetrics();
-        assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+        assertEquals(CREDENTIAL_TYPE_PIN, minMetrics.credType);
         assertEquals(TEST_VALUE, minMetrics.length);
         assertEquals(0, minMetrics.numeric); // numeric can doesn't really require digits.
         assertEquals(0, minMetrics.letters);
@@ -104,7 +105,7 @@
     public void testGetMinMetrics_numericComplex() {
         PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC_COMPLEX);
         PasswordMetrics minMetrics = policy.getMinMetrics();
-        assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+        assertEquals(CREDENTIAL_TYPE_PIN, minMetrics.credType);
         assertEquals(TEST_VALUE, minMetrics.length);
         assertEquals(0, minMetrics.numeric);
         assertEquals(0, minMetrics.letters);
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
index 56c685a..3d18337 100644
--- a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
@@ -20,6 +20,7 @@
 
 import static org.testng.Assert.expectThrows;
 
+import android.app.appsearch.exceptions.AppSearchException;
 import android.content.Context;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -89,8 +90,12 @@
                 });
 
         // Verify the NullPointException has been thrown.
-        ExecutionException executionException = expectThrows(ExecutionException.class,
-                putDocumentsFuture::get);
-        assertThat(executionException.getCause()).isInstanceOf(NullPointerException.class);
+        ExecutionException executionException =
+                expectThrows(ExecutionException.class, putDocumentsFuture::get);
+        assertThat(executionException.getCause()).isInstanceOf(AppSearchException.class);
+        AppSearchException appSearchException = (AppSearchException) executionException.getCause();
+        assertThat(appSearchException.getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_INTERNAL_ERROR);
+        assertThat(appSearchException.getMessage()).startsWith("NullPointerException");
     }
 }
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
index 22c71b52..e7b88c8 100644
--- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
+++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
@@ -32,6 +32,7 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.ICancellationSignal;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
@@ -189,4 +190,15 @@
         verifyNoMoreInteractions(mRemote);
     }
 
+    @Test
+    public void testClose_whileActive() throws RemoteException {
+        mConnection.startCapture(mSurface, mRemote);
+
+        mCallback.completeStartRequest();
+        assertTrue(mConnection.isActive());
+
+        mConnection.close();
+        mCallback.completeEndRequest();
+        assertFalse(mConnection.isActive());
+    }
 }
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
index c393d68..8225afc 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
@@ -244,7 +244,8 @@
                         PendingIntent.getActivity(
                                 context, 0, new Intent("action1"), FLAG_IMMUTABLE)))
                 .addAction(new RemoteAction(icon1, "title2", "desc2",
-                          PendingIntent.getActivity(context, 0, new Intent("action2"), 0)))
+                        PendingIntent.getActivity(context, 0, new Intent("action2"),
+                                FLAG_IMMUTABLE)))
                 .setEntityType(TextClassifier.TYPE_EMAIL, 0.5f)
                 .setEntityType(TextClassifier.TYPE_PHONE, 0.4f)
                 .build();
diff --git a/core/tests/nfctests/Android.bp b/core/tests/nfctests/Android.bp
new file mode 100644
index 0000000..335cea1
--- /dev/null
+++ b/core/tests/nfctests/Android.bp
@@ -0,0 +1,38 @@
+// Copyright 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "NfcManagerTests",
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+    ],
+    libs: [
+        "android.test.runner",
+    ],
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
+    certificate: "platform",
+    test_suites: ["device-tests"],
+}
diff --git a/core/tests/nfctests/AndroidManifest.xml b/core/tests/nfctests/AndroidManifest.xml
new file mode 100644
index 0000000..99e2c34c
--- /dev/null
+++ b/core/tests/nfctests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.nfc">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!-- This is a self-instrumenting test package. -->
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.nfc"
+                     android:label="NFC Manager Tests">
+    </instrumentation>
+
+</manifest>
+
diff --git a/core/tests/nfctests/AndroidTest.xml b/core/tests/nfctests/AndroidTest.xml
new file mode 100644
index 0000000..490d6f5
--- /dev/null
+++ b/core/tests/nfctests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 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.
+-->
+<configuration description="Config for NFC Manager test cases">
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-suite-tag" value="apct-instrumentation"/>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="NfcManagerTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-tag" value="NfcManagerTests"/>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.nfc" />
+        <option name="hidden-api-checks" value="false"/>
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+    </test>
+</configuration>
diff --git a/core/tests/nfctests/OWNERS b/core/tests/nfctests/OWNERS
new file mode 100644
index 0000000..34b095c
--- /dev/null
+++ b/core/tests/nfctests/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/nfc/OWNERS
diff --git a/core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java b/core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
new file mode 100644
index 0000000..43f9b6f
--- /dev/null
+++ b/core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 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 android.nfc;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.nfc.NfcAdapter.ControllerAlwaysOnListener;
+import android.os.RemoteException;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Test of {@link NfcControllerAlwaysOnListener}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NfcControllerAlwaysOnListenerTest {
+
+    private INfcAdapter mNfcAdapter = mock(INfcAdapter.class);
+
+    private Throwable mThrowRemoteException = new RemoteException("RemoteException");
+
+    private static Executor getExecutor() {
+        return new Executor() {
+            @Override
+            public void execute(Runnable command) {
+                command.run();
+            }
+        };
+    }
+
+    private static void verifyListenerInvoked(ControllerAlwaysOnListener listener) {
+        verify(listener, times(1)).onControllerAlwaysOnChanged(anyBoolean());
+    }
+
+    @Test
+    public void testRegister_RegisterUnregister() throws RemoteException {
+        NfcControllerAlwaysOnListener mListener =
+                new NfcControllerAlwaysOnListener(mNfcAdapter);
+        ControllerAlwaysOnListener mockListener1 = mock(ControllerAlwaysOnListener.class);
+        ControllerAlwaysOnListener mockListener2 = mock(ControllerAlwaysOnListener.class);
+
+        // Verify that the state listener registered with the NFC Adapter
+        mListener.register(getExecutor(), mockListener1);
+        verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+        // Register a second client and no new call to NFC Adapter
+        mListener.register(getExecutor(), mockListener2);
+        verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+        // Unregister first listener
+        mListener.unregister(mockListener1);
+        verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+        verify(mNfcAdapter, times(0)).unregisterControllerAlwaysOnListener(any());
+
+        // Unregister second listener and the state listener registered with the NFC Adapter
+        mListener.unregister(mockListener2);
+        verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+        verify(mNfcAdapter, times(1)).unregisterControllerAlwaysOnListener(any());
+    }
+
+    @Test
+    public void testRegister_FirstRegisterFails() throws RemoteException {
+        NfcControllerAlwaysOnListener mListener =
+                new NfcControllerAlwaysOnListener(mNfcAdapter);
+        ControllerAlwaysOnListener mockListener1 = mock(ControllerAlwaysOnListener.class);
+        ControllerAlwaysOnListener mockListener2 = mock(ControllerAlwaysOnListener.class);
+
+        // Throw a remote exception whenever first registering
+        doThrow(mThrowRemoteException).when(mNfcAdapter).registerControllerAlwaysOnListener(
+                any());
+
+        mListener.register(getExecutor(), mockListener1);
+        verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+        // No longer throw an exception, instead succeed
+        doNothing().when(mNfcAdapter).registerControllerAlwaysOnListener(any());
+
+        // Register a different listener
+        mListener.register(getExecutor(), mockListener2);
+        verify(mNfcAdapter, times(2)).registerControllerAlwaysOnListener(any());
+
+        // Ensure first and second listener were invoked
+        mListener.onControllerAlwaysOnChanged(true);
+        verifyListenerInvoked(mockListener1);
+        verifyListenerInvoked(mockListener2);
+    }
+
+    @Test
+    public void testRegister_RegisterSameListenerTwice() throws RemoteException {
+        NfcControllerAlwaysOnListener mListener =
+                new NfcControllerAlwaysOnListener(mNfcAdapter);
+        ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class);
+
+        // Register the same listener Twice
+        mListener.register(getExecutor(), mockListener);
+        mListener.register(getExecutor(), mockListener);
+        verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+        // Invoke a state change and ensure the listener is only called once
+        mListener.onControllerAlwaysOnChanged(true);
+        verifyListenerInvoked(mockListener);
+    }
+
+    @Test
+    public void testNotify_AllListenersNotified() throws RemoteException {
+
+        NfcControllerAlwaysOnListener listener = new NfcControllerAlwaysOnListener(mNfcAdapter);
+        List<ControllerAlwaysOnListener> mockListeners = new ArrayList<>();
+        for (int i = 0; i < 10; i++) {
+            ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class);
+            listener.register(getExecutor(), mockListener);
+            mockListeners.add(mockListener);
+        }
+
+        // Invoke a state change and ensure all listeners are invoked
+        listener.onControllerAlwaysOnChanged(true);
+        for (ControllerAlwaysOnListener mListener : mockListeners) {
+            verifyListenerInvoked(mListener);
+        }
+    }
+
+    @Test
+    public void testStateChange_CorrectValue() {
+        runStateChangeValue(true, true);
+        runStateChangeValue(false, false);
+
+    }
+
+    private void runStateChangeValue(boolean isEnabledIn, boolean isEnabledOut) {
+        NfcControllerAlwaysOnListener listener = new NfcControllerAlwaysOnListener(mNfcAdapter);
+        ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class);
+        listener.register(getExecutor(), mockListener);
+        listener.onControllerAlwaysOnChanged(isEnabledIn);
+        verify(mockListener, times(1)).onControllerAlwaysOnChanged(isEnabledOut);
+        verify(mockListener, times(0)).onControllerAlwaysOnChanged(!isEnabledOut);
+    }
+}
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
index 6c03ddc..e9e58c6 100644
--- a/graphics/java/android/graphics/RecordingCanvas.java
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -226,10 +226,12 @@
      */
     public void drawRipple(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
             CanvasProperty<Float> radius, CanvasProperty<Paint> paint,
-            CanvasProperty<Float> progress, RuntimeShader shader) {
+            CanvasProperty<Float> progress, CanvasProperty<Float> turbulencePhase,
+            RuntimeShader shader) {
         nDrawRipple(mNativeCanvasWrapper, cx.getNativeContainer(), cy.getNativeContainer(),
                 radius.getNativeContainer(), paint.getNativeContainer(),
-                progress.getNativeContainer(), shader.getNativeShaderBuilder());
+                progress.getNativeContainer(), turbulencePhase.getNativeContainer(),
+                shader.getNativeShaderBuilder());
     }
 
     /**
@@ -290,7 +292,7 @@
             long propCy, long propRadius, long propPaint);
     @CriticalNative
     private static native void nDrawRipple(long renderer, long propCx, long propCy, long propRadius,
-            long propPaint, long propProgress, long runtimeEffect);
+            long propPaint, long propProgress, long turbulencePhase, long runtimeEffect);
     @CriticalNative
     private static native void nDrawRoundRect(long renderer, long propLeft, long propTop,
             long propRight, long propBottom, long propRx, long propRy, long propPaint);
diff --git a/graphics/java/android/graphics/drawable/RippleAnimationSession.java b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
index c317831..1cd4cf1 100644
--- a/graphics/java/android/graphics/drawable/RippleAnimationSession.java
+++ b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
@@ -40,6 +40,8 @@
     private static final String TAG = "RippleAnimationSession";
     private static final int ENTER_ANIM_DURATION = 450;
     private static final int EXIT_ANIM_DURATION = 300;
+    private static final long NOISE_ANIMATION_DURATION = 7000;
+    private static final long MAX_NOISE_PHASE = NOISE_ANIMATION_DURATION / 120;
     private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
     private static final Interpolator FAST_OUT_SLOW_IN =
             new PathInterpolator(0.4f, 0f, 0.2f, 1f);
@@ -49,7 +51,7 @@
     private Runnable mOnUpdate;
     private long mStartTime;
     private boolean mForceSoftware;
-    private boolean mAnimateSparkle;
+    private Animator mLoopAnimation;
 
     RippleAnimationSession(@NonNull AnimationProperties<Float, Paint> properties,
             boolean forceSoftware) {
@@ -88,16 +90,6 @@
         return this;
     }
 
-    public boolean shouldAnimateSparkle() {
-        return mAnimateSparkle && ValueAnimator.getDurationScale() > 0;
-    }
-
-    public float getSparklePhase() {
-        final long now = AnimationUtils.currentAnimationTimeMillis();
-        final long elapsed = now - mStartTime;
-        return  (float) elapsed / 800;
-    }
-
     private boolean isHwAccelerated(Canvas canvas) {
         return canvas.isHardwareAccelerated() && !mForceSoftware;
     }
@@ -114,7 +106,7 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
-                mAnimateSparkle = false;
+                if (mLoopAnimation != null) mLoopAnimation.cancel();
                 Consumer<RippleAnimationSession> onEnd = mOnSessionEnd;
                 if (onEnd != null) onEnd.accept(RippleAnimationSession.this);
             }
@@ -148,7 +140,7 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
-                mAnimateSparkle = false;
+                if (mLoopAnimation != null) mLoopAnimation.cancel();
                 Consumer<RippleAnimationSession> onEnd = mOnSessionEnd;
                 if (onEnd != null) onEnd.accept(RippleAnimationSession.this);
             }
@@ -167,24 +159,42 @@
         RenderNodeAnimator expand =
                 new RenderNodeAnimator(props.getProgress(), .5f);
         expand.setTarget(canvas);
-        startAnimation(expand);
+        RenderNodeAnimator loop = new RenderNodeAnimator(props.getNoisePhase(), MAX_NOISE_PHASE);
+        loop.setTarget(canvas);
+        startAnimation(expand, loop);
     }
 
-    private void startAnimation(Animator expand) {
+    private void startAnimation(Animator expand, Animator loop) {
         expand.setDuration(ENTER_ANIM_DURATION);
         expand.addListener(new AnimatorListener(this));
         expand.setInterpolator(FAST_OUT_SLOW_IN);
         expand.start();
-        mAnimateSparkle = true;
+        loop.setDuration(NOISE_ANIMATION_DURATION);
+        loop.addListener(new AnimatorListener(this) {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                mLoopAnimation = null;
+            }
+        });
+        loop.setInterpolator(LINEAR_INTERPOLATOR);
+        loop.start();
+        if (mLoopAnimation != null) mLoopAnimation.cancel();
+        mLoopAnimation = loop;
     }
 
     private void enterSoftware() {
         ValueAnimator expand = ValueAnimator.ofFloat(0f, 0.5f);
         expand.addUpdateListener(updatedAnimation -> {
             notifyUpdate();
-            mProperties.getShader().setProgress((Float) expand.getAnimatedValue());
+            mProperties.getShader().setProgress((float) expand.getAnimatedValue());
         });
-        startAnimation(expand);
+        ValueAnimator loop = ValueAnimator.ofFloat(0f, MAX_NOISE_PHASE);
+        loop.addUpdateListener(updatedAnimation -> {
+            notifyUpdate();
+            mProperties.getShader().setNoisePhase((float) loop.getAnimatedValue());
+        });
+        startAnimation(expand, loop);
     }
 
     @NonNull AnimationProperties<Float, Paint> getProperties() {
@@ -198,6 +208,7 @@
                     CanvasProperty.createFloat(mProperties.getX()),
                     CanvasProperty.createFloat(mProperties.getY()),
                     CanvasProperty.createFloat(mProperties.getMaxRadius()),
+                    CanvasProperty.createFloat(mProperties.getNoisePhase()),
                     CanvasProperty.createPaint(mProperties.getPaint()),
                     CanvasProperty.createFloat(mProperties.getProgress()),
                     mProperties.getShader());
@@ -236,16 +247,18 @@
     static class AnimationProperties<FloatType, PaintType> {
         private final FloatType mProgress;
         private final FloatType mMaxRadius;
+        private final FloatType mNoisePhase;
         private final PaintType mPaint;
         private final RippleShader mShader;
         private FloatType mX;
         private FloatType mY;
 
-        AnimationProperties(FloatType x, FloatType y, FloatType maxRadius,
+        AnimationProperties(FloatType x, FloatType y, FloatType maxRadius, FloatType noisePhase,
                 PaintType paint, FloatType progress, RippleShader shader) {
             mY = y;
             mX = x;
             mMaxRadius = maxRadius;
+            mNoisePhase = noisePhase;
             mPaint = paint;
             mShader = shader;
             mProgress = progress;
@@ -279,5 +292,9 @@
         RippleShader getShader() {
             return mShader;
         }
+
+        FloatType getNoisePhase() {
+            return mNoisePhase;
+        }
     }
 }
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 0865332..c972a24 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -858,15 +858,6 @@
         }
         for (int i = 0; i < mRunningAnimations.size(); i++) {
             RippleAnimationSession s = mRunningAnimations.get(i);
-            if (s.shouldAnimateSparkle()) {
-                final float phase = s.getSparklePhase();
-                if (useCanvasProps) {
-                    s.getCanvasProperties().getShader().setNoisePhase(phase);
-                } else {
-                    s.getProperties().getShader().setNoisePhase(phase);
-                }
-                invalidateSelf();
-            }
             if (useCanvasProps) {
                 RippleAnimationSession.AnimationProperties<CanvasProperty<Float>,
                         CanvasProperty<Paint>>
@@ -883,7 +874,7 @@
                     yProp = p.getY();
                 }
                 can.drawRipple(xProp, yProp, p.getMaxRadius(), p.getPaint(),
-                        p.getProgress(), p.getShader());
+                        p.getProgress(), p.getNoisePhase(), p.getShader());
             } else {
                 RippleAnimationSession.AnimationProperties<Float, Paint> p =
                         s.getProperties();
@@ -953,7 +944,7 @@
         shader.setRadius(radius);
         shader.setProgress(.0f);
         properties = new RippleAnimationSession.AnimationProperties<>(
-                cx, cy, radius, p, 0f, shader);
+                cx, cy, radius, 0f, p, 0f, shader);
         if (mMaskShader == null) {
             shader.setShader(null);
         } else {
diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java
index 4608d02..c1c6afc 100644
--- a/graphics/java/android/graphics/drawable/RippleShader.java
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -167,6 +167,9 @@
         final float turbulencePhase = (float) ((mProgress + mNoisePhase * 0.333f) * 5f * Math.PI);
         setUniform("in_turbulencePhase", turbulencePhase);
 
+        //
+        // Keep in sync with: frameworks/base/libs/hwui/pipeline/skia/AnimatedDrawables.h
+        //
         final float scale = 1.5f;
         setUniform("in_tCircle1", new float[]{
                 (float) (scale * 0.5 + (turbulencePhase * 0.01 * Math.cos(scale * 0.55))),
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index f3baad7..602cd5d 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -43,10 +43,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Superior 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Superior 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla inferior completa"</string>
-    <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utilizar el modo una mano"</string>
+    <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Usar Modo una mano"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para salir, desliza el dedo hacia arriba desde la parte inferior de la pantalla o toca cualquier zona que haya encima de la aplicación"</string>
-    <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar modo una mano"</string>
-    <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Salir del modo una mano"</string>
+    <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar Modo una mano"</string>
+    <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Salir del Modo una mano"</string>
     <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Ajustes de las burbujas de <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menú adicional"</string>
     <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Volver a añadir a la pila"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index fed3ea9..a17f543 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -62,10 +62,10 @@
     <string name="bubbles_user_education_title" msgid="2112319053732691899">"گپ بااستفاده از حبابک‌ها"</string>
     <string name="bubbles_user_education_description" msgid="4215862563054175407">"مکالمه‌های جدید به‌صورت نمادهای شناور یا حبابک‌ها نشان داده می‌شوند. برای باز کردن حبابک‌ها ضربه بزنید. برای جابه‌جایی، آن را بکشید."</string>
     <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"کنترل حبابک‌ها در هرزمانی"</string>
-    <string name="bubbles_user_education_manage" msgid="3460756219946517198">"برای خاموش کردن «حبابک‌ها» از این برنامه، روی «مدیریت» ضربه بزنید"</string>
+    <string name="bubbles_user_education_manage" msgid="3460756219946517198">"برای خاموش کردن حبابک‌ها از این برنامه، روی «مدیریت» ضربه بزنید"</string>
     <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"متوجه‌ام"</string>
     <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"هیچ حبابک جدیدی وجود ندارد"</string>
-    <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"حبابک‌ها اخیر و حبابک‌ها ردشده اینجا ظاهر خواهند شد"</string>
+    <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"حبابک‌های اخیر و حبابک‌های ردشده اینجا ظاهر خواهند شد"</string>
     <string name="notification_bubble_title" msgid="6082910224488253378">"حباب"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"مدیریت"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"حبابک رد شد."</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index a969386..b2c0055 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -67,7 +67,7 @@
     <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"हाल ही के बबल्स मौजूद नहीं हैं"</string>
     <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"हाल ही के बबल्स और हटाए गए बबल्स यहां दिखेंगे"</string>
     <string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string>
-    <string name="manage_bubbles_text" msgid="7730624269650594419">"प्रबंधित करें"</string>
+    <string name="manage_bubbles_text" msgid="7730624269650594419">"मैनेज करें"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल खारिज किया गया."</string>
     <string name="restart_button_description" msgid="5887656107651190519">"इस ऐप्लिकेशन को रीस्टार्ट करने और फ़ुल स्क्रीन पर देखने के लिए टैप करें."</string>
     <string name="got_it" msgid="4428750913636945527">"ठीक है"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
index 8ca54e0..ef98a9c 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
@@ -19,6 +19,6 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="notification_channel_tv_pip" msgid="2576686079160402435">"תמונה בתוך תמונה"</string>
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(תוכנית ללא כותרת)"</string>
-    <string name="pip_close" msgid="9135220303720555525">"‏סגור PIP"</string>
+    <string name="pip_close" msgid="9135220303720555525">"‏סגירת PIP"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"מסך מלא"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 530d40a..0c64c76 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -62,7 +62,7 @@
     <string name="bubbles_user_education_title" msgid="2112319053732691899">"Калкып чыкма билдирмелер аркылуу маектешүү"</string>
     <string name="bubbles_user_education_description" msgid="4215862563054175407">"Жаңы жазышуулар калкыма сүрөтчөлөр же калкып чыкма билдирмелер түрүндө көрүнөт. Калкып чыкма билдирмелерди ачуу үчүн таптап коюңуз. Жылдыруу үчүн сүйрөңүз."</string>
     <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Калкып чыкма билдирмелерди каалаган убакта көзөмөлдөңүз"</string>
-    <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Бул колдонмодогу калкып чыкма билдирмелерди өчүрүү үчүн, \"Башкарууну\" басыңыз"</string>
+    <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Бул колдонмодогу калкып чыкма билдирмелерди өчүрүү үчүн \"Башкарууну\" басыңыз"</string>
     <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Түшүндүм"</string>
     <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Азырынча эч нерсе жок"</string>
     <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Акыркы жана жабылган калкып чыкма билдирмелер ушул жерде көрүнөт"</string>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 882ac37..dfa364a 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -30,7 +30,7 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"आकार बदल्नुहोस्"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"एप विभाजित स्क्रिनमा काम नगर्न सक्छ।"</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अनुप्रयोगले विभाजित-स्क्रिनलाई समर्थन गर्दैन।"</string>
-    <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"यो अनुप्रयोगले सहायक प्रदर्शनमा काम नगर्नसक्छ।"</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>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"बायाँ भाग फुल स्क्रिन"</string>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 6698a01..a138fee 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -40,7 +40,7 @@
     <integer name="long_press_dock_anim_duration">250</integer>
 
     <!-- Animation duration for translating of one handed when trigger / dismiss. -->
-    <integer name="config_one_handed_translate_animation_duration">300</integer>
+    <integer name="config_one_handed_translate_animation_duration">800</integer>
 
     <!-- One handed mode default offset % of display size -->
     <fraction name="config_one_handed_offset">40%</fraction>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
index 125e322..25dd3ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
@@ -22,8 +22,7 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.view.SurfaceControl;
-import android.view.animation.Interpolator;
-import android.view.animation.OvershootInterpolator;
+import android.view.animation.BaseInterpolator;
 import android.window.WindowContainerToken;
 
 import androidx.annotation.VisibleForTesting;
@@ -54,7 +53,7 @@
     public @interface TransitionDirection {
     }
 
-    private final Interpolator mOvershootInterpolator;
+    private final OneHandedInterpolator mInterpolator;
     private final OneHandedSurfaceTransactionHelper mSurfaceTransactionHelper;
     private final HashMap<WindowContainerToken, OneHandedTransitionAnimator> mAnimatorMap =
             new HashMap<>();
@@ -64,7 +63,7 @@
      */
     public OneHandedAnimationController(Context context) {
         mSurfaceTransactionHelper = new OneHandedSurfaceTransactionHelper(context);
-        mOvershootInterpolator = new OvershootInterpolator();
+        mInterpolator = new OneHandedInterpolator();
     }
 
     @SuppressWarnings("unchecked")
@@ -102,7 +101,7 @@
     OneHandedTransitionAnimator setupOneHandedTransitionAnimator(
             OneHandedTransitionAnimator animator) {
         animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper);
-        animator.setInterpolator(mOvershootInterpolator);
+        animator.setInterpolator(mInterpolator);
         animator.setFloatValues(FRACTION_START, FRACTION_END);
         return animator;
     }
@@ -112,6 +111,8 @@
      *
      * @param <T> Type of property to animate, either offset (float)
      */
+    // TODO: Refactoring to use SpringAnimation and DynamicAnimation instead of using ValueAnimator
+    //  to implement One-Handed transition animation. (b/185129031)
     public abstract static class OneHandedTransitionAnimator<T> extends ValueAnimator implements
             ValueAnimator.AnimatorUpdateListener,
             ValueAnimator.AnimatorListener {
@@ -297,4 +298,15 @@
             };
         }
     }
+
+    /**
+     * An Interpolator for One-Handed transition animation.
+     */
+    public class OneHandedInterpolator extends BaseInterpolator {
+        @Override
+        public float getInterpolation(float input) {
+            return (float) (Math.pow(2, -10 * input) * Math.sin(((input - 4.0f) / 4.0f)
+                    * (2.0f * Math.PI) / 4.0f) + 1);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 19098fd..0a86ad8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -343,7 +343,6 @@
                     mOneHandedAccessibilityUtil.getOneHandedStartDescription());
             mDisplayAreaOrganizer.scheduleOffset(0, yOffSet);
             mTimeoutHandler.resetTimer();
-
             mOneHandedUiEventLogger.writeEvent(
                     OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_IN);
         }
@@ -351,12 +350,7 @@
 
     @VisibleForTesting
     void stopOneHanded() {
-        if (mDisplayAreaOrganizer.isInOneHanded()) {
-            mOneHandedAccessibilityUtil.announcementForScreenReader(
-                    mOneHandedAccessibilityUtil.getOneHandedStopDescription());
-            mDisplayAreaOrganizer.scheduleOffset(0, 0);
-            mTimeoutHandler.removeTimer();
-        }
+        stopOneHanded(OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT);
     }
 
     private void stopOneHanded(int uiEvent) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
index d4f229c..3af0ff0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
@@ -216,7 +216,7 @@
         }
 
         ArrayList<RemoteAction> mediaActions = new ArrayList<>();
-        boolean isPlaying = mMediaController.getPlaybackState().isActive();
+        boolean isPlaying = mMediaController.getPlaybackState().isActiveState();
         long actions = mMediaController.getPlaybackState().getActions();
 
         // Prev action
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
index e50bde2..6494f89 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
@@ -23,7 +23,7 @@
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.LAUNCHER_TITLE
+import com.android.server.wm.flicker.HOME_WINDOW_TITLE
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.launchSplitScreen
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
@@ -62,7 +62,7 @@
     override val ignoredWindows: List<String>
         get() = listOf(LAUNCHER_PACKAGE_NAME, LIVE_WALLPAPER_PACKAGE_NAME,
             splitScreenApp.defaultWindowName, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
-            WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME, *LAUNCHER_TITLE)
+            WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME, *HOME_WINDOW_TITLE)
 
     @FlakyTest(bugId = 169271943)
     @Test
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 4c4a152..3056e97 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -820,10 +820,11 @@
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint,
                             uirenderer::CanvasPropertyPrimitive* progress,
+                            uirenderer::CanvasPropertyPrimitive* turbulencePhase,
                             const SkRuntimeShaderBuilder& effectBuilder) {
     sk_sp<uirenderer::skiapipeline::AnimatedRipple> drawable(
             new uirenderer::skiapipeline::AnimatedRipple(x, y, radius, paint, progress,
-                                                         effectBuilder));
+                                                         turbulencePhase, effectBuilder));
     mCanvas->drawDrawable(drawable.get());
 }
 
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index e0a0be5..995f00c 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -153,6 +153,7 @@
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint,
                             uirenderer::CanvasPropertyPrimitive* progress,
+                            uirenderer::CanvasPropertyPrimitive* turbulencePhase,
                             const SkRuntimeShaderBuilder& effectBuilder) override;
 
     virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override;
diff --git a/libs/hwui/canvas/CanvasOps.h b/libs/hwui/canvas/CanvasOps.h
index 855cd0d..173f394 100644
--- a/libs/hwui/canvas/CanvasOps.h
+++ b/libs/hwui/canvas/CanvasOps.h
@@ -152,32 +152,66 @@
     sp<uirenderer::CanvasPropertyPrimitive> radius;
     sp<uirenderer::CanvasPropertyPaint> paint;
     sp<uirenderer::CanvasPropertyPrimitive> progress;
+    sp<uirenderer::CanvasPropertyPrimitive> turbulencePhase;
     sk_sp<SkRuntimeEffect> effect;
 
+    const float PI = 3.1415926535897932384626;
+    const float PI_ROTATE_RIGHT = PI * 0.0078125;
+    const float PI_ROTATE_LEFT = PI * -0.0078125;
+    const float SCALE = 1.5;
+    const float CIRCLE_X_1 = 0.01 * cos(SCALE * 0.55);
+    const float CIRCLE_Y_1 = 0.01 * sin(SCALE * 0.55);
+    const float CIRCLE_X_2 = -0.0066 * cos(SCALE * 0.45);
+    const float CIRCLE_Y_2 = -0.0066 * sin(SCALE * 0.45);
+    const float CIRCLE_X_3 = -0.0066 * cos(SCALE * 0.35);
+    const float CIRCLE_Y_3 = -0.0066 * sin(SCALE * 0.35);
+
     void draw(SkCanvas* canvas) const {
         SkRuntimeShaderBuilder runtimeEffectBuilder(effect);
 
-        SkRuntimeShaderBuilder::BuilderUniform center = runtimeEffectBuilder.uniform("in_origin");
-        if (center.fVar != nullptr) {
-            center = SkV2{x->value, y->value};
-        }
+        setUniform2f(runtimeEffectBuilder, "in_origin", x->value, y->value);
+        setUniform(runtimeEffectBuilder, "in_radius", radius);
+        setUniform(runtimeEffectBuilder, "in_progress", progress);
+        setUniform(runtimeEffectBuilder, "in_turbulencePhase", turbulencePhase);
 
-        SkRuntimeShaderBuilder::BuilderUniform radiusU =
-                runtimeEffectBuilder.uniform("in_radius");
-        if (radiusU.fVar != nullptr) {
-            radiusU = radius->value;
-        }
-
-        SkRuntimeShaderBuilder::BuilderUniform progressU =
-                runtimeEffectBuilder.uniform("in_progress");
-        if (progressU.fVar != nullptr) {
-            progressU = progress->value;
-        }
+        //
+        // Keep in sync with:
+        // frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java
+        //
+        const float turbulence = turbulencePhase->value;
+        setUniform2f(runtimeEffectBuilder, "in_tCircle1", SCALE * 0.5 + (turbulence * CIRCLE_X_1),
+                     SCALE * 0.5 + (turbulence * CIRCLE_Y_1));
+        setUniform2f(runtimeEffectBuilder, "in_tCircle2", SCALE * 0.2 + (turbulence * CIRCLE_X_2),
+                     SCALE * 0.2 + (turbulence * CIRCLE_Y_2));
+        setUniform2f(runtimeEffectBuilder, "in_tCircle3", SCALE + (turbulence * CIRCLE_X_3),
+                     SCALE + (turbulence * CIRCLE_Y_3));
+        const float rotation1 = turbulence * PI_ROTATE_RIGHT + 1.7 * PI;
+        setUniform2f(runtimeEffectBuilder, "in_tRotation1", cos(rotation1), sin(rotation1));
+        const float rotation2 = turbulence * PI_ROTATE_LEFT + 2 * PI;
+        setUniform2f(runtimeEffectBuilder, "in_tRotation2", cos(rotation2), sin(rotation2));
+        const float rotation3 = turbulence * PI_ROTATE_RIGHT + 2.75 * PI;
+        setUniform2f(runtimeEffectBuilder, "in_tRotation3", cos(rotation3), sin(rotation3));
 
         SkPaint paintMod = paint->value;
         paintMod.setShader(runtimeEffectBuilder.makeShader(nullptr, false));
         canvas->drawCircle(x->value, y->value, radius->value, paintMod);
     }
+
+    void setUniform(SkRuntimeShaderBuilder& effect, std::string name,
+                    sp<uirenderer::CanvasPropertyPrimitive> property) const {
+        SkRuntimeShaderBuilder::BuilderUniform uniform = effect.uniform(name.c_str());
+        if (uniform.fVar != nullptr) {
+            uniform = property->value;
+        }
+    }
+
+    void setUniform2f(SkRuntimeShaderBuilder effect, std::string name, float a, float b) const {
+        SkRuntimeShaderBuilder::BuilderUniform uniform = effect.uniform(name.c_str());
+        if (uniform.fVar != nullptr) {
+            uniform = SkV2{a, b};
+        }
+    }
+
     ASSERT_DRAWABLE()
 };
 
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index c1feb76..837b055 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -146,6 +146,7 @@
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint,
                             uirenderer::CanvasPropertyPrimitive* progress,
+                            uirenderer::CanvasPropertyPrimitive* turbulencePhase,
                             const SkRuntimeShaderBuilder& effectBuilder) = 0;
 
     virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) = 0;
diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
index 855d56e..eb5a88a 100644
--- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
+++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
@@ -141,20 +141,22 @@
     canvas->drawCircle(xProp, yProp, radiusProp, paintProp);
 }
 
-static void android_view_DisplayListCanvas_drawRippleProps(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr,
-                                                           jlong xPropPtr, jlong yPropPtr,
-                                                           jlong radiusPropPtr, jlong paintPropPtr,
-                                                           jlong progressPropPtr,
-                                                           jlong builderPtr) {
+static void android_view_DisplayListCanvas_drawRippleProps(
+        CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jlong xPropPtr, jlong yPropPtr,
+        jlong radiusPropPtr, jlong paintPropPtr, jlong progressPropPtr, jlong turbulencePhasePtr,
+        jlong builderPtr) {
     Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
     CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr);
     CanvasPropertyPrimitive* yProp = reinterpret_cast<CanvasPropertyPrimitive*>(yPropPtr);
     CanvasPropertyPrimitive* radiusProp = reinterpret_cast<CanvasPropertyPrimitive*>(radiusPropPtr);
+    CanvasPropertyPrimitive* turbulencePhaseProp =
+            reinterpret_cast<CanvasPropertyPrimitive*>(turbulencePhasePtr);
     CanvasPropertyPaint* paintProp = reinterpret_cast<CanvasPropertyPaint*>(paintPropPtr);
     CanvasPropertyPrimitive* progressProp =
             reinterpret_cast<CanvasPropertyPrimitive*>(progressPropPtr);
     SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(builderPtr);
-    canvas->drawRipple(xProp, yProp, radiusProp, paintProp, progressProp, *builder);
+    canvas->drawRipple(xProp, yProp, radiusProp, paintProp, progressProp, turbulencePhaseProp,
+                       *builder);
 }
 
 static void android_view_DisplayListCanvas_drawWebViewFunctor(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jint functor) {
@@ -169,19 +171,22 @@
 const char* const kClassPathName = "android/graphics/RecordingCanvas";
 
 static JNINativeMethod gMethods[] = {
-    // ------------ @CriticalNative --------------
-    { "nCreateDisplayListCanvas", "(JII)J",     (void*) android_view_DisplayListCanvas_createDisplayListCanvas },
-    { "nResetDisplayListCanvas",  "(JJII)V",    (void*) android_view_DisplayListCanvas_resetDisplayListCanvas },
-    { "nGetMaximumTextureWidth",  "()I",        (void*) android_view_DisplayListCanvas_getMaxTextureSize },
-    { "nGetMaximumTextureHeight", "()I",        (void*) android_view_DisplayListCanvas_getMaxTextureSize },
-    { "nEnableZ",                 "(JZ)V",      (void*) android_view_DisplayListCanvas_enableZ },
-    { "nFinishRecording",         "(JJ)V",      (void*) android_view_DisplayListCanvas_finishRecording },
-    { "nDrawRenderNode",          "(JJ)V",      (void*) android_view_DisplayListCanvas_drawRenderNode },
-    { "nDrawTextureLayer",        "(JJ)V",      (void*) android_view_DisplayListCanvas_drawTextureLayer },
-    { "nDrawCircle",              "(JJJJJ)V",   (void*) android_view_DisplayListCanvas_drawCircleProps },
-    { "nDrawRoundRect",           "(JJJJJJJJ)V",(void*) android_view_DisplayListCanvas_drawRoundRectProps },
-    { "nDrawWebViewFunctor",      "(JI)V",      (void*) android_view_DisplayListCanvas_drawWebViewFunctor },
-    { "nDrawRipple",              "(JJJJJJJ)V", (void*) android_view_DisplayListCanvas_drawRippleProps },
+        // ------------ @CriticalNative --------------
+        {"nCreateDisplayListCanvas", "(JII)J",
+         (void*)android_view_DisplayListCanvas_createDisplayListCanvas},
+        {"nResetDisplayListCanvas", "(JJII)V",
+         (void*)android_view_DisplayListCanvas_resetDisplayListCanvas},
+        {"nGetMaximumTextureWidth", "()I", (void*)android_view_DisplayListCanvas_getMaxTextureSize},
+        {"nGetMaximumTextureHeight", "()I",
+         (void*)android_view_DisplayListCanvas_getMaxTextureSize},
+        {"nEnableZ", "(JZ)V", (void*)android_view_DisplayListCanvas_enableZ},
+        {"nFinishRecording", "(JJ)V", (void*)android_view_DisplayListCanvas_finishRecording},
+        {"nDrawRenderNode", "(JJ)V", (void*)android_view_DisplayListCanvas_drawRenderNode},
+        {"nDrawTextureLayer", "(JJ)V", (void*)android_view_DisplayListCanvas_drawTextureLayer},
+        {"nDrawCircle", "(JJJJJ)V", (void*)android_view_DisplayListCanvas_drawCircleProps},
+        {"nDrawRoundRect", "(JJJJJJJJ)V", (void*)android_view_DisplayListCanvas_drawRoundRectProps},
+        {"nDrawWebViewFunctor", "(JI)V", (void*)android_view_DisplayListCanvas_drawWebViewFunctor},
+        {"nDrawRipple", "(JJJJJJJJ)V", (void*)android_view_DisplayListCanvas_drawRippleProps},
 };
 
 int register_android_view_DisplayListCanvas(JNIEnv* env) {
diff --git a/libs/hwui/pipeline/skia/AnimatedDrawables.h b/libs/hwui/pipeline/skia/AnimatedDrawables.h
index 7859145..7d65be1 100644
--- a/libs/hwui/pipeline/skia/AnimatedDrawables.h
+++ b/libs/hwui/pipeline/skia/AnimatedDrawables.h
@@ -19,6 +19,7 @@
 #include <SkCanvas.h>
 #include <SkDrawable.h>
 #include <SkRuntimeEffect.h>
+#include <math.h>
 #include <utils/RefBase.h>
 #include "CanvasProperty.h"
 
@@ -61,12 +62,14 @@
                    uirenderer::CanvasPropertyPrimitive* radius,
                    uirenderer::CanvasPropertyPaint* paint,
                    uirenderer::CanvasPropertyPrimitive* progress,
+                   uirenderer::CanvasPropertyPrimitive* turbulencePhase,
                    const SkRuntimeShaderBuilder& effectBuilder)
             : mX(x)
             , mY(y)
             , mRadius(radius)
             , mPaint(paint)
             , mProgress(progress)
+            , mTurbulencePhase(turbulencePhase)
             , mRuntimeEffectBuilder(effectBuilder) {}
 
 protected:
@@ -77,22 +80,28 @@
         return SkRect::MakeLTRB(x - radius, y - radius, x + radius, y + radius);
     }
     virtual void onDraw(SkCanvas* canvas) override {
-        SkRuntimeShaderBuilder::BuilderUniform center = mRuntimeEffectBuilder.uniform("in_origin");
-        if (center.fVar != nullptr) {
-            center = SkV2{mX->value, mY->value};
-        }
+        setUniform2f("in_origin", mX->value, mY->value);
+        setUniform("in_radius", mRadius);
+        setUniform("in_progress", mProgress);
+        setUniform("in_turbulencePhase", mTurbulencePhase);
 
-        SkRuntimeShaderBuilder::BuilderUniform radiusU =
-                mRuntimeEffectBuilder.uniform("in_radius");
-        if (radiusU.fVar != nullptr) {
-            radiusU = mRadius->value;
-        }
-
-        SkRuntimeShaderBuilder::BuilderUniform progressU =
-                mRuntimeEffectBuilder.uniform("in_progress");
-        if (progressU.fVar != nullptr) {
-            progressU = mProgress->value;
-        }
+        //
+        // Keep in sync with:
+        // frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java
+        //
+        const float turbulencePhase = mTurbulencePhase->value;
+        setUniform2f("in_tCircle1", SCALE * 0.5 + (turbulencePhase * CIRCLE_X_1),
+                     SCALE * 0.5 + (turbulencePhase * CIRCLE_Y_1));
+        setUniform2f("in_tCircle2", SCALE * 0.2 + (turbulencePhase * CIRCLE_X_2),
+                     SCALE * 0.2 + (turbulencePhase * CIRCLE_Y_2));
+        setUniform2f("in_tCircle3", SCALE + (turbulencePhase * CIRCLE_X_3),
+                     SCALE + (turbulencePhase * CIRCLE_Y_3));
+        const float rotation1 = turbulencePhase * PI_ROTATE_RIGHT + 1.7 * PI;
+        setUniform2f("in_tRotation1", cos(rotation1), sin(rotation1));
+        const float rotation2 = turbulencePhase * PI_ROTATE_LEFT + 2 * PI;
+        setUniform2f("in_tRotation2", cos(rotation2), sin(rotation2));
+        const float rotation3 = turbulencePhase * PI_ROTATE_RIGHT + 2.75 * PI;
+        setUniform2f("in_tRotation3", cos(rotation3), sin(rotation3));
 
         SkPaint paint = mPaint->value;
         paint.setShader(mRuntimeEffectBuilder.makeShader(nullptr, false));
@@ -105,7 +114,35 @@
     sp<uirenderer::CanvasPropertyPrimitive> mRadius;
     sp<uirenderer::CanvasPropertyPaint> mPaint;
     sp<uirenderer::CanvasPropertyPrimitive> mProgress;
+    sp<uirenderer::CanvasPropertyPrimitive> mTurbulencePhase;
     SkRuntimeShaderBuilder mRuntimeEffectBuilder;
+
+    const float PI = 3.1415926535897932384626;
+    const float PI_ROTATE_RIGHT = PI * 0.0078125;
+    const float PI_ROTATE_LEFT = PI * -0.0078125;
+    const float SCALE = 1.5;
+    const float CIRCLE_X_1 = 0.01 * cos(SCALE * 0.55);
+    const float CIRCLE_Y_1 = 0.01 * sin(SCALE * 0.55);
+    const float CIRCLE_X_2 = -0.0066 * cos(SCALE * 0.45);
+    const float CIRCLE_Y_2 = -0.0066 * sin(SCALE * 0.45);
+    const float CIRCLE_X_3 = -0.0066 * cos(SCALE * 0.35);
+    const float CIRCLE_Y_3 = -0.0066 * sin(SCALE * 0.35);
+
+    virtual void setUniform(std::string name, sp<uirenderer::CanvasPropertyPrimitive> property) {
+        SkRuntimeShaderBuilder::BuilderUniform uniform =
+                mRuntimeEffectBuilder.uniform(name.c_str());
+        if (uniform.fVar != nullptr) {
+            uniform = property->value;
+        }
+    }
+
+    virtual void setUniform2f(std::string name, float a, float b) {
+        SkRuntimeShaderBuilder::BuilderUniform uniform =
+                mRuntimeEffectBuilder.uniform(name.c_str());
+        if (uniform.fVar != nullptr) {
+            uniform = SkV2{a, b};
+        }
+    }
 };
 
 class AnimatedCircle : public SkDrawable {
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 71f533c..ab00dd5 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -24,6 +24,7 @@
 #include "SkClipStack.h"
 #include "SkRect.h"
 #include "SkM44.h"
+#include "utils/GLUtils.h"
 
 namespace android {
 namespace uirenderer {
@@ -170,6 +171,8 @@
         setScissor(info.height, clipRegion.getBounds());
     }
 
+    // WebView may swallow GL errors, so catch them here
+    GL_CHECKPOINT(LOW);
     mWebViewHandle->drawGl(info);
 
     if (clearStencilAfterFunctor) {
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 82814de..9e73f04 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -60,12 +60,11 @@
     // Add the marker annotation to allow HWUI to determine where the current
     // clip/transformation should be applied
     SkVector vector = rect.getSimpleRadii();
-    const int dataSize = 2;
-    float data[dataSize];
+    float data[2];
     data[0] = vector.x();
     data[1] = vector.y();
     mRecorder.drawAnnotation(rect.rect(), HOLE_PUNCH_ANNOTATION.c_str(),
-                             SkData::MakeWithCopy(data, dataSize));
+                             SkData::MakeWithCopy(data, 2 * sizeof(float)));
 
     // Clear the current rect within the layer itself
     SkPaint paint = SkPaint();
@@ -115,9 +114,10 @@
                                      uirenderer::CanvasPropertyPrimitive* radius,
                                      uirenderer::CanvasPropertyPaint* paint,
                                      uirenderer::CanvasPropertyPrimitive* progress,
+                                     uirenderer::CanvasPropertyPrimitive* turbulencePhase,
                                      const SkRuntimeShaderBuilder& effectBuilder) {
     drawDrawable(mDisplayList->allocateDrawable<AnimatedRipple>(x, y, radius, paint, progress,
-                                                                effectBuilder));
+                                                                turbulencePhase, effectBuilder));
 }
 
 void SkiaRecordingCanvas::enableZ(bool enableZ) {
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index 06f2a27..4deb3b9 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -75,6 +75,7 @@
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint,
                             uirenderer::CanvasPropertyPrimitive* progress,
+                            uirenderer::CanvasPropertyPrimitive* turbulencePhase,
                             const SkRuntimeShaderBuilder& effectBuilder) override;
 
     virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
diff --git a/libs/hwui/pipeline/skia/TransformCanvas.cpp b/libs/hwui/pipeline/skia/TransformCanvas.cpp
index 6bfbb0d..a6e4c4c 100644
--- a/libs/hwui/pipeline/skia/TransformCanvas.cpp
+++ b/libs/hwui/pipeline/skia/TransformCanvas.cpp
@@ -22,7 +22,7 @@
 
 void TransformCanvas::onDrawAnnotation(const SkRect& rect, const char* key, SkData* value) {
     if (HOLE_PUNCH_ANNOTATION == key) {
-        auto* rectParams = static_cast<const float*>(value->data());
+        auto* rectParams = reinterpret_cast<const float*>(value->data());
         float radiusX = rectParams[0];
         float radiusY = rectParams[1];
         SkRRect roundRect = SkRRect::MakeRectXY(rect, radiusX, radiusY);
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 9eacc74..e7d30ebb 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.LongDef;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -492,26 +493,15 @@
 
     /**
      * Returns whether this is considered as an active playback state.
-     * <p>
-     * The playback state is considered as an active if the state is one of the following:
-     * <ul>
-     * <li>{@link #STATE_BUFFERING}</li>
-     * <li>{@link #STATE_CONNECTING}</li>
-     * <li>{@link #STATE_FAST_FORWARDING}</li>
-     * <li>{@link #STATE_PLAYING}</li>
-     * <li>{@link #STATE_REWINDING}</li>
-     * <li>{@link #STATE_SKIPPING_TO_NEXT}</li>
-     * <li>{@link #STATE_SKIPPING_TO_PREVIOUS}</li>
-     * <li>{@link #STATE_SKIPPING_TO_QUEUE_ITEM}</li>
-     * </ul>
+     * @hide
      */
-    public boolean isActive() {
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public boolean isActiveState() {
         switch (mState) {
             case PlaybackState.STATE_FAST_FORWARDING:
             case PlaybackState.STATE_REWINDING:
             case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
             case PlaybackState.STATE_SKIPPING_TO_NEXT:
-            case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
             case PlaybackState.STATE_BUFFERING:
             case PlaybackState.STATE_CONNECTING:
             case PlaybackState.STATE_PLAYING:
diff --git a/packages/CompanionDeviceManager/res/values-fa/strings.xml b/packages/CompanionDeviceManager/res/values-fa/strings.xml
index 83cc263..07d04aa 100644
--- a/packages/CompanionDeviceManager/res/values-fa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fa/strings.xml
@@ -22,6 +22,6 @@
     <string name="profile_name_watch" msgid="576290739483672360">"ساعت"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"‏مجاز کردن &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; برای مدیریت کردن &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_summary" msgid="2059360676631420073">"این برنامه برای مدیریت <xliff:g id="PROFILE_NAME">%1$s</xliff:g> شما لازم است. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
-    <string name="consent_yes" msgid="8344487259618762872">"مجاز"</string>
-    <string name="consent_no" msgid="2640796915611404382">"مجاز نیست"</string>
+    <string name="consent_yes" msgid="8344487259618762872">"مجاز بودن"</string>
+    <string name="consent_no" msgid="2640796915611404382">"مجاز نبودن"</string>
 </resources>
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
index c62402d..bb93af9 100644
--- a/packages/Connectivity/framework/Android.bp
+++ b/packages/Connectivity/framework/Android.bp
@@ -100,6 +100,7 @@
         "//frameworks/base",
 
         // Tests using hidden APIs
+        "//cts/tests/netlegacy22.api",
         "//external/sl4a:__subpackages__",
         "//frameworks/base/tests/net:__subpackages__",
         "//frameworks/libs/net/common/testutils",
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index 4719772..9e2cd3e 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -13,7 +13,7 @@
     method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackAsUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
-    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
     method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean);
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index 2194575..3ca7475 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -277,7 +277,7 @@
     method @NonNull public int[] getAdministratorUids();
     method @Nullable public static String getCapabilityCarrierName(int);
     method @Nullable public String getSsid();
-    method @NonNull public java.util.Set<java.lang.Integer> getSubIds();
+    method @NonNull public java.util.Set<java.lang.Integer> getSubscriptionIds();
     method @NonNull public int[] getTransportTypes();
     method public boolean isPrivateDnsBroken();
     method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
@@ -308,7 +308,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String);
-    method @NonNull public android.net.NetworkCapabilities.Builder setSubIds(@NonNull java.util.Set<java.lang.Integer>);
+    method @NonNull public android.net.NetworkCapabilities.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>);
     method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo);
   }
 
@@ -338,7 +338,7 @@
 
   public static class NetworkRequest.Builder {
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
-    method @NonNull public android.net.NetworkRequest.Builder setSubIds(@NonNull java.util.Set<java.lang.Integer>);
+    method @NonNull public android.net.NetworkRequest.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>);
   }
 
   public final class NetworkScore implements android.os.Parcelable {
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
index 92a792b..a2e218d 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
@@ -68,5 +68,11 @@
                     return cm.startOrGetTestNetworkManager();
                 }
         );
+
+        SystemServiceRegistry.registerContextAwareService(
+                DnsResolverServiceManager.DNS_RESOLVER_SERVICE,
+                DnsResolverServiceManager.class,
+                (context, serviceBinder) -> new DnsResolverServiceManager(serviceBinder)
+        );
     }
 }
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index c4a0d69..96f2de6 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -5362,10 +5362,10 @@
      * {@link #unregisterNetworkCallback(NetworkCallback)}.
      *
      * @param request {@link NetworkRequest} describing this request.
-     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
-     *                If null, the callback is invoked on the default internal Handler.
      * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
      *                        the callback must not be shared - it uniquely specifies this request.
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     *                If null, the callback is invoked on the default internal Handler.
      * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
      * @throws SecurityException if missing the appropriate permissions.
      * @throws RuntimeException if the app already has too many callbacks registered.
@@ -5380,7 +5380,8 @@
             NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
     })
     public void requestBackgroundNetwork(@NonNull NetworkRequest request,
-            @NonNull Handler handler, @NonNull NetworkCallback networkCallback) {
+            @NonNull NetworkCallback networkCallback,
+            @SuppressLint("ListenerLast") @NonNull Handler handler) {
         final NetworkCapabilities nc = request.networkCapabilities;
         sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST,
                 TYPE_NONE, new CallbackHandler(handler));
diff --git a/packages/Connectivity/framework/src/android/net/DnsResolverServiceManager.java b/packages/Connectivity/framework/src/android/net/DnsResolverServiceManager.java
new file mode 100644
index 0000000..79009e8
--- /dev/null
+++ b/packages/Connectivity/framework/src/android/net/DnsResolverServiceManager.java
@@ -0,0 +1,45 @@
+/*
+ * 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 android.net;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+
+/**
+ * Provides a way to obtain the DnsResolver binder objects.
+ *
+ * @hide
+ */
+public class DnsResolverServiceManager {
+    /** Service name for the DNS resolver. Keep in sync with DnsResolverService.h */
+    public static final String DNS_RESOLVER_SERVICE = "dnsresolver";
+
+    private final IBinder mResolver;
+
+    DnsResolverServiceManager(IBinder resolver) {
+        mResolver = resolver;
+    }
+
+    /**
+     * Get an {@link IBinder} representing the DnsResolver stable AIDL interface
+     *
+     * @return {@link android.net.IDnsResolver} IBinder.
+     */
+    @NonNull
+    public IBinder getService() {
+        return mResolver;
+    }
+}
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index c4277c3..775c88f 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -1749,7 +1749,7 @@
         combineSSIDs(nc);
         combineRequestor(nc);
         combineAdministratorUids(nc);
-        combineSubIds(nc);
+        combineSubscriptionIds(nc);
     }
 
     /**
@@ -1771,7 +1771,7 @@
                 && (onlyImmutable || satisfiedByUids(nc))
                 && (onlyImmutable || satisfiedBySSID(nc))
                 && (onlyImmutable || satisfiedByRequestor(nc))
-                && (onlyImmutable || satisfiedBySubIds(nc)));
+                && (onlyImmutable || satisfiedBySubscriptionIds(nc)));
     }
 
     /**
@@ -1868,7 +1868,7 @@
                 && equalsPrivateDnsBroken(that)
                 && equalsRequestor(that)
                 && equalsAdministratorUids(that)
-                && equalsSubIds(that);
+                && equalsSubscriptionIds(that);
     }
 
     @Override
@@ -2346,7 +2346,7 @@
      * @hide
      */
     @NonNull
-    public NetworkCapabilities setSubIds(@NonNull Set<Integer> subIds) {
+    public NetworkCapabilities setSubscriptionIds(@NonNull Set<Integer> subIds) {
         mSubIds = new ArraySet(Objects.requireNonNull(subIds));
         return this;
     }
@@ -2362,14 +2362,14 @@
      */
     @NonNull
     @SystemApi
-    public Set<Integer> getSubIds() {
+    public Set<Integer> getSubscriptionIds() {
         return new ArraySet<>(mSubIds);
     }
 
     /**
      * Tests if the subscription ID set of this network is the same as that of the passed one.
      */
-    private boolean equalsSubIds(@NonNull NetworkCapabilities nc) {
+    private boolean equalsSubscriptionIds(@NonNull NetworkCapabilities nc) {
         return Objects.equals(mSubIds, nc.mSubIds);
     }
 
@@ -2378,7 +2378,7 @@
      * If specified in the request, the passed one need to have at least one subId and at least
      * one of them needs to be in the request set.
      */
-    private boolean satisfiedBySubIds(@NonNull NetworkCapabilities nc) {
+    private boolean satisfiedBySubscriptionIds(@NonNull NetworkCapabilities nc) {
         if (mSubIds.isEmpty()) return true;
         if (nc.mSubIds.isEmpty()) return false;
         for (final Integer subId : nc.mSubIds) {
@@ -2395,7 +2395,7 @@
      * <p>If both subscription IDs are not equal, they belong to different subscription
      * (or no subscription). In this case, it would not make sense to add them together.
      */
-    private void combineSubIds(@NonNull NetworkCapabilities nc) {
+    private void combineSubscriptionIds(@NonNull NetworkCapabilities nc) {
         if (!Objects.equals(mSubIds, nc.mSubIds)) {
             throw new IllegalStateException("Can't combine two subscription ID sets");
         }
@@ -2737,8 +2737,8 @@
          */
         @NonNull
         @SystemApi
-        public Builder setSubIds(@NonNull final Set<Integer> subIds) {
-            mCaps.setSubIds(subIds);
+        public Builder setSubscriptionIds(@NonNull final Set<Integer> subIds) {
+            mCaps.setSubscriptionIds(subIds);
             return this;
         }
 
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index 78e1011..90aac0e 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -508,8 +508,8 @@
          */
         @NonNull
         @SystemApi
-        public Builder setSubIds(@NonNull Set<Integer> subIds) {
-            mNetworkCapabilities.setSubIds(subIds);
+        public Builder setSubscriptionIds(@NonNull Set<Integer> subIds) {
+            mNetworkCapabilities.setSubscriptionIds(subIds);
             return this;
         }
     }
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml
index a24456e..f57061a 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml
@@ -18,5 +18,5 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"વ્યવસ્થાપકે ચાલુ કરેલ"</string>
-    <string name="disabled_by_admin" msgid="4023569940620832713">"વ્યવસ્થાપકે બંધ કરેલ"</string>
+    <string name="disabled_by_admin" msgid="4023569940620832713">"વ્યવસ્થાપકે બંધ કરેલું"</string>
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml
index 199a2d6..bddf43c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml
@@ -18,5 +18,5 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Attivata dall\'amministratore"</string>
-    <string name="disabled_by_admin" msgid="4023569940620832713">"Disattivata dall\'amministratore"</string>
+    <string name="disabled_by_admin" msgid="4023569940620832713">"Opzione disattivata dall\'amministratore"</string>
 </resources>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index a834784..173e959 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -762,7 +762,7 @@
                   android:showForAllUsers="true"
                   android:finishOnTaskLaunch="true"
                   android:launchMode="singleInstance"
-                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+                  android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden"
                   android:visibleToInstantApps="true">
         </activity>
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 3363f8e..03d844a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -31,14 +31,16 @@
     companion object {
         const val ANIMATION_DURATION = 500L
         const val ANIMATION_DURATION_FADE_OUT_CONTENT = 183L
-        const val ANIMATION_DURATION_FADE_IN_WINDOW = 216L
-        const val ANIMATION_DELAY_FADE_IN_WINDOW = 166L
+        const val ANIMATION_DURATION_FADE_IN_WINDOW = 217L
+        const val ANIMATION_DELAY_FADE_IN_WINDOW = 167L
         private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
         private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
         private const val ANIMATION_DELAY_NAV_FADE_IN =
                 ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN
         private const val LAUNCH_TIMEOUT = 1000L
 
+        private val CONTENT_FADE_OUT_INTERPOLATOR = PathInterpolator(0f, 0f, 0.2f, 1f)
+        private val WINDOW_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0.6f, 1f)
         private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f)
         private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
 
@@ -418,12 +420,12 @@
                 val contentAlphaProgress = getProgress(linearProgress, 0,
                         ANIMATION_DURATION_FADE_OUT_CONTENT)
                 state.contentAlpha =
-                        1 - Interpolators.ALPHA_OUT.getInterpolation(contentAlphaProgress)
+                        1 - CONTENT_FADE_OUT_INTERPOLATOR.getInterpolation(contentAlphaProgress)
 
                 val backgroundAlphaProgress = getProgress(linearProgress,
                         ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW)
                 state.backgroundAlpha =
-                        1 - Interpolators.ALPHA_IN.getInterpolation(backgroundAlphaProgress)
+                        1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(backgroundAlphaProgress)
 
                 applyStateToWindow(window, state)
                 navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 01ec447..3da4521 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -9,6 +9,7 @@
 import android.graphics.Rect
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.InsetDrawable
 import android.graphics.drawable.LayerDrawable
 import android.view.GhostView
 import android.view.View
@@ -186,6 +187,10 @@
                 return drawable
             }
 
+            if (drawable is InsetDrawable) {
+                return drawable.drawable?.let { findGradientDrawable(it) }
+            }
+
             if (drawable is LayerDrawable) {
                 for (i in 0 until drawable.numberOfLayers) {
                     val maybeGradient = drawable.getDrawable(i)
@@ -255,6 +260,11 @@
         }
 
         private fun setXfermode(background: Drawable, mode: PorterDuffXfermode?) {
+            if (background is InsetDrawable) {
+                background.drawable?.let { setXfermode(it, mode) }
+                return
+            }
+
             if (background !is LayerDrawable) {
                 background.setXfermode(mode)
                 return
@@ -323,6 +333,11 @@
                 return
             }
 
+            if (drawable is InsetDrawable) {
+                drawable.drawable?.let { applyBackgroundRadii(it, radii) }
+                return
+            }
+
             if (drawable !is LayerDrawable) {
                 return
             }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 00bea8d..47a373e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -60,6 +60,8 @@
      */
     void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags);
     void startActivity(Intent intent, boolean dismissShade);
+    void startActivity(Intent intent, boolean dismissShade,
+            @Nullable ActivityLaunchAnimator.Controller animationController);
     void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade);
     void startActivity(Intent intent, boolean dismissShade, Callback callback);
     void postStartActivityDismissingKeyguard(Intent intent, int delay);
diff --git a/packages/SystemUI/res/drawable/ic_brightness.xml b/packages/SystemUI/res/drawable/ic_brightness.xml
index f443332..842af26 100644
--- a/packages/SystemUI/res/drawable/ic_brightness.xml
+++ b/packages/SystemUI/res/drawable/ic_brightness.xml
@@ -1,29 +1,23 @@
 <!--
-Copyright (C) 2020 The Android Open Source Project
+  ~ 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.
+  -->
 
-   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.0"
-        android:viewportHeight="24.0">
-
-    <path
-        android:pathData="M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z"
-    />
-
-    <path
-        android:pathData=" M20,8.69 V4h-4.69L12,0.69L8.69,4H4v4.69L0.69,12L4,15.31V20h4.69L12,23.31L15.31,20H20v-4.69L23.31,12L20,8.69z M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S14.76,7 12,7z"
-        android:fillColor="#FFFFFF" />
-</vector>
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Levels in drawables go from 0 to 10000 -->
+    <!-- "One third" of the range per icon -->
+    <item android:maxLevel="3333" android:drawable="@drawable/ic_brightness_low" />
+    <item android:maxLevel="6666" android:drawable="@drawable/ic_brightness_medium" />
+    <item android:maxLevel="10000" android:drawable="@drawable/ic_brightness_full" />
+</level-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_brightness_thumb.xml b/packages/SystemUI/res/drawable/ic_brightness_full.xml
similarity index 86%
rename from packages/SystemUI/res/drawable/ic_brightness_thumb.xml
rename to packages/SystemUI/res/drawable/ic_brightness_full.xml
index d721988..f443332 100644
--- a/packages/SystemUI/res/drawable/ic_brightness_thumb.xml
+++ b/packages/SystemUI/res/drawable/ic_brightness_full.xml
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2017 The Android Open Source Project
+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.
@@ -21,9 +21,9 @@
 
     <path
         android:pathData="M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z"
-        android:fillColor="?android:attr/colorBackgroundFloating" />
+    />
 
     <path
         android:pathData=" M20,8.69 V4h-4.69L12,0.69L8.69,4H4v4.69L0.69,12L4,15.31V20h4.69L12,23.31L15.31,20H20v-4.69L23.31,12L20,8.69z M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S14.76,7 12,7z"
-        android:fillColor="?android:attr/colorControlActivated" />
+        android:fillColor="#FFFFFF" />
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_brightness_low.xml b/packages/SystemUI/res/drawable/ic_brightness_low.xml
new file mode 100644
index 0000000..b463556
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_brightness_low.xml
@@ -0,0 +1,10 @@
+<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="@android:color/white"
+      android:pathData="M20,8.69L20,4h-4.69L12,0.69 8.69,4L4,4v4.69L0.69,12 4,15.31L4,20h4.69L12,23.31 15.31,20L20,20v-4.69L23.31,12 20,8.69zM18,14.48L18,18h-3.52L12,20.48 9.52,18L6,18v-3.52L3.52,12 6,9.52L6,6h3.52L12,3.52 14.48,6L18,6v3.52L20.48,12 18,14.48zM12,9c1.65,0 3,1.35 3,3s-1.35,3 -3,3 -3,-1.35 -3,-3 1.35,-3 3,-3m0,-2c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_brightness_medium.xml b/packages/SystemUI/res/drawable/ic_brightness_medium.xml
new file mode 100644
index 0000000..80acc4d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_brightness_medium.xml
@@ -0,0 +1,10 @@
+<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="@android:color/white"
+      android:pathData="M20,8.69L20,4h-4.69L12,0.69 8.69,4L4,4v4.69L0.69,12 4,15.31L4,20h4.69L12,23.31 15.31,20L20,20v-4.69L23.31,12 20,8.69zM18,14.48L18,18h-3.52L12,20.48 9.52,18L6,18v-3.52L3.52,12 6,9.52L6,6h3.52L12,3.52 14.48,6L18,6v3.52L20.48,12 18,14.48zM12,17c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5v10z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml b/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml
index 6022206..77b9871 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml
@@ -23,13 +23,18 @@
         <item android:id="@android:id/mask">
             <shape android:shape="rectangle">
                 <solid android:color="@android:color/white"/>
+                <corners android:radius="@dimen/screenshot_button_corner_radius"/>
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="rectangle">
+                <solid android:color="?attr/underSurfaceColor"/>
                 <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
         <item>
             <shape android:shape="rectangle">
-            <stroke android:width="1dp" android:color="@color/qs_footer_action_border"/>
-                <solid android:color="@android:color/transparent"/>
+                <stroke android:width="1dp" android:color="@color/qs_footer_action_border"/>
                 <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
diff --git a/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml b/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml
new file mode 100644
index 0000000..4e9d380
--- /dev/null
+++ b/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<!-- dot drawable for ongoing system privacy events -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <solid
+        android:color="@color/privacy_circle"/>
+    <size
+        android:width="6dp"
+        android:height="6dp"
+        />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_tile_medium_empty.xml b/packages/SystemUI/res/layout/people_tile_medium_empty.xml
index 7d9cbb9..4236493 100644
--- a/packages/SystemUI/res/layout/people_tile_medium_empty.xml
+++ b/packages/SystemUI/res/layout/people_tile_medium_empty.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:theme="@android:style/Theme.DeviceDefault.DayNight"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
@@ -22,57 +22,49 @@
     <LinearLayout
         android:background="@drawable/people_space_tile_view_card"
         android:id="@+id/item"
-        android:orientation="vertical"
+        android:gravity="center"
+        android:paddingHorizontal="16dp"
+        android:orientation="horizontal"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
-
+        <ImageView
+            android:id="@+id/person_icon"
+            android:layout_marginTop="-2dp"
+            android:layout_marginStart="-2dp"
+            android:layout_width="64dp"
+            android:layout_height="64dp" />
+        <ImageView
+            android:id="@+id/availability"
+            android:layout_marginStart="-2dp"
+            android:layout_width="10dp"
+            android:layout_height="10dp"
+            android:background="@drawable/circle_green_10dp" />
         <LinearLayout
-            android:orientation="horizontal"
-            android:gravity="center"
-            android:layout_gravity="center"
-            android:paddingVertical="2dp"
-            android:paddingHorizontal="8dp"
+            android:orientation="vertical"
+            android:paddingStart="6dp"
+            android:gravity="top"
             android:layout_width="match_parent"
-            android:layout_height="match_parent">
-            <ImageView
-                android:id="@+id/person_icon"
-                android:layout_width="64dp"
-                android:layout_height="64dp"/>
-            <ImageView
-                android:id="@+id/availability"
-                android:layout_marginStart="-2dp"
-                android:layout_width="10dp"
-                android:layout_height="10dp"
-                android:background="@drawable/circle_green_10dp"/>
-            <LinearLayout
-                android:orientation="vertical"
-                android:paddingStart="6dp"
-                android:gravity="top"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-
-                <TextView
-                    android:id="@+id/name"
-                    android:text="@string/empty_user_name"
-                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                    android:textColor="?android:attr/textColorPrimary"
-                    android:textSize="14sp"
-                    android:maxLines="1"
-                    android:ellipsize="end"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"/>
-
-                <TextView
-                    android:id="@+id/last_interaction"
-                    android:text="@string/empty_status"
-                    android:textColor="?android:attr/textColorSecondary"
-                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                    android:textSize="12sp"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:maxLines="3"
-                    android:ellipsize="end"/>
-            </LinearLayout>
+            android:layout_height="wrap_content">
+            <TextView
+                android:id="@+id/name"
+                android:text="@string/empty_user_name"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textColor="?android:attr/textColorPrimary"
+                android:textSize="14sp"
+                android:maxLines="1"
+                android:ellipsize="end"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+            <TextView
+                android:id="@+id/last_interaction"
+                android:text="@string/empty_status"
+                android:textColor="?android:attr/textColorSecondary"
+                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                android:textSize="12sp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:maxLines="3"
+                android:ellipsize="end" />
         </LinearLayout>
     </LinearLayout>
-</LinearLayout>
\ No newline at end of file
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
index c9e4945..7070660 100644
--- a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
+++ b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
@@ -97,7 +97,7 @@
             android:gravity="bottom"
             android:layout_gravity="center_vertical"
             android:orientation="horizontal"
-            android:paddingTop="4dp"
+            android:paddingTop="2dp"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:clipToOutline="true">
diff --git a/packages/SystemUI/res/layout/people_tile_small.xml b/packages/SystemUI/res/layout/people_tile_small.xml
index 34aa8e4..7c28fc1 100644
--- a/packages/SystemUI/res/layout/people_tile_small.xml
+++ b/packages/SystemUI/res/layout/people_tile_small.xml
@@ -21,7 +21,7 @@
     <LinearLayout
         android:id="@+id/item"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
+        android:layout_height="match_parent"
         android:layout_gravity="center"
         android:background="@drawable/people_space_tile_view_card"
         android:orientation="vertical"
@@ -42,12 +42,12 @@
             android:tint="?android:attr/colorAccent"
             android:layout_gravity="center"
             android:layout_width="18dp"
-            android:layout_height="22dp"
-            android:layout_weight="1" />
+            android:layout_height="22dp" />
 
         <TextView
             android:id="@+id/messages_count"
             android:layout_gravity="center"
+            android:gravity="center"
             android:paddingStart="8dp"
             android:paddingEnd="8dp"
             android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
@@ -59,7 +59,6 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:visibility="gone"
-            android:layout_weight="1"
             />
 
         <TextView
@@ -67,7 +66,6 @@
             android:layout_gravity="center"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_weight="1"
             android:ellipsize="end"
             android:maxLines="1"
             android:paddingHorizontal="4dp"
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
index ae3adb8..4fcce24 100644
--- a/packages/SystemUI/res/layout/remote_input.xml
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -31,8 +31,7 @@
             android:paddingTop="2dp"
             android:paddingStart="16dp"
             android:paddingEnd="12dp"
-            android:layout_marginRight="20dp"
-            android:layout_marginLeft="20dp"
+            android:layout_marginLeft="16dp"
             android:layout_marginTop="5dp"
             android:layout_marginBottom="16dp"
             android:layout_gravity="start|center_vertical"
@@ -52,12 +51,10 @@
             android:layout_gravity="center_vertical">
 
         <ImageButton
-        android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:layout_gravity="center"
-                android:paddingLeft="10dp"
-                android:layout_marginBottom="12dp"
-                android:layout_marginEnd="12dp"
+                android:layout_width="48dp"
+                android:layout_height="48dp"
+                android:layout_gravity="center_horizontal|bottom"
+                android:layout_marginBottom="22dp"
                 android:id="@+id/remote_input_send"
                 android:src="@drawable/ic_send"
                 android:contentDescription="@*android:string/ime_action_send"
@@ -69,8 +66,8 @@
                 android:id="@+id/remote_input_progress"
                 android:layout_width="24dp"
                 android:layout_height="24dp"
-                android:layout_marginBottom="12dp"
-                android:layout_gravity="center_vertical"
+                android:layout_marginBottom="34dp"
+                android:layout_gravity="center_horizontal|bottom"
                 android:visibility="invisible"
                 android:indeterminate="true"
                 style="?android:attr/progressBarStyleSmall" />
diff --git a/packages/SystemUI/res/layout/rounded_corners.xml b/packages/SystemUI/res/layout/rounded_corners.xml
index db892d7..04fe918 100644
--- a/packages/SystemUI/res/layout/rounded_corners.xml
+++ b/packages/SystemUI/res/layout/rounded_corners.xml
@@ -14,6 +14,8 @@
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 -->
+
+<!-- TODO: remove this in favor of requiring top and bottom layouts -->
 <com.android.systemui.RegionInterceptingFrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/rounded_corners_default"
@@ -26,6 +28,25 @@
         android:layout_gravity="left|top"
         android:tint="#ff000000"
         android:src="@drawable/rounded"/>
+
+    <FrameLayout
+        android:id="@+id/privacy_dot_left_container"
+        android:layout_height="@dimen/status_bar_height"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_padding_top"
+        android:layout_marginLeft="8dp"
+        android:layout_gravity="left|top"
+        android:visibility="invisible" >
+        <ImageView
+            android:id="@+id/privacy_dot_left"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center"
+            android:src="@drawable/system_animation_ongoing_dot"
+            android:visibility="visible" />
+    </FrameLayout>
+
+
     <ImageView
         android:id="@+id/right"
         android:layout_width="12dp"
@@ -33,4 +54,22 @@
         android:tint="#ff000000"
         android:layout_gravity="right|bottom"
         android:src="@drawable/rounded"/>
+    <FrameLayout
+        android:id="@+id/privacy_dot_right_container"
+        android:layout_height="@dimen/status_bar_height"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_padding_top"
+        android:layout_marginRight="8dp"
+        android:layout_gravity="right|top"
+        android:visibility="invisible" >
+        <ImageView
+            android:id="@+id/privacy_dot_right"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center"
+            android:src="@drawable/system_animation_ongoing_dot"
+            android:visibility="visible" />
+
+    </FrameLayout>
+
 </com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/layout/rounded_corners_bottom.xml b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
index dde1248..720e47b 100644
--- a/packages/SystemUI/res/layout/rounded_corners_bottom.xml
+++ b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
@@ -26,6 +26,24 @@
         android:layout_gravity="left|bottom"
         android:tint="#ff000000"
         android:src="@drawable/rounded_corner_bottom"/>
+
+    <FrameLayout
+        android:id="@+id/privacy_dot_left_container"
+        android:layout_height="@dimen/status_bar_height"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_padding_top"
+        android:layout_marginLeft="0dp"
+        android:layout_gravity="left|bottom"
+        android:visibility="invisible" >
+        <ImageView
+            android:id="@+id/privacy_dot"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_vertical|right"
+            android:src="@drawable/system_animation_ongoing_dot"
+            android:visibility="visible" />
+    </FrameLayout>
+
     <ImageView
         android:id="@+id/right"
         android:layout_width="12dp"
@@ -33,4 +51,21 @@
         android:tint="#ff000000"
         android:layout_gravity="right|bottom"
         android:src="@drawable/rounded_corner_bottom"/>
+    <FrameLayout
+        android:id="@+id/privacy_dot_right_container"
+        android:layout_height="@dimen/status_bar_height"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_padding_top"
+        android:layout_marginRight="0dp"
+        android:layout_gravity="right|bottom"
+        android:visibility="invisible" >
+        <ImageView
+            android:id="@+id/privacy_dot"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_vertical|left"
+            android:src="@drawable/system_animation_ongoing_dot"
+            android:visibility="visible" />
+    </FrameLayout>
+
 </com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/layout/rounded_corners_top.xml b/packages/SystemUI/res/layout/rounded_corners_top.xml
index 813c97d..6abe406 100644
--- a/packages/SystemUI/res/layout/rounded_corners_top.xml
+++ b/packages/SystemUI/res/layout/rounded_corners_top.xml
@@ -26,6 +26,24 @@
         android:layout_gravity="left|top"
         android:tint="#ff000000"
         android:src="@drawable/rounded_corner_top"/>
+
+    <FrameLayout
+        android:id="@+id/privacy_dot_left_container"
+        android:layout_height="@*android:dimen/status_bar_height_portrait"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_padding_top"
+        android:layout_marginLeft="0dp"
+        android:layout_gravity="left|top"
+        android:visibility="invisible" >
+        <ImageView
+            android:id="@+id/privacy_dot"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_vertical|right"
+            android:src="@drawable/system_animation_ongoing_dot"
+            android:visibility="visible" />
+    </FrameLayout>
+
     <ImageView
         android:id="@+id/right"
         android:layout_width="12dp"
@@ -33,4 +51,24 @@
         android:tint="#ff000000"
         android:layout_gravity="right|top"
         android:src="@drawable/rounded_corner_top"/>
+
+    <FrameLayout
+        android:id="@+id/privacy_dot_right_container"
+        android:layout_height="@*android:dimen/status_bar_height_portrait"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_padding_top"
+        android:layout_marginRight="0dp"
+        android:layout_gravity="right|top"
+        android:visibility="invisible" >
+        <ImageView
+            android:id="@+id/privacy_dot"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_vertical|left"
+            android:src="@drawable/system_animation_ongoing_dot"
+            android:visibility="visible" />
+
+    </FrameLayout>
+
+
 </com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/layout/system_event_animation_window.xml b/packages/SystemUI/res/layout/system_event_animation_window.xml
new file mode 100644
index 0000000..c92dec9
--- /dev/null
+++ b/packages/SystemUI/res/layout/system_event_animation_window.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_vertical|end"
+    android:paddingTop="@dimen/status_bar_padding_top"
+    android:paddingEnd="8dp"
+    >
+
+    <ImageView
+        android:id="@+id/dot_view"
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:layout_gravity="center_vertical|end"
+        android:src="@drawable/system_animation_ongoing_dot"
+        android:visibility="invisible"
+        />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 62ac75e..5d9c909 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1397,6 +1397,7 @@
     <dimen name="max_people_avatar_size_for_large_content">64dp</dimen>
     <dimen name="max_people_avatar_size">108dp</dimen>
     <dimen name="name_text_size_for_small">14sp</dimen>
+    <dimen name="name_text_size_for_medium">14sp</dimen>
     <dimen name="name_text_size_for_large">24sp</dimen>
     <dimen name="content_text_size_for_medium">12sp</dimen>
     <dimen name="content_text_size_for_large">14sp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
index 983b1bb..62d5a45 100644
--- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
@@ -91,6 +91,13 @@
     }
 
     @Override
+    public void startActivity(Intent intent, boolean dismissShade,
+            @Nullable ActivityLaunchAnimator.Controller animationController) {
+        mActualStarter.ifPresent(
+                starter -> starter.get().startActivity(intent, dismissShade, animationController));
+    }
+
+    @Override
     public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade) {
         mActualStarter.ifPresent(
                 starter -> starter.get().startActivity(intent, onlyProvisioned, dismissShade));
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index a686fc0..cbfdce5 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -79,6 +79,8 @@
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.NotificationFilter;
@@ -352,6 +354,8 @@
     @Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy;
     @Inject Lazy<NavigationBarOverlayController> mNavbarButtonsControllerLazy;
     @Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager;
+    @Inject Lazy<SystemStatusAnimationScheduler> mSystemStatusAnimationSchedulerLazy;
+    @Inject Lazy<PrivacyDotViewController> mPrivacyDotViewControllerLazy;
 
     @Inject
     public Dependency() {
@@ -561,6 +565,10 @@
 
         mProviders.put(NavigationBarOverlayController.class, mNavbarButtonsControllerLazy::get);
 
+        mProviders.put(SystemStatusAnimationScheduler.class,
+                mSystemStatusAnimationSchedulerLazy::get);
+        mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get);
+
         Dependency.setInstance(this);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 5fa98bc..d07723e 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -88,6 +88,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.SecureSetting;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.settings.SecureSettings;
@@ -126,6 +127,7 @@
     private DisplayManager.DisplayListener mDisplayListener;
     private CameraAvailabilityListener mCameraListener;
     private final UserTracker mUserTracker;
+    private final PrivacyDotViewController mDotViewController;
 
     //TODO: These are piecemeal being updated to Points for now to support non-square rounded
     // corners. for now it is only supposed when reading the intrinsic size from the drawables with
@@ -140,6 +142,11 @@
     protected View[] mOverlays;
     @Nullable
     private DisplayCutoutView[] mCutoutViews;
+    //TODO:
+    View mTopLeftDot;
+    View mTopRightDot;
+    View mBottomLeftDot;
+    View mBottomRightDot;
     private float mDensity;
     private WindowManager mWindowManager;
     private int mRotation;
@@ -147,6 +154,8 @@
     private Handler mHandler;
     private boolean mPendingRotationChange;
     private boolean mIsRoundedCornerMultipleRadius;
+    private int mStatusBarHeightPortrait;
+    private int mStatusBarHeightLandscape;
 
     private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback =
             new CameraAvailabilityListener.CameraTransitionCallback() {
@@ -205,13 +214,15 @@
             SecureSettings secureSettings,
             BroadcastDispatcher broadcastDispatcher,
             TunerService tunerService,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            PrivacyDotViewController dotViewController) {
         super(context);
         mMainHandler = handler;
         mSecureSettings = secureSettings;
         mBroadcastDispatcher = broadcastDispatcher;
         mTunerService = tunerService;
         mUserTracker = userTracker;
+        mDotViewController = dotViewController;
     }
 
     @Override
@@ -222,6 +233,7 @@
         }
         mHandler = startHandlerThread();
         mHandler.post(this::startOnScreenDecorationsThread);
+        mDotViewController.setUiExecutor(mHandler::post);
     }
 
     @VisibleForTesting
@@ -286,6 +298,7 @@
 
     private void setupDecorations() {
         if (hasRoundedCorners() || shouldDrawCutout()) {
+            updateStatusBarHeight();
             final DisplayCutout cutout = getCutout();
             final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll();
             int rotatedPos;
@@ -298,6 +311,10 @@
                     removeOverlay(i);
                 }
             }
+            // Overlays have been created, send the dots to the controller
+            //TODO: need a better way to do this
+            mDotViewController.initialize(
+                    mTopLeftDot, mTopRightDot, mBottomLeftDot, mBottomRightDot);
         } else {
             removeAllOverlays();
         }
@@ -431,14 +448,21 @@
     private View overlayForPosition(@BoundsPosition int pos) {
         switch (pos) {
             case BOUNDS_POSITION_TOP:
-                return LayoutInflater.from(mContext)
+            case BOUNDS_POSITION_LEFT:
+                View top = LayoutInflater.from(mContext)
                         .inflate(R.layout.rounded_corners_top, null);
+                mTopLeftDot = top.findViewById(R.id.privacy_dot_left_container);
+                mTopRightDot = top.findViewById(R.id.privacy_dot_right_container);
+                return top;
             case BOUNDS_POSITION_BOTTOM:
-                return LayoutInflater.from(mContext)
+            case BOUNDS_POSITION_RIGHT:
+                View bottom =  LayoutInflater.from(mContext)
                         .inflate(R.layout.rounded_corners_bottom, null);
+                mBottomLeftDot = bottom.findViewById(R.id.privacy_dot_left_container);
+                mBottomRightDot = bottom.findViewById(R.id.privacy_dot_right_container);
+                return bottom;
             default:
-                return LayoutInflater.from(mContext)
-                        .inflate(R.layout.rounded_corners, null);
+                throw new IllegalArgumentException("Unknown bounds position");
         }
     }
 
@@ -575,6 +599,11 @@
             View child;
             for (int j = 0; j < size; j++) {
                 child = ((ViewGroup) mOverlays[i]).getChildAt(j);
+                if (child.getId() == R.id.privacy_dot_left_container
+                        || child.getId() == R.id.privacy_dot_right_container) {
+                    // Exclude privacy dot from color inversion (for now?)
+                    continue;
+                }
                 if (child instanceof ImageView) {
                     ((ImageView) child).setImageTintList(tintList);
                 } else if (child instanceof DisplayCutoutView) {
@@ -611,10 +640,15 @@
         Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(),
                 "must call on " + mHandler.getLooper().getThread()
                         + ", but was " + Thread.currentThread());
+
+        int newRotation = mContext.getDisplay().getRotation();
+        if (mRotation != newRotation) {
+            mDotViewController.updateRotation(newRotation);
+        }
+
         if (mPendingRotationChange) {
             return;
         }
-        int newRotation = mContext.getDisplay().getRotation();
         if (newRotation != mRotation) {
             mRotation = newRotation;
 
@@ -630,6 +664,14 @@
         }
     }
 
+    private void updateStatusBarHeight() {
+        mStatusBarHeightLandscape = mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height_landscape);
+        mStatusBarHeightPortrait = mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height_portrait);
+        mDotViewController.setStatusBarHeights(mStatusBarHeightPortrait, mStatusBarHeightLandscape);
+    }
+
     private void updateRoundedCornerRadii() {
         // We should eventually move to just using the intrinsic size of the drawables since
         // they should be sized to the exact pixels they want to cover. Therefore I'm purposely not
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java
index 2661d89..b544599 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java
@@ -60,17 +60,31 @@
     /**
      * Returns a copy of the list containing all the active AppOps that the controller tracks.
      *
-     * @return List of active AppOps information
+     * @return List of active AppOps information, without paused elements.
      */
     List<AppOpItem> getActiveAppOps();
 
     /**
+     * Returns a copy of the list containing all the active AppOps that the controller tracks.
+     *
+     * @param showPaused {@code true} to also obtain paused items. {@code false} otherwise.
+     * @return List of active AppOps information
+     */
+    List<AppOpItem> getActiveAppOps(boolean showPaused);
+
+    /**
      * Returns a copy of the list containing all the active AppOps that the controller tracks, for
      * a given user id.
      *
      * @param userId User id to track
+     * @param showPaused {@code true} to also obtain paused items. {@code false} otherwise.
      *
      * @return List of active AppOps information for that user id
      */
-    List<AppOpItem> getActiveAppOpsForUser(int userId);
+    List<AppOpItem> getActiveAppOpsForUser(int userId, boolean showPaused);
+
+    /**
+     * @return whether this controller is considering the microphone as muted.
+     */
+    boolean isMicMuted();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 994401d..534f93e 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -241,9 +241,9 @@
             AppOpItem item = getAppOpItemLocked(mActiveItems, code, uid, packageName);
             if (item == null && active) {
                 item = new AppOpItem(code, uid, packageName, mClock.elapsedRealtime());
-                if (code == AppOpsManager.OP_RECORD_AUDIO) {
+                if (isOpMicrophone(code)) {
                     item.setDisabled(isAnyRecordingPausedLocked(uid));
-                } else if (code == AppOpsManager.OP_CAMERA) {
+                } else if (isOpCamera(code)) {
                     item.setDisabled(mCameraDisabled);
                 }
                 mActiveItems.add(item);
@@ -298,6 +298,11 @@
         return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName);
     }
 
+    @WorkerThread
+    public List<AppOpItem> getActiveAppOps() {
+        return getActiveAppOps(false);
+    }
+
     /**
      * Returns a copy of the list containing all the active AppOps that the controller tracks.
      *
@@ -306,8 +311,8 @@
      * @return List of active AppOps information
      */
     @WorkerThread
-    public List<AppOpItem> getActiveAppOps() {
-        return getActiveAppOpsForUser(UserHandle.USER_ALL);
+    public List<AppOpItem> getActiveAppOps(boolean showPaused) {
+        return getActiveAppOpsForUser(UserHandle.USER_ALL, showPaused);
     }
 
     /**
@@ -321,7 +326,7 @@
      * @return List of active AppOps information for that user id
      */
     @WorkerThread
-    public List<AppOpItem> getActiveAppOpsForUser(int userId) {
+    public List<AppOpItem> getActiveAppOpsForUser(int userId, boolean showPaused) {
         Assert.isNotMainThread();
         List<AppOpItem> list = new ArrayList<>();
         synchronized (mActiveItems) {
@@ -330,7 +335,8 @@
                 AppOpItem item = mActiveItems.get(i);
                 if ((userId == UserHandle.USER_ALL
                         || UserHandle.getUserId(item.getUid()) == userId)
-                        && isUserVisible(item.getPackageName()) && !item.isDisabled()) {
+                        && isUserVisible(item.getPackageName())
+                        && (showPaused || !item.isDisabled())) {
                     list.add(item);
                 }
             }
@@ -441,9 +447,9 @@
                 AppOpItem item = mActiveItems.get(i);
 
                 boolean paused = false;
-                if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) {
+                if (isOpMicrophone(item.getCode())) {
                     paused = isAnyRecordingPausedLocked(item.getUid());
-                } else if (item.getCode() == AppOpsManager.OP_CAMERA) {
+                } else if (isOpCamera(item.getCode())) {
                     paused = mCameraDisabled;
                 }
 
@@ -502,6 +508,19 @@
         });
     }
 
+    @Override
+    public boolean isMicMuted() {
+        return mMicMuted;
+    }
+
+    private boolean isOpCamera(int op) {
+        return op == AppOpsManager.OP_CAMERA || op == AppOpsManager.OP_PHONE_CALL_CAMERA;
+    }
+
+    private boolean isOpMicrophone(int op) {
+        return op == AppOpsManager.OP_RECORD_AUDIO || op == AppOpsManager.OP_PHONE_CALL_MICROPHONE;
+    }
+
     protected class H extends Handler {
         H(Looper looper) {
             super(looper);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
index de00d50..1a94473 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
@@ -17,10 +17,20 @@
 package com.android.systemui.keyguard;
 
 import android.annotation.IntDef;
+import android.app.IWallpaperManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.os.Bundle;
 import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.Trace;
+import android.util.DisplayMetrics;
+
+import androidx.annotation.Nullable;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 
 import java.io.FileDescriptor;
@@ -31,7 +41,7 @@
 import javax.inject.Inject;
 
 /**
- * Tracks the wakefulness lifecycle.
+ * Tracks the wakefulness lifecycle, including why we're waking or sleeping.
  */
 @SysUISingleton
 public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observer> implements
@@ -51,13 +61,30 @@
     public static final int WAKEFULNESS_AWAKE = 2;
     public static final int WAKEFULNESS_GOING_TO_SLEEP = 3;
 
+    private final Context mContext;
+    private final DisplayMetrics mDisplayMetrics;
+    private final IWallpaperManager mWallpaperManagerService;
+
     private int mWakefulness = WAKEFULNESS_ASLEEP;
+
     private @PowerManager.WakeReason int mLastWakeReason = PowerManager.WAKE_REASON_UNKNOWN;
+
+    @Nullable
+    private Point mLastWakeOriginLocation = null;
+
     private @PowerManager.GoToSleepReason int mLastSleepReason =
             PowerManager.GO_TO_SLEEP_REASON_MIN;
 
+    @Nullable
+    private Point mLastSleepOriginLocation = null;
+
     @Inject
-    public WakefulnessLifecycle() {
+    public WakefulnessLifecycle(
+            Context context,
+            @Nullable IWallpaperManager wallpaperManagerService) {
+        mContext = context;
+        mDisplayMetrics = context.getResources().getDisplayMetrics();
+        mWallpaperManagerService = wallpaperManagerService;
     }
 
     public @Wakefulness int getWakefulness() {
@@ -85,6 +112,15 @@
         }
         setWakefulness(WAKEFULNESS_WAKING);
         mLastWakeReason = pmWakeReason;
+        updateLastWakeOriginLocation();
+
+        try {
+            mWallpaperManagerService.notifyWakingUp(
+                    mLastWakeOriginLocation.x, mLastWakeOriginLocation.y, new Bundle());
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+
         dispatch(Observer::onStartedWakingUp);
     }
 
@@ -102,6 +138,15 @@
         }
         setWakefulness(WAKEFULNESS_GOING_TO_SLEEP);
         mLastSleepReason = pmSleepReason;
+        updateLastSleepOriginLocation();
+
+        try {
+            mWallpaperManagerService.notifyGoingToSleep(
+                    mLastSleepOriginLocation.x, mLastSleepOriginLocation.y, new Bundle());
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+
         dispatch(Observer::onStartedGoingToSleep);
     }
 
@@ -124,6 +169,60 @@
         Trace.traceCounter(Trace.TRACE_TAG_APP, "wakefulness", wakefulness);
     }
 
+    private void updateLastWakeOriginLocation() {
+        mLastWakeOriginLocation = null;
+
+        switch (mLastWakeReason) {
+            case PowerManager.WAKE_REASON_POWER_BUTTON:
+                mLastWakeOriginLocation = getPowerButtonOrigin();
+                break;
+            default:
+                mLastWakeOriginLocation = getDefaultWakeSleepOrigin();
+                break;
+        }
+    }
+
+    private void updateLastSleepOriginLocation() {
+        mLastSleepOriginLocation = null;
+
+        switch (mLastSleepReason) {
+            case PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON:
+                mLastSleepOriginLocation = getPowerButtonOrigin();
+                break;
+            default:
+                mLastSleepOriginLocation = getDefaultWakeSleepOrigin();
+                break;
+        }
+    }
+
+    /**
+     * Returns the point on the screen closest to the physical power button.
+     */
+    private Point getPowerButtonOrigin() {
+        final boolean isPortrait = mContext.getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_PORTRAIT;
+
+        if (isPortrait) {
+            return new Point(
+                    mDisplayMetrics.widthPixels,
+                    mContext.getResources().getDimensionPixelSize(
+                            R.dimen.physical_power_button_center_screen_location_y));
+        } else {
+            return new Point(
+                    mContext.getResources().getDimensionPixelSize(
+                            R.dimen.physical_power_button_center_screen_location_y),
+                    mDisplayMetrics.heightPixels);
+        }
+    }
+
+    /**
+     * Returns the point on the screen used as the default origin for wake/sleep events. This is the
+     * middle-bottom of the screen.
+     */
+    private Point getDefaultWakeSleepOrigin() {
+        return new Point(mDisplayMetrics.widthPixels / 2, mDisplayMetrics.heightPixels);
+    }
+
     public interface Observer {
         default void onStartedWakingUp() {}
         default void onFinishedWakingUp() {}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index a0b5521..38a6186 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -23,18 +23,14 @@
 import static com.android.systemui.people.PeopleTileViewHelper.getSizeInDp;
 
 import android.app.Activity;
-import android.app.INotificationManager;
-import android.app.people.IPeopleManager;
 import android.app.people.PeopleSpaceTile;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.LauncherApps;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.Outline;
 import android.graphics.drawable.GradientDrawable;
 import android.os.Bundle;
-import android.os.ServiceManager;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -43,7 +39,6 @@
 
 import com.android.systemui.R;
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -56,19 +51,13 @@
     private static final String TAG = "PeopleSpaceActivity";
     private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
 
-    private IPeopleManager mPeopleManager;
     private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
-    private INotificationManager mNotificationManager;
-    private LauncherApps mLauncherApps;
     private Context mContext;
-    private NotificationEntryManager mNotificationEntryManager;
     private int mAppWidgetId;
 
     @Inject
-    public PeopleSpaceActivity(NotificationEntryManager notificationEntryManager,
-            PeopleSpaceWidgetManager peopleSpaceWidgetManager) {
+    public PeopleSpaceActivity(PeopleSpaceWidgetManager peopleSpaceWidgetManager) {
         super();
-        mNotificationEntryManager = notificationEntryManager;
         mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
 
     }
@@ -77,11 +66,6 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mContext = getApplicationContext();
-        mNotificationManager = INotificationManager.Stub.asInterface(
-                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
-        mPeopleManager = IPeopleManager.Stub.asInterface(
-                ServiceManager.getService(Context.PEOPLE_SERVICE));
-        mLauncherApps = mContext.getSystemService(LauncherApps.class);
         mAppWidgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID,
                 INVALID_APPWIDGET_ID);
         setResult(RESULT_CANCELED);
@@ -92,10 +76,8 @@
         List<PeopleSpaceTile> priorityTiles = new ArrayList<>();
         List<PeopleSpaceTile> recentTiles = new ArrayList<>();
         try {
-            priorityTiles = PeopleSpaceUtils.getPriorityTiles(mContext, mNotificationManager,
-                    mPeopleManager, mLauncherApps, mNotificationEntryManager);
-            recentTiles = PeopleSpaceUtils.getRecentTiles(mContext, mNotificationManager,
-                    mPeopleManager, mLauncherApps, mNotificationEntryManager);
+            priorityTiles = mPeopleSpaceWidgetManager.getPriorityTiles();
+            recentTiles = mPeopleSpaceWidgetManager.getRecentTiles();
         } catch (Exception e) {
             Log.e(TAG, "Couldn't retrieve conversations", e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 0fbe1f7..a160379 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -19,8 +19,6 @@
 import static android.app.Notification.CATEGORY_MISSED_CALL;
 import static android.app.Notification.EXTRA_MESSAGES;
 
-import android.annotation.NonNull;
-import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.people.ConversationChannel;
 import android.app.people.IPeopleManager;
@@ -36,16 +34,13 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.icu.text.MeasureFormat;
-import android.icu.util.Measure;
-import android.icu.util.MeasureUnit;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.ContactsContract;
-import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.util.Log;
@@ -65,7 +60,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import java.text.SimpleDateFormat;
-import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -73,7 +67,6 @@
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -85,9 +78,6 @@
     /** Turns on debugging information about People Space. */
     public static final boolean DEBUG = true;
     private static final String TAG = "PeopleSpaceUtils";
-    private static final int DAYS_IN_A_WEEK = 7;
-    private static final int MIN_HOUR = 1;
-    private static final int ONE_DAY = 1;
     public static final String PACKAGE_NAME = "package_name";
     public static final String USER_ID = "user_id";
     public static final String SHORTCUT_ID = "shortcut_id";
@@ -127,58 +117,6 @@
         }
     }
 
-    /** Returns a list of map entries corresponding to user's priority conversations. */
-    @NonNull
-    public static List<PeopleSpaceTile> getPriorityTiles(
-            Context context, INotificationManager notificationManager, IPeopleManager peopleManager,
-            LauncherApps launcherApps, NotificationEntryManager notificationEntryManager)
-            throws Exception {
-        List<ConversationChannelWrapper> conversations =
-                notificationManager.getConversations(
-                        false).getList();
-        // Add priority conversations to tiles list.
-        Stream<ShortcutInfo> priorityConversations = conversations.stream()
-                .filter(c -> c.getNotificationChannel() != null
-                        && c.getNotificationChannel().isImportantConversation())
-                .map(c -> c.getShortcutInfo());
-        List<PeopleSpaceTile> priorityTiles = getSortedTiles(peopleManager, launcherApps,
-                priorityConversations);
-        priorityTiles = augmentTilesFromVisibleNotifications(
-                context, priorityTiles, notificationEntryManager);
-        return priorityTiles;
-    }
-
-    /** Returns a list of map entries corresponding to user's recent conversations. */
-    @NonNull
-    public static List<PeopleSpaceTile> getRecentTiles(
-            Context context, INotificationManager notificationManager, IPeopleManager peopleManager,
-            LauncherApps launcherApps, NotificationEntryManager notificationEntryManager)
-            throws Exception {
-        if (DEBUG) Log.d(TAG, "Add recent conversations");
-        List<ConversationChannelWrapper> conversations =
-                notificationManager.getConversations(
-                        false).getList();
-        Stream<ShortcutInfo> nonPriorityConversations = conversations.stream()
-                .filter(c -> c.getNotificationChannel() == null
-                        || !c.getNotificationChannel().isImportantConversation())
-                .map(c -> c.getShortcutInfo());
-
-        List<ConversationChannel> recentConversationsList =
-                peopleManager.getRecentConversations().getList();
-        Stream<ShortcutInfo> recentConversations = recentConversationsList
-                .stream()
-                .map(c -> c.getShortcutInfo());
-
-        Stream<ShortcutInfo> mergedStream = Stream.concat(nonPriorityConversations,
-                recentConversations);
-        List<PeopleSpaceTile> recentTiles =
-                getSortedTiles(peopleManager, launcherApps, mergedStream);
-
-        recentTiles = augmentTilesFromVisibleNotifications(
-                context, recentTiles, notificationEntryManager);
-        return recentTiles;
-    }
-
     /** Returns stored widgets for the conversation specified. */
     public static Set<String> getStoredWidgetIds(SharedPreferences sp, PeopleTileKey key) {
         if (!key.isValid()) {
@@ -255,7 +193,8 @@
         return augmentedTile.get(0);
     }
 
-    static List<PeopleSpaceTile> augmentTilesFromVisibleNotifications(Context context,
+    /** Adds to {@code tiles} any visible notifications. */
+    public static List<PeopleSpaceTile> augmentTilesFromVisibleNotifications(Context context,
             List<PeopleSpaceTile> tiles, NotificationEntryManager notificationEntryManager) {
         if (notificationEntryManager == null) {
             Log.w(TAG, "NotificationEntryManager is null");
@@ -356,11 +295,12 @@
     }
 
     /** Returns a list sorted by ascending last interaction time from {@code stream}. */
-    private static List<PeopleSpaceTile> getSortedTiles(IPeopleManager peopleManager,
-            LauncherApps launcherApps,
+    public static List<PeopleSpaceTile> getSortedTiles(IPeopleManager peopleManager,
+            LauncherApps launcherApps, UserManager userManager,
             Stream<ShortcutInfo> stream) {
         return stream
                 .filter(Objects::nonNull)
+                .filter(c -> !userManager.isQuietModeEnabled(c.getUserHandle()))
                 .map(c -> new PeopleSpaceTile.Builder(c, launcherApps).build())
                 .filter(c -> shouldKeepConversation(c))
                 .map(c -> c.toBuilder().setLastInteractionTimestamp(
@@ -426,36 +366,6 @@
         return bitmap;
     }
 
-    /** Returns a readable status describing the {@code lastInteraction}. */
-    public static String getLastInteractionString(Context context, long lastInteraction) {
-        if (lastInteraction == 0L) {
-            Log.e(TAG, "Could not get valid last interaction");
-            return context.getString(R.string.basic_status);
-        }
-        long now = System.currentTimeMillis();
-        Duration durationSinceLastInteraction = Duration.ofMillis(now - lastInteraction);
-        MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(),
-                MeasureFormat.FormatWidth.WIDE);
-        if (durationSinceLastInteraction.toHours() < MIN_HOUR) {
-            return context.getString(R.string.timestamp, formatter.formatMeasures(
-                    new Measure(durationSinceLastInteraction.toMinutes(), MeasureUnit.MINUTE)));
-        } else if (durationSinceLastInteraction.toDays() < ONE_DAY) {
-            return context.getString(R.string.timestamp, formatter.formatMeasures(
-                    new Measure(durationSinceLastInteraction.toHours(),
-                            MeasureUnit.HOUR)));
-        } else if (durationSinceLastInteraction.toDays() < DAYS_IN_A_WEEK) {
-            return context.getString(R.string.timestamp, formatter.formatMeasures(
-                    new Measure(durationSinceLastInteraction.toHours(),
-                            MeasureUnit.DAY)));
-        } else {
-            return context.getString(durationSinceLastInteraction.toDays() == DAYS_IN_A_WEEK
-                            ? R.string.timestamp : R.string.over_timestamp,
-                    formatter.formatMeasures(
-                            new Measure(durationSinceLastInteraction.toDays() / DAYS_IN_A_WEEK,
-                                    MeasureUnit.WEEK)));
-        }
-    }
-
     /**
      * Returns whether the {@code conversation} should be kept for display in the People Space.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 51af47d..6b917c5 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -33,7 +33,6 @@
 import static com.android.systemui.people.PeopleSpaceUtils.convertDrawableToBitmap;
 import static com.android.systemui.people.PeopleSpaceUtils.getUserId;
 
-import android.annotation.ColorInt;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.app.people.ConversationStatus;
@@ -41,27 +40,28 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
-import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.icu.text.MeasureFormat;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
 import android.net.Uri;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.IconDrawableFactory;
 import android.util.Log;
-import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.widget.RemoteViews;
 import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.people.widget.LaunchConversationActivity;
 import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
 
 import java.text.NumberFormat;
+import java.time.Duration;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
@@ -76,6 +76,10 @@
     public static final boolean DEBUG = true;
     private static final String TAG = "PeopleTileView";
 
+    private static final int DAYS_IN_A_WEEK = 7;
+    private static final int ONE_DAY = 1;
+    private static final int MAX_WEEKS = 2;
+
     public static final int LAYOUT_SMALL = 0;
     public static final int LAYOUT_MEDIUM = 1;
     public static final int LAYOUT_LARGE = 2;
@@ -83,7 +87,9 @@
     private static final int MIN_CONTENT_MAX_LINES = 2;
 
     private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_CONTENT = 14 + 12 + 4 + 16;
-    private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT = 8 + 4 + 4 + 8;
+    private static final int MIN_MEDIUM_VERTICAL_PADDING = 4;
+    private static final int MAX_MEDIUM_PADDING = 16;
+    private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT_BEFORE_PADDING = 4 + 4;
     private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL = 6 + 4 + 8;
     private static final int FIXED_WIDTH_DIMENS_FOR_SMALL = 4 + 4;
 
@@ -96,6 +102,8 @@
 
     public static final String EMPTY_STRING = "";
 
+    private int mMediumVerticalPadding;
+
     private Context mContext;
     private PeopleSpaceTile mTile;
     private float mDensity;
@@ -205,7 +213,8 @@
     private int getContentHeightForLayout(int lineHeight) {
         switch (mLayoutSize) {
             case LAYOUT_MEDIUM:
-                return mHeight - (lineHeight + FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT);
+                return mHeight - (lineHeight + FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT_BEFORE_PADDING
+                        + mMediumVerticalPadding * 2);
             case LAYOUT_LARGE:
                 return mHeight - (getSizeInDp(
                         R.dimen.max_people_avatar_size_for_large_content) + lineHeight
@@ -224,7 +233,16 @@
         }
         // Small layout used below a certain minimum mWidth with any mHeight.
         if (mWidth >= getSizeInDp(R.dimen.required_width_for_medium)) {
-            if (DEBUG) Log.d(TAG, "Medium view for mWidth: " + mWidth + " mHeight: " + mHeight);
+            int spaceAvailableForPadding =
+                    mHeight - (getSizeInDp(R.dimen.avatar_size_for_medium) + 4 + getLineHeight(
+                            getSizeInDp(R.dimen.name_text_size_for_medium)));
+            if (DEBUG) {
+                Log.d(TAG, "Medium view for mWidth: " + mWidth + " mHeight: " + mHeight
+                        + " with padding space: " + spaceAvailableForPadding);
+            }
+            int maxVerticalPadding = Math.min(Math.floorDiv(spaceAvailableForPadding, 2),
+                    MAX_MEDIUM_PADDING);
+            mMediumVerticalPadding = Math.max(MIN_MEDIUM_VERTICAL_PADDING, maxVerticalPadding);
             return LAYOUT_MEDIUM;
         }
         // Small layout can always handle our minimum mWidth and mHeight for our widget.
@@ -347,11 +365,7 @@
             setMaxLines(views);
             CharSequence content = mTile.getNotificationContent();
             views = setPunctuationRemoteViewsFields(views, content);
-            // TODO(b/184931139): Update to RemoteViews wrapper to set via attribute once available
-            @ColorInt int color = Utils.getColorAttr(mContext,
-                    android.R.attr.textColorPrimary).getDefaultColor();
-            views.setInt(R.id.text_content, "setTextColor", color);
-
+            views.setColorAttr(R.id.text_content, "setTextColor", android.R.attr.textColorPrimary);
             views.setTextViewText(R.id.text_content, mTile.getNotificationContent());
             views.setViewVisibility(R.id.image, View.GONE);
             views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message);
@@ -398,9 +412,7 @@
         views.setViewVisibility(R.id.messages_count, View.GONE);
         setMaxLines(views);
         // Secondary text color for statuses.
-        @ColorInt int secondaryColor = Utils.getColorAttr(mContext,
-                android.R.attr.textColorSecondary).getDefaultColor();
-        views.setInt(R.id.text_content, "setTextColor", secondaryColor);
+        views.setColorAttr(R.id.text_content, "setTextColor", android.R.attr.textColorSecondary);
         views.setTextViewText(R.id.text_content, statusText);
 
         Icon statusIcon = status.getIcon();
@@ -541,6 +553,14 @@
             views.setViewVisibility(R.id.text_content, View.VISIBLE);
             views.setViewVisibility(R.id.subtext, View.GONE);
         }
+
+        if (mLayoutSize == LAYOUT_MEDIUM) {
+            if (DEBUG) Log.d(TAG, "Set vertical padding: " + mMediumVerticalPadding);
+            int horizontalPadding = (int) Math.floor(MAX_MEDIUM_PADDING * mDensity);
+            int verticalPadding = (int) Math.floor(mMediumVerticalPadding * mDensity);
+            views.setViewPadding(R.id.item, horizontalPadding, verticalPadding, horizontalPadding,
+                    verticalPadding);
+        }
         return views;
     }
 
@@ -551,9 +571,16 @@
             views.setViewVisibility(R.id.predefined_icon, View.GONE);
             views.setViewVisibility(R.id.messages_count, View.GONE);
         }
-        String status = PeopleSpaceUtils.getLastInteractionString(mContext,
+        String status = getLastInteractionString(mContext,
                 mTile.getLastInteractionTimestamp());
-        views.setTextViewText(R.id.last_interaction, status);
+        if (status != null) {
+            if (DEBUG) Log.d(TAG, "Show last interaction");
+            views.setViewVisibility(R.id.last_interaction, View.VISIBLE);
+            views.setTextViewText(R.id.last_interaction, status);
+        } else {
+            if (DEBUG) Log.d(TAG, "Hide last interaction");
+            views.setViewVisibility(R.id.last_interaction, View.GONE);
+        }
         return views;
     }
 
@@ -599,4 +626,34 @@
                 hasNewStory);
         return convertDrawableToBitmap(personDrawable);
     }
+
+    /** Returns a readable status describing the {@code lastInteraction}. */
+    @Nullable
+    public static String getLastInteractionString(Context context, long lastInteraction) {
+        if (lastInteraction == 0L) {
+            Log.e(TAG, "Could not get valid last interaction");
+            return null;
+        }
+        long now = System.currentTimeMillis();
+        Duration durationSinceLastInteraction = Duration.ofMillis(now - lastInteraction);
+        MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(),
+                MeasureFormat.FormatWidth.WIDE);
+        if (durationSinceLastInteraction.toDays() <= ONE_DAY) {
+            return null;
+        } else if (durationSinceLastInteraction.toDays() < DAYS_IN_A_WEEK) {
+            return context.getString(R.string.timestamp, formatter.formatMeasures(
+                    new Measure(durationSinceLastInteraction.toHours(),
+                            MeasureUnit.DAY)));
+        } else if (durationSinceLastInteraction.toDays() <= DAYS_IN_A_WEEK * 2) {
+            return context.getString(durationSinceLastInteraction.toDays() == DAYS_IN_A_WEEK
+                            ? R.string.timestamp : R.string.over_timestamp,
+                    formatter.formatMeasures(
+                            new Measure(durationSinceLastInteraction.toDays() / DAYS_IN_A_WEEK,
+                                    MeasureUnit.WEEK)));
+        } else {
+            // Over 2 weeks ago
+            return context.getString(R.string.over_timestamp,
+                    formatter.formatMeasures(new Measure(MAX_WEEKS, MeasureUnit.WEEK)));
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
index c01a52d..c416b5e 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
@@ -23,11 +23,13 @@
 import android.os.Bundle;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.service.notification.NotificationStats;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.UnlaunchableAppActivity;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.internal.statusbar.IStatusBarService;
@@ -48,15 +50,17 @@
     private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
     private NotificationEntryManager mNotificationEntryManager;
     private final Optional<BubblesManager> mBubblesManagerOptional;
+    private final UserManager mUserManager;
     private boolean mIsForTesting;
     private IStatusBarService mIStatusBarService;
 
     @Inject
     public LaunchConversationActivity(NotificationEntryManager notificationEntryManager,
-            Optional<BubblesManager> bubblesManagerOptional) {
+            Optional<BubblesManager> bubblesManagerOptional, UserManager userManager) {
         super();
         mNotificationEntryManager = notificationEntryManager;
         mBubblesManagerOptional = bubblesManagerOptional;
+        mUserManager = userManager;
     }
 
     @Override
@@ -80,12 +84,24 @@
             }
             mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_CLICKED);
             try {
+
+                if (mUserManager.isQuietModeEnabled(userHandle)) {
+                    if (DEBUG) Log.d(TAG, "Cannot launch app when quieted");
+                    final Intent dialogIntent =
+                            UnlaunchableAppActivity.createInQuietModeDialogIntent(
+                                    userHandle.getIdentifier());
+                    this.getApplicationContext().startActivity(dialogIntent);
+                    finish();
+                    return;
+                }
+
                 NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
                         notificationKey);
                 if (entry != null && entry.canBubble() && mBubblesManagerOptional.isPresent()) {
                     if (DEBUG) Log.d(TAG, "Open bubble for conversation");
                     mBubblesManagerOptional.get().expandStackAndSelectBubble(entry);
                     // Just opt-out and don't cancel the notification for bubbles.
+                    finish();
                     return;
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index d63dc4a..6cb7c31 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -31,7 +31,9 @@
 import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetOptionsAndView;
 import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetViews;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.PendingIntent;
@@ -51,7 +53,9 @@
 import android.os.Bundle;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.preference.PreferenceManager;
+import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
@@ -76,6 +80,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Stream;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -96,6 +101,8 @@
     private NotificationEntryManager mNotificationEntryManager;
     private PackageManager mPackageManager;
     private PeopleSpaceWidgetProvider mPeopleSpaceWidgetProvider;
+    private INotificationManager mINotificationManager;
+    private UserManager mUserManager;
     public UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
     @GuardedBy("mLock")
     public static Map<PeopleTileKey, PeopleSpaceWidgetProvider.TileConversationListener>
@@ -121,17 +128,21 @@
         mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
         mPackageManager = mContext.getPackageManager();
         mPeopleSpaceWidgetProvider = new PeopleSpaceWidgetProvider();
+        mINotificationManager = INotificationManager.Stub.asInterface(
+                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+        mUserManager = context.getSystemService(UserManager.class);
     }
 
     /**
      * AppWidgetManager setter used for testing.
      */
     @VisibleForTesting
-    protected void setAppWidgetManager(
+    public void setAppWidgetManager(
             AppWidgetManager appWidgetManager, IPeopleManager iPeopleManager,
             PeopleManager peopleManager, LauncherApps launcherApps,
             NotificationEntryManager notificationEntryManager, PackageManager packageManager,
-            boolean isForTesting, PeopleSpaceWidgetProvider peopleSpaceWidgetProvider) {
+            boolean isForTesting, PeopleSpaceWidgetProvider peopleSpaceWidgetProvider,
+            UserManager userManager, INotificationManager notificationManager) {
         mAppWidgetManager = appWidgetManager;
         mIPeopleManager = iPeopleManager;
         mPeopleManager = peopleManager;
@@ -140,6 +151,8 @@
         mPackageManager = packageManager;
         mIsForTesting = isForTesting;
         mPeopleSpaceWidgetProvider = peopleSpaceWidgetProvider;
+        mUserManager = userManager;
+        mINotificationManager = notificationManager;
     }
 
     /**
@@ -783,4 +796,52 @@
         ComponentName componentName = new ComponentName(mContext, PeopleSpaceWidgetProvider.class);
         return mAppWidgetManager.requestPinAppWidget(componentName, extras, successCallback);
     }
+
+    /** Returns a list of map entries corresponding to user's priority conversations. */
+    @NonNull
+    public List<PeopleSpaceTile> getPriorityTiles()
+            throws Exception {
+        List<ConversationChannelWrapper> conversations =
+                mINotificationManager.getConversations(true).getList();
+        // Add priority conversations to tiles list.
+        Stream<ShortcutInfo> priorityConversations = conversations.stream()
+                .filter(c -> c.getNotificationChannel() != null
+                        && c.getNotificationChannel().isImportantConversation())
+                .map(c -> c.getShortcutInfo());
+        List<PeopleSpaceTile> priorityTiles = PeopleSpaceUtils.getSortedTiles(mIPeopleManager,
+                mLauncherApps, mUserManager,
+                priorityConversations);
+        priorityTiles = PeopleSpaceUtils.augmentTilesFromVisibleNotifications(
+                mContext, priorityTiles, mNotificationEntryManager);
+        return priorityTiles;
+    }
+
+    /** Returns a list of map entries corresponding to user's recent conversations. */
+    @NonNull
+    public List<PeopleSpaceTile> getRecentTiles()
+            throws Exception {
+        if (DEBUG) Log.d(TAG, "Add recent conversations");
+        List<ConversationChannelWrapper> conversations =
+                mINotificationManager.getConversations(false).getList();
+        Stream<ShortcutInfo> nonPriorityConversations = conversations.stream()
+                .filter(c -> c.getNotificationChannel() == null
+                        || !c.getNotificationChannel().isImportantConversation())
+                .map(c -> c.getShortcutInfo());
+
+        List<ConversationChannel> recentConversationsList =
+                mIPeopleManager.getRecentConversations().getList();
+        Stream<ShortcutInfo> recentConversations = recentConversationsList
+                .stream()
+                .map(c -> c.getShortcutInfo());
+
+        Stream<ShortcutInfo> mergedStream = Stream.concat(nonPriorityConversations,
+                recentConversations);
+        List<PeopleSpaceTile> recentTiles =
+                PeopleSpaceUtils.getSortedTiles(mIPeopleManager, mLauncherApps, mUserManager,
+                        mergedStream);
+
+        recentTiles = PeopleSpaceUtils.augmentTilesFromVisibleNotifications(
+                mContext, recentTiles, mNotificationEntryManager);
+        return recentTiles;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
index f87ea7c..feb27d80 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
@@ -29,6 +29,7 @@
 import androidx.annotation.MainThread
 import androidx.annotation.VisibleForTesting
 import androidx.annotation.WorkerThread
+import com.android.systemui.appops.AppOpsController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -48,7 +49,6 @@
         return PrivacyDialog(context, list, starter)
     }
 }
-
 /**
  * Controller for [PrivacyDialog].
  *
@@ -66,6 +66,7 @@
     private val uiExecutor: Executor,
     private val privacyLogger: PrivacyLogger,
     private val keyguardStateController: KeyguardStateController,
+    private val appOpsController: AppOpsController,
     @VisibleForTesting private val dialogProvider: DialogProvider
 ) {
 
@@ -79,7 +80,8 @@
         @Background backgroundExecutor: Executor,
         @Main uiExecutor: Executor,
         privacyLogger: PrivacyLogger,
-        keyguardStateController: KeyguardStateController
+        keyguardStateController: KeyguardStateController,
+        appOpsController: AppOpsController
     ) : this(
             permissionManager,
             packageManager,
@@ -90,6 +92,7 @@
             uiExecutor,
             privacyLogger,
             keyguardStateController,
+            appOpsController,
             defaultDialogProvider
     )
 
@@ -127,7 +130,9 @@
     }
 
     @WorkerThread
-    private fun permGroupUsage(): List<PermGroupUsage> = permissionManager.indicatorAppOpUsageData
+    private fun permGroupUsage(): List<PermGroupUsage> {
+        return permissionManager.getIndicatorAppOpUsageData(appOpsController.isMicMuted)
+    }
 
     /**
      * Show the [PrivacyDialog]
@@ -261,4 +266,4 @@
             starter: (String, Int) -> Unit
         ): PrivacyDialog
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
index 4c617ed..63ec6e5 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
@@ -54,7 +54,8 @@
 data class PrivacyItem(
     val privacyType: PrivacyType,
     val application: PrivacyApplication,
-    val timeStampElapsed: Long = UNKNOWN_TIMESTAMP
+    val timeStampElapsed: Long = UNKNOWN_TIMESTAMP,
+    val paused: Boolean = false
 ) {
     val log = "(${privacyType.logName}, ${application.packageName}(${application.uid}), " +
             "$timeStampElapsed)"
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index f7e2a31..8b27b6e 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -257,7 +257,7 @@
             privacyList = emptyList()
             return
         }
-        val list = appOpsController.getActiveAppOpsForUser(UserHandle.USER_ALL).filter {
+        val list = appOpsController.getActiveAppOps(true).filter {
             UserHandle.getUserId(it.uid) in currentUserIds ||
                     it.code == AppOpsManager.OP_PHONE_CALL_MICROPHONE ||
                     it.code == AppOpsManager.OP_PHONE_CALL_CAMERA
@@ -279,7 +279,9 @@
 
         // Anything earlier than this timestamp can be removed
         val removeBeforeTime = systemClock.elapsedRealtime() - TIME_TO_HOLD_INDICATORS
-        val mustKeep = privacyList.filter { it.timeStampElapsed > removeBeforeTime && it !in list }
+        val mustKeep = privacyList.filter {
+            it.timeStampElapsed > removeBeforeTime && !(it isIn list)
+        }
 
         // There are items we must keep because they haven't been around for enough time.
         if (mustKeep.isNotEmpty()) {
@@ -291,7 +293,18 @@
             logger.logPrivacyItemsUpdateScheduled(delay)
             holdingRunnableCanceler = bgExecutor.executeDelayed(updateListAndNotifyChanges, delay)
         }
-        return list + mustKeep
+        return list.filter { !it.paused } + mustKeep
+    }
+
+    /**
+     * Ignores the paused status to determine if the element is in the list
+     */
+    private infix fun PrivacyItem.isIn(list: List<PrivacyItem>): Boolean {
+        return list.any {
+            it.privacyType == privacyType &&
+                    it.application == application &&
+                    it.timeStampElapsed == timeStampElapsed
+        }
     }
 
     private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? {
@@ -308,7 +321,7 @@
             return null
         }
         val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid)
-        return PrivacyItem(type, app, appOpItem.timeStartedElapsed)
+        return PrivacyItem(type, app, appOpItem.timeStartedElapsed, appOpItem.isDisabled)
     }
 
     interface Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index 3467838..74ae3a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -32,6 +32,7 @@
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.R;
+import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.globalactions.GlobalActionsDialogLite;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -65,6 +66,7 @@
     private final MetricsLogger mMetricsLogger;
     private final FalsingManager mFalsingManager;
     private final SettingsButton mSettingsButton;
+    private final View mSettingsButtonContainer;
     private final TextView mBuildText;
     private final View mEdit;
     private final MultiUserSwitch mMultiUserSwitch;
@@ -152,6 +154,7 @@
         mFalsingManager = falsingManager;
 
         mSettingsButton = mView.findViewById(R.id.settings_button);
+        mSettingsButtonContainer = mView.findViewById(R.id.settings_button_container);
         mBuildText = mView.findViewById(R.id.build);
         mEdit = mView.findViewById(android.R.id.edit);
         mMultiUserSwitch = mView.findViewById(R.id.multi_user_switch);
@@ -258,10 +261,12 @@
         mView.disable(state2, isTunerEnabled());
     }
 
-
     private void startSettingsActivity() {
+        ActivityLaunchAnimator.Controller animationController =
+                mSettingsButtonContainer != null ? ActivityLaunchAnimator.Controller.fromView(
+                        mSettingsButtonContainer) : null;
         mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
-                true /* dismissShade */);
+                true /* dismissShade */, animationController);
     }
 
     private boolean isTunerEnabled() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index b728b43..6f19276 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -184,8 +184,7 @@
 
     @Override
     public CharSequence getTileLabel() {
-        CharSequence qawLabel = mQuickAccessWalletClient.getServiceLabel();
-        return qawLabel == null ? mLabel : qawLabel;
+        return mLabel;
     }
 
     private void queryWalletCards() {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index dbd6758..b60fd13 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -48,7 +48,7 @@
         super(context, attrs);
     }
 
-    // Inflated from quick_settings_brightness_dialog or quick_settings_brightness_dialog_thick
+    // Inflated from quick_settings_brightness_dialog
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index f57fd21..f4266a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -41,6 +41,7 @@
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.notification.stack.ViewState;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 
@@ -82,6 +83,9 @@
     private Rect mClipRect = new Rect();
     private int mCutoutHeight;
     private int mGapHeight;
+    private int mIndexOfFirstViewInShelf = -1;
+    private int mIndexOfFirstViewInOverflowingSection = -1;
+
     private NotificationShelfController mController;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
@@ -159,30 +163,49 @@
     }
 
     /** Update the state of the shelf. */
-    public void updateState(AmbientState ambientState) {
+    public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
+            AmbientState ambientState) {
         ExpandableView lastView = ambientState.getLastVisibleBackgroundChild();
         ShelfState viewState = (ShelfState) getViewState();
         if (mShowNotificationShelf && lastView != null) {
-            float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding()
-                    + ambientState.getStackTranslation();
             ExpandableViewState lastViewState = lastView.getViewState();
-            float viewEnd = lastViewState.yTranslation + lastViewState.height;
             viewState.copyFrom(lastViewState);
+
             viewState.height = getIntrinsicHeight();
-            viewState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - viewState.height,
-                    getFullyClosedTranslation());
             viewState.zTranslation = ambientState.getBaseZHeight();
             viewState.clipTopAmount = 0;
             viewState.alpha = 1f - ambientState.getHideAmount();
             viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
             viewState.hideSensitive = false;
             viewState.xTranslation = getTranslationX();
+            viewState.hasItemsInStableShelf = lastViewState.inShelf;
+            viewState.firstViewInShelf = algorithmState.firstViewInShelf;
+            viewState.firstViewInOverflowSection = algorithmState.firstViewInOverflowSection;
             if (mNotGoneIndex != -1) {
                 viewState.notGoneIndex = Math.min(viewState.notGoneIndex, mNotGoneIndex);
             }
-            viewState.hasItemsInStableShelf = lastViewState.inShelf;
+
             viewState.hidden = !mAmbientState.isShadeExpanded()
-                    || mAmbientState.isQsCustomizerShowing();
+                    || mAmbientState.isQsCustomizerShowing()
+                    || algorithmState.firstViewInShelf == null;
+
+            final int indexOfFirstViewInShelf = algorithmState.visibleChildren.indexOf(
+                    algorithmState.firstViewInShelf);
+
+            if (mAmbientState.isExpansionChanging()
+                    && algorithmState.firstViewInShelf != null
+                    && indexOfFirstViewInShelf > 0) {
+
+                // Show shelf if section before it is showing.
+                final ExpandableView viewBeforeShelf = algorithmState.visibleChildren.get(
+                        indexOfFirstViewInShelf - 1);
+                if (viewBeforeShelf.getViewState().hidden) {
+                    viewState.hidden = true;
+                }
+            }
+
+            final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
+            viewState.yTranslation = stackEnd - viewState.height;
         } else {
             viewState.hidden = true;
             viewState.location = ExpandableViewState.LOCATION_GONE;
@@ -199,13 +222,11 @@
         if (!mShowNotificationShelf) {
             return;
         }
-
         mShelfIcons.resetViewStates();
         float shelfStart = getTranslationY();
         float numViewsInShelf = 0.0f;
         View lastChild = mAmbientState.getLastVisibleBackgroundChild();
         mNotGoneIndex = -1;
-        float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2;
         //  find the first view that doesn't overlap with the shelf
         int notGoneIndex = 0;
         int colorOfViewBeforeLast = NO_COLOR;
@@ -219,7 +240,7 @@
         float currentScrollVelocity = mAmbientState.getCurrentScrollVelocity();
         boolean scrollingFast = currentScrollVelocity > mScrollFastThreshold
                 || (mAmbientState.isExpansionChanging()
-                        && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold);
+                && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold);
         boolean expandingAnimated = mAmbientState.isExpansionChanging()
                 && !mAmbientState.isPanelTracking();
         int baseZHeight = mAmbientState.getBaseZHeight();
@@ -233,22 +254,37 @@
             if (!child.needsClippingToShelf() || child.getVisibility() == GONE) {
                 continue;
             }
-
             float notificationClipEnd;
             boolean aboveShelf = ViewState.getFinalTranslationZ(child) > baseZHeight
                     || child.isPinned();
             boolean isLastChild = child == lastChild;
             float rowTranslationY = child.getTranslationY();
+
+            final float inShelfAmount = updateShelfTransformation(i, child, scrollingFast,
+                    expandingAnimated, isLastChild);
+
+            final float stackEnd = mAmbientState.getStackY()
+                    + mAmbientState.getStackHeight();
+            // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
             if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) {
-                notificationClipEnd = shelfStart + getIntrinsicHeight();
+                notificationClipEnd = stackEnd;
+            } else if (mAmbientState.isExpansionChanging()) {
+                if (mIndexOfFirstViewInOverflowingSection != -1
+                    && i >= mIndexOfFirstViewInOverflowingSection) {
+                    // Clip notifications in (section overflowing into shelf) to shelf start.
+                    notificationClipEnd = shelfStart - mPaddingBetweenElements;
+                } else {
+                    // Clip notifications before the section overflowing into shelf
+                    // to stackEnd because we do not show the shelf if the section right before the
+                    // shelf is still hidden.
+                    notificationClipEnd = stackEnd;
+                }
             } else {
                 notificationClipEnd = shelfStart - mPaddingBetweenElements;
             }
             int clipTop = updateNotificationClipHeight(child, notificationClipEnd, notGoneIndex);
             clipTopAmount = Math.max(clipTop, clipTopAmount);
 
-            final float inShelfAmount = updateShelfTransformation(child, scrollingFast,
-                    expandingAnimated, isLastChild);
             // If the current row is an ExpandableNotificationRow, update its color, roundedness,
             // and icon state.
             if (child instanceof ExpandableNotificationRow) {
@@ -314,19 +350,23 @@
                                 distanceToGapTop / mGapHeight);
                         previousAnv.setBottomRoundness(firstElementRoundness,
                                 false /* don't animate */);
-                        backgroundTop = (int) distanceToGapBottom;
                     }
                 }
                 previousAnv = anv;
             }
         }
+
         clipTransientViews();
 
         setClipTopAmount(clipTopAmount);
-        boolean isHidden = getViewState().hidden || clipTopAmount >= getIntrinsicHeight();
-        if (mShowNotificationShelf) {
-            setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
-        }
+
+        boolean isHidden = getViewState().hidden
+                || clipTopAmount >= getIntrinsicHeight()
+                || !mShowNotificationShelf
+                || numViewsInShelf < 1f;
+
+        // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
+        setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
         setBackgroundTop(backgroundTop);
         setFirstElementRoundness(firstElementRoundness);
         mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
@@ -339,11 +379,10 @@
                 continue;
             }
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            updateIconClipAmount(row);
             updateContinuousClipping(row);
         }
-        boolean hideBackground = numViewsInShelf < 1.0f;
-        setHideBackground(hideBackground || backgroundForceHidden);
+        boolean hideBackground = isHidden;
+        setHideBackground(hideBackground);
         if (mNotGoneIndex == -1) {
             mNotGoneIndex = notGoneIndex;
         }
@@ -476,10 +515,10 @@
     /**
      * @return the amount how much this notification is in the shelf
      */
-    private float updateShelfTransformation(ExpandableView view, boolean scrollingFast,
+    private float updateShelfTransformation(int i, ExpandableView view, boolean scrollingFast,
             boolean expandingAnimated, boolean isLastChild) {
 
-        // Let calculate how much the view is in the shelf
+        // Let's calculate how much the view is in the shelf
         float viewStart = view.getTranslationY();
         int fullHeight = view.getActualHeight() + mPaddingBetweenElements;
         float iconTransformStart = calculateIconTransformationStart(view);
@@ -496,15 +535,21 @@
                     transformDistance,
                     view.getMinHeight() - getIntrinsicHeight());
         }
+
         float viewEnd = viewStart + fullHeight;
         float fullTransitionAmount = 0.0f;
         float iconTransitionAmount = 0.0f;
         float shelfStart = getTranslationY();
-
-        if (viewEnd >= shelfStart
+        if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) {
+            // TODO(b/172289889) handle icon placement for notification that is clipped by the shelf
+            if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) {
+                fullTransitionAmount = 1f;
+                iconTransitionAmount = 1f;
+            }
+        } else if (viewEnd >= shelfStart
                 && (!mAmbientState.isUnlockHintRunning() || view.isInShelf())
                 && (mAmbientState.isShadeExpanded()
-                        || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) {
+                || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) {
 
             if (viewStart < shelfStart) {
                 float fullAmount = (shelfStart - viewStart) / fullHeight;
@@ -572,7 +617,7 @@
                     && !mNoAnimationsInThisFrame;
         }
         iconState.clampedAppearAmount = clampedAmount;
-        setIconTransformationAmount(view, transitionAmount, isLastChild);
+        setIconTransformationAmount(view, transitionAmount);
     }
 
     private boolean isTargetClipped(ExpandableView view) {
@@ -585,12 +630,10 @@
                 + view.getContentTranslation()
                 + view.getRelativeTopPadding(target)
                 + target.getHeight();
-
         return endOfTarget >= getTranslationY() - mPaddingBetweenElements;
     }
 
-    private void setIconTransformationAmount(ExpandableView view, float transitionAmount,
-            boolean isLastChild) {
+    private void setIconTransformationAmount(ExpandableView view, float transitionAmount) {
         if (!(view instanceof ExpandableNotificationRow)) {
             return;
         }
@@ -601,7 +644,6 @@
             return;
         }
         iconState.alpha = transitionAmount;
-
         boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf();
         iconState.hidden = isAppearing
                 || (view instanceof ExpandableNotificationRow
@@ -610,8 +652,8 @@
                 || (transitionAmount == 0.0f && !iconState.isAnimating(icon))
                 || row.isAboveShelf()
                 || row.showingPulsing()
-                || (!row.isInShelf() && isLastChild)
                 || row.getTranslationZ() > mAmbientState.getBaseZHeight();
+
         iconState.iconAppearAmount = iconState.hidden? 0f : transitionAmount;
 
         // Fade in icons at shelf start
@@ -790,8 +832,19 @@
         mController = notificationShelfController;
     }
 
+    public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
+        mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
+    }
+
+    public void setFirstViewInOverflowingSection(ExpandableView firstViewInOverflowingSection) {
+        mIndexOfFirstViewInOverflowingSection =
+                mHostLayoutController.indexOfChild(firstViewInOverflowingSection);
+    }
+
     private class ShelfState extends ExpandableViewState {
         private boolean hasItemsInStableShelf;
+        private ExpandableView firstViewInShelf;
+        private ExpandableView firstViewInOverflowSection;
 
         @Override
         public void applyToView(View view) {
@@ -800,6 +853,8 @@
             }
 
             super.applyToView(view);
+            setIndexOfFirstViewInShelf(firstViewInShelf);
+            setFirstViewInOverflowingSection(firstViewInOverflowSection);
             updateAppearance();
             setHasItemsInStableShelf(hasItemsInStableShelf);
             mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
@@ -812,6 +867,8 @@
             }
 
             super.animateTo(child, properties);
+            setIndexOfFirstViewInShelf(firstViewInShelf);
+            setFirstViewInOverflowingSection(firstViewInOverflowSection);
             updateAppearance();
             setHasItemsInStableShelf(hasItemsInStableShelf);
             mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
index 1e935c1..4f70fdb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
@@ -22,6 +22,7 @@
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 import com.android.systemui.statusbar.phone.StatusBarNotificationPresenter;
@@ -103,9 +104,10 @@
         return mView.getHeight();
     }
 
-    public void updateState(AmbientState ambientState) {
+    public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
+            AmbientState ambientState) {
         mAmbientState = ambientState;
-        mView.updateState(ambientState);
+        mView.updateState(algorithmState, ambientState);
     }
 
     public int getIntrinsicHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
new file mode 100644
index 0000000..5ab71bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -0,0 +1,352 @@
+/*
+ * 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.events
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ObjectAnimator
+import android.annotation.UiThread
+import android.util.Log
+import android.view.Gravity
+import android.view.View
+import android.widget.FrameLayout
+
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+
+import java.lang.IllegalStateException
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Understands how to keep the persistent privacy dot in the corner of the screen in
+ * ScreenDecorations, which does not rotate with the device.
+ *
+ * The basic principle here is that each dot will sit in a box that is equal to the margins of the
+ * status bar (specifically the status_bar_contents view in PhoneStatusBarView). Each dot container
+ * will have its gravity set towards the corner (i.e., top-right corner gets top|right gravity), and
+ * the contained ImageView will be set to center_vertical and away from the corner horizontally. The
+ * Views will match the status bar top padding and status bar height so that the dot can appear to
+ * reside directly after the status bar system contents (basically to the right of the battery).
+ *
+ * NOTE: any operation that modifies views directly must run on the provided executor, because
+ * these views are owned by ScreenDecorations and it runs in its own thread
+ */
+
+@SysUISingleton
+class PrivacyDotViewController @Inject constructor(
+    @Main val mainExecutor: Executor,
+    val animationScheduler: SystemStatusAnimationScheduler
+) {
+    private var rotation = 0
+    private var leftSize = 0
+    private var rightSize = 0
+
+    private var sbHeightPortrait = 0
+    private var sbHeightLandscape = 0
+
+    private var hasMultipleHeights = false
+    private var needsHeightUpdate = false
+    private var needsRotationUpdate = false
+    private var needsMarginUpdate = false
+
+    private lateinit var tl: View
+    private lateinit var tr: View
+    private lateinit var bl: View
+    private lateinit var br: View
+
+    // Track which corner is active (based on orientation + RTL)
+    private var designatedCorner: View? = null
+
+    // Privacy dots are created in ScreenDecoration's UiThread, which is not the main thread
+    private var uiExecutor: Executor? = null
+
+    private val views: Sequence<View>
+        get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
+
+    fun setUiExecutor(e: Executor) {
+        uiExecutor = e
+    }
+
+    @UiThread
+    fun updateRotation(rot: Int) {
+        if (rot == rotation) {
+            return
+        }
+
+        // A rotation has started, hide the views to avoid flicker
+        setCornerVisibilities(View.INVISIBLE)
+
+        if (hasMultipleHeights && (rotation % 2) != (rot % 2)) {
+            // we've changed from vertical to horizontal; update status bar height
+            needsHeightUpdate = true
+        }
+
+        rotation = rot
+        needsRotationUpdate = true
+    }
+
+    @UiThread
+    private fun updateHeights(rot: Int) {
+        val height = when (rot) {
+            0, 2 -> sbHeightPortrait
+            1, 3 -> sbHeightLandscape
+            else -> 0
+        }
+
+        views.forEach { it.layoutParams.height = height }
+    }
+
+    // Update the gravity and margins of the privacy views
+    @UiThread
+    private fun updateRotations() {
+        // To keep a view in the corner, its gravity is always the description of its current corner
+        // Therefore, just figure out which view is in which corner. This turns out to be something
+        // like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and
+        // rotating the device counter-clockwise increments rotation by 1
+
+        views.forEach { corner ->
+            val rotatedCorner = rotatedCorner(cornerForView(corner))
+            (corner.layoutParams as FrameLayout.LayoutParams).apply {
+                gravity = rotatedCorner.toGravity()
+            }
+
+            // Set the dot's view gravity to hug the status bar
+            (corner.findViewById<View>(R.id.privacy_dot)
+                    .layoutParams as FrameLayout.LayoutParams)
+                        .gravity = rotatedCorner.innerGravity()
+        }
+    }
+
+    @UiThread
+    private fun updateCornerSizes() {
+        views.forEach { corner ->
+            val rotatedCorner = rotatedCorner(cornerForView(corner))
+            val w = widthForCorner(rotatedCorner)
+            Log.d(TAG, "updateCornerSizes: setting (${cornerForView(corner)}) to $w")
+            (corner.layoutParams as FrameLayout.LayoutParams).width = w
+            corner.requestLayout()
+        }
+    }
+
+    // Designated view will be the one at statusbar's view.END
+    @UiThread
+    private fun selectDesignatedCorner(): View? {
+        if (!this::tl.isInitialized) {
+            return null
+        }
+
+        val isRtl = tl.isLayoutRtl
+
+        return when (rotation) {
+            0 -> if (isRtl) tl else tr
+            1 -> if (isRtl) tr else br
+            2 -> if (isRtl) br else bl
+            3 -> if (isRtl) bl else tl
+            else -> throw IllegalStateException("unknown rotation")
+        }
+    }
+
+    // Track the current designated corner and maybe animate to a new rotation
+    @UiThread
+    private fun updateDesignatedCorner(newCorner: View) {
+        designatedCorner = newCorner
+
+        if (animationScheduler.hasPersistentDot) {
+            designatedCorner!!.visibility = View.VISIBLE
+            designatedCorner!!.alpha = 0f
+            designatedCorner!!.animate()
+                    .alpha(1.0f)
+                    .setDuration(300)
+                    .start()
+        }
+    }
+
+    @UiThread
+    private fun setCornerVisibilities(vis: Int) {
+        views.forEach { corner ->
+            corner.visibility = vis
+        }
+    }
+
+    private fun cornerForView(v: View): Int {
+        return when (v) {
+            tl -> TOP_LEFT
+            tr -> TOP_RIGHT
+            bl -> BOTTOM_LEFT
+            br -> BOTTOM_RIGHT
+            else -> throw IllegalArgumentException("not a corner view")
+        }
+    }
+
+    private fun rotatedCorner(corner: Int): Int {
+        var modded = corner - rotation
+        if (modded < 0) {
+            modded += 4
+        }
+
+        return modded
+    }
+
+    private fun widthForCorner(corner: Int): Int {
+        return when (corner) {
+            TOP_LEFT, BOTTOM_LEFT -> leftSize
+            TOP_RIGHT, BOTTOM_RIGHT -> rightSize
+            else -> throw IllegalArgumentException("Unknown corner")
+        }
+    }
+
+    fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) {
+        if (this::tl.isInitialized && this::tr.isInitialized &&
+                this::bl.isInitialized && this::br.isInitialized) {
+            if (tl == topLeft && tr == topRight && bl == bottomLeft && br == bottomRight) {
+                return
+            }
+        }
+
+        tl = topLeft
+        tr = topRight
+        bl = bottomLeft
+        br = bottomRight
+
+        designatedCorner = selectDesignatedCorner()
+        mainExecutor.execute {
+            animationScheduler.addCallback(systemStatusAnimationCallback)
+        }
+    }
+
+    /**
+     * Set the status bar height in portrait and landscape, in pixels. If they are the same you can
+     * pass the same value twice
+     */
+    fun setStatusBarHeights(portrait: Int, landscape: Int) {
+        sbHeightPortrait = portrait
+        sbHeightLandscape = landscape
+
+        hasMultipleHeights = portrait != landscape
+    }
+
+    /**
+     * The dot view containers will fill the margin in order to position the dots correctly
+     *
+     * @param left the space between the status bar contents and the left side of the screen
+     * @param right space between the status bar contents and the right side of the screen
+     */
+    fun setStatusBarMargins(left: Int, right: Int) {
+        leftSize = left
+        rightSize = right
+
+        needsMarginUpdate = true
+
+        // Margins come after PhoneStatusBarView does a layout pass, and so will always happen
+        // after rotation changes. It is safe to execute the updates from here
+        uiExecutor?.execute {
+            doUpdates(needsRotationUpdate, needsHeightUpdate, needsMarginUpdate)
+        }
+    }
+
+    private fun doUpdates(rot: Boolean, height: Boolean, width: Boolean) {
+        var newDesignatedCorner: View? = null
+
+        if (rot) {
+            needsRotationUpdate = false
+            updateRotations()
+            newDesignatedCorner = selectDesignatedCorner()
+        }
+
+        if (height) {
+            needsHeightUpdate = false
+            updateHeights(rotation)
+        }
+
+        if (width) {
+            needsMarginUpdate = false
+            updateCornerSizes()
+        }
+
+        if (newDesignatedCorner != null && newDesignatedCorner != designatedCorner) {
+            updateDesignatedCorner(newDesignatedCorner)
+        }
+    }
+
+    private val systemStatusAnimationCallback: SystemStatusAnimationCallback =
+            object : SystemStatusAnimationCallback {
+        override fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? {
+            if (designatedCorner == null) {
+                return null
+            }
+
+            val alpha = ObjectAnimator.ofFloat(
+                    designatedCorner, "alpha", 0f, 1f)
+            alpha.duration = DURATION
+            alpha.interpolator = Interpolators.ALPHA_OUT
+            alpha.addListener(object : AnimatorListenerAdapter() {
+                override fun onAnimationStart(animator: Animator) {
+                    uiExecutor?.execute { designatedCorner?.visibility = View.VISIBLE }
+                }
+            })
+            return alpha
+        }
+
+        override fun onHidePersistentDot(): Animator? {
+            if (designatedCorner == null) {
+                return null
+            }
+
+            val alpha = ObjectAnimator.ofFloat(
+                    designatedCorner, "alpha", 1f, 0f)
+            alpha.duration = DURATION
+            alpha.interpolator = Interpolators.ALPHA_OUT
+            alpha.addListener(object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animator: Animator) {
+                    uiExecutor?.execute { designatedCorner?.visibility = View.INVISIBLE }
+                }
+            })
+            alpha.start()
+            return null
+        }
+    }
+}
+
+const val TOP_LEFT = 0
+const val TOP_RIGHT = 1
+const val BOTTOM_RIGHT = 2
+const val BOTTOM_LEFT = 3
+private const val DURATION = 160L
+private const val TAG = "PrivacyDotViewController"
+
+private fun Int.toGravity(): Int {
+    return when (this) {
+        TOP_LEFT -> Gravity.TOP or Gravity.LEFT
+        TOP_RIGHT -> Gravity.TOP or Gravity.RIGHT
+        BOTTOM_LEFT -> Gravity.BOTTOM or Gravity.LEFT
+        BOTTOM_RIGHT -> Gravity.BOTTOM or Gravity.RIGHT
+        else -> throw IllegalArgumentException("Not a corner")
+    }
+}
+
+private fun Int.innerGravity(): Int {
+    return when (this) {
+        TOP_LEFT -> Gravity.CENTER_VERTICAL or Gravity.RIGHT
+        TOP_RIGHT -> Gravity.CENTER_VERTICAL or Gravity.LEFT
+        BOTTOM_LEFT -> Gravity.CENTER_VERTICAL or Gravity.RIGHT
+        BOTTOM_RIGHT -> Gravity.CENTER_VERTICAL or Gravity.LEFT
+        else -> throw IllegalArgumentException("Not a corner")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
new file mode 100644
index 0000000..6380dc0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.events
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.ImageView
+import com.android.settingslib.graph.ThemedBatteryDrawable
+import com.android.systemui.R
+import com.android.systemui.privacy.OngoingPrivacyChip
+import com.android.systemui.privacy.PrivacyItem
+
+interface StatusEvent {
+    val priority: Int
+    // Whether or not to force the status bar open and show a dot
+    val forceVisible: Boolean
+    val viewCreator: (context: Context) -> View
+}
+
+class BatteryEvent : StatusEvent {
+    override val priority = 50
+    override val forceVisible = false
+
+    override val viewCreator: (context: Context) -> View = { context ->
+        val iv = ImageView(context)
+        iv.setImageDrawable(ThemedBatteryDrawable(context, Color.WHITE))
+        iv.setBackgroundDrawable(ColorDrawable(Color.GREEN))
+        iv
+    }
+
+    override fun toString(): String {
+        return javaClass.simpleName
+    }
+}
+class PrivacyEvent : StatusEvent {
+    override val priority = 100
+    override val forceVisible = true
+    var privacyItems: List<PrivacyItem> = listOf()
+
+    override val viewCreator: (context: Context) -> View = { context ->
+        val v = LayoutInflater.from(context)
+                .inflate(R.layout.ongoing_privacy_chip, null) as OngoingPrivacyChip
+        v.privacyList = privacyItems
+        v
+    }
+
+    override fun toString(): String {
+        return javaClass.simpleName
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
new file mode 100644
index 0000000..6209630
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -0,0 +1,137 @@
+/*
+ * 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.events
+
+import android.animation.ValueAnimator
+import android.content.Context
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.FrameLayout
+
+import com.android.systemui.R
+import com.android.systemui.statusbar.SuperStatusBarViewFactory
+import com.android.systemui.statusbar.phone.StatusBarWindowController
+import com.android.systemui.statusbar.phone.StatusBarWindowView
+
+import javax.inject.Inject
+
+/**
+ * //TODO: this _probably_ doesn't control a window anymore
+ * Controls the window for system event animations.
+ */
+class SystemEventChipAnimationController @Inject constructor(
+    private val context: Context,
+    private val statusBarViewFactory: SuperStatusBarViewFactory,
+    private val statusBarWindowController: StatusBarWindowController
+) : SystemStatusChipAnimationCallback {
+    var showPersistentDot = false
+        set(value) {
+            field = value
+            statusBarWindowController.setForceStatusBarVisible(value)
+            maybeUpdateShowDot()
+        }
+
+    private lateinit var animationWindowView: FrameLayout
+    private lateinit var animationDotView: View
+    private lateinit var statusBarWindowView: StatusBarWindowView
+    private var currentAnimatedView: View? = null
+
+    // TODO: move to dagger
+    private var initialized = false
+
+    override fun onChipAnimationStart(
+        viewCreator: (context: Context) -> View,
+        @SystemAnimationState state: Int
+    ) {
+        if (!initialized) init()
+
+        if (state == ANIMATING_IN) {
+            currentAnimatedView = viewCreator(context)
+            animationWindowView.addView(currentAnimatedView, layoutParamsDefault)
+
+            // We are animating IN; chip comes in from View.END
+            currentAnimatedView?.apply {
+                translationX = width.toFloat()
+                alpha = 0f
+                visibility = View.VISIBLE
+            }
+        } else {
+            // We are animating away
+            currentAnimatedView?.apply {
+                translationX = 0f
+                alpha = 1f
+            }
+        }
+    }
+
+    override fun onChipAnimationEnd(@SystemAnimationState state: Int) {
+        if (state == ANIMATING_IN) {
+            // Finished animating in
+            currentAnimatedView?.apply {
+                translationX = 0f
+                alpha = 1f
+            }
+        } else {
+            // Finished animating away
+            currentAnimatedView?.apply {
+                visibility = View.INVISIBLE
+            }
+            animationWindowView.removeView(currentAnimatedView)
+        }
+    }
+
+    override fun onChipAnimationUpdate(
+        animator: ValueAnimator,
+        @SystemAnimationState state: Int
+    ) {
+        // Alpha is parameterized 0,1, and translation from (width, 0)
+        currentAnimatedView?.apply {
+            val amt = animator.animatedValue as Float
+
+            alpha = amt
+
+            val w = width
+            val translation = (1 - amt) * w
+            translationX = translation
+        }
+    }
+
+    private fun maybeUpdateShowDot() {
+        if (!initialized) return
+        if (!showPersistentDot && currentAnimatedView == null) {
+            animationDotView.visibility = View.INVISIBLE
+        }
+    }
+
+    private fun init() {
+        initialized = true
+        statusBarWindowView = statusBarViewFactory.statusBarWindowView
+        animationWindowView = LayoutInflater.from(context)
+                .inflate(R.layout.system_event_animation_window, null) as FrameLayout
+        animationDotView = animationWindowView.findViewById(R.id.dot_view)
+        val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+        lp.gravity = Gravity.END or Gravity.CENTER_VERTICAL
+        statusBarWindowView.addView(animationWindowView, lp)
+    }
+
+    private val layoutParamsDefault = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).also {
+        it.gravity = Gravity.END or Gravity.CENTER_VERTICAL
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
new file mode 100644
index 0000000..b481823
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.events
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.privacy.PrivacyItem
+import com.android.systemui.privacy.PrivacyItemController
+import com.android.systemui.statusbar.policy.BatteryController
+import javax.inject.Inject
+
+/**
+ * Listens for system events (battery, privacy, connectivity) and allows listeners
+ * to show status bar animations when they happen
+ */
+@SysUISingleton
+class SystemEventCoordinator @Inject constructor(
+    private val batteryController: BatteryController,
+    private val privacyController: PrivacyItemController
+) {
+    private lateinit var scheduler: SystemStatusAnimationScheduler
+
+    fun startObserving() {
+        /* currently unused
+        batteryController.addCallback(batteryStateListener)
+        */
+        privacyController.addCallback(privacyStateListener)
+    }
+
+    fun stopObserving() {
+        /* currently unused
+        batteryController.removeCallback(batteryStateListener)
+        */
+        privacyController.removeCallback(privacyStateListener)
+    }
+
+    fun attachScheduler(s: SystemStatusAnimationScheduler) {
+        this.scheduler = s
+    }
+
+    fun notifyPluggedIn() {
+        scheduler.onStatusEvent(BatteryEvent())
+    }
+
+    fun notifyPrivacyItemsEmpty() {
+        scheduler.setShouldShowPersistentPrivacyIndicator(false)
+    }
+
+    fun notifyPrivacyItemsChanged() {
+        val event = PrivacyEvent()
+        event.privacyItems = privacyStateListener.currentPrivacyItems
+        scheduler.onStatusEvent(event)
+    }
+
+    private val batteryStateListener = object : BatteryController.BatteryStateChangeCallback {
+        var plugged = false
+        var stateKnown = false
+        override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+            if (!stateKnown) {
+                stateKnown = true
+                plugged = pluggedIn
+                notifyListeners()
+                return
+            }
+
+            if (plugged != pluggedIn) {
+                plugged = pluggedIn
+                notifyListeners()
+            }
+        }
+
+        private fun notifyListeners() {
+            // We only care about the plugged in status
+            if (plugged) notifyPluggedIn()
+        }
+    }
+
+    private val privacyStateListener = object : PrivacyItemController.Callback {
+        var currentPrivacyItems = listOf<PrivacyItem>()
+
+        override fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>) {
+            if (privacyItems.isNotEmpty() && currentPrivacyItems.containsAll(privacyItems)) {
+                return
+            }
+            currentPrivacyItems = privacyItems
+            notifyListeners()
+        }
+
+        private fun notifyListeners() {
+            if (currentPrivacyItems.isEmpty()) {
+                notifyPrivacyItemsEmpty()
+            } else {
+                notifyPrivacyItemsChanged()
+            }
+        }
+    }
+}
+
+private const val TAG = "SystemEventCoordinator"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
new file mode 100644
index 0000000..b85d031
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
@@ -0,0 +1,333 @@
+/*
+ * 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.events
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.annotation.IntDef
+import android.content.Context
+import android.os.Process
+import android.util.Log
+import android.view.View
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.phone.StatusBarWindowController
+import com.android.systemui.statusbar.policy.CallbackController
+import com.android.systemui.util.Assert
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
+
+import javax.inject.Inject
+
+/**
+ * Dead-simple scheduler for system status events. Obeys the following principles (all values TBD):
+ *      - Avoiding log spam by only allowing 12 events per minute (1event/5s)
+ *      - Waits 100ms to schedule any event for debouncing/prioritization
+ *      - Simple prioritization: Privacy > Battery > connectivity (encoded in StatusEvent)
+ *      - Only schedules a single event, and throws away lowest priority events
+ *
+ * There are 4 basic stages of animation at play here:
+ *      1. System chrome animation OUT
+ *      2. Chip animation IN
+ *      3. Chip animation OUT; potentially into a dot
+ *      4. System chrome animation IN
+ *
+ * Thus we can keep all animations synchronized with two separate ValueAnimators, one for system
+ * chrome and the other for the chip. These can animate from 0,1 and listeners can parameterize
+ * their respective views based on the progress of the animator. Interpolation differences TBD
+ */
+@SysUISingleton
+class SystemStatusAnimationScheduler @Inject constructor(
+    private val coordinator: SystemEventCoordinator,
+    private val chipAnimationController: SystemEventChipAnimationController,
+    private val statusBarWindowController: StatusBarWindowController,
+    private val systemClock: SystemClock,
+    @Main private val executor: DelayableExecutor
+) : CallbackController<SystemStatusAnimationCallback> {
+
+    /** True from the time a scheduled event starts until it's animation finishes */
+    var isActive = false
+        private set
+
+    @SystemAnimationState var animationState: Int = IDLE
+        private set
+
+    /** True if the persistent privacy dot should be active */
+    var hasPersistentDot = false
+        private set
+
+    private var scheduledEvent: StatusEvent? = null
+    private var cancelExecutionRunnable: Runnable? = null
+    private val listeners = mutableSetOf<SystemStatusAnimationCallback>()
+
+    init {
+        coordinator.attachScheduler(this)
+    }
+
+    fun onStatusEvent(event: StatusEvent) {
+        // Ignore any updates until the system is up and running
+        if (isTooEarly()) {
+            return
+        }
+
+        // Don't deal with threading for now (no need let's be honest)
+        Assert.isMainThread()
+        if (event.priority > scheduledEvent?.priority ?: -1) {
+            if (DEBUG) {
+                Log.d(TAG, "scheduling event $event")
+            }
+            scheduleEvent(event)
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "ignoring event $event")
+            }
+        }
+    }
+
+    private fun clearDotIfVisible() {
+        notifyHidePersistentDot()
+    }
+
+    fun setShouldShowPersistentPrivacyIndicator(should: Boolean) {
+        if (hasPersistentDot == should) {
+            return
+        }
+
+        hasPersistentDot = should
+
+        if (!hasPersistentDot) {
+            clearDotIfVisible()
+        }
+    }
+
+    private fun isTooEarly(): Boolean {
+        Log.d(TAG, "time=> ${systemClock.uptimeMillis() - Process.getStartUptimeMillis()}")
+        return systemClock.uptimeMillis() - Process.getStartUptimeMillis() < MIN_UPTIME
+    }
+
+    /**
+     * Clear the scheduled event (if any) and schedule a new one
+     */
+    private fun scheduleEvent(event: StatusEvent) {
+        scheduledEvent = event
+        if (scheduledEvent!!.forceVisible) {
+            hasPersistentDot = true
+        }
+
+        // Schedule the animation to start after a debounce period
+        cancelExecutionRunnable = executor.executeDelayed({
+            cancelExecutionRunnable = null
+            animationState = ANIMATING_IN
+            statusBarWindowController.setForceStatusBarVisible(true)
+
+            val entranceAnimator = ValueAnimator.ofFloat(1f, 0f)
+            entranceAnimator.duration = ENTRANCE_ANIM_LENGTH
+            entranceAnimator.addListener(systemAnimatorAdapter)
+            entranceAnimator.addUpdateListener(systemUpdateListener)
+
+            val chipAnimator = ValueAnimator.ofFloat(0f, 1f)
+            chipAnimator.duration = CHIP_ANIM_LENGTH
+            chipAnimator.addListener(
+                    ChipAnimatorAdapter(RUNNING_CHIP_ANIM, scheduledEvent!!.viewCreator))
+            chipAnimator.addUpdateListener(chipUpdateListener)
+
+            val aSet2 = AnimatorSet()
+            aSet2.playSequentially(entranceAnimator, chipAnimator)
+            aSet2.start()
+
+            executor.executeDelayed({
+                animationState = ANIMATING_OUT
+
+                val systemAnimator = ValueAnimator.ofFloat(0f, 1f)
+                systemAnimator.duration = ENTRANCE_ANIM_LENGTH
+                systemAnimator.addListener(systemAnimatorAdapter)
+                systemAnimator.addUpdateListener(systemUpdateListener)
+
+                val chipAnimator = ValueAnimator.ofFloat(1f, 0f)
+                chipAnimator.duration = CHIP_ANIM_LENGTH
+                chipAnimator.addListener(ChipAnimatorAdapter(IDLE, scheduledEvent!!.viewCreator))
+                chipAnimator.addUpdateListener(chipUpdateListener)
+
+                val aSet2 = AnimatorSet()
+
+                aSet2.play(chipAnimator).before(systemAnimator)
+                if (hasPersistentDot) {
+                    val dotAnim = notifyTransitionToPersistentDot()
+                    if (dotAnim != null) aSet2.playTogether(systemAnimator, dotAnim)
+                }
+
+                aSet2.start()
+
+                statusBarWindowController.setForceStatusBarVisible(false)
+                scheduledEvent = null
+            }, DISPLAY_LENGTH)
+        }, DELAY)
+    }
+
+    private fun notifyTransitionToPersistentDot(): Animator? {
+        val anims: List<Animator> = listeners.mapNotNull {
+            it.onSystemStatusAnimationTransitionToPersistentDot()
+        }
+        if (anims.isNotEmpty()) {
+            val aSet = AnimatorSet()
+            aSet.playTogether(anims)
+            return aSet
+        }
+
+        return null
+    }
+
+    private fun notifyHidePersistentDot(): Animator? {
+        val anims: List<Animator> = listeners.mapNotNull {
+            it.onHidePersistentDot()
+        }
+
+        if (anims.isNotEmpty()) {
+            val aSet = AnimatorSet()
+            aSet.playTogether(anims)
+            return aSet
+        }
+
+        return null
+    }
+
+    private fun notifySystemStart() {
+        listeners.forEach { it.onSystemChromeAnimationStart() }
+    }
+
+    private fun notifySystemFinish() {
+        listeners.forEach { it.onSystemChromeAnimationEnd() }
+    }
+
+    private fun notifySystemAnimationUpdate(anim: ValueAnimator) {
+        listeners.forEach { it.onSystemChromeAnimationUpdate(anim) }
+    }
+
+    override fun addCallback(listener: SystemStatusAnimationCallback) {
+        Assert.isMainThread()
+
+        if (listeners.isEmpty()) {
+            coordinator.startObserving()
+        }
+        listeners.add(listener)
+    }
+
+    override fun removeCallback(listener: SystemStatusAnimationCallback) {
+        Assert.isMainThread()
+
+        listeners.remove(listener)
+        if (listeners.isEmpty()) {
+            coordinator.stopObserving()
+        }
+    }
+
+    private val systemUpdateListener = ValueAnimator.AnimatorUpdateListener {
+        anim -> notifySystemAnimationUpdate(anim)
+    }
+
+    private val systemAnimatorAdapter = object : AnimatorListenerAdapter() {
+        override fun onAnimationEnd(p0: Animator?) {
+            notifySystemFinish()
+        }
+
+        override fun onAnimationStart(p0: Animator?) {
+            notifySystemStart()
+        }
+    }
+
+    private val chipUpdateListener = ValueAnimator.AnimatorUpdateListener {
+        anim -> chipAnimationController.onChipAnimationUpdate(anim, animationState)
+    }
+
+    inner class ChipAnimatorAdapter(
+        @SystemAnimationState val endState: Int,
+        val viewCreator: (context: Context) -> View
+    ) : AnimatorListenerAdapter() {
+        override fun onAnimationEnd(p0: Animator?) {
+            chipAnimationController.onChipAnimationEnd(animationState)
+            animationState = endState
+        }
+
+        override fun onAnimationStart(p0: Animator?) {
+            chipAnimationController.onChipAnimationStart(viewCreator, animationState)
+        }
+    }
+}
+
+/**
+ * The general idea here is that this scheduler will run two value animators, and provide
+ * animator-like callbacks for each kind of animation. The SystemChrome animation is expected to
+ * create space for the chip animation to display. This means hiding the system elements in the
+ * status bar and keyguard.
+ *
+ * TODO: the chip animation really only has one client, we can probably remove it from this
+ * interface
+ *
+ * The value animators themselves are simple animators from 0.0 to 1.0. Listeners can apply any
+ * interpolation they choose but realistically these are most likely to be simple alpha transitions
+ */
+interface SystemStatusAnimationCallback {
+    @JvmDefault fun onSystemChromeAnimationUpdate(animator: ValueAnimator) {}
+    @JvmDefault fun onSystemChromeAnimationStart() {}
+    @JvmDefault fun onSystemChromeAnimationEnd() {}
+
+    // Best method name, change my mind
+    @JvmDefault fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? { return null }
+    @JvmDefault fun onHidePersistentDot(): Animator? { return null }
+}
+
+interface SystemStatusChipAnimationCallback {
+    fun onChipAnimationUpdate(animator: ValueAnimator, @SystemAnimationState state: Int) {}
+
+    fun onChipAnimationStart(
+        viewCreator: (context: Context) -> View,
+        @SystemAnimationState state: Int
+    ) {}
+
+    fun onChipAnimationEnd(@SystemAnimationState state: Int) {}
+}
+
+/**
+ */
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(
+        value = [
+            IDLE, ANIMATING_IN, RUNNING_CHIP_ANIM, ANIMATING_OUT
+        ]
+)
+annotation class SystemAnimationState
+
+/** No animation is in progress */
+const val IDLE = 0
+/** System is animating out, and chip is animating in */
+const val ANIMATING_IN = 1
+/** Chip has animated in and is awaiting exit animation, and optionally playing its own animation */
+const val RUNNING_CHIP_ANIM = 2
+/** Chip is animating away and system is animating back */
+const val ANIMATING_OUT = 3
+
+private const val TAG = "SystemStatusAnimationScheduler"
+private const val DELAY: Long = 100
+private const val DISPLAY_LENGTH = 5000L
+private const val ENTRANCE_ANIM_LENGTH = 500L
+private const val CHIP_ANIM_LENGTH = 500L
+private const val MIN_UPTIME: Long = 5 * 1000
+
+private const val DEBUG = false
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
index 5748c4a..2537b19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
@@ -18,6 +18,7 @@
 
 import android.content.Intent;
 import android.service.notification.StatusBarNotification;
+import android.view.View;
 
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
@@ -33,7 +34,8 @@
     void startNotificationGutsIntent(Intent intent, int appUid,
             ExpandableNotificationRow row);
 
-    void startHistoryIntent(boolean showHistory);
+    /** Called when the user clicks "Manage" or "History" in the Shade. */
+    void startHistoryIntent(View view, boolean showHistory);
 
     default boolean isCollapsingToShowActivityOverLockscreen() {
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index 4ed5056..3bf0ddb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -106,10 +106,6 @@
         showHistory(mShowHistory);
     }
 
-    public boolean isButtonVisible() {
-        return mManageButton.getAlpha() != 0.0f;
-    }
-
     @Override
     public ExpandableViewState createExpandableViewState() {
         return new FooterViewState();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 8446b4e6..caf4720 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -60,7 +60,7 @@
     private NotificationShelf mShelf;
     private int mZDistanceBetweenElements;
     private int mBaseZHeight;
-    private int mMaxLayoutHeight;
+    private int mContentHeight;
     private ExpandableView mLastVisibleBackgroundChild;
     private float mCurrentScrollVelocity;
     private int mStatusBarState;
@@ -84,6 +84,75 @@
     private boolean mIsShadeOpening;
     private float mSectionPadding;
 
+    /** Distance of top of notifications panel from top of screen. */
+    private float mStackY = 0;
+
+    /** Height of notifications panel. */
+    private float mStackHeight = 0;
+
+    /** Fraction of shade expansion. */
+    private float mExpansionFraction;
+
+    /** Height of the notifications panel without top padding when expansion completes. */
+    private float mStackEndHeight;
+
+    /**
+     * @return Height of the notifications panel without top padding when expansion completes.
+     */
+    public float getStackEndHeight() {
+        return mStackEndHeight;
+    }
+
+    /**
+     * @param stackEndHeight Height of the notifications panel without top padding
+     *                       when expansion completes.
+     */
+    public void setStackEndHeight(float stackEndHeight) {
+        mStackEndHeight = stackEndHeight;
+    }
+
+    /**
+     * @param stackY Distance of top of notifications panel from top of screen.
+     */
+    public void setStackY(float stackY) {
+        mStackY = stackY;
+    }
+
+    /**
+     * @return Distance of top of notifications panel from top of screen.
+     */
+    public float getStackY() {
+        return mStackY;
+    }
+
+    /**
+     * @param expansionFraction Fraction of shade expansion.
+     */
+    public void setExpansionFraction(float expansionFraction) {
+        mExpansionFraction = expansionFraction;
+    }
+
+    /**
+     * @return Fraction of shade expansion.
+     */
+    public float getExpansionFraction() {
+        return mExpansionFraction;
+    }
+
+    /**
+     * @param stackHeight Height of notifications panel.
+     */
+    public void setStackHeight(float stackHeight) {
+        mStackHeight = stackHeight;
+    }
+
+    /**
+     * @return Height of notifications panel.
+     */
+    public float getStackHeight() {
+        return mStackHeight;
+    }
+
     /** Tracks the state from AlertingNotificationManager#hasNotifications() */
     private boolean mHasAlertEntries;
 
@@ -263,8 +332,8 @@
         if (mDozeAmount == 1.0f && !isPulseExpanding()) {
             return mShelf.getHeight();
         }
-        int height = Math.max(mLayoutMinHeight,
-                Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding);
+        int height = (int) Math.max(mLayoutMinHeight,
+                Math.min(mLayoutHeight, mContentHeight) - mTopPadding);
         if (ignorePulseHeight) {
             return height;
         }
@@ -313,8 +382,12 @@
         return mShelf;
     }
 
-    public void setLayoutMaxHeight(int maxLayoutHeight) {
-        mMaxLayoutHeight = maxLayoutHeight;
+    public void setContentHeight(int contentHeight) {
+        mContentHeight = contentHeight;
+    }
+
+    public float getContentHeight() {
+        return mContentHeight;
     }
 
     /**
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 fb72ac3..751573a 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
@@ -658,6 +658,14 @@
             y = getHeight() - getEmptyBottomMargin();
             mDebugPaint.setColor(Color.GREEN);
             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+
+            y = (int) (mAmbientState.getStackY());
+            mDebugPaint.setColor(Color.CYAN);
+            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+
+            y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight());
+            mDebugPaint.setColor(Color.BLUE);
+            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
         }
     }
 
@@ -1123,18 +1131,37 @@
                 mTopPaddingNeedsAnimation = true;
                 mNeedsAnimation = true;
             }
+            updateStackPosition();
             requestChildrenUpdate();
             notifyHeightChangeListener(null, animate);
         }
     }
 
     /**
+     * Apply expansion fraction to the y position and height of the notifications panel.
+     */
+    private void updateStackPosition() {
+        // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD
+        mAmbientState.setStackY(
+                MathUtils.lerp(0, mTopPadding, mAmbientState.getExpansionFraction()));
+        final float shadeBottom = getHeight() - getEmptyBottomMargin();
+        mAmbientState.setStackEndHeight(shadeBottom - mTopPadding);
+        mAmbientState.setStackHeight(
+                MathUtils.lerp(0, shadeBottom - mTopPadding, mAmbientState.getExpansionFraction()));
+    }
+
+    /**
      * Update the height of the panel.
      *
      * @param height the expanded height of the panel
      */
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public void setExpandedHeight(float height) {
+        final float shadeBottom = getHeight() - getEmptyBottomMargin();
+        final float expansionFraction = MathUtils.constrain(height / shadeBottom, 0f, 1f);
+        mAmbientState.setExpansionFraction(expansionFraction);
+        updateStackPosition();
+
         mExpandedHeight = height;
         setIsExpanded(height > 0);
         int minExpansionHeight = getMinExpansionHeight();
@@ -2067,7 +2094,8 @@
         mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin;
         updateScrollability();
         clampScrollPosition();
-        mAmbientState.setLayoutMaxHeight(mContentHeight);
+        updateStackPosition();
+        mAmbientState.setContentHeight(mContentHeight);
     }
 
     /**
@@ -4849,7 +4877,7 @@
             clearNotifications(ROWS_ALL, true /* closeShade */);
         });
         footerView.setManageButtonClickListener(v -> {
-            mNotificationActivityStarter.startHistoryIntent(mFooterView.isHistoryShown());
+            mNotificationActivityStarter.startHistoryIntent(v, mFooterView.isHistoryShown());
         });
         setFooterView(footerView);
     }
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 7776e69..4fc49ed 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
@@ -788,6 +788,10 @@
         return mView.getTranslationX();
     }
 
+    public int indexOfChild(View view) {
+        return mView.indexOfChild(view);
+    }
+
     public void setOnHeightChangedListener(
             ExpandableView.OnHeightChangedListener listener) {
         mView.setOnHeightChangedListener(listener);
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 bbdbe80..3e1a781 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
@@ -20,21 +20,22 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
-import android.util.Log;
 import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.dagger.SilentHeader;
 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.FooterView;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * The Algorithm of the {@link com.android.systemui.statusbar.notification.stack
@@ -92,20 +93,16 @@
 
         // First we reset the view states to their default values.
         resetChildViewStates();
-
         initAlgorithmState(mHostView, algorithmState, ambientState);
-
         updatePositionsForState(algorithmState, ambientState);
-
         updateZValuesForState(algorithmState, ambientState);
-
         updateHeadsUpStates(algorithmState, ambientState);
         updatePulsingStates(algorithmState, ambientState);
 
         updateDimmedActivatedHideSensitive(ambientState, algorithmState);
         updateClipping(algorithmState, ambientState);
         updateSpeedBumpState(algorithmState, speedBumpIndex);
-        updateShelfState(ambientState);
+        updateShelfState(algorithmState, ambientState);
         getNotificationChildrenStates(algorithmState, ambientState);
     }
 
@@ -144,10 +141,13 @@
 
     }
 
-    private void updateShelfState(AmbientState ambientState) {
+    private void updateShelfState(
+            StackScrollAlgorithmState algorithmState,
+            AmbientState ambientState) {
+
         NotificationShelf shelf = ambientState.getShelf();
         if (shelf != null) {
-            shelf.updateState(ambientState);
+            shelf.updateState(algorithmState, ambientState);
         }
     }
 
@@ -172,7 +172,8 @@
                     && ((ExpandableNotificationRow) child).isPinned();
             if (mClipNotificationScrollToTop
                     && (!state.inShelf || (isHeadsUp && !firstHeadsUp))
-                    && newYTranslation < clipStart) {
+                    && newYTranslation < clipStart
+                    && !ambientState.isShadeOpening()) {
                 // The previous view is overlapping on top, clip!
                 float overlapAmount = clipStart - newYTranslation;
                 state.clipTopAmount = (int) overlapAmount;
@@ -217,7 +218,6 @@
     private void initAlgorithmState(ViewGroup hostView, StackScrollAlgorithmState state,
             AmbientState ambientState) {
         float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */);
-
         int scrollY = ambientState.getScrollY();
 
         // Due to the overScroller, the stackscroller can have negative scroll state. This is
@@ -230,7 +230,6 @@
         state.visibleChildren.clear();
         state.visibleChildren.ensureCapacity(childCount);
         int notGoneIndex = 0;
-        ExpandableView lastView = null;
         for (int i = 0; i < childCount; i++) {
             ExpandableView v = (ExpandableView) hostView.getChildAt(i);
             if (v.getVisibility() != View.GONE) {
@@ -255,12 +254,101 @@
                 }
             }
         }
-        ExpandableNotificationRow expandingNotification = ambientState.getExpandingNotification();
-        state.indexOfExpandingNotification = expandingNotification != null
-                ? expandingNotification.isChildInGroup()
-                ? state.visibleChildren.indexOf(expandingNotification.getNotificationParent())
-                : state.visibleChildren.indexOf(expandingNotification)
-                : -1;
+
+        state.firstViewInShelf = null;
+        // Save y, sectionStart, sectionEnd from when shade is fully expanded.
+        // Consider updating these states in updateContentView instead so that we don't have to
+        // recalculate in every frame.
+        float currentY = -scrollY;
+        int sectionStartIndex = 0;
+        int sectionEndIndex = 0;
+        for (int i = 0; i < state.visibleChildren.size(); i++) {
+            final ExpandableView view = state.visibleChildren.get(i);
+            // Add space between sections.
+            final boolean applyGapHeight = childNeedsGapHeight(
+                    ambientState.getSectionProvider(), i,
+                    view, getPreviousView(i, state));
+            if (applyGapHeight) {
+                currentY += mGapHeight;
+            }
+
+            // Save index of first view in the shelf
+            final float shelfStart = ambientState.getStackEndHeight()
+                    - ambientState.getShelf().getIntrinsicHeight();
+            if (currentY >= shelfStart
+                    && !(view instanceof FooterView)
+                    && state.firstViewInShelf == null) {
+                state.firstViewInShelf = view;
+            }
+
+            // Record y position when fully expanded
+            ExpansionData expansionData = new ExpansionData();
+            expansionData.fullyExpandedY = currentY;
+            state.expansionData.put(view, expansionData);
+
+            if (ambientState.getSectionProvider()
+                    .beginsSection(view, getPreviousView(i, state))) {
+
+                // Save section start/end for views in the section before this new section
+                ExpandableView sectionStartView = state.visibleChildren.get(sectionStartIndex);
+                final float sectionStart =
+                        state.expansionData.get(sectionStartView).fullyExpandedY;
+
+                ExpandableView sectionEndView = state.visibleChildren.get(sectionEndIndex);
+                float sectionEnd = state.expansionData.get(sectionEndView).fullyExpandedY
+                        + sectionEndView.getIntrinsicHeight();
+
+                // If we show the shelf, trim section end to shelf start
+                // This means section end > start for views in the shelf
+                if (state.firstViewInShelf != null && sectionEnd > shelfStart) {
+                    sectionEnd = shelfStart;
+                }
+
+                // Update section bounds of every view in the previous section
+                // Consider using shared SectionInfo for views in same section to avoid looping back
+                for (int j = sectionStartIndex; j < i; j++) {
+                    ExpandableView sectionView = state.visibleChildren.get(j);
+                    ExpansionData viewExpansionData =
+                            state.expansionData.get(sectionView);
+                    viewExpansionData.sectionStart = sectionStart;
+                    viewExpansionData.sectionEnd = sectionEnd;
+                    state.expansionData.put(sectionView, viewExpansionData);
+                }
+                sectionStartIndex = i;
+
+                if (view instanceof FooterView) {
+                    // Also record section bounds for FooterView (same as its own)
+                    // because it is the last view and we won't get to this point again
+                    // after the loop ends
+                    ExpansionData footerExpansionData = state.expansionData.get(view);
+                    footerExpansionData.sectionStart = expansionData.fullyExpandedY;
+                    footerExpansionData.sectionEnd = expansionData.fullyExpandedY
+                            + view.getIntrinsicHeight();
+                    state.expansionData.put(view, footerExpansionData);
+                }
+            }
+            sectionEndIndex = i;
+            currentY = currentY
+                    + getMaxAllowedChildHeight(view)
+                    + mPaddingBetweenElements;
+        }
+
+        // Which view starts the section of the view right before the shelf?
+        // Save it for later when we clip views in that section to shelf start.
+        state.firstViewInOverflowSection = null;
+        if (state.firstViewInShelf != null) {
+            ExpandableView nextView = null;
+            final int startIndex = state.visibleChildren.indexOf(state.firstViewInShelf);
+            for (int i = startIndex - 1; i >= 0; i--) {
+                ExpandableView view = state.visibleChildren.get(i);
+                if (nextView != null && ambientState.getSectionProvider()
+                        .beginsSection(nextView, view)) {
+                    break;
+                }
+                nextView = view;
+            }
+            state.firstViewInOverflowSection = nextView;
+        }
     }
 
     private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex,
@@ -272,6 +360,10 @@
         return notGoneIndex;
     }
 
+    private ExpandableView getPreviousView(int i, StackScrollAlgorithmState algorithmState) {
+        return i > 0 ? algorithmState.visibleChildren.get(i - 1) : null;
+    }
+
     /**
      * Determine the positions for the views. This is the main part of the algorithm.
      *
@@ -288,6 +380,15 @@
         }
     }
 
+    private void setLocation(ExpandableViewState expandableViewState, float currentYPosition,
+            int i) {
+        expandableViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
+        if (currentYPosition <= 0) {
+            expandableViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
+        }
+    }
+
+    // TODO(b/172289889) polish shade open from HUN
     /**
      * Populates the {@link ExpandableViewState} for a single child.
      *
@@ -306,53 +407,84 @@
             StackScrollAlgorithmState algorithmState,
             AmbientState ambientState,
             float currentYPosition) {
-        ExpandableView child = algorithmState.visibleChildren.get(i);
-        ExpandableView previousChild = i > 0 ? algorithmState.visibleChildren.get(i - 1) : null;
+
+        ExpandableView view = algorithmState.visibleChildren.get(i);
+        ExpandableViewState viewState = view.getViewState();
+        viewState.location = ExpandableViewState.LOCATION_UNKNOWN;
+        viewState.alpha = 1f - ambientState.getHideAmount();
+
+        if (view.mustStayOnScreen() && viewState.yTranslation >= 0) {
+            // Even if we're not scrolled away we're in view and we're also not in the
+            // shelf. We can relax the constraints and let us scroll off the top!
+            float end = viewState.yTranslation + viewState.height + ambientState.getStackY();
+            viewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
+        }
+
+        // TODO(b/172289889) move sectionFraction and showSection to initAlgorithmState
+        // Get fraction of section showing, and later apply it to view height and gaps between views
+        float sectionFraction = 1f;
+        boolean showSection = true;
+
+        if (!ambientState.isOnKeyguard()
+                && !ambientState.isPulseExpanding()
+                && ambientState.isExpansionChanging()) {
+
+            final ExpansionData expansionData = algorithmState.expansionData.get(view);
+            final float sectionHeight = expansionData.sectionEnd - expansionData.sectionStart;
+            sectionFraction = MathUtils.constrain(
+                    (ambientState.getStackHeight() - expansionData.sectionStart) / sectionHeight,
+                    0f, 1f);
+            showSection = expansionData.sectionStart < ambientState.getStackHeight();
+        }
+
+        // Add gap between sections.
         final boolean applyGapHeight =
                 childNeedsGapHeight(
                         ambientState.getSectionProvider(), i,
-                        child, previousChild);
-        ExpandableViewState childViewState = child.getViewState();
-        childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
-
+                        view, getPreviousView(i, algorithmState));
         if (applyGapHeight) {
-            currentYPosition += mGapHeight;
-        }
-        int childHeight = getMaxAllowedChildHeight(child);
-        childViewState.yTranslation = currentYPosition;
-        childViewState.alpha = 1f - ambientState.getHideAmount();
-
-        boolean isFooterView = child instanceof FooterView;
-        boolean isEmptyShadeView = child instanceof EmptyShadeView;
-
-        childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
-        float inset = ambientState.getTopPadding() + ambientState.getStackTranslation()
-                + ambientState.getSectionPadding();
-        if (child.mustStayOnScreen() && childViewState.yTranslation >= 0) {
-            // Even if we're not scrolled away we're in view and we're also not in the
-            // shelf. We can relax the constraints and let us scroll off the top!
-            float end = childViewState.yTranslation + childViewState.height + inset;
-            childViewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
-        }
-        if (isFooterView) {
-            childViewState.yTranslation = Math.min(childViewState.yTranslation,
-                    ambientState.getInnerHeight() - childHeight);
-        } else if (isEmptyShadeView) {
-            childViewState.yTranslation = ambientState.getInnerHeight() - childHeight
-                    + ambientState.getStackTranslation() * 0.25f;
-        } else if (child != ambientState.getTrackedHeadsUpRow()) {
-            clampPositionToShelf(child, childViewState, ambientState);
+            currentYPosition += sectionFraction * mGapHeight;
         }
 
-        currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
-        if (currentYPosition <= 0) {
-            childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
-        }
-        if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) {
-            Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
+        viewState.yTranslation = currentYPosition;
+
+        if (view instanceof SectionHeaderView) {
+            // Add padding before sections for overscroll effect.
+            viewState.yTranslation += ambientState.getSectionPadding();
         }
 
-        childViewState.yTranslation += inset;
+        if (view != ambientState.getTrackedHeadsUpRow()) {
+            if (ambientState.isExpansionChanging()) {
+                viewState.hidden = !showSection;
+                viewState.inShelf = algorithmState.firstViewInShelf != null
+                        && i >= algorithmState.visibleChildren.indexOf(
+                                algorithmState.firstViewInShelf)
+                        && !(view instanceof FooterView);
+            } else {
+                // When pulsing (incoming notification on AOD), innerHeight is 0; clamp all
+                // to shelf start, thereby hiding all notifications (except the first one, which we
+                // later unhide in updatePulsingState)
+                final int shelfStart = ambientState.getInnerHeight()
+                        - ambientState.getShelf().getIntrinsicHeight();
+                if (!(view instanceof FooterView)) {
+                    viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart);
+                }
+                if (viewState.yTranslation >= shelfStart) {
+                    viewState.hidden = !view.isExpandAnimationRunning()
+                            && !view.hasExpandingChild()
+                            && !(view instanceof FooterView);
+                    viewState.inShelf = true;
+                    // Notifications in the shelf cannot be visible HUNs.
+                    viewState.headsUpIsVisible = false;
+                }
+            }
+            viewState.height = (int) MathUtils.lerp(
+                    0, getMaxAllowedChildHeight(view), sectionFraction);
+        }
+
+        currentYPosition += viewState.height + sectionFraction * mPaddingBetweenElements;
+        setLocation(view.getViewState(), currentYPosition, i);
+        viewState.yTranslation += ambientState.getStackY();
         return currentYPosition;
     }
 
@@ -393,10 +525,10 @@
             int visibleIndex,
             View child,
             View previousChild) {
-
-        boolean needsGapHeight = sectionProvider.beginsSection(child, previousChild)
-                && visibleIndex > 0;
-        return needsGapHeight;
+        return sectionProvider.beginsSection(child, previousChild)
+                && visibleIndex > 0
+                && !(previousChild instanceof SilentHeader)
+                && !(child instanceof FooterView);
     }
 
     private void updatePulsingStates(StackScrollAlgorithmState algorithmState,
@@ -514,42 +646,6 @@
         childState.yTranslation = newTranslation;
     }
 
-    /**
-     * Clamp the height of the child down such that its end is at most on the beginning of
-     * the shelf.
-     *
-     * @param childViewState the view state of the child
-     * @param ambientState   the ambient state
-     */
-    private void clampPositionToShelf(ExpandableView child,
-            ExpandableViewState childViewState,
-            AmbientState ambientState) {
-        if (ambientState.getShelf() == null) {
-            return;
-        }
-
-        ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow();
-        boolean isBeforeTrackedHeadsUp = trackedHeadsUpRow != null
-                && mHostView.indexOfChild(child) < mHostView.indexOfChild(trackedHeadsUpRow);
-
-        int shelfStart = ambientState.getInnerHeight()
-                - ambientState.getShelf().getIntrinsicHeight();
-        if (ambientState.isAppearing() && !child.isAboveShelf() && !isBeforeTrackedHeadsUp) {
-            // Don't show none heads-up notifications while in appearing phase.
-            childViewState.yTranslation = Math.max(childViewState.yTranslation, shelfStart);
-        }
-        childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
-        if (child instanceof SectionHeaderView) {
-            // Add padding before sections for overscroll effect.
-            childViewState.yTranslation += ambientState.getSectionPadding();
-        }
-        if (childViewState.yTranslation >= shelfStart) {
-            childViewState.hidden = !child.isExpandAnimationRunning() && !child.hasExpandingChild();
-            childViewState.inShelf = true;
-            childViewState.headsUpIsVisible = false;
-        }
-    }
-
     protected int getMaxAllowedChildHeight(View child) {
         if (child instanceof ExpandableView) {
             ExpandableView expandableView = (ExpandableView) child;
@@ -641,6 +737,35 @@
         this.mIsExpanded = isExpanded;
     }
 
+    /**
+     * Data used to layout views while shade expansion changes.
+     */
+    public class ExpansionData {
+
+        /**
+         * Y position of top of first view in section.
+         */
+        public float sectionStart;
+
+        /**
+         * Y position of bottom of last view in section.
+         */
+        public float sectionEnd;
+
+        /**
+         * Y position of view when shade is fully expanded.
+         * Does not include distance between top notifications panel and top of screen.
+         */
+        public float fullyExpandedY;
+
+        /**
+         * Whether this notification is in the same section as the notification right before the
+         * shelf. Used to determine which notification should be clipped to shelf start while
+         * shade expansion changes.
+         */
+        public boolean inOverflowingSection;
+    }
+
     public class StackScrollAlgorithmState {
 
         /**
@@ -649,15 +774,26 @@
         public int scrollY;
 
         /**
+         * First view in shelf.
+         */
+        public ExpandableView firstViewInShelf;
+
+        /**
+         * First view in section overflowing into shelf while shade expansion changes.
+         */
+        public ExpandableView firstViewInOverflowSection;
+
+        /**
+         * Map of view to ExpansionData used for layout during shade expansion.
+         * Use view instead of index as key, because visibleChildren indices do not match the ones
+         * used in the shelf.
+         */
+        public Map<ExpandableView, ExpansionData> expansionData = new HashMap<>();
+
+        /**
          * The children from the host view which are not gone.
          */
-        public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>();
-
-        private int indexOfExpandingNotification;
-
-        public int getIndexOfExpandingNotification() {
-            return indexOfExpandingNotification;
-        }
+        public final ArrayList<ExpandableView> visibleChildren = new ArrayList<>();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 40e6b40..4e57e44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -19,10 +19,16 @@
 import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS;
 import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
 
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
+
+import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.app.Fragment;
 import android.os.Bundle;
 import android.os.Parcelable;
+import android.util.Log;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -36,6 +42,9 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
@@ -44,6 +53,8 @@
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 
+import org.jetbrains.annotations.NotNull;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -55,7 +66,8 @@
  * updated by the StatusBarIconController and DarkIconManager while it is attached.
  */
 public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks,
-        StatusBarStateController.StateListener {
+        StatusBarStateController.StateListener,
+        SystemStatusAnimationCallback {
 
     public static final String TAG = "CollapsedStatusBarFragment";
     private static final String EXTRA_PANEL_STATE = "panel_state";
@@ -78,6 +90,8 @@
     private View mOperatorNameFrame;
     private CommandQueue mCommandQueue;
     private OngoingCallController mOngoingCallController;
+    private final SystemStatusAnimationScheduler mAnimationScheduler;
+    private final PrivacyDotViewController mDotViewController;
 
     private List<String> mBlockedIcons = new ArrayList<>();
 
@@ -103,8 +117,14 @@
     };
 
     @Inject
-    public CollapsedStatusBarFragment(OngoingCallController ongoingCallController) {
+    public CollapsedStatusBarFragment(
+            OngoingCallController ongoingCallController,
+            SystemStatusAnimationScheduler animationScheduler,
+            PrivacyDotViewController dotViewController
+    ) {
         mOngoingCallController = ongoingCallController;
+        mAnimationScheduler = animationScheduler;
+        mDotViewController = dotViewController;
     }
 
     @Override
@@ -127,6 +147,9 @@
     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         mStatusBar = (PhoneStatusBarView) view;
+        View contents = mStatusBar.findViewById(R.id.status_bar_contents);
+        contents.addOnLayoutChangeListener(mStatusBarLayoutListener);
+        updateStatusBarLocation(contents.getLeft(), contents.getRight());
         if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) {
             mStatusBar.restoreHierarchyState(
                     savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
@@ -143,6 +166,7 @@
         showClock(false);
         initEmergencyCryptkeeperText();
         initOperatorName();
+        mAnimationScheduler.addCallback(this);
     }
 
     @Override
@@ -208,6 +232,7 @@
         if (displayId != getContext().getDisplayId()) {
             return;
         }
+        Log.d(TAG, "disable: ");
         state1 = adjustDisableFlags(state1);
         final int old1 = mDisabled1;
         final int diff1 = state1 ^ old1;
@@ -292,19 +317,22 @@
         return false;
     }
 
-    public void hideSystemIconArea(boolean animate) {
+    private void hideSystemIconArea(boolean animate) {
         animateHide(mSystemIconArea, animate);
     }
 
-    public void showSystemIconArea(boolean animate) {
-        animateShow(mSystemIconArea, animate);
+    private void showSystemIconArea(boolean animate) {
+        // Only show the system icon area if we are not currently animating
+        if (mAnimationScheduler.getAnimationState() == IDLE) {
+            animateShow(mSystemIconArea, animate);
+        }
     }
 
-    public void hideClock(boolean animate) {
+    private void hideClock(boolean animate) {
         animateHiddenState(mClockView, clockHiddenMode(), animate);
     }
 
-    public void showClock(boolean animate) {
+    private void showClock(boolean animate) {
         animateShow(mClockView, animate);
     }
 
@@ -425,12 +453,60 @@
     }
 
     @Override
-    public void onStateChanged(int newState) {
-
-    }
+    public void onStateChanged(int newState) { }
 
     @Override
     public void onDozingChanged(boolean isDozing) {
         disable(getContext().getDisplayId(), mDisabled1, mDisabled1, false /* animate */);
     }
+
+    @Override
+    public void onSystemChromeAnimationStart() {
+        if (mAnimationScheduler.getAnimationState() == ANIMATING_OUT
+                && !isSystemIconAreaDisabled()) {
+            mSystemIconArea.setVisibility(View.VISIBLE);
+            mSystemIconArea.setAlpha(0f);
+        }
+    }
+
+    @Override
+    public void onSystemChromeAnimationEnd() {
+        // Make sure the system icons are out of the way
+        if (mAnimationScheduler.getAnimationState() == ANIMATING_IN) {
+            mSystemIconArea.setVisibility(View.INVISIBLE);
+            mSystemIconArea.setAlpha(0f);
+        } else {
+            if (isSystemIconAreaDisabled()) {
+                // don't unhide
+                return;
+            }
+
+            mSystemIconArea.setAlpha(1f);
+            mSystemIconArea.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
+    public void onSystemChromeAnimationUpdate(@NotNull ValueAnimator animator) {
+        mSystemIconArea.setAlpha((float) animator.getAnimatedValue());
+    }
+
+    private boolean isSystemIconAreaDisabled() {
+        return (mDisabled1 & DISABLE_SYSTEM_INFO) != 0 || (mDisabled2 & DISABLE2_SYSTEM_ICONS) != 0;
+    }
+
+    private void updateStatusBarLocation(int left, int right) {
+        int leftMargin = left - mStatusBar.getLeft();
+        int rightMargin = mStatusBar.getRight() - right;
+
+        mDotViewController.setStatusBarMargins(leftMargin, rightMargin);
+    }
+
+    // Listen for view end changes of PhoneStatusBarView and publish that to the privacy dot
+    private View.OnLayoutChangeListener mStatusBarLayoutListener =
+            (view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                if (left != oldLeft || right != oldRight) {
+                    updateStatusBarLocation(left, right);
+                }
+            };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index c3325b1..b83039b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -68,6 +68,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.LockPatternUtils;
@@ -154,6 +155,7 @@
     private StatusBar mStatusBar;
     private KeyguardAffordanceHelper mAffordanceHelper;
     private FalsingManager mFalsingManager;
+    @Nullable private Executor mUiExecutor;
     private boolean mUserSetupComplete;
     private boolean mPrewarmBound;
     private Messenger mPrewarmMessenger;
@@ -429,7 +431,7 @@
     }
 
     private void updateWalletVisibility() {
-        if (mDozing || !mWalletEnabled) {
+        if (mDozing || !mWalletEnabled || !mHasCard) {
             mWalletButton.setVisibility(GONE);
         } else {
             mWalletButton.setVisibility(VISIBLE);
@@ -659,6 +661,13 @@
         updateCameraVisibility();
     }
 
+    @Override
+    public void onKeyguardShowingChanged() {
+        if (mKeyguardStateController.isShowing()) {
+            queryWalletCards();
+        }
+    }
+
     private void inflateCameraPreview() {
         View previewBefore = mCameraPreview;
         boolean visibleBefore = false;
@@ -897,18 +906,20 @@
     public void initWallet(QuickAccessWalletClient client, Executor uiExecutor, boolean enabled) {
         mQuickAccessWalletClient = client;
         mWalletEnabled = enabled && client.isWalletFeatureAvailable();
+        mUiExecutor = uiExecutor;
+        queryWalletCards();
 
-        if (mWalletEnabled) {
-            queryWalletCards(uiExecutor);
-        }
         updateWalletVisibility();
     }
 
-    private void queryWalletCards(Executor uiExecutor) {
+    private void queryWalletCards() {
+        if (!mWalletEnabled || mUiExecutor == null) {
+            return;
+        }
         GetWalletCardsRequest request =
                 new GetWalletCardsRequest(1 /* cardWidth */, 1 /* cardHeight */,
                         1 /* iconSizePx */, 2 /* maxCards */);
-        mQuickAccessWalletClient.getWalletCards(uiExecutor, request, mCardRetriever);
+        mQuickAccessWalletClient.getWalletCards(mUiExecutor, request, mCardRetriever);
     }
 
     private void onWalletClick(View v) {
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 0694737..c22fec9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -18,7 +18,10 @@
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
 
+import android.animation.ValueAnimator;
 import android.annotation.ColorInt;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -47,6 +50,8 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -64,8 +69,11 @@
 /**
  * The header group on Keyguard.
  */
-public class KeyguardStatusBarView extends RelativeLayout
-        implements BatteryStateChangeCallback, OnUserInfoChangedListener, ConfigurationListener {
+public class KeyguardStatusBarView extends RelativeLayout implements
+        BatteryStateChangeCallback,
+        OnUserInfoChangedListener,
+        ConfigurationListener,
+        SystemStatusAnimationCallback {
 
     private static final int LAYOUT_NONE = 0;
     private static final int LAYOUT_CUTOUT = 1;
@@ -96,6 +104,8 @@
     private ViewGroup mStatusIconArea;
     private int mLayoutState = LAYOUT_NONE;
 
+    private SystemStatusAnimationScheduler mAnimationScheduler;
+
     /**
      * Draw this many pixels into the left/right side of the cutout to optimally use the space
      */
@@ -125,6 +135,7 @@
         loadDimens();
         loadBlockList();
         mBatteryController = Dependency.get(BatteryController.class);
+        mAnimationScheduler = Dependency.get(SystemStatusAnimationScheduler.class);
     }
 
     @Override
@@ -349,6 +360,7 @@
         mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
         mIconManager.setBlockList(mBlockedIcons);
         Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
+        mAnimationScheduler.addCallback(this);
         onThemeChanged();
     }
 
@@ -358,6 +370,7 @@
         Dependency.get(UserInfoController.class).removeCallback(this);
         Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager);
         Dependency.get(ConfigurationController.class).removeCallback(this);
+        mAnimationScheduler.removeCallback(this);
     }
 
     @Override
@@ -509,4 +522,30 @@
             mBatteryView.dump(fd, pw, args);
         }
     }
+
+    /** SystemStatusAnimationCallback */
+    @Override
+    public void onSystemChromeAnimationStart() {
+        if (mAnimationScheduler.getAnimationState() == ANIMATING_OUT) {
+            mSystemIconsContainer.setVisibility(View.VISIBLE);
+            mSystemIconsContainer.setAlpha(0f);
+        }
+    }
+
+    @Override
+    public void onSystemChromeAnimationEnd() {
+        // Make sure the system icons are out of the way
+        if (mAnimationScheduler.getAnimationState() == ANIMATING_IN) {
+            mSystemIconsContainer.setVisibility(View.INVISIBLE);
+            mSystemIconsContainer.setAlpha(0f);
+        } else {
+            mSystemIconsContainer.setAlpha(1f);
+            mSystemIconsContainer.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
+    public void onSystemChromeAnimationUpdate(ValueAnimator anim) {
+        mSystemIconsContainer.setAlpha((float) anim.getAnimatedValue());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 1c8bda7..c4d8840 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -2018,7 +2018,7 @@
                 right = getView().getRight();
             } else {
                 top = Math.min(qsPanelBottomY, mSplitShadeNotificationsTopPadding);
-                bottom = getExpandedHeight() - mSplitShadeNotificationsTopPadding;
+                bottom = mNotificationStackScrollLayoutController.getHeight();
                 left = mNotificationStackScrollLayoutController.getLeft();
                 right = mNotificationStackScrollLayoutController.getRight();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index 5aecb72..0c8122c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -666,7 +666,9 @@
         pw.println(TAG + ":");
         pw.println("  mKeyguardDisplayMode=" + mKeyguardDisplayMode);
         pw.println(mCurrentState);
-        mNotificationShadeView.getViewRootImpl().dump("  ", pw);
+        if (mNotificationShadeView != null && mNotificationShadeView.getViewRootImpl() != null) {
+            mNotificationShadeView.getViewRootImpl().dump("  ", pw);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index d386ebd..43d525d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -208,6 +208,8 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.charging.WiredChargingRippleController;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -426,6 +428,8 @@
     private final DemoModeController mDemoModeController;
     private NotificationsController mNotificationsController;
     private final OngoingCallController mOngoingCallController;
+    private final SystemStatusAnimationScheduler mAnimationScheduler;
+    private final PrivacyDotViewController mDotViewController;
 
     // expanded notifications
     // the sliding/resizing panel within the notification window
@@ -794,6 +798,8 @@
             BrightnessSlider.Factory brightnessSliderFactory,
             WiredChargingRippleController chargingRippleAnimationController,
             OngoingCallController ongoingCallController,
+            SystemStatusAnimationScheduler animationScheduler,
+            PrivacyDotViewController dotViewController,
             TunerService tunerService,
             FeatureFlags featureFlags) {
         super(context);
@@ -875,6 +881,8 @@
         mBrightnessSliderFactory = brightnessSliderFactory;
         mChargingRippleAnimationController = chargingRippleAnimationController;
         mOngoingCallController = ongoingCallController;
+        mAnimationScheduler = animationScheduler;
+        mDotViewController = dotViewController;
         mFeatureFlags = featureFlags;
 
         tunerService.addTunable(
@@ -1171,7 +1179,10 @@
                 }).getFragmentManager()
                 .beginTransaction()
                 .replace(R.id.status_bar_container,
-                        new CollapsedStatusBarFragment(mOngoingCallController),
+                        new CollapsedStatusBarFragment(
+                                mOngoingCallController,
+                                mAnimationScheduler,
+                                mDotViewController),
                         CollapsedStatusBarFragment.TAG)
                 .commit();
 
@@ -1788,7 +1799,15 @@
 
     @Override
     public void startActivity(Intent intent, boolean dismissShade) {
-        startActivityDismissingKeyguard(intent, false, dismissShade);
+        startActivityDismissingKeyguard(intent, false /* onlyProvisioned */, dismissShade);
+    }
+
+    @Override
+    public void startActivity(Intent intent, boolean dismissShade,
+            ActivityLaunchAnimator.Controller animationController) {
+        startActivityDismissingKeyguard(intent, false, dismissShade,
+                false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */,
+                0 /* flags */, animationController);
     }
 
     @Override
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 3404528..4356b52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -39,6 +39,7 @@
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.util.EventLog;
+import android.view.View;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.NotificationVisibility;
@@ -462,7 +463,7 @@
         mActivityStarter.dismissKeyguardThenExecute(() -> {
             AsyncTask.execute(() -> {
                 ActivityLaunchAnimator.Controller animationController = null;
-                if (!mStatusBar.isOccluded() && mStatusBar.areLaunchAnimationsEnabled()) {
+                if (mStatusBar.areLaunchAnimationsEnabled()) {
                     animationController = new StatusBarLaunchAnimatorController(
                             mNotificationAnimationProvider.getAnimatorController(row), mStatusBar,
                             true /* isActivityIntent */);
@@ -495,7 +496,7 @@
     }
 
     @Override
-    public void startHistoryIntent(boolean showHistory) {
+    public void startHistoryIntent(View view, boolean showHistory) {
         mActivityStarter.dismissKeyguardThenExecute(() -> {
             AsyncTask.execute(() -> {
                 Intent intent = showHistory ? new Intent(
@@ -506,11 +507,27 @@
                 if (showHistory) {
                     tsb.addNextIntent(intent);
                 }
-                tsb.startActivities(null, UserHandle.CURRENT);
 
-                // Putting it back on the main thread, since we're touching views
-                mMainThreadHandler.post(() -> mCommandQueue.animateCollapsePanels(
-                        CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */));
+                ActivityLaunchAnimator.Controller animationController = null;
+                if (mStatusBar.areLaunchAnimationsEnabled()) {
+                    animationController = new StatusBarLaunchAnimatorController(
+                            ActivityLaunchAnimator.Controller.fromView(view), mStatusBar,
+                            true /* isActivityIntent */);
+                }
+
+                mActivityLaunchAnimator.startIntentWithAnimation(animationController,
+                        (adapter) -> tsb.startActivities(
+                                getActivityOptions(mStatusBar.getDisplayId(), adapter),
+                                UserHandle.CURRENT));
+
+                // Note that other cases when we should still collapse (like activity already on
+                // top) is handled by the StatusBarLaunchAnimatorController.
+                boolean shouldCollapse = animationController == null;
+                if (shouldCollapse) {
+                    // Putting it back on the main thread, since we're touching views
+                    mMainThreadHandler.post(() -> mCommandQueue.animateCollapsePanels(
+                            CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */));
+                }
             });
             return true;
         }, null, false /* afterKeyguardGone */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 2c2779e..24e6db8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -61,6 +61,8 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.charging.WiredChargingRippleController;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
@@ -209,6 +211,8 @@
             BrightnessSlider.Factory brightnessSliderFactory,
             WiredChargingRippleController chargingRippleAnimationController,
             OngoingCallController ongoingCallController,
+            SystemStatusAnimationScheduler animationScheduler,
+            PrivacyDotViewController dotViewController,
             TunerService tunerService,
             FeatureFlags featureFlags) {
         return new StatusBar(
@@ -293,6 +297,8 @@
                 brightnessSliderFactory,
                 chargingRippleAnimationController,
                 ongoingCallController,
+                animationScheduler,
+                dotViewController,
                 tunerService,
                 featureFlags);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
index d195062..d1a2c8a 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
@@ -222,6 +222,11 @@
         if (mIsDismissed) {
             return;
         }
+        int cardWidthPx = mCardCarousel.getCardWidthPx();
+        int cardHeightPx = mCardCarousel.getCardHeightPx();
+        if (cardWidthPx == 0 || cardHeightPx == 0) {
+            return;
+        }
         if (!mHasRegisteredListener) {
             // Listener is registered even when device is locked. Should only be registered once.
             mWalletClient.addWalletServiceEventListener(this);
@@ -231,8 +236,6 @@
         mWalletView.show();
         mWalletView.hideErrorMessage();
         int iconSizePx = mContext.getResources().getDimensionPixelSize(R.dimen.wallet_icon_size);
-        int cardWidthPx = mCardCarousel.getCardWidthPx();
-        int cardHeightPx = mCardCarousel.getCardHeightPx();
         GetWalletCardsRequest request =
                 new GetWalletCardsRequest(cardWidthPx, cardHeightPx, iconSizePx, MAX_CARDS);
         mWalletClient.getWalletCards(mExecutor, request, this);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 74b79d5..81bb819 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -268,6 +268,7 @@
             public void onStop() {
                 mSysUiMainExecutor.execute(() -> {
                     if (oneHanded.isOneHandedEnabled()) {
+                        // Log metrics for 3-button navigation mode.
                         oneHanded.stopOneHanded(
                                 OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT);
                     } else if (oneHanded.isSwipeToNotificationEnabled()) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 59262cf..3252750 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -63,6 +63,7 @@
 import com.android.systemui.R.dimen;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.settings.SecureSettings;
@@ -94,6 +95,8 @@
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
     private UserTracker mUserTracker;
+    @Mock
+    private PrivacyDotViewController mDotViewController;
 
     @Before
     public void setup() {
@@ -116,7 +119,7 @@
         mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
 
         mScreenDecorations = spy(new ScreenDecorations(mContext, mMainHandler, mSecureSettings,
-                mBroadcastDispatcher, mTunerService, mUserTracker) {
+                mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController) {
             @Override
             public void start() {
                 super.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 3f0831c..78c6717 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -233,9 +233,9 @@
                 TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME,
                 AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
         assertEquals(2,
-                mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID)).size());
-        assertEquals(1,
-                mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID_OTHER)).size());
+                mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID), false).size());
+        assertEquals(1, mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID_OTHER),
+                false).size());
     }
 
     @Test
@@ -245,11 +245,11 @@
         mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
                 TEST_UID_NON_USER_SENSITIVE, mExemptedRolePkgName, true);
         assertEquals(0, mController.getActiveAppOpsForUser(
-                UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size());
+                UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size());
         mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
                 TEST_UID_NON_USER_SENSITIVE, SYSTEM_PKG, true);
         assertEquals(0, mController.getActiveAppOpsForUser(
-                UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size());
+                UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size());
     }
 
     @Test
@@ -441,7 +441,19 @@
     }
 
     @Test
-    public void testOnlyRecordAudioPaused() {
+    public void testPausedPhoneCallMicrophoneFilteredOut() {
+        mController.addCallback(new int[]{AppOpsManager.OP_PHONE_CALL_MICROPHONE}, mCallback);
+        mTestableLooper.processAllMessages();
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mController.getActiveAppOps().isEmpty());
+    }
+
+    @Test
+    public void testOnlyRecordAudioPhoneCallMicrophonePaused() {
         mController.addCallback(new int[]{
                 AppOpsManager.OP_RECORD_AUDIO,
                 AppOpsManager.OP_CAMERA
@@ -532,6 +544,40 @@
     }
 
     @Test
+    public void testPhoneCallMicrophoneFilteredWhenMicDisabled() {
+        mController.addCallback(
+                new int[]{AppOpsManager.OP_PHONE_CALL_MICROPHONE, AppOpsManager.OP_CAMERA},
+                mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        List<AppOpItem> list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_PHONE_CALL_MICROPHONE, list.get(0).getCode());
+        assertFalse(list.get(0).isDisabled());
+
+        // Add a camera op, and disable the microphone. The camera op should be the only op returned
+        mController.onSensorBlockedChanged(MICROPHONE, true);
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
+
+
+        // Re enable the microphone, and verify the op returns
+        mController.onSensorBlockedChanged(MICROPHONE, false);
+        mTestableLooper.processAllMessages();
+
+        list = mController.getActiveAppOps();
+        assertEquals(2, list.size());
+        int micIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 1 : 0;
+        assertEquals(AppOpsManager.OP_PHONE_CALL_MICROPHONE, list.get(micIdx).getCode());
+    }
+
+    @Test
     public void testCameraFilteredWhenCameraDisabled() {
         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA},
                 mCallback);
@@ -563,6 +609,39 @@
         assertEquals(AppOpsManager.OP_CAMERA, list.get(cameraIdx).getCode());
     }
 
+    @Test
+    public void testPhoneCallCameraFilteredWhenCameraDisabled() {
+        mController.addCallback(
+                new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_PHONE_CALL_CAMERA},
+                mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_PHONE_CALL_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        List<AppOpItem> list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(0).getCode());
+        assertFalse(list.get(0).isDisabled());
+
+        // Add an audio op, and disable the camera. The audio op should be the only op returned
+        mController.onSensorBlockedChanged(CAMERA, true);
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode());
+
+        // Re enable the camera, and verify the op returns
+        mController.onSensorBlockedChanged(CAMERA, false);
+        mTestableLooper.processAllMessages();
+
+        list = mController.getActiveAppOps();
+        assertEquals(2, list.size());
+        int cameraIdx = list.get(0).getCode() == AppOpsManager.OP_PHONE_CALL_CAMERA ? 0 : 1;
+        assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(cameraIdx).getCode());
+    }
+
     private class TestHandler extends AppOpsControllerImpl.H {
         TestHandler(Looper looper) {
             mController.super(looper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
index 42e88b0..63ce98a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import android.app.IWallpaperManager;
 import android.os.PowerManager;
 import android.testing.AndroidTestingRunner;
 
@@ -43,9 +44,12 @@
     private WakefulnessLifecycle mWakefulness;
     private WakefulnessLifecycle.Observer mWakefulnessObserver;
 
+    private IWallpaperManager mWallpaperManager;
+
     @Before
     public void setUp() throws Exception {
-        mWakefulness = new WakefulnessLifecycle();
+        mWallpaperManager = mock(IWallpaperManager.class);
+        mWakefulness = new WakefulnessLifecycle(mContext, mWallpaperManager);
         mWakefulnessObserver = mock(WakefulnessLifecycle.Observer.class);
         mWakefulness.addObserver(mWakefulnessObserver);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index 0ce03ad..81ca4c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -22,8 +22,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
@@ -35,10 +33,7 @@
 
 import android.app.INotificationManager;
 import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
 import android.app.Person;
-import android.app.people.ConversationChannel;
 import android.app.people.IPeopleManager;
 import android.app.people.PeopleSpaceTile;
 import android.appwidget.AppWidgetManager;
@@ -47,7 +42,6 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -57,7 +51,6 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.ContactsContract;
-import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.util.DisplayMetrics;
@@ -81,10 +74,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
@@ -185,8 +176,6 @@
     @Mock
     private IPeopleManager mPeopleManager;
     @Mock
-    private LauncherApps mLauncherApps;
-    @Mock
     private IAppWidgetService mIAppWidgetService;
     @Mock
     private AppWidgetManager mAppWidgetManager;
@@ -239,84 +228,6 @@
     }
 
     @Test
-    public void testGetRecentTilesReturnsSortedListWithOnlyRecentConversations() throws Exception {
-        // Ensure the less-recent Important conversation is before more recent conversations.
-        ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID_1, false, 3);
-        ConversationChannelWrapper newerImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID_1 + 1, true, 3);
-        ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID_1 + 2,
-                true, 1);
-        when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
-                new ParceledListSlice(Arrays.asList(
-                        newerNonImportantConversation, newerImportantConversation,
-                        olderImportantConversation)));
-
-        // Ensure the non-Important conversation is sorted between these recent conversations.
-        ConversationChannel recentConversationBeforeNonImportantConversation =
-                getConversationChannel(
-                        SHORTCUT_ID_1 + 3, 4);
-        ConversationChannel recentConversationAfterNonImportantConversation =
-                getConversationChannel(SHORTCUT_ID_1 + 4,
-                        2);
-        when(mPeopleManager.getRecentConversations()).thenReturn(
-                new ParceledListSlice(Arrays.asList(recentConversationAfterNonImportantConversation,
-                        recentConversationBeforeNonImportantConversation)));
-
-        List<String> orderedShortcutIds = PeopleSpaceUtils.getRecentTiles(
-                mContext, mNotificationManager, mPeopleManager,
-                mLauncherApps, mNotificationEntryManager)
-                .stream().map(tile -> tile.getId()).collect(Collectors.toList());
-
-        // Check for sorted recent conversations.
-        assertThat(orderedShortcutIds).containsExactly(
-                recentConversationBeforeNonImportantConversation.getShortcutInfo().getId(),
-                newerNonImportantConversation.getShortcutInfo().getId(),
-                recentConversationAfterNonImportantConversation.getShortcutInfo().getId())
-                .inOrder();
-    }
-
-    @Test
-    public void testGetPriorityTilesReturnsSortedListWithOnlyImportantConversations()
-            throws Exception {
-        // Ensure the less-recent Important conversation is before more recent conversations.
-        ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID_1, false, 3);
-        ConversationChannelWrapper newerImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID_1 + 1, true, 3);
-        ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID_1 + 2,
-                true, 1);
-        when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
-                new ParceledListSlice(Arrays.asList(
-                        newerNonImportantConversation, newerImportantConversation,
-                        olderImportantConversation)));
-
-        // Ensure the non-Important conversation is sorted between these recent conversations.
-        ConversationChannel recentConversationBeforeNonImportantConversation =
-                getConversationChannel(
-                        SHORTCUT_ID_1 + 3, 4);
-        ConversationChannel recentConversationAfterNonImportantConversation =
-                getConversationChannel(SHORTCUT_ID_1 + 4,
-                        2);
-        when(mPeopleManager.getRecentConversations()).thenReturn(
-                new ParceledListSlice(Arrays.asList(recentConversationAfterNonImportantConversation,
-                        recentConversationBeforeNonImportantConversation)));
-
-        List<String> orderedShortcutIds = PeopleSpaceUtils.getPriorityTiles(
-                mContext, mNotificationManager, mPeopleManager,
-                mLauncherApps, mNotificationEntryManager)
-                .stream().map(tile -> tile.getId()).collect(Collectors.toList());
-
-        // Check for sorted priority conversations.
-        assertThat(orderedShortcutIds).containsExactly(
-                newerImportantConversation.getShortcutInfo().getId(),
-                olderImportantConversation.getShortcutInfo().getId())
-                .inOrder();
-    }
-
-    @Test
     public void testGetMessagingStyleMessagesNoMessage() {
         Notification notification = new Notification.Builder(mContext, "test")
                 .setContentTitle("TEST_TITLE")
@@ -570,30 +481,4 @@
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
     }
-
-    private ConversationChannelWrapper getConversationChannelWrapper(String shortcutId,
-            boolean importantConversation, long lastInteractionTimestamp) throws Exception {
-        ConversationChannelWrapper convo = new ConversationChannelWrapper();
-        NotificationChannel notificationChannel = new NotificationChannel(shortcutId,
-                "channel" + shortcutId,
-                NotificationManager.IMPORTANCE_DEFAULT);
-        notificationChannel.setImportantConversation(importantConversation);
-        convo.setNotificationChannel(notificationChannel);
-        convo.setShortcutInfo(new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel(
-                "name").build());
-        when(mPeopleManager.getLastInteraction(anyString(), anyInt(),
-                eq(shortcutId))).thenReturn(lastInteractionTimestamp);
-        return convo;
-    }
-
-    private ConversationChannel getConversationChannel(String shortcutId,
-            long lastInteractionTimestamp) throws Exception {
-        ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel(
-                "name").build();
-        ConversationChannel convo = new ConversationChannel(shortcutInfo, 0, null, null,
-                lastInteractionTimestamp, false);
-        when(mPeopleManager.getLastInteraction(anyString(), anyInt(),
-                eq(shortcutId))).thenReturn(lastInteractionTimestamp);
-        return convo;
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
index 107ac83..3cc55f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
@@ -114,8 +114,6 @@
 
         when(mMockContext.getString(R.string.birthday_status)).thenReturn(
                 mContext.getString(R.string.birthday_status));
-        when(mMockContext.getString(R.string.basic_status)).thenReturn(
-                mContext.getString(R.string.basic_status));
         when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
         when(mMockContext.getString(R.string.over_timestamp)).thenReturn(
                 mContext.getString(R.string.over_timestamp));
@@ -126,7 +124,6 @@
         when(resources.getConfiguration()).thenReturn(configuration);
         when(resources.getDisplayMetrics()).thenReturn(displayMetrics);
         TextView textView = mock(TextView.class);
-        // when(new TextView(mMockContext)).thenReturn(textView);
         when(textView.getLineHeight()).thenReturn(16);
         when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
         mPeopleTileViewHelper = new PeopleTileViewHelper(mContext,
@@ -134,16 +131,41 @@
     }
 
     @Test
-    public void testCreateRemoteViewsWithLastInteractionTime() {
+    public void testCreateRemoteViewsWithLastInteractionTimeUnderOneDayHidden() {
         RemoteViews views = new PeopleTileViewHelper(mContext,
                 PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
         View result = views.apply(mContext, null);
 
+        // Not showing last interaction.
+        assertEquals(View.GONE, result.findViewById(R.id.last_interaction).getVisibility());
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_large));
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_height_for_large));
+        RemoteViews largeView = new PeopleTileViewHelper(mContext,
+                PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+        View largeResult = largeView.apply(mContext, null);
+
+        // Not showing last interaction.
+        assertEquals(View.GONE, largeResult.findViewById(R.id.last_interaction).getVisibility());
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithLastInteractionTime() {
+        PeopleSpaceTile tileWithLastInteraction =
+                PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setLastInteractionTimestamp(
+                        123445L).build();
+        RemoteViews views = new PeopleTileViewHelper(mContext,
+                tileWithLastInteraction, 0, mOptions).getViews();
+        View result = views.apply(mContext, null);
+
         TextView name = (TextView) result.findViewById(R.id.name);
         assertEquals(name.getText(), NAME);
         // Has last interaction.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.last_interaction).getVisibility());
         TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
-        assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+        assertEquals(lastInteraction.getText(), "Over 2 weeks ago");
         // No availability.
         assertEquals(View.GONE, result.findViewById(R.id.availability).getVisibility());
         // Shows person icon.
@@ -154,7 +176,7 @@
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_medium) - 1);
         RemoteViews smallView = new PeopleTileViewHelper(mContext,
-                PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+                tileWithLastInteraction, 0, mOptions).getViews();
         View smallResult = smallView.apply(mContext, null);
 
         // Show name over predefined icon.
@@ -171,14 +193,15 @@
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_height_for_large));
         RemoteViews largeView = new PeopleTileViewHelper(mContext,
-                PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+                tileWithLastInteraction, 0, mOptions).getViews();
         View largeResult = largeView.apply(mContext, null);
 
         name = (TextView) largeResult.findViewById(R.id.name);
         assertEquals(name.getText(), NAME);
         // Has last interaction.
+        assertEquals(View.VISIBLE, largeResult.findViewById(R.id.last_interaction).getVisibility());
         lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
-        assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+        assertEquals(lastInteraction.getText(), "Over 2 weeks ago");
         // No availability.
         assertEquals(View.GONE, result.findViewById(R.id.availability).getVisibility());
         // Shows person icon.
@@ -202,8 +225,7 @@
         TextView name = (TextView) result.findViewById(R.id.name);
         assertEquals(name.getText(), NAME);
         // Has last interaction over status.
-        TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
-        assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+        assertEquals(View.GONE, result.findViewById(R.id.last_interaction).getVisibility());
         // Has availability.
         assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
         // Has person icon.
@@ -237,14 +259,13 @@
         name = (TextView) largeResult.findViewById(R.id.name);
         assertEquals(name.getText(), NAME);
         // Has last interaction.
-        lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
-        assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+        assertEquals(View.GONE, largeResult.findViewById(R.id.last_interaction).getVisibility());
         // Has availability.
-        assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+        assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility());
         // Shows person icon.
-        assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+        assertEquals(View.VISIBLE, largeResult.findViewById(R.id.person_icon).getVisibility());
         // No status.
-        assertThat((View) result.findViewById(R.id.text_content)).isNull();
+        assertThat((View) largeResult.findViewById(R.id.text_content)).isNull();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
index 0ef3ca2..ccb40e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
@@ -29,6 +29,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.service.notification.NotificationListenerService;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
@@ -83,6 +84,8 @@
     private BubblesManager mBubblesManager;
     @Mock
     private NotificationListenerService.Ranking mRanking;
+    @Mock
+    private UserManager mUserManager;
 
     @Captor
     private ArgumentCaptor<NotificationVisibility> mNotificationVisibilityCaptor;
@@ -93,7 +96,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mActivity = new LaunchConversationActivity(mNotificationEntryManager,
-                Optional.of(mBubblesManager));
+                Optional.of(mBubblesManager), mUserManager);
         mActivity.setIsForTesting(true, mIStatusBarService);
         mIntent = new Intent();
         mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, "tile ID");
@@ -113,6 +116,7 @@
         when(mNotifEntryCanBubble.canBubble()).thenReturn(true);
         when(mNotifEntryNoRanking.getRanking()).thenReturn(null);
         when(mRanking.getRank()).thenReturn(NOTIF_RANK);
+        when(mUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
     }
 
     @Test
@@ -176,4 +180,18 @@
                 anyInt(), any(), anyInt(), anyInt(), any());
         verify(mBubblesManager, times(1)).expandStackAndSelectBubble(eq(mNotifEntryCanBubble));
     }
+
+    @Test
+    public void testQuietModeOpensQuietModeDialog() throws Exception {
+        mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY,
+                NOTIF_KEY);
+        when(mUserManager.isQuietModeEnabled(eq(USER_HANDLE))).thenReturn(true);
+        mActivity.setIntent(mIntent);
+        mActivity.onCreate(new Bundle());
+
+        assertThat(mActivity.isFinishing()).isTrue();
+        verify(mIStatusBarService, never()).onNotificationClear(any(),
+                anyInt(), any(), anyInt(), anyInt(), any());
+        verify(mBubblesManager, never()).expandStackAndSelectBubble(any());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index 7125500..e9be8d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -35,7 +35,9 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -45,8 +47,10 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.app.Person;
 import android.app.people.ConversationChannel;
 import android.app.people.ConversationStatus;
@@ -59,11 +63,14 @@
 import android.content.SharedPreferences;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.os.UserManager;
+import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 
@@ -95,6 +102,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -169,6 +177,10 @@
     private NotificationEntryManager mNotificationEntryManager;
     @Mock
     private PackageManager mPackageManager;
+    @Mock
+    private INotificationManager mNotificationManager;
+    @Mock
+    private UserManager mUserManager;
 
     @Captor
     private ArgumentCaptor<NotificationHandler> mListenerCaptor;
@@ -189,7 +201,8 @@
         mProvider = new PeopleSpaceWidgetProvider();
         mProvider.setPeopleSpaceWidgetManager(mManager);
         mManager.setAppWidgetManager(mAppWidgetManager, mIPeopleManager, mPeopleManager,
-                mLauncherApps, mNotificationEntryManager, mPackageManager, true, mProvider);
+                mLauncherApps, mNotificationEntryManager, mPackageManager, true, mProvider,
+                mUserManager, mNotificationManager);
         mManager.attach(mListenerService);
 
         verify(mListenerService).addNotificationHandler(mListenerCaptor.capture());
@@ -201,6 +214,98 @@
         addTileForWidget(PERSON_TILE_WITH_SAME_URI, WIDGET_ID_WITH_SAME_URI);
         when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITHOUT_SHORTCUT)))
                 .thenReturn(new Bundle());
+        when(mUserManager.isQuietModeEnabled(any())).thenReturn(false);
+    }
+
+    @Test
+    public void testGetRecentTilesReturnsSortedListWithOnlyRecentConversations() throws Exception {
+        // Ensure the less-recent Important conversation is before more recent conversations.
+        ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper(
+                SHORTCUT_ID, false, 3);
+        ConversationChannelWrapper newerImportantConversation = getConversationChannelWrapper(
+                SHORTCUT_ID + 1, true, 3);
+        ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
+                SHORTCUT_ID + 2,
+                true, 1);
+        when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
+                new ParceledListSlice(Arrays.asList(
+                        newerNonImportantConversation, newerImportantConversation,
+                        olderImportantConversation)));
+
+        // Ensure the non-Important conversation is sorted between these recent conversations.
+        ConversationChannel recentConversationBeforeNonImportantConversation =
+                getConversationChannel(
+                        SHORTCUT_ID + 3, 4);
+        ConversationChannel recentConversationAfterNonImportantConversation =
+                getConversationChannel(SHORTCUT_ID + 4,
+                        2);
+        when(mIPeopleManager.getRecentConversations()).thenReturn(
+                new ParceledListSlice(Arrays.asList(recentConversationAfterNonImportantConversation,
+                        recentConversationBeforeNonImportantConversation)));
+
+        List<String> orderedShortcutIds = mManager.getRecentTiles()
+                .stream().map(tile -> tile.getId()).collect(Collectors.toList());
+
+        // Check for sorted recent conversations.
+        assertThat(orderedShortcutIds).containsExactly(
+                recentConversationBeforeNonImportantConversation.getShortcutInfo().getId(),
+                newerNonImportantConversation.getShortcutInfo().getId(),
+                recentConversationAfterNonImportantConversation.getShortcutInfo().getId())
+                .inOrder();
+    }
+
+    @Test
+    public void testGetPriorityTilesReturnsSortedListWithOnlyImportantConversations()
+            throws Exception {
+        // Ensure the less-recent Important conversation is before more recent conversations.
+        ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper(
+                SHORTCUT_ID, false, 3);
+        ConversationChannelWrapper newerImportantConversation = getConversationChannelWrapper(
+                SHORTCUT_ID + 1, true, 3);
+        ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
+                SHORTCUT_ID + 2,
+                true, 1);
+        when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
+                new ParceledListSlice(Arrays.asList(
+                        newerNonImportantConversation, newerImportantConversation,
+                        olderImportantConversation)));
+
+        List<String> orderedShortcutIds = mManager.getPriorityTiles()
+                .stream().map(tile -> tile.getId()).collect(Collectors.toList());
+
+        // Check for sorted priority conversations.
+        assertThat(orderedShortcutIds).containsExactly(
+                newerImportantConversation.getShortcutInfo().getId(),
+                olderImportantConversation.getShortcutInfo().getId())
+                .inOrder();
+    }
+
+    @Test
+    public void testGetTilesReturnsNothingInQuietMode()
+            throws Exception {
+        // Ensure the less-recent Important conversation is before more recent conversations.
+        ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper(
+                SHORTCUT_ID, false, 3);
+        ConversationChannelWrapper newerImportantConversation = getConversationChannelWrapper(
+                SHORTCUT_ID + 1, true, 3);
+        ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
+                SHORTCUT_ID + 2,
+                true, 1);
+        when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
+                new ParceledListSlice(Arrays.asList(
+                        newerNonImportantConversation, newerImportantConversation,
+                        olderImportantConversation)));
+        ConversationChannel recentConversation =
+                getConversationChannel(
+                        SHORTCUT_ID + 3, 4);
+        when(mIPeopleManager.getRecentConversations()).thenReturn(
+                new ParceledListSlice(Arrays.asList(recentConversation)));
+
+        when(mUserManager.isQuietModeEnabled(any())).thenReturn(true);
+
+        // Check nothing returned.
+        assertThat(mManager.getPriorityTiles()).isEmpty();
+        assertThat(mManager.getRecentTiles()).isEmpty();
     }
 
     @Test
@@ -1208,4 +1313,30 @@
         editor.putStringSet(contactUri.toString(), storedWidgetIdsByUri);
         editor.apply();
     }
+
+    private ConversationChannelWrapper getConversationChannelWrapper(String shortcutId,
+            boolean importantConversation, long lastInteractionTimestamp) throws Exception {
+        ConversationChannelWrapper convo = new ConversationChannelWrapper();
+        NotificationChannel notificationChannel = new NotificationChannel(shortcutId,
+                "channel" + shortcutId,
+                NotificationManager.IMPORTANCE_DEFAULT);
+        notificationChannel.setImportantConversation(importantConversation);
+        convo.setNotificationChannel(notificationChannel);
+        convo.setShortcutInfo(new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel(
+                "name").build());
+        when(mIPeopleManager.getLastInteraction(anyString(), anyInt(),
+                eq(shortcutId))).thenReturn(lastInteractionTimestamp);
+        return convo;
+    }
+
+    private ConversationChannel getConversationChannel(String shortcutId,
+            long lastInteractionTimestamp) throws Exception {
+        ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel(
+                "name").build();
+        ConversationChannel convo = new ConversationChannel(shortcutInfo, 0, null, null,
+                lastInteractionTimestamp, false);
+        when(mIPeopleManager.getLastInteraction(anyString(), anyInt(),
+                eq(shortcutId))).thenReturn(lastInteractionTimestamp);
+        return convo;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
index 791dd12..05a1e4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
@@ -28,6 +28,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.appops.AppOpsController
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.privacy.logging.PrivacyLogger
 import com.android.systemui.settings.UserTracker
@@ -43,6 +44,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Captor
@@ -86,6 +88,8 @@
     private lateinit var privacyLogger: PrivacyLogger
     @Mock
     private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var appOpsController: AppOpsController
     @Captor
     private lateinit var dialogDismissedCaptor: ArgumentCaptor<PrivacyDialog.OnDialogDismissed>
     @Captor
@@ -131,6 +135,7 @@
                 uiExecutor,
                 privacyLogger,
                 keyguardStateController,
+                appOpsController,
                 dialogProvider
         )
     }
@@ -143,18 +148,27 @@
     }
 
     @Test
+    fun testMicMutedParameter() {
+        `when`(appOpsController.isMicMuted).thenReturn(true)
+        controller.showDialog(context)
+        backgroundExecutor.runAllReady()
+
+        verify(permissionManager).getIndicatorAppOpUsageData(true)
+    }
+
+    @Test
     fun testPermissionManagerOnlyCalledInBackgroundThread() {
         controller.showDialog(context)
-        verify(permissionManager, never()).indicatorAppOpUsageData
+        verify(permissionManager, never()).getIndicatorAppOpUsageData(anyBoolean())
         backgroundExecutor.runAllReady()
-        verify(permissionManager).indicatorAppOpUsageData
+        verify(permissionManager).getIndicatorAppOpUsageData(anyBoolean())
     }
 
     @Test
     fun testPackageManagerOnlyCalledInBackgroundThread() {
         val usage = createMockPermGroupUsage()
         `when`(usage.isPhoneCall).thenReturn(false)
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage))
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
 
         controller.showDialog(context)
         verify(packageManager, never()).getApplicationInfoAsUser(anyString(), anyInt(), anyInt())
@@ -217,7 +231,7 @@
                 isPhoneCall = false,
                 attribution = TEST_ATTRIBUTION
         )
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage))
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -246,7 +260,7 @@
                 packageName = "${TEST_PACKAGE_NAME}_microphone",
                 permGroupName = PERM_MICROPHONE
         )
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_microphone, usage_camera)
         )
 
@@ -269,7 +283,7 @@
                 packageName = "${TEST_PACKAGE_NAME}_recent",
                 isActive = false
         )
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_recent, usage_active)
         )
 
@@ -292,7 +306,7 @@
                 isActive = true,
                 lastAccess = 1L
         )
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_active, usage_active_moreRecent)
         )
         controller.showDialog(context)
@@ -319,7 +333,7 @@
                 isActive = false,
                 lastAccess = 2L
         )
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_recent, usage_mostRecent, usage_moreRecent)
         )
 
@@ -342,7 +356,7 @@
                 permGroupName = PERM_LOCATION
         )
 
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_camera, usage_location, usage_microphone)
         )
         `when`(privacyItemController.micCameraAvailable).thenReturn(false)
@@ -366,7 +380,7 @@
                 permGroupName = PERM_LOCATION
         )
 
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_camera, usage_location, usage_microphone)
         )
         `when`(privacyItemController.locationAvailable).thenReturn(false)
@@ -392,7 +406,7 @@
                 permGroupName = PERM_LOCATION
         )
 
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_camera, usage_location, usage_microphone)
         )
         `when`(privacyItemController.micCameraAvailable).thenReturn(true)
@@ -416,7 +430,7 @@
                 permGroupName = PERM_LOCATION
         )
 
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
                 listOf(usage_camera, usage_location, usage_microphone)
         )
         `when`(privacyItemController.micCameraAvailable).thenReturn(false)
@@ -433,7 +447,8 @@
         val usage_enterprise = createMockPermGroupUsage(
                 uid = generateUidForUser(ENT_USER_ID)
         )
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage_enterprise))
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+                .thenReturn(listOf(usage_enterprise))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -446,7 +461,8 @@
         val usage_other = createMockPermGroupUsage(
                 uid = generateUidForUser(ENT_USER_ID + 1)
         )
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage_other))
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+                .thenReturn(listOf(usage_other))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -514,7 +530,8 @@
     }
 
     private fun setUpDefaultMockResponses() {
-        `when`(permissionManager.indicatorAppOpUsageData).thenReturn(emptyList())
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(emptyList())
+        `when`(appOpsController.isMicMuted).thenReturn(false)
 
         `when`(packageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
                 .thenAnswer {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
index bba1c6a..e4d7b1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -46,7 +46,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyList
 import org.mockito.Captor
 import org.mockito.Mock
@@ -156,7 +156,7 @@
     fun testDistinctItems() {
         doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0)))
-                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+                .`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.addCallback(callback)
         executor.runAllReady()
@@ -168,7 +168,7 @@
     fun testSimilarItemsDifferentTimeStamp() {
         doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1)))
-                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+                .`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.addCallback(callback)
         executor.runAllReady()
@@ -215,7 +215,7 @@
 
     @Test
     fun testMultipleCallbacksAreUpdated() {
-        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         val otherCallback = mock(PrivacyItemController.Callback::class.java)
         privacyItemController.addCallback(callback)
@@ -233,7 +233,7 @@
 
     @Test
     fun testRemoveCallback() {
-        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
         val otherCallback = mock(PrivacyItemController.Callback::class.java)
         privacyItemController.addCallback(callback)
         privacyItemController.addCallback(otherCallback)
@@ -254,7 +254,7 @@
     fun testListShouldNotHaveNull() {
         doReturn(listOf(AppOpItem(AppOpsManager.OP_ACTIVATE_VPN, TEST_UID, "", 0),
                         AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
-                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+                .`when`(appOpsController).getActiveAppOps(anyBoolean())
         privacyItemController.addCallback(callback)
         executor.runAllReady()
         executor.runAllReady()
@@ -292,7 +292,7 @@
 
         doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
-                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+                .`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.addCallback(callback)
         executor.runAllReady()
@@ -306,7 +306,7 @@
     @Test
     fun testNotUpdated_LocationChangeWhenOnlyMicCamera() {
         doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
-                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+                .`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.addCallback(callback)
         changeLocation(false)
@@ -338,7 +338,7 @@
     fun testLogListUpdated() {
         doReturn(listOf(
                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0))
-        ).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        ).`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.addCallback(callback)
         executor.runAllReady()
@@ -362,10 +362,10 @@
     }
 
     @Test
-    fun testListRequestedForAllUsers() {
+    fun testListRequestedShowPaused() {
         privacyItemController.addCallback(callback)
         executor.runAllReady()
-        verify(appOpsController).getActiveAppOpsForUser(UserHandle.USER_ALL)
+        verify(appOpsController).getActiveAppOps(true)
     }
 
     @Test
@@ -377,7 +377,7 @@
         doReturn(listOf(
                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0),
                 AppOpItem(AppOpsManager.OP_CAMERA, otherUserUid, TEST_PACKAGE_NAME, 0))
-        ).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        ).`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext)
         executor.runAllReady()
@@ -401,7 +401,7 @@
                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0),
                 AppOpItem(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, 0),
                 AppOpItem(AppOpsManager.OP_PHONE_CALL_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0))
-        ).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        ).`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext)
         executor.runAllReady()
@@ -424,7 +424,7 @@
                 AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0),
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0),
                 AppOpItem(AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, 0))
-        ).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        ).`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext)
         executor.runAllReady()
@@ -442,7 +442,7 @@
     fun testPassageOfTimeDoesNotRemoveIndicators() {
         doReturn(listOf(
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
-        )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        )).`when`(appOpsController).getActiveAppOps(anyBoolean())
 
         privacyItemController.addCallback(callback)
 
@@ -458,12 +458,12 @@
         // Start with some element at time 0
         doReturn(listOf(
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
-        )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        )).`when`(appOpsController).getActiveAppOps(anyBoolean())
         privacyItemController.addCallback(callback)
         executor.runAllReady()
 
         // Then remove it at time HOLD + 1
-        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
         fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS + 1)
 
         verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
@@ -481,12 +481,12 @@
         // Start with some element at time 0
         doReturn(listOf(
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
-        )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        )).`when`(appOpsController).getActiveAppOps(anyBoolean())
         privacyItemController.addCallback(callback)
         executor.runAllReady()
 
         // Then remove it at time HOLD - 1
-        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
         fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1)
 
         verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
@@ -504,12 +504,12 @@
         // Start with some element at time 0
         doReturn(listOf(
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
-        )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        )).`when`(appOpsController).getActiveAppOps(anyBoolean())
         privacyItemController.addCallback(callback)
         executor.runAllReady()
 
         // Then remove it at time HOLD - 1
-        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
         fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1)
 
         verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
@@ -525,6 +525,30 @@
         assertTrue(privacyItemController.privacyList.isEmpty())
     }
 
+    @Test
+    fun testPausedElementsAreRemoved() {
+        val item = AppOpItem(
+                AppOpsManager.OP_RECORD_AUDIO,
+                TEST_UID,
+                TEST_PACKAGE_NAME,
+                elapsedTime
+        )
+        `when`(appOpsController.getActiveAppOps(anyBoolean())).thenReturn(listOf(item))
+        privacyItemController.addCallback(callback)
+        executor.runAllReady()
+
+        item.isDisabled = true
+        fakeClock.advanceTime(1)
+        verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
+        argCaptorCallback.value.onActiveStateChanged(
+                AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false)
+
+        executor.runAllReady()
+
+        verify(callback).onPrivacyItemsChanged(emptyList())
+        assertTrue(privacyItemController.privacyList.isEmpty())
+    }
+
     private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
     private fun changeLocation(value: Boolean?) = changeProperty(LOCATION_INDICATOR, value)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index d236023..4bba0d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -233,6 +233,11 @@
     }
 
     @Test
+    public void testGetTileLabel() {
+        assertEquals(mContext.getString(R.string.wallet_title), mTile.getTileLabel().toString());
+    }
+
+    @Test
     public void testHandleUpdateState_hasCard_deviceLocked_tileInactive() {
         when(mKeyguardStateController.isUnlocked()).thenReturn(false);
         QSTile.State state = new QSTile.State();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index 67fd5eb..929a7e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -37,6 +37,8 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
 
@@ -58,6 +60,8 @@
     private View mCenteredNotificationAreaView;
     private StatusBarStateController mStatusBarStateController;
     private OngoingCallController mOngoingCallController;
+    private SystemStatusAnimationScheduler mAnimationScheduler;
+    private PrivacyDotViewController mDotViewController;
 
     public CollapsedStatusBarFragmentTest() {
         super(CollapsedStatusBarFragment.class);
@@ -212,6 +216,11 @@
     @Override
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         mOngoingCallController = mock(OngoingCallController.class);
-        return new CollapsedStatusBarFragment(mOngoingCallController);
+        mAnimationScheduler = mock(SystemStatusAnimationScheduler.class);
+        mDotViewController = mock(PrivacyDotViewController.class);
+        return new CollapsedStatusBarFragment(
+                mOngoingCallController,
+                mAnimationScheduler,
+                mDotViewController);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 08d6d2d..11f96c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -38,6 +38,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.IWallpaperManager;
 import android.app.Notification;
 import android.app.StatusBarManager;
 import android.app.trust.TrustManager;
@@ -113,6 +114,8 @@
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.charging.WiredChargingRippleController;
+import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -263,8 +266,11 @@
     @Mock private BrightnessSlider.Factory mBrightnessSliderFactory;
     @Mock private WiredChargingRippleController mWiredChargingRippleController;
     @Mock private OngoingCallController mOngoingCallController;
+    @Mock private SystemStatusAnimationScheduler mAnimationScheduler;
+    @Mock private PrivacyDotViewController mDotViewController;
     @Mock private TunerService mTunerService;
     @Mock private FeatureFlags mFeatureFlags;
+    @Mock private IWallpaperManager mWallpaperManager;
     private ShadeController mShadeController;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
     private InitController mInitController = new InitController();
@@ -323,7 +329,8 @@
 
         when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
 
-        WakefulnessLifecycle wakefulnessLifecycle = new WakefulnessLifecycle();
+        WakefulnessLifecycle wakefulnessLifecycle =
+                new WakefulnessLifecycle(mContext, mWallpaperManager);
         wakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
         wakefulnessLifecycle.dispatchFinishedWakingUp();
 
@@ -429,6 +436,8 @@
                 mBrightnessSliderFactory,
                 mWiredChargingRippleController,
                 mOngoingCallController,
+                mAnimationScheduler,
+                mDotViewController,
                 mTunerService,
                 mFeatureFlags);
         when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 4471778..40439a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -36,7 +36,6 @@
 
 import android.app.Instrumentation;
 import android.net.ConnectivityManager;
-import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
@@ -323,34 +322,37 @@
 
     public void setConnectivityViaCallbackInNetworkControllerForVcn(
             int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
-        mNetCapabilities.setTransportInfo(info);
-        setConnectivityCommon(networkType, validated, isConnected);
-        mDefaultCallbackInNetworkController.onCapabilitiesChanged(
-                mNetwork, new NetworkCapabilities(mNetCapabilities));
+        final NetworkCapabilities.Builder builder =
+                new NetworkCapabilities.Builder(mNetCapabilities);
+        builder.setTransportInfo(info);
+        setConnectivityCommon(builder, networkType, validated, isConnected);
+        mDefaultCallbackInNetworkController.onCapabilitiesChanged(mNetwork, builder.build());
     }
 
     public void setConnectivityViaCallbackInNetworkController(
             int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) {
+        final NetworkCapabilities.Builder builder =
+                new NetworkCapabilities.Builder(mNetCapabilities);
         if (networkType == NetworkCapabilities.TRANSPORT_WIFI) {
-            mNetCapabilities.setTransportInfo(wifiInfo);
+            builder.setTransportInfo(wifiInfo);
         }
-        setConnectivityCommon(networkType, validated, isConnected);
-        mDefaultCallbackInNetworkController.onCapabilitiesChanged(
-                mNetwork, new NetworkCapabilities(mNetCapabilities));
+        setConnectivityCommon(builder, networkType, validated, isConnected);
+        mDefaultCallbackInNetworkController.onCapabilitiesChanged(mNetwork, builder.build());
     }
 
     public void setConnectivityViaCallbackInWifiTracker(
             int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) {
+        final NetworkCapabilities.Builder builder =
+                new NetworkCapabilities.Builder(mNetCapabilities);
         if (networkType == NetworkCapabilities.TRANSPORT_WIFI) {
-            mNetCapabilities.setTransportInfo(wifiInfo);
+            builder.setTransportInfo(wifiInfo);
         }
-        setConnectivityCommon(networkType, validated, isConnected);
+        setConnectivityCommon(builder, networkType, validated, isConnected);
         if (networkType == NetworkCapabilities.TRANSPORT_WIFI) {
             if (isConnected) {
-                mNetworkCallback.onAvailable(mNetwork,
-                        new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false);
-                mNetworkCallback.onCapabilitiesChanged(
-                        mNetwork, new NetworkCapabilities(mNetCapabilities));
+                final NetworkCapabilities newCap = builder.build();
+                mNetworkCallback.onAvailable(mNetwork);
+                mNetworkCallback.onCapabilitiesChanged(mNetwork, newCap);
             } else {
                 mNetworkCallback.onLost(mNetwork);
             }
@@ -359,16 +361,16 @@
 
     public void setConnectivityViaCallbackInWifiTrackerForVcn(
             int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
-        mNetCapabilities.setTransportInfo(info);
-        setConnectivityCommon(networkType, validated, isConnected);
+        final NetworkCapabilities.Builder builder =
+                new NetworkCapabilities.Builder(mNetCapabilities);
+        builder.setTransportInfo(info);
+        setConnectivityCommon(builder, networkType, validated, isConnected);
         if (networkType == NetworkCapabilities.TRANSPORT_CELLULAR) {
             if (isConnected) {
-                mNetworkCallback.onAvailable(mNetwork,
-                        new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false);
-                mNetworkCallback.onCapabilitiesChanged(
-                        mNetwork, new NetworkCapabilities(mNetCapabilities));
-                mDefaultCallbackInWifiTracker.onCapabilitiesChanged(
-                        mNetwork, new NetworkCapabilities(mNetCapabilities));
+                final NetworkCapabilities newCap = builder.build();
+                mNetworkCallback.onAvailable(mNetwork);
+                mNetworkCallback.onCapabilitiesChanged(mNetwork, newCap);
+                mDefaultCallbackInWifiTracker.onCapabilitiesChanged(mNetwork, newCap);
             } else {
                 mNetworkCallback.onLost(mNetwork);
             }
@@ -377,26 +379,28 @@
 
     public void setConnectivityViaDefaultCallbackInWifiTracker(
             int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) {
+        final NetworkCapabilities.Builder builder =
+                new NetworkCapabilities.Builder(mNetCapabilities);
         if (networkType == NetworkCapabilities.TRANSPORT_WIFI) {
-            mNetCapabilities.setTransportInfo(wifiInfo);
+            builder.setTransportInfo(wifiInfo);
         }
-        setConnectivityCommon(networkType, validated, isConnected);
+        setConnectivityCommon(builder, networkType, validated, isConnected);
         mDefaultCallbackInWifiTracker.onCapabilitiesChanged(
-                mNetwork, new NetworkCapabilities(mNetCapabilities));
+                mNetwork, builder.build());
     }
 
-    private void setConnectivityCommon(
+    private static void setConnectivityCommon(NetworkCapabilities.Builder builder,
         int networkType, boolean validated, boolean isConnected){
         // TODO: Separate out into several NetworkCapabilities.
         if (isConnected) {
-            mNetCapabilities.addTransportType(networkType);
+            builder.addTransportType(networkType);
         } else {
-            mNetCapabilities.removeTransportType(networkType);
+            builder.removeTransportType(networkType);
         }
         if (validated) {
-            mNetCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
         } else {
-            mNetCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
         }
     }
 
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 ed87a40..c38a547 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
@@ -202,8 +202,8 @@
     @Test
     public void testNetworkRequest() {
         verify(mConnectivityManager, times(1)).registerNetworkCallback(argThat(
-                (NetworkRequest request) -> request.networkCapabilities.getUids() == null
-                        && request.networkCapabilities.getCapabilities().length == 0
+                (NetworkRequest request) ->
+                        request.equals(new NetworkRequest.Builder().clearCapabilities().build())
                 ), any(NetworkCallback.class));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
index 653946e..6f6ef72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
@@ -71,15 +71,17 @@
 public class WalletScreenControllerTest extends SysuiTestCase {
 
     private static final int MAX_CARDS = 10;
+    private static final int CARD_CAROUSEL_WIDTH = 10;
     private static final String CARD_ID = "card_id";
     private static final CharSequence SHORTCUT_SHORT_LABEL = "View all";
     private static final CharSequence SHORTCUT_LONG_LABEL = "Add a payment method";
     private static final CharSequence SERVICE_LABEL = "Wallet app";
-    private final WalletView mWalletView = new WalletView(mContext);
     private final Drawable mWalletLogo = mContext.getDrawable(android.R.drawable.ic_lock_lock);
     private final Intent mWalletIntent = new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET)
             .setComponent(new ComponentName(mContext.getPackageName(), "WalletActivity"));
 
+    private WalletView mWalletView;
+
     @Mock
     QuickAccessWalletClient mWalletClient;
     @Mock
@@ -104,6 +106,8 @@
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
         when(mUserTracker.getUserContext()).thenReturn(mContext);
+        mWalletView = new WalletView(mContext);
+        mWalletView.getCardCarousel().setExpectedViewWidth(CARD_CAROUSEL_WIDTH);
         when(mWalletClient.getLogo()).thenReturn(mWalletLogo);
         when(mWalletClient.getShortcutLongLabel()).thenReturn(SHORTCUT_LONG_LABEL);
         when(mWalletClient.getShortcutShortLabel()).thenReturn(SHORTCUT_SHORT_LABEL);
@@ -132,7 +136,12 @@
 
         verify(mWalletClient).getWalletCards(any(), any(), mCallbackCaptor.capture());
 
-        mCallbackCaptor.getValue().onWalletCardsRetrieved(response);
+        QuickAccessWalletClient.OnWalletCardsRetrievedCallback callback =
+                mCallbackCaptor.getValue();
+
+        assertEquals(mController, callback);
+
+        callback.onWalletCardsRetrieved(response);
         mTestableLooper.processAllMessages();
 
         assertEquals(VISIBLE, mWalletView.getCardCarouselContainer().getVisibility());
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4d96162..16e6671 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -630,7 +630,9 @@
     }
 
     private static IDnsResolver getDnsResolver(Context context) {
-        return IDnsResolver.Stub.asInterface(DnsResolverServiceManager.getService(context));
+        final DnsResolverServiceManager dsm = context.getSystemService(
+                DnsResolverServiceManager.class);
+        return IDnsResolver.Stub.asInterface(dsm.getService());
     }
 
     /** Handler thread used for all of the handlers below. */
@@ -1924,7 +1926,7 @@
         newNc.setAdministratorUids(new int[0]);
         if (!checkAnyPermissionOf(
                 callerPid, callerUid, android.Manifest.permission.NETWORK_FACTORY)) {
-            newNc.setSubIds(Collections.emptySet());
+            newNc.setSubscriptionIds(Collections.emptySet());
         }
 
         return newNc;
@@ -3969,17 +3971,16 @@
                 // multilayer requests, returning as soon as a NetworkAgentInfo satisfies a request
                 // is important so as to not evaluate lower priority requests further in
                 // nri.mRequests.
-                final boolean isNetworkNeeded = candidate.isSatisfyingRequest(req.requestId)
-                        // Note that this catches two important cases:
-                        // 1. Unvalidated cellular will not be reaped when unvalidated WiFi
-                        //    is currently satisfying the request.  This is desirable when
-                        //    cellular ends up validating but WiFi does not.
-                        // 2. Unvalidated WiFi will not be reaped when validated cellular
-                        //    is currently satisfying the request.  This is desirable when
-                        //    WiFi ends up validating and out scoring cellular.
-                        || nri.getSatisfier().getCurrentScore()
-                        < candidate.getCurrentScoreAsValidated();
-                return isNetworkNeeded;
+                final NetworkAgentInfo champion = req.equals(nri.getActiveRequest())
+                        ? nri.getSatisfier() : null;
+                // Note that this catches two important cases:
+                // 1. Unvalidated cellular will not be reaped when unvalidated WiFi
+                //    is currently satisfying the request.  This is desirable when
+                //    cellular ends up validating but WiFi does not.
+                // 2. Unvalidated WiFi will not be reaped when validated cellular
+                //    is currently satisfying the request.  This is desirable when
+                //    WiFi ends up validating and out scoring cellular.
+                return mNetworkRanker.mightBeat(req, champion, candidate.getValidatedScoreable());
             }
         }
 
@@ -5726,7 +5727,7 @@
         }
         mAppOpsManager.checkPackage(callerUid, callerPackageName);
 
-        if (!nc.getSubIds().isEmpty()) {
+        if (!nc.getSubscriptionIds().isEmpty()) {
             enforceNetworkFactoryPermission();
         }
     }
@@ -6148,6 +6149,7 @@
     @Override
     public int registerNetworkProvider(Messenger messenger, String name) {
         enforceNetworkFactoryOrSettingsPermission();
+        Objects.requireNonNull(messenger, "messenger must be non-null");
         NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger,
                 nextNetworkProviderId(), () -> unregisterNetworkProvider(messenger));
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi));
@@ -7811,7 +7813,7 @@
             @NonNull final Collection<NetworkRequestInfo> networkRequests) {
         final NetworkReassignment changes = new NetworkReassignment();
 
-        // Gather the list of all relevant agents and sort them by score.
+        // Gather the list of all relevant agents.
         final ArrayList<NetworkAgentInfo> nais = new ArrayList<>();
         for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
             if (!nai.everConnected) {
@@ -9186,6 +9188,7 @@
     @Override
     public void unregisterConnectivityDiagnosticsCallback(
             @NonNull IConnectivityDiagnosticsCallback callback) {
+        Objects.requireNonNull(callback, "callback must be non-null");
         mConnectivityDiagnosticsHandler.sendMessage(
                 mConnectivityDiagnosticsHandler.obtainMessage(
                         ConnectivityDiagnosticsHandler
@@ -9556,6 +9559,7 @@
      */
     @Override
     public void unregisterQosCallback(@NonNull final IQosCallback callback) {
+        Objects.requireNonNull(callback, "callback must be non-null");
         mQosCallbackTracker.unregisterCallback(callback);
     }
 
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 9f91dd6..483250a 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -174,9 +174,9 @@
 # Disk usage stats for verifying quota correctness
 3121 pm_package_stats (manual_time|2|3),(quota_time|2|3),(manual_data|2|2),(quota_data|2|2),(manual_cache|2|2),(quota_cache|2|2)
 # Snapshot statistics
-3130 pm_snapshot_stats (build_count|1|1),(reuse_count|1|1),(big_builds|1|1),(quick_rebuilds|1|1),(max_build_time|1|3),(cumm_build_time|1|3)
+3130 pm_snapshot_stats (build_count|1|1),(reuse_count|1|1),(big_builds|1|1),(short_lived|1|1),(max_build_time|1|3),(cumm_build_time|2|3)
 # Snapshot rebuild instance
-3131 pm_snapshot_rebuild (build_time|1|3),(elapsed|1|3)
+3131 pm_snapshot_rebuild (build_time|1|3),(lifetime|1|3)
 
 # ---------------------------
 # InputMethodManagerService.java
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index df6ab5d..3ba4c34 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -19,9 +19,10 @@
 import static android.Manifest.permission.MANAGE_SENSOR_PRIVACY;
 import static android.app.ActivityManager.RunningServiceInfo;
 import static android.app.ActivityManager.RunningTaskInfo;
-import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
 import static android.content.Intent.EXTRA_PACKAGE_NAME;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR;
@@ -140,11 +141,15 @@
     private final UserManagerInternal mUserManagerInternal;
     private final ActivityManager mActivityManager;
     private final ActivityTaskManager mActivityTaskManager;
+    private final AppOpsManager mAppOpsManager;
+
+    private final IBinder mAppOpsRestrictionToken = new Binder();
 
     private SensorPrivacyManagerInternalImpl mSensorPrivacyManagerInternal;
 
     public SensorPrivacyService(Context context) {
         super(context);
+        mAppOpsManager = context.getSystemService(AppOpsManager.class);
         mUserManagerInternal = getLocalService(UserManagerInternal.class);
         mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(context);
         mActivityManager = context.getSystemService(ActivityManager.class);
@@ -194,10 +199,20 @@
                 }
             }
 
+            for (int i = 0; i < mIndividualEnabled.size(); i++) {
+                int userId = mIndividualEnabled.keyAt(i);
+                SparseBooleanArray userIndividualEnabled =
+                        mIndividualEnabled.get(i);
+                for (int j = 0; j < userIndividualEnabled.size(); j++) {
+                    int sensor = userIndividualEnabled.keyAt(i);
+                    boolean enabled = userIndividualEnabled.valueAt(j);
+                    setUserRestriction(userId, sensor, enabled);
+                }
+            }
+
             int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_CAMERA};
-            AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class);
-            appOpsManager.startWatchingNoted(micAndCameraOps, this);
-            appOpsManager.startWatchingStarted(micAndCameraOps, this);
+            mAppOpsManager.startWatchingNoted(micAndCameraOps, this);
+            mAppOpsManager.startWatchingStarted(micAndCameraOps, this);
 
             mContext.registerReceiver(new BroadcastReceiver() {
                 @Override
@@ -221,7 +236,7 @@
         public void onOpNoted(int code, int uid, String packageName,
                 String attributionTag, @AppOpsManager.OpFlags int flags,
                 @AppOpsManager.Mode int result) {
-            if (result != MODE_ALLOWED || (flags & AppOpsManager.OP_FLAGS_ALL_TRUSTED) == 0) {
+            if (result != MODE_IGNORED || (flags & AppOpsManager.OP_FLAGS_ALL_TRUSTED) == 0) {
                 return;
             }
 
@@ -1125,6 +1140,9 @@
             mSensorPrivacyManagerInternal.dispatch(userId, sensor, enabled);
             SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser =
                     mIndividualSensorListeners.get(userId);
+
+            setUserRestriction(userId, sensor, enabled);
+
             if (listenersForUser == null) {
                 return;
             }
@@ -1152,6 +1170,18 @@
         }
     }
 
+    private void setUserRestriction(int userId, int sensor, boolean enabled) {
+        if (sensor == CAMERA) {
+            mAppOpsManager.setUserRestrictionForUser(OP_CAMERA, enabled,
+                    mAppOpsRestrictionToken, new String[]{}, userId);
+        } else if (sensor == MICROPHONE) {
+            mAppOpsManager.setUserRestrictionForUser(OP_RECORD_AUDIO, enabled,
+                    mAppOpsRestrictionToken, new String[]{}, userId);
+            mAppOpsManager.setUserRestrictionForUser(OP_RECORD_AUDIO_HOTWORD, enabled,
+                    mAppOpsRestrictionToken, new String[]{}, userId);
+        }
+    }
+
     private final class DeathRecipient implements IBinder.DeathRecipient {
 
         private ISensorPrivacyListener mListener;
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 98bfa28..2d486c4 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2551,11 +2551,12 @@
      * Send a notification to registrants that the configs of physical channel has changed for
      * a particular subscription.
      *
+     * @param phoneId the phone id.
      * @param subId the subId
      * @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel.
      */
-    public void notifyPhysicalChannelConfigForSubscriber(
-            int subId, List<PhysicalChannelConfig> configs) {
+    public void notifyPhysicalChannelConfigForSubscriber(int phoneId, int subId,
+            List<PhysicalChannelConfig> configs) {
         if (!checkNotifyPermission("notifyPhysicalChannelConfig()")) {
             return;
         }
@@ -2567,7 +2568,6 @@
         }
 
         synchronized (mRecords) {
-            int phoneId = SubscriptionManager.getPhoneId(subId);
             if (validatePhoneId(phoneId)) {
                 mPhysicalChannelConfigs.set(phoneId, configs);
                 for (Record r : mRecords) {
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 039f4d9..4b52057 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -729,7 +729,7 @@
 
         // If multiple subscription IDs exist, they MUST all point to the same subscription
         // group. Otherwise undefined behavior may occur.
-        for (int subId : networkCapabilities.getSubIds()) {
+        for (int subId : networkCapabilities.getSubscriptionIds()) {
             // Verify that all subscriptions point to the same group
             if (subGrp != null && !subGrp.equals(snapshot.getGroupForSubId(subId))) {
                 Slog.wtf(TAG, "Got multiple subscription groups for a single network");
@@ -1003,14 +1003,14 @@
         }
 
         private boolean requiresRestartForCarrierWifi(NetworkCapabilities caps) {
-            if (!caps.hasTransport(TRANSPORT_WIFI) || caps.getSubIds() == null) {
+            if (!caps.hasTransport(TRANSPORT_WIFI) || caps.getSubscriptionIds() == null) {
                 return false;
             }
 
             synchronized (mCaps) {
                 for (NetworkCapabilities existing : mCaps.values()) {
                     if (existing.hasTransport(TRANSPORT_WIFI)
-                            && caps.getSubIds().equals(existing.getSubIds())) {
+                            && caps.getSubscriptionIds().equals(existing.getSubscriptionIds())) {
                         // Restart if any immutable capabilities have changed
                         return existing.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
                                 != caps.hasCapability(NET_CAPABILITY_NOT_RESTRICTED);
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index b103def..b7ef10a 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -199,13 +199,12 @@
         @Override
         public void onPropertiesChanged(Properties properties) {
             synchronized (mDeviceConfigLock) {
-                for (String key : properties.getKeyset()) {
+                for (final String packageName : properties.getKeyset()) {
                     try {
                         // Check if the package is installed before caching it.
-                        final String packageName = keyToPackageName(key);
                         mPackageManager.getPackageInfo(packageName, 0);
                         final GamePackageConfiguration config =
-                                GamePackageConfiguration.fromProperties(key, properties);
+                                GamePackageConfiguration.fromProperties(packageName, properties);
                         if (config.isValid()) {
                             putConfig(config);
                         } else {
@@ -290,8 +289,8 @@
         private final String mPackageName;
         private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs;
 
-        private GamePackageConfiguration(String keyName) {
-            mPackageName = keyToPackageName(keyName);
+        private GamePackageConfiguration(String packageName) {
+            mPackageName = packageName;
             mModeConfigs = new ArrayMap<>();
         }
 
@@ -563,9 +562,9 @@
         }
     }
 
-    private void loadDeviceConfigLocked() {
+    void loadDeviceConfigLocked() {
         final List<PackageInfo> packages = mPackageManager.getInstalledPackages(0);
-        final String[] packageNames = packages.stream().map(e -> packageNameToKey(e.packageName))
+        final String[] packageNames = packages.stream().map(e -> e.packageName)
                 .toArray(String[]::new);
         synchronized (mDeviceConfigLock) {
             final Properties properties = DeviceConfig.getProperties(
@@ -680,8 +679,7 @@
                         case ACTION_PACKAGE_CHANGED:
                             synchronized (mDeviceConfigLock) {
                                 Properties properties = DeviceConfig.getProperties(
-                                        DeviceConfig.NAMESPACE_GAME_OVERLAY,
-                                        packageNameToKey(packageName));
+                                        DeviceConfig.NAMESPACE_GAME_OVERLAY, packageName);
                                 for (String key : properties.getKeyset()) {
                                     GamePackageConfiguration config =
                                             GamePackageConfiguration.fromProperties(key,
@@ -692,7 +690,9 @@
                             break;
                         case ACTION_PACKAGE_REMOVED:
                             disableCompatScale(packageName);
-                            mConfigs.remove(packageName);
+                            synchronized (mDeviceConfigLock) {
+                                mConfigs.remove(packageName);
+                            }
                             break;
                         default:
                             // do nothing
@@ -710,23 +710,6 @@
         mDeviceConfigListener = new DeviceConfigListener();
     }
 
-    /**
-     * Valid package name characters are [a-zA-Z0-9_] with a '.' delimiter. Policy keys can only use
-     * [a-zA-Z0-9_] so we must handle periods. We do this by appending a '_' to any existing
-     * sequence of '_', then we replace all '.' chars with '_';
-     */
-    private static String packageNameToKey(String name) {
-        return name.replaceAll("(_+)", "_$1").replaceAll("\\.", "_");
-    }
-
-    /**
-     * Replace the last '_' in a sequence with '.' (this can be one or more chars), then replace the
-     * resulting special case '_.' with just '_' to get the original package name.
-     */
-    private static String keyToPackageName(String key) {
-        return key.replaceAll("(_)(?!\\1)", ".").replaceAll("_\\.", "_");
-    }
-
     private String dumpDeviceConfigs() {
         StringBuilder out = new StringBuilder();
         for (String key : mConfigs.keySet()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index d56fd12..b795b54 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -375,12 +375,12 @@
         }
 
         @Override
-        public void onFeaturesRetrieved(byte[] features, int enrollmentId) {
+        public void onFeaturesRetrieved(byte[] features) {
 
         }
 
         @Override
-        public void onFeatureSet(int enrollmentId, byte feature) {
+        public void onFeatureSet(byte feature) {
 
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
index c63af7e..f1bfd53 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
@@ -32,6 +32,7 @@
  */
 public class TestHal extends IFace.Stub {
     private static final String TAG = "face.aidl.TestHal";
+
     @Override
     public SensorProps[] getSensorProps() {
         Slog.w(TAG, "getSensorProps");
@@ -102,16 +103,16 @@
             }
 
             @Override
-            public void getFeatures(int enrollmentId) throws RemoteException {
+            public void getFeatures() throws RemoteException {
                 Slog.w(TAG, "getFeatures");
-                cb.onFeaturesRetrieved(new byte[0], enrollmentId);
+                cb.onFeaturesRetrieved(new byte[0]);
             }
 
             @Override
-            public void setFeature(HardwareAuthToken hat, int enrollmentId,
-                    byte feature, boolean enabled) throws RemoteException {
+            public void setFeature(HardwareAuthToken hat, byte feature, boolean enabled)
+                    throws RemoteException {
                 Slog.w(TAG, "setFeature");
-                cb.onFeatureSet(enrollmentId, feature);
+                cb.onFeatureSet(feature);
             }
 
             @Override
diff --git a/services/core/java/com/android/server/connectivity/FullScore.java b/services/core/java/com/android/server/connectivity/FullScore.java
index 52da566..14cec09 100644
--- a/services/core/java/com/android/server/connectivity/FullScore.java
+++ b/services/core/java/com/android/server/connectivity/FullScore.java
@@ -259,6 +259,14 @@
     }
 
     /**
+     * Returns this score but validated.
+     */
+    public FullScore asValidated() {
+        return new FullScore(mLegacyInt, mPolicies | (1L << POLICY_IS_VALIDATED),
+                mKeepConnectedReason);
+    }
+
+    /**
      * For backward compatibility, get the legacy int.
      * This will be removed before S is published.
      */
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 584174e..18becd4 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -950,6 +950,26 @@
     }
 
     /**
+     * Returns a Scoreable identical to this NAI, but validated.
+     *
+     * This is useful to probe what scoring would be if this network validated, to know
+     * whether to provisionally keep a network that may or may not validate.
+     *
+     * @return a Scoreable identical to this NAI, but validated.
+     */
+    public NetworkRanker.Scoreable getValidatedScoreable() {
+        return new NetworkRanker.Scoreable() {
+            @Override public FullScore getScore() {
+                return mScore.asValidated();
+            }
+
+            @Override public NetworkCapabilities getCapsNoCopy() {
+                return networkCapabilities;
+            }
+        };
+    }
+
+    /**
      * Return a {@link NetworkStateSnapshot} for this network.
      */
     @NonNull
diff --git a/services/core/java/com/android/server/connectivity/NetworkRanker.java b/services/core/java/com/android/server/connectivity/NetworkRanker.java
index 2b345e5..346af44 100644
--- a/services/core/java/com/android/server/connectivity/NetworkRanker.java
+++ b/services/core/java/com/android/server/connectivity/NetworkRanker.java
@@ -234,16 +234,17 @@
         NetworkAgentInfo bestNetwork = null;
         int bestScore = Integer.MIN_VALUE;
         for (final NetworkAgentInfo nai : nais) {
-            if (nai.getCurrentScore() > bestScore) {
+            final int naiScore = nai.getCurrentScore();
+            if (naiScore > bestScore) {
                 bestNetwork = nai;
-                bestScore = nai.getCurrentScore();
+                bestScore = naiScore;
             }
         }
         return bestNetwork;
     }
 
     /**
-     * Returns whether an offer has a chance to beat a champion network for a request.
+     * Returns whether a {@link Scoreable} has a chance to beat a champion network for a request.
      *
      * Offers are sent by network providers when they think they might be able to make a network
      * with the characteristics contained in the offer. If the offer has no chance to beat
@@ -257,15 +258,15 @@
      *
      * @param request The request to evaluate against.
      * @param champion The currently best network for this request.
-     * @param offer The offer.
+     * @param contestant The offer.
      * @return Whether the offer stands a chance to beat the champion.
      */
     public boolean mightBeat(@NonNull final NetworkRequest request,
             @Nullable final NetworkAgentInfo champion,
-            @NonNull final NetworkOffer offer) {
+            @NonNull final Scoreable contestant) {
         // If this network can't even satisfy the request then it can't beat anything, not
         // even an absence of network. It can't satisfy it anyway.
-        if (!request.canBeSatisfiedBy(offer.caps)) return false;
+        if (!request.canBeSatisfiedBy(contestant.getCapsNoCopy())) return false;
         // If there is no satisfying network, then this network can beat, because some network
         // is always better than no network.
         if (null == champion) return true;
@@ -274,25 +275,24 @@
             // Otherwise rank them.
             final ArrayList<Scoreable> candidates = new ArrayList<>();
             candidates.add(champion);
-            candidates.add(offer);
-            return offer == getBestNetworkByPolicy(candidates, champion);
+            candidates.add(contestant);
+            return contestant == getBestNetworkByPolicy(candidates, champion);
         } else {
-            return mightBeatByLegacyInt(request, champion.getScore(), offer);
+            return mightBeatByLegacyInt(champion.getScore(), contestant);
         }
     }
 
     /**
-     * Returns whether an offer might beat a champion according to the legacy int.
+     * Returns whether a contestant might beat a champion according to the legacy int.
      */
-    public boolean mightBeatByLegacyInt(@NonNull final NetworkRequest request,
-            @Nullable final FullScore championScore,
-            @NonNull final NetworkOffer offer) {
+    private boolean mightBeatByLegacyInt(@Nullable final FullScore championScore,
+            @NonNull final Scoreable contestant) {
         final int offerIntScore;
-        if (offer.caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+        if (contestant.getCapsNoCopy().hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
             // If the offer might have Internet access, then it might validate.
-            offerIntScore = offer.score.getLegacyIntAsValidated();
+            offerIntScore = contestant.getScore().getLegacyIntAsValidated();
         } else {
-            offerIntScore = offer.score.getLegacyInt();
+            offerIntScore = contestant.getScore().getLegacyInt();
         }
         return championScore.getLegacyInt() < offerIntScore;
     }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 7994fcc..94a5099 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3177,7 +3177,7 @@
 
     @BinderThread
     @Override
-    public void reportPerceptible(IBinder windowToken, boolean perceptible) {
+    public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
         Objects.requireNonNull(windowToken, "windowToken must not be null");
         int uid = Binder.getCallingUid();
         synchronized (mMethodMap) {
@@ -5992,9 +5992,8 @@
 
         @BinderThread
         @Override
-        public void reportStartInput(IBinder startInputToken, IVoidResultCallback resultCallback) {
-            CallbackUtils.onResult(resultCallback,
-                    () -> mImms.reportStartInput(mToken, startInputToken));
+        public void reportStartInputAsync(IBinder startInputToken) {
+            mImms.reportStartInput(mToken, startInputToken);
         }
 
         @BinderThread
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 6244743..885093d 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1840,7 +1840,7 @@
 
         @BinderThread
         @Override
-        public void reportPerceptible(IBinder windowClient, boolean perceptible) {
+        public void reportPerceptibleAsync(IBinder windowClient, boolean perceptible) {
             reportNotSupported();
         }
 
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 045e06d0..2ffc62a 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -178,8 +178,9 @@
     }
 
     /** Logs that a provider has entered or exited stationary throttling. */
-    public void logProviderStationaryThrottled(String provider, boolean throttled) {
-        addLogEvent(EVENT_PROVIDER_STATIONARY_THROTTLED, provider, throttled);
+    public void logProviderStationaryThrottled(String provider, boolean throttled,
+            ProviderRequest request) {
+        addLogEvent(EVENT_PROVIDER_STATIONARY_THROTTLED, provider, throttled, request);
     }
 
     /** Logs that the location power save mode has changed. */
@@ -217,7 +218,7 @@
                         (Integer) args[1], (CallerIdentity) args[2]);
             case EVENT_PROVIDER_STATIONARY_THROTTLED:
                 return new ProviderStationaryThrottledEvent(timeDelta, (String) args[0],
-                        (Boolean) args[1]);
+                        (Boolean) args[1], (ProviderRequest) args[2]);
             case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE:
                 return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]);
             default:
@@ -355,17 +356,19 @@
     private static final class ProviderStationaryThrottledEvent extends ProviderEvent {
 
         private final boolean mStationaryThrottled;
+        private final ProviderRequest mRequest;
 
         ProviderStationaryThrottledEvent(long timeDelta, String provider,
-                boolean stationaryThrottled) {
+                boolean stationaryThrottled, ProviderRequest request) {
             super(timeDelta, provider);
             mStationaryThrottled = stationaryThrottled;
+            mRequest = request;
         }
 
         @Override
         public String getLogString() {
             return mProvider + " provider stationary/idle " + (mStationaryThrottled ? "throttled"
-                    : "unthrottled");
+                    : "unthrottled") + ", request = " + mRequest;
         }
     }
 
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
index ab7e526..22a675a 100644
--- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -206,7 +206,7 @@
                 if (D) {
                     Log.d(TAG, mName + " provider stationary throttled");
                 }
-                EVENT_LOG.logProviderStationaryThrottled(mName, true);
+                EVENT_LOG.logProviderStationaryThrottled(mName, true, mOutgoingRequest);
             }
 
             if (mDeliverLastLocationCallback != null) {
@@ -224,7 +224,7 @@
             }
         } else {
             if (oldThrottlingIntervalMs != INTERVAL_DISABLED) {
-                EVENT_LOG.logProviderStationaryThrottled(mName, false);
+                EVENT_LOG.logProviderStationaryThrottled(mName, false, mOutgoingRequest);
                 if (D) {
                     Log.d(TAG, mName + " provider stationary unthrottled");
                 }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 59f00a2..6cded50 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1261,7 +1261,8 @@
         return getCredentialTypeInternal(userId) != CREDENTIAL_TYPE_NONE;
     }
 
-    private void setKeystorePassword(byte[] password, int userHandle) {
+    @VisibleForTesting /** Note: this method is overridden in unit tests */
+    void setKeystorePassword(byte[] password, int userHandle) {
         AndroidKeyStoreMaintenance.onUserPasswordChanged(userHandle, password);
     }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index abcf4fb..b10d56b 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -384,7 +384,7 @@
         if (mPlaybackState == null) {
             return false;
         }
-        return mPlaybackState.isActive() == expected;
+        return mPlaybackState.isActiveState() == expected;
     }
 
     /**
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
index 8bd3b1e..42b7c9d3 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
@@ -100,7 +100,7 @@
 
         IntentFilter deletionFilter = new IntentFilter(ACTION_HISTORY_DELETION);
         deletionFilter.addDataScheme(SCHEME_DELETION);
-        mContext.registerReceiver(mFileCleaupReceiver, deletionFilter);
+        mContext.registerReceiver(mFileCleanupReceiver, deletionFilter);
     }
 
     public void init() {
@@ -273,13 +273,36 @@
         }
     }
 
+    /**
+     * Remove the first entry from the list of history files whose file matches the given file path.
+     *
+     * This method is necessary for anything that only has an absolute file path rather than an
+     * AtomicFile object from the list of history files.
+     *
+     * filePath should be an absolute path.
+     */
+    void removeFilePathFromHistory(String filePath) {
+        if (filePath == null) {
+            return;
+        }
+
+        Iterator<AtomicFile> historyFileItr = mHistoryFiles.iterator();
+        while (historyFileItr.hasNext()) {
+            final AtomicFile af = historyFileItr.next();
+            if (af != null && filePath.equals(af.getBaseFile().getAbsolutePath())) {
+                historyFileItr.remove();
+                return;
+            }
+        }
+    }
+
     private void deleteFile(AtomicFile file) {
         if (DEBUG) {
             Slog.d(TAG, "Removed " + file.getBaseFile().getName());
         }
         file.delete();
         // TODO: delete all relevant bitmaps, once they exist
-        mHistoryFiles.remove(file);
+        removeFilePathFromHistory(file.getBaseFile().getAbsolutePath());
     }
 
     private void scheduleDeletion(File file, long creationTime, int retentionDays) {
@@ -342,7 +365,7 @@
         }
     }
 
-    private final BroadcastReceiver mFileCleaupReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mFileCleanupReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
@@ -358,7 +381,7 @@
                             Slog.d(TAG, "Removed " + fileToDelete.getBaseFile().getName());
                         }
                         fileToDelete.delete();
-                        mHistoryFiles.remove(fileToDelete);
+                        removeFilePathFromHistory(filePath);
                     }
                 } catch (Exception e) {
                     Slog.e(TAG, "Failed to delete notification history file", e);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3ddfe6c..1e6e5cb 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -380,8 +380,6 @@
     static final int INVALID_UID = -1;
     static final String ROOT_PKG = "root";
 
-    static final boolean ENABLE_BLOCKED_TOASTS = true;
-
     static final String[] DEFAULT_ALLOWED_ADJUSTMENTS = new String[] {
             Adjustment.KEY_CONTEXTUAL_ACTIONS,
             Adjustment.KEY_TEXT_REPLIES,
@@ -3165,34 +3163,11 @@
             }
 
             final int callingUid = Binder.getCallingUid();
-            final UserHandle callingUser = Binder.getCallingUserHandle();
+            checkCallerIsSameApp(pkg);
             final boolean isSystemToast = isCallerSystemOrPhone()
                     || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
-            final boolean isPackageSuspended = isPackagePaused(pkg);
-            final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg,
-                    callingUid);
-
-            final boolean appIsForeground;
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                appIsForeground = mActivityManager.getUidImportance(callingUid)
-                        == IMPORTANCE_FOREGROUND;
-            } finally {
-                Binder.restoreCallingIdentity(callingIdentity);
-            }
-
-            if (ENABLE_BLOCKED_TOASTS && !isSystemToast && ((notificationsDisabledForPackage
-                    && !appIsForeground) || isPackageSuspended)) {
-                Slog.e(TAG, "Suppressing toast from package " + pkg
-                        + (isPackageSuspended ? " due to package suspended."
-                        : " by user request."));
-                return;
-            }
-
             boolean isAppRenderedToast = (callback != null);
-            if (blockToast(callingUid, isSystemToast, isAppRenderedToast)) {
-                Slog.w(TAG, "Blocking custom toast from package " + pkg
-                        + " due to package not in the foreground at time the toast was posted");
+            if (!checkCanEnqueueToast(pkg, callingUid, isAppRenderedToast, isSystemToast)) {
                 return;
             }
 
@@ -3246,6 +3221,39 @@
             }
         }
 
+        private boolean checkCanEnqueueToast(String pkg, int callingUid,
+                boolean isAppRenderedToast, boolean isSystemToast) {
+            final boolean isPackageSuspended = isPackagePaused(pkg);
+            final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg,
+                    callingUid);
+
+            final boolean appIsForeground;
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                appIsForeground = mActivityManager.getUidImportance(callingUid)
+                        == IMPORTANCE_FOREGROUND;
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+
+            if (!isSystemToast && ((notificationsDisabledForPackage && !appIsForeground)
+                    || isPackageSuspended)) {
+                Slog.e(TAG, "Suppressing toast from package " + pkg
+                        + (isPackageSuspended ? " due to package suspended."
+                        : " by user request."));
+                return false;
+            }
+
+            if (blockToast(callingUid, isSystemToast, isAppRenderedToast,
+                    isPackageInForegroundForToast(callingUid))) {
+                Slog.w(TAG, "Blocking custom toast from package " + pkg
+                        + " due to package not in the foreground at time the toast was posted");
+                return false;
+            }
+
+            return true;
+        }
+
         @Override
         public void cancelToast(String pkg, IBinder token) {
             Slog.i(TAG, "cancelToast pkg=" + pkg + " token=" + token);
@@ -7692,12 +7700,13 @@
             boolean isWithinQuota =
                     mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG)
                             || isExemptFromRateLimiting(record.pkg, userId);
+            boolean isPackageInForeground = isPackageInForegroundForToast(record.uid);
 
             if (tryShowToast(
-                    record, rateLimitingEnabled, isWithinQuota)) {
+                    record, rateLimitingEnabled, isWithinQuota, isPackageInForeground)) {
                 scheduleDurationReachedLocked(record, lastToastWasTextRecord);
                 mIsCurrentToastShown = true;
-                if (rateLimitingEnabled) {
+                if (rateLimitingEnabled && !isPackageInForeground) {
                     mToastRateLimiter.noteEvent(userId, record.pkg, TOAST_QUOTA_TAG);
                 }
                 return;
@@ -7713,14 +7722,15 @@
 
     /** Returns true if it successfully showed the toast. */
     private boolean tryShowToast(ToastRecord record, boolean rateLimitingEnabled,
-            boolean isWithinQuota) {
-        if (rateLimitingEnabled && !isWithinQuota) {
+            boolean isWithinQuota, boolean isPackageInForeground) {
+        if (rateLimitingEnabled && !isWithinQuota && !isPackageInForeground) {
             reportCompatRateLimitingToastsChange(record.uid);
             Slog.w(TAG, "Package " + record.pkg + " is above allowed toast quota, the "
                     + "following toast was blocked and discarded: " + record);
             return false;
         }
-        if (blockToast(record.uid, record.isSystemToast, record.isAppRendered())) {
+        if (blockToast(record.uid, record.isSystemToast, record.isAppRendered(),
+                isPackageInForeground)) {
             Slog.w(TAG, "Blocking custom toast from package " + record.pkg
                     + " due to package not in the foreground at the time of showing the toast");
             return false;
@@ -7911,10 +7921,11 @@
      * with targetSdk < R. For apps with targetSdk R+, text toasts are not app-rendered, so
      * isAppRenderedToast == true means it's a custom toast.
      */
-    private boolean blockToast(int uid, boolean isSystemToast, boolean isAppRenderedToast) {
+    private boolean blockToast(int uid, boolean isSystemToast, boolean isAppRenderedToast,
+            boolean isPackageInForeground) {
         return isAppRenderedToast
                 && !isSystemToast
-                && !isPackageInForegroundForToast(uid)
+                && !isPackageInForeground
                 && CompatChanges.isChangeEnabled(CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK, uid);
     }
 
diff --git a/services/core/java/com/android/server/om/OverlayReferenceMapper.java b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
index a9dbb2f..94a1f3b 100644
--- a/services/core/java/com/android/server/om/OverlayReferenceMapper.java
+++ b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Pair;
 import android.util.Slog;
 
@@ -28,10 +30,10 @@
 import com.android.server.SystemConfig;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
@@ -84,14 +86,15 @@
      * See class comment for specific types.
      */
     @GuardedBy("mLock")
-    private final Map<String, Map<String, Set<String>>> mActorToTargetToOverlays = new HashMap<>();
+    private final ArrayMap<String, ArrayMap<String, ArraySet<String>>> mActorToTargetToOverlays =
+            new ArrayMap<>();
 
     /**
      * Keys are actor package names, values are generic package names the actor should be able
      * to see.
      */
     @GuardedBy("mLock")
-    private final Map<String, Set<String>> mActorPkgToPkgs = new HashMap<>();
+    private final ArrayMap<String, Set<String>> mActorPkgToPkgs = new ArrayMap<>();
 
     @GuardedBy("mLock")
     private boolean mDeferRebuild;
@@ -160,21 +163,26 @@
      *
      * @param pkg the package to add
      * @param otherPkgs map of other packages to consider, excluding {@param pkg}
+     * @return Set of packages that may have changed visibility
      */
-    public void addPkg(AndroidPackage pkg, Map<String, AndroidPackage> otherPkgs) {
+    public ArraySet<String> addPkg(AndroidPackage pkg, Map<String, AndroidPackage> otherPkgs) {
         synchronized (mLock) {
+            ArraySet<String> changed = new ArraySet<>();
+
             if (!pkg.getOverlayables().isEmpty()) {
-                addTarget(pkg, otherPkgs);
+                addTarget(pkg, otherPkgs, changed);
             }
 
             // TODO(b/135203078): Replace with isOverlay boolean flag check; fix test mocks
             if (!mProvider.getTargetToOverlayables(pkg).isEmpty()) {
-                addOverlay(pkg, otherPkgs);
+                addOverlay(pkg, otherPkgs, changed);
             }
 
             if (!mDeferRebuild) {
                 rebuild();
             }
+
+            return changed;
         }
     }
 
@@ -184,27 +192,40 @@
      * of {@link SystemConfig#getNamedActors()}.
      *
      * @param pkgName name to remove, as was added through {@link #addPkg(AndroidPackage, Map)}
+     * @return Set of packages that may have changed visibility
      */
-    public void removePkg(String pkgName) {
+    public ArraySet<String> removePkg(String pkgName) {
         synchronized (mLock) {
-            removeTarget(pkgName);
-            removeOverlay(pkgName);
+            ArraySet<String> changedPackages = new ArraySet<>();
+            removeTarget(pkgName, changedPackages);
+            removeOverlay(pkgName, changedPackages);
 
             if (!mDeferRebuild) {
                 rebuild();
             }
+
+            return changedPackages;
         }
     }
 
-    private void removeTarget(String target) {
+    /**
+     * @param changedPackages Ongoing collection of packages that may have changed visibility
+     */
+    private void removeTarget(String target, @NonNull Collection<String> changedPackages) {
         synchronized (mLock) {
-            Iterator<Map<String, Set<String>>> iterator =
-                    mActorToTargetToOverlays.values().iterator();
-            while (iterator.hasNext()) {
-                Map<String, Set<String>> next = iterator.next();
-                next.remove(target);
-                if (next.isEmpty()) {
-                    iterator.remove();
+            int size = mActorToTargetToOverlays.size();
+            for (int index = size - 1; index >= 0; index--) {
+                ArrayMap<String, ArraySet<String>> targetToOverlays =
+                        mActorToTargetToOverlays.valueAt(index);
+                if (targetToOverlays.containsKey(target)) {
+                    targetToOverlays.remove(target);
+
+                    String actor = mActorToTargetToOverlays.keyAt(index);
+                    changedPackages.add(mProvider.getActorPkg(actor));
+
+                    if (targetToOverlays.isEmpty()) {
+                        mActorToTargetToOverlays.removeAt(index);
+                    }
                 }
             }
         }
@@ -215,16 +236,19 @@
      *
      * If a target overlays itself, it will not be associated with itself, as only one half of the
      * relationship needs to exist for visibility purposes.
+     *
+     * @param changedPackages Ongoing collection of packages that may have changed visibility
      */
-    private void addTarget(AndroidPackage targetPkg, Map<String, AndroidPackage> otherPkgs) {
+    private void addTarget(AndroidPackage targetPkg, Map<String, AndroidPackage> otherPkgs,
+            @NonNull Collection<String> changedPackages) {
         synchronized (mLock) {
             String target = targetPkg.getPackageName();
-            removeTarget(target);
+            removeTarget(target, changedPackages);
 
             Map<String, String> overlayablesToActors = targetPkg.getOverlayables();
             for (String overlayable : overlayablesToActors.keySet()) {
                 String actor = overlayablesToActors.get(overlayable);
-                addTargetToMap(actor, target);
+                addTargetToMap(actor, target, changedPackages);
 
                 for (AndroidPackage overlayPkg : otherPkgs.values()) {
                     Map<String, Set<String>> targetToOverlayables =
@@ -235,18 +259,38 @@
                     }
 
                     if (overlayables.contains(overlayable)) {
-                        addOverlayToMap(actor, target, overlayPkg.getPackageName());
+                        String overlay = overlayPkg.getPackageName();
+                        addOverlayToMap(actor, target, overlay, changedPackages);
                     }
                 }
             }
         }
     }
 
-    private void removeOverlay(String overlay) {
+    /**
+     * @param changedPackages Ongoing collection of packages that may have changed visibility
+     */
+    private void removeOverlay(String overlay, @NonNull Collection<String> changedPackages) {
         synchronized (mLock) {
-            for (Map<String, Set<String>> targetToOverlays : mActorToTargetToOverlays.values()) {
-                for (Set<String> overlays : targetToOverlays.values()) {
-                    overlays.remove(overlay);
+            int actorsSize = mActorToTargetToOverlays.size();
+            for (int actorIndex = actorsSize - 1; actorIndex >= 0; actorIndex--) {
+                ArrayMap<String, ArraySet<String>> targetToOverlays =
+                        mActorToTargetToOverlays.valueAt(actorIndex);
+                int targetsSize = targetToOverlays.size();
+                for (int targetIndex = targetsSize - 1; targetIndex >= 0; targetIndex--) {
+                    final Set<String> overlays = targetToOverlays.valueAt(targetIndex);
+
+                    if (overlays.remove(overlay)) {
+                        String actor = mActorToTargetToOverlays.keyAt(actorIndex);
+                        changedPackages.add(mProvider.getActorPkg(actor));
+
+                        // targetToOverlays should not be removed here even if empty as the actor
+                        // will still have visibility to the target even if no overlays exist
+                    }
+                }
+
+                if (targetToOverlays.isEmpty()) {
+                    mActorToTargetToOverlays.removeAt(actorIndex);
                 }
             }
         }
@@ -257,11 +301,14 @@
      *
      * If an overlay targets itself, it will not be associated with itself, as only one half of the
      * relationship needs to exist for visibility purposes.
+     *
+     * @param changedPackages Ongoing collection of packages that may have changed visibility
      */
-    private void addOverlay(AndroidPackage overlayPkg, Map<String, AndroidPackage> otherPkgs) {
+    private void addOverlay(AndroidPackage overlayPkg, Map<String, AndroidPackage> otherPkgs,
+            @NonNull Collection<String> changedPackages) {
         synchronized (mLock) {
             String overlay = overlayPkg.getPackageName();
-            removeOverlay(overlay);
+            removeOverlay(overlay, changedPackages);
 
             Map<String, Set<String>> targetToOverlayables =
                     mProvider.getTargetToOverlayables(overlayPkg);
@@ -280,7 +327,7 @@
                     if (TextUtils.isEmpty(actor)) {
                         continue;
                     }
-                    addOverlayToMap(actor, targetPkgName, overlay);
+                    addOverlayToMap(actor, targetPkgName, overlay, changedPackages);
                 }
             }
         }
@@ -312,7 +359,8 @@
                     continue;
                 }
 
-                Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+                ArrayMap<String, ArraySet<String>> targetToOverlays =
+                        mActorToTargetToOverlays.get(actor);
                 Set<String> pkgs = new HashSet<>();
 
                 for (String target : targetToOverlays.keySet()) {
@@ -326,36 +374,51 @@
         }
     }
 
-    private void addTargetToMap(String actor, String target) {
-        Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+    /**
+     * @param changedPackages Ongoing collection of packages that may have changed visibility
+     */
+    private void addTargetToMap(String actor, String target,
+            @NonNull Collection<String> changedPackages) {
+        ArrayMap<String, ArraySet<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
         if (targetToOverlays == null) {
-            targetToOverlays = new HashMap<>();
+            targetToOverlays = new ArrayMap<>();
             mActorToTargetToOverlays.put(actor, targetToOverlays);
         }
 
-        Set<String> overlays = targetToOverlays.get(target);
+        ArraySet<String> overlays = targetToOverlays.get(target);
         if (overlays == null) {
-            overlays = new HashSet<>();
+            overlays = new ArraySet<>();
             targetToOverlays.put(target, overlays);
         }
+
+        // For now, only actors themselves can gain or lose visibility through package changes
+        changedPackages.add(mProvider.getActorPkg(actor));
     }
 
-    private void addOverlayToMap(String actor, String target, String overlay) {
+    /**
+     * @param changedPackages Ongoing collection of packages that may have changed visibility
+     */
+    private void addOverlayToMap(String actor, String target, String overlay,
+            @NonNull Collection<String> changedPackages) {
         synchronized (mLock) {
-            Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+            ArrayMap<String, ArraySet<String>> targetToOverlays =
+                    mActorToTargetToOverlays.get(actor);
             if (targetToOverlays == null) {
-                targetToOverlays = new HashMap<>();
+                targetToOverlays = new ArrayMap<>();
                 mActorToTargetToOverlays.put(actor, targetToOverlays);
             }
 
-            Set<String> overlays = targetToOverlays.get(target);
+            ArraySet<String> overlays = targetToOverlays.get(target);
             if (overlays == null) {
-                overlays = new HashSet<>();
+                overlays = new ArraySet<>();
                 targetToOverlays.put(target, overlays);
             }
 
             overlays.add(overlay);
         }
+
+        // For now, only actors themselves can gain or lose visibility through package changes
+        changedPackages.add(mProvider.getActorPkg(actor));
     }
 
     public interface Provider {
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index ca8202f..4f527f2 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -662,11 +662,27 @@
                 removePackage(newPkgSetting);
             }
             mStateProvider.runWithState((settings, users) -> {
-                addPackageInternal(newPkgSetting, settings);
+                ArraySet<String> additionalChangedPackages =
+                        addPackageInternal(newPkgSetting, settings);
                 synchronized (mCacheLock) {
                     if (mShouldFilterCache != null) {
                         updateShouldFilterCacheForPackage(mShouldFilterCache, null, newPkgSetting,
                                 settings, users, settings.size());
+                        if (additionalChangedPackages != null) {
+                            for (int index = 0; index < additionalChangedPackages.size(); index++) {
+                                String changedPackage = additionalChangedPackages.valueAt(index);
+                                PackageSetting changedPkgSetting = settings.get(changedPackage);
+                                if (changedPkgSetting == null) {
+                                    // It's possible for the overlay mapper to know that an actor
+                                    // package changed via an explicit reference, even if the actor
+                                    // isn't installed, so skip if that's the case.
+                                    continue;
+                                }
+
+                                updateShouldFilterCacheForPackage(mShouldFilterCache, null,
+                                        changedPkgSetting, settings, users, settings.size());
+                            }
+                        }
                     } // else, rebuild entire cache when system is ready
                 }
             });
@@ -676,7 +692,12 @@
         }
     }
 
-    private void addPackageInternal(PackageSetting newPkgSetting,
+    /**
+     * @return Additional packages that may have had their viewing visibility changed and may need
+     * to be updated in the cache. Returns null if there are no additional packages.
+     */
+    @Nullable
+    private ArraySet<String> addPackageInternal(PackageSetting newPkgSetting,
             ArrayMap<String, PackageSetting> existingSettings) {
         if (Objects.equals("android", newPkgSetting.name)) {
             // let's set aside the framework signatures
@@ -692,8 +713,7 @@
 
         final AndroidPackage newPkg = newPkgSetting.pkg;
         if (newPkg == null) {
-            // nothing to add
-            return;
+            return null;
         }
 
         if (mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts())) {
@@ -765,8 +785,13 @@
                 existingPkgs.put(pkgSetting.name, pkgSetting.pkg);
             }
         }
-        mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs);
+
+        ArraySet<String> changedPackages =
+                mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs);
+
         mFeatureConfig.updatePackageState(newPkgSetting, false /*removed*/);
+
+        return changedPackages;
     }
 
     @GuardedBy("mCacheLock")
@@ -1080,7 +1105,9 @@
                 }
             }
 
-            mOverlayReferenceMapper.removePkg(setting.name);
+            ArraySet<String> additionalChangedPackages =
+                    mOverlayReferenceMapper.removePkg(setting.name);
+
             mFeatureConfig.updatePackageState(setting, true /*removed*/);
 
             // After removing all traces of the package, if it's part of a shared user, re-add other
@@ -1109,6 +1136,25 @@
                                 siblingSetting, settings, users, settings.size());
                     }
                 }
+
+                if (mShouldFilterCache != null) {
+                    if (additionalChangedPackages != null) {
+                        for (int index = 0; index < additionalChangedPackages.size(); index++) {
+                            String changedPackage = additionalChangedPackages.valueAt(index);
+                            PackageSetting changedPkgSetting = settings.get(changedPackage);
+                            if (changedPkgSetting == null) {
+                                // It's possible for the overlay mapper to know that an actor
+                                // package changed via an explicit reference, even if the actor
+                                // isn't installed, so skip if that's the case.
+                                continue;
+                            }
+
+                            updateShouldFilterCacheForPackage(mShouldFilterCache, null,
+                                    changedPkgSetting, settings, users, settings.size());
+                        }
+                    }
+                }
+
                 onChanged();
             }
         });
diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java
index 6875b8a..ec79483 100644
--- a/services/core/java/com/android/server/pm/DumpState.java
+++ b/services/core/java/com/android/server/pm/DumpState.java
@@ -45,6 +45,7 @@
     public static final int DUMP_QUERIES = 1 << 26;
     public static final int DUMP_KNOWN_PACKAGES = 1 << 27;
     public static final int DUMP_PER_UID_READ_TIMEOUTS = 1 << 28;
+    public static final int DUMP_SNAPSHOT_STATISTICS = 1 << 29;
 
     public static final int OPTION_SHOW_FILTERS = 1 << 0;
     public static final int OPTION_DUMP_ALL_COMPONENTS = 1 << 1;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 85c5a5e..59039ba 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1914,6 +1914,18 @@
      */
     private interface Computer {
 
+        /**
+         * Administrative statistics: record that the snapshot has been used.  Every call
+         * to use() increments the usage counter.
+         */
+        void use();
+
+        /**
+         * Fetch the snapshot usage counter.
+         * @return The number of times this snapshot was used.
+         */
+        int getUsed();
+
         @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType,
                 int flags, @PrivateResolveFlags int privateResolveFlags, int filterCallingUid,
                 int userId, boolean resolveForStart, boolean allowDynamicSplits);
@@ -2065,6 +2077,9 @@
      */
     private static class ComputerEngine implements Computer {
 
+        // The administrative use counter.
+        private int mUsed = 0;
+
         // Cached attributes.  The names in this class are the same as the
         // names in PackageManagerService; see that class for documentation.
         protected final Settings mSettings;
@@ -2157,6 +2172,20 @@
             mService = args.service;
         }
 
+        /**
+         * Record that the snapshot was used.
+         */
+        public void use() {
+            mUsed++;
+        }
+
+        /**
+         * Return the usage counter.
+         */
+        public int getUsed() {
+            return mUsed;
+        }
+
         public @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
                 String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags,
                 int filterCallingUid, int userId, boolean resolveForStart,
@@ -4885,121 +4914,11 @@
      */
     private final Object mSnapshotLock = new Object();
 
-    // A counter of all queries that hit the current snapshot.
-    @GuardedBy("mSnapshotLock")
-    private int mSnapshotHits = 0;
-
-    // A class to record snapshot statistics.
-    private static class SnapshotStatistics {
-        // A build time is "big" if it takes longer than 5ms.
-        private static final long SNAPSHOT_BIG_BUILD_TIME_NS = TimeUnit.MILLISECONDS.toNanos(5);
-
-        // A snapshot is in quick succession to the previous snapshot if it less than
-        // 100ms since the previous snapshot.
-        private static final long SNAPSHOT_QUICK_REBUILD_INTERVAL_NS =
-                TimeUnit.MILLISECONDS.toNanos(100);
-
-        // The interval between snapshot statistics logging, in ns.
-        private static final long SNAPSHOT_LOG_INTERVAL_NS = TimeUnit.MINUTES.toNanos(10);
-
-        // The throttle parameters for big build reporting.  Do not report more than this
-        // many events in a single log interval.
-        private static final int SNAPSHOT_BUILD_REPORT_LIMIT = 10;
-
-        // The time the snapshot statistics were last logged.
-        private long mStatisticsSent = 0;
-
-        // The number of build events logged since the last periodic log.
-        private int mLoggedBuilds = 0;
-
-        // The time of the last build.
-        private long mLastBuildTime = 0;
-
-        // The number of times the snapshot has been rebuilt since the statistics were
-        // last logged.
-        private int mRebuilds = 0;
-
-        // The number of times the snapshot has been used since it was rebuilt.
-        private int mReused = 0;
-
-        // The number of "big" build times since the last log.  "Big" is defined by
-        // SNAPSHOT_BIG_BUILD_TIME.
-        private int mBigBuilds = 0;
-
-        // The number of quick rebuilds.  "Quick" is defined by
-        // SNAPSHOT_QUICK_REBUILD_INTERVAL_NS.
-        private int mQuickRebuilds = 0;
-
-        // The time take to build a snapshot.  This is cumulative over the rebuilds recorded
-        // in mRebuilds, so the average time to build a snapshot is given by
-        // mBuildTimeNs/mRebuilds.
-        private int mBuildTimeNs = 0;
-
-        // The maximum build time since the last log.
-        private long mMaxBuildTimeNs = 0;
-
-        // The constant that converts ns to ms.  This is the divisor.
-        private final long NS_TO_MS = TimeUnit.MILLISECONDS.toNanos(1);
-
-        // Convert ns to an int ms.  The maximum range of this method is about 24 days.
-        // There is no expectation that an event will take longer than that.
-        private int nsToMs(long ns) {
-            return (int) (ns / NS_TO_MS);
-        }
-
-        // The single method records a rebuild.  The "now" parameter is passed in because
-        // the caller needed it to computer the duration, so pass it in to avoid
-        // recomputing it.
-        private void rebuild(long now, long done, int hits) {
-            if (mStatisticsSent == 0) {
-                mStatisticsSent = now;
-            }
-            final long elapsed = now - mLastBuildTime;
-            final long duration = done - now;
-            mLastBuildTime = now;
-
-            if (mMaxBuildTimeNs < duration) {
-                mMaxBuildTimeNs = duration;
-            }
-            mRebuilds++;
-            mReused += hits;
-            mBuildTimeNs += duration;
-
-            boolean log_build = false;
-            if (duration > SNAPSHOT_BIG_BUILD_TIME_NS) {
-                log_build = true;
-                mBigBuilds++;
-            }
-            if (elapsed < SNAPSHOT_QUICK_REBUILD_INTERVAL_NS) {
-                log_build = true;
-                mQuickRebuilds++;
-            }
-            if (log_build && mLoggedBuilds < SNAPSHOT_BUILD_REPORT_LIMIT) {
-                EventLogTags.writePmSnapshotRebuild(nsToMs(duration), nsToMs(elapsed));
-                mLoggedBuilds++;
-            }
-
-            final long log_interval = now - mStatisticsSent;
-            if (log_interval >= SNAPSHOT_LOG_INTERVAL_NS) {
-                EventLogTags.writePmSnapshotStats(mRebuilds, mReused,
-                                                  mBigBuilds, mQuickRebuilds,
-                                                  nsToMs(mMaxBuildTimeNs),
-                                                  nsToMs(mBuildTimeNs));
-                mStatisticsSent = now;
-                mRebuilds = 0;
-                mReused = 0;
-                mBuildTimeNs = 0;
-                mMaxBuildTimeNs = 0;
-                mBigBuilds = 0;
-                mQuickRebuilds = 0;
-                mLoggedBuilds = 0;
-            }
-        }
-    }
-
-    // Snapshot statistics.
-    @GuardedBy("mLock")
-    private final SnapshotStatistics mSnapshotStatistics = new SnapshotStatistics();
+    /**
+     * The snapshot statistics.  These are collected to track performance and to identify
+     * situations in which the snapshots are misbehaving.
+     */
+    private final SnapshotStatistics mSnapshotStatistics;
 
     // The snapshot disable/enable switch.  An image with the flag set true uses snapshots
     // and an image with the flag set false does not use snapshots.
@@ -5033,10 +4952,9 @@
             Computer c = mSnapshotComputer;
             if (sSnapshotCorked && (c != null)) {
                 // Snapshots are corked, which means new ones should not be built right now.
+                c.use();
                 return c;
             }
-            // Deliberately capture the value pre-increment
-            final int hits = mSnapshotHits++;
             if (sSnapshotInvalid || (c == null)) {
                 // The snapshot is invalid if it is marked as invalid or if it is null.  If it
                 // is null, then it is currently being rebuilt by rebuildSnapshot().
@@ -5046,7 +4964,7 @@
                     // self-consistent (the lock is being held) and is current as of the time
                     // this function is entered.
                     if (sSnapshotInvalid) {
-                        rebuildSnapshot(hits);
+                        rebuildSnapshot();
                     }
 
                     // Guaranteed to be non-null.  mSnapshotComputer is only be set to null
@@ -5056,6 +4974,7 @@
                     c = mSnapshotComputer;
                 }
             }
+            c.use();
             return c;
         }
     }
@@ -5065,16 +4984,16 @@
      * threads from using the invalid computer until it is rebuilt.
      */
     @GuardedBy("mLock")
-    private void rebuildSnapshot(int hits) {
-        final long now = System.nanoTime();
+    private void rebuildSnapshot() {
+        final long now = SystemClock.currentTimeMicro();
+        final int hits = mSnapshotComputer == null ? -1 : mSnapshotComputer.getUsed();
         mSnapshotComputer = null;
         sSnapshotInvalid = false;
         final Snapshot args = new Snapshot(Snapshot.SNAPPED);
         mSnapshotComputer = new ComputerEngine(args);
-        final long done = System.nanoTime();
+        final long done = SystemClock.currentTimeMicro();
 
         mSnapshotStatistics.rebuild(now, done, hits);
-        mSnapshotHits = 0;
     }
 
     /**
@@ -6327,6 +6246,7 @@
         mSnapshotEnabled = false;
         mLiveComputer = createLiveComputer();
         mSnapshotComputer = null;
+        mSnapshotStatistics = null;
 
         mPackages.putAll(testParams.packages);
         mEnableFreeCacheV2 = testParams.enableFreeCacheV2;
@@ -6479,17 +6399,20 @@
         mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
         mDomainVerificationManager.setConnection(mDomainVerificationConnection);
 
-        // Create the computer as soon as the state objects have been installed.  The
-        // cached computer is the same as the live computer until the end of the
-        // constructor, at which time the invalidation method updates it.  The cache is
-        // corked initially to ensure a cached computer is not built until the end of the
-        // constructor.
-        mSnapshotEnabled = SNAPSHOT_ENABLED;
-        sSnapshotCorked = true;
-        sSnapshotInvalid = true;
-        mLiveComputer = createLiveComputer();
-        mSnapshotComputer = null;
-        registerObserver();
+        synchronized (mLock) {
+            // Create the computer as soon as the state objects have been installed.  The
+            // cached computer is the same as the live computer until the end of the
+            // constructor, at which time the invalidation method updates it.  The cache is
+            // corked initially to ensure a cached computer is not built until the end of the
+            // constructor.
+            mSnapshotEnabled = SNAPSHOT_ENABLED;
+            sSnapshotCorked = true;
+            sSnapshotInvalid = true;
+            mSnapshotStatistics = new SnapshotStatistics();
+            mLiveComputer = createLiveComputer();
+            mSnapshotComputer = null;
+            registerObserver();
+        }
 
         // CHECKSTYLE:OFF IndentationCheck
         synchronized (mInstallLock) {
@@ -23957,6 +23880,7 @@
                 pw.println("    dexopt: dump dexopt state");
                 pw.println("    compiler-stats: dump compiler statistics");
                 pw.println("    service-permissions: dump permissions required by services");
+                pw.println("    snapshot: dump snapshot statistics");
                 pw.println("    known-packages: dump known packages");
                 pw.println("    <package.name>: info about given package");
                 return;
@@ -24105,6 +24029,8 @@
                 dumpState.setDump(DumpState.DUMP_KNOWN_PACKAGES);
             } else if ("t".equals(cmd) || "timeouts".equals(cmd)) {
                 dumpState.setDump(DumpState.DUMP_PER_UID_READ_TIMEOUTS);
+            } else if ("snapshot".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_SNAPSHOT_STATISTICS);
             } else if ("write".equals(cmd)) {
                 synchronized (mLock) {
                     writeSettingsLPrTEMP();
@@ -24433,6 +24359,22 @@
                 pw.println(")");
             }
         }
+
+        if (!checkin && dumpState.isDumping(DumpState.DUMP_SNAPSHOT_STATISTICS)) {
+            pw.println("Snapshot statistics");
+            if (!mSnapshotEnabled) {
+                pw.println("  Snapshots disabled");
+            } else {
+                int hits = 0;
+                synchronized (mSnapshotLock) {
+                    if (mSnapshotComputer != null) {
+                        hits = mSnapshotComputer.getUsed();
+                    }
+                }
+                final long now = SystemClock.currentTimeMicro();
+                mSnapshotStatistics.dump(pw, "  ", now, hits, true);
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/SnapshotStatistics.java b/services/core/java/com/android/server/pm/SnapshotStatistics.java
new file mode 100644
index 0000000..c425bad5
--- /dev/null
+++ b/services/core/java/com/android/server/pm/SnapshotStatistics.java
@@ -0,0 +1,622 @@
+/*
+ * 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.server.pm;
+
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.text.TextUtils;
+
+import com.android.server.EventLogTags;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * This class records statistics about PackageManagerService snapshots.  It maintains two sets of
+ * statistics: a periodic set which represents the last 10 minutes, and a cumulative set since
+ * process boot.  The key metrics that are recorded are:
+ * <ul>
+ * <li> The time to create a snapshot - this is the performance cost of a snapshot
+ * <li> The lifetime of the snapshot - creation time over lifetime is the amortized cost
+ * <li> The number of times a snapshot is reused - this is the number of times lock
+ *      contention was avoided.
+ * </ul>
+
+ * The time conversions in this class are designed to keep arithmetic using ints, rather
+ * than longs.  Raw times are supplied as longs in units of us.  These are left long.
+ * Rebuild durations however, are converted to ints.  An int can express a duration of
+ * approximately 35 minutes.  This is longer than any expected snapshot rebuild time, so
+ * an int is satisfactory.  The exception is the cumulative rebuild time over the course
+ * of a monitoring cycle: this value is kept long since the cycle time is one week and in
+ * a badly behaved system, the rebuild time might exceed 35 minutes.
+
+ * @hide
+ */
+public class SnapshotStatistics {
+    /**
+     * The interval at which statistics should be ticked.  It is 60s.  The interval is in
+     * units of milliseconds because that is what's required by Handler.sendMessageDelayed().
+     */
+    public static final int SNAPSHOT_TICK_INTERVAL_MS = 60 * 1000;
+
+    /**
+     * The number of ticks for long statistics.  This is one week.
+     */
+    public static final int SNAPSHOT_LONG_TICKS = 7 * 24 * 60;
+
+    /**
+     * The number snapshot event logs that can be generated in a single logging interval.
+     * A small number limits the logging generated by this class.  A snapshot event log is
+     * generated for every big snapshot build time, up to the limit, or whenever the
+     * maximum build time is exceeded in the logging interval.
+     */
+    public static final int SNAPSHOT_BUILD_REPORT_LIMIT = 10;
+
+    /**
+     * The number of microseconds in a millisecond.
+     */
+    private static final int US_IN_MS = 1000;
+
+    /**
+     * A snapshot build time is "big" if it takes longer than 10ms.
+     */
+    public static final int SNAPSHOT_BIG_BUILD_TIME_US = 10 * US_IN_MS;
+
+    /**
+     * A snapshot build time is reportable if it takes longer than 30ms.  Testing shows
+     * that this is very rare.
+     */
+    public static final int SNAPSHOT_REPORTABLE_BUILD_TIME_US = 30 * US_IN_MS;
+
+    /**
+     * A snapshot is short-lived it used fewer than 5 times.
+     */
+    public static final int SNAPSHOT_SHORT_LIFETIME = 5;
+
+    /**
+     * The lock to control access to this object.
+     */
+    private final Object mLock = new Object();
+
+    /**
+     * The bins for the build time histogram.  Values are in us.
+     */
+    private final BinMap mTimeBins;
+
+    /**
+     * The bins for the snapshot use histogram.
+     */
+    private final BinMap mUseBins;
+
+    /**
+     * The number of events reported in the current tick.
+     */
+    private int mEventsReported = 0;
+
+    /**
+     * The tick counter.  At the default tick interval, this wraps every 4000 years or so.
+     */
+    private int mTicks = 0;
+
+    /**
+     * The handler used for the periodic ticks.
+     */
+    private Handler mHandler = null;
+
+    /**
+     * Convert ns to an int ms.  The maximum range of this method is about 24 days.  There
+     * is no expectation that an event will take longer than that.
+     */
+    private int usToMs(int us) {
+        return us / US_IN_MS;
+    }
+
+    /**
+     * This class exists to provide a fast bin lookup for histograms.  An instance has an
+     * integer array that maps incoming values to bins.  Values larger than the array are
+     * mapped to the top-most bin.
+     */
+    private static class BinMap {
+
+        // The number of bins
+        private int mCount;
+        // The mapping of low integers to bins
+        private int[] mBinMap;
+        // The maximum mapped value.  Values at or above this are mapped to the
+        // top bin.
+        private int mMaxBin;
+        // A copy of the original key
+        private int[] mUserKey;
+
+        /**
+         * Create a bin map.  The input is an array of integers, which must be
+         * monotonically increasing (this is not checked).  The result is an integer array
+         * as long as the largest value in the input.
+         */
+        BinMap(int[] userKey) {
+            mUserKey = Arrays.copyOf(userKey, userKey.length);
+            // The number of bins is the length of the keys, plus 1 (for the max).
+            mCount = mUserKey.length + 1;
+            // The maximum value is one more than the last one in the map.
+            mMaxBin = mUserKey[mUserKey.length - 1] + 1;
+            mBinMap = new int[mMaxBin + 1];
+
+            int j = 0;
+            for (int i = 0; i < mUserKey.length; i++) {
+                while (j <= mUserKey[i]) {
+                    mBinMap[j] = i;
+                    j++;
+                }
+            }
+            mBinMap[mMaxBin] = mUserKey.length;
+        }
+
+        /**
+         * Map a value to a bin.
+         */
+        public int getBin(int x) {
+            if (x >= 0 && x < mMaxBin) {
+                return mBinMap[x];
+            } else if (x >= mMaxBin) {
+                return mBinMap[mMaxBin];
+            } else {
+                // x is negative.  The bin will not be used.
+                return 0;
+            }
+        }
+
+        /**
+         * The number of bins in this map
+         */
+        public int count() {
+            return mCount;
+        }
+
+        /**
+         * For convenience, return the user key.
+         */
+        public int[] userKeys() {
+            return mUserKey;
+        }
+    }
+
+    /**
+     * A complete set of statistics.  These are public, making it simpler for a client to
+     * fetch the individual fields.
+     */
+    public class Stats {
+
+        /**
+         * The start time for this set of statistics, in us.
+         */
+        public long mStartTimeUs = 0;
+
+        /**
+         * The completion time for this set of statistics, in ns.  A value of zero means
+         * the statistics are still active.
+         */
+        public long mStopTimeUs = 0;
+
+        /**
+         * The build-time histogram.  The total number of rebuilds is the sum over the
+         * histogram entries.
+         */
+        public int[] mTimes;
+
+        /**
+         * The reuse histogram.  The total number of snapshot uses is the sum over the
+         * histogram entries.
+         */
+        public int[] mUsed;
+
+        /**
+         * The total number of rebuilds.  This could be computed by summing over the use
+         * bins, but is maintained separately for convenience.
+         */
+        public int mTotalBuilds = 0;
+
+        /**
+         * The total number of times any snapshot was used.
+         */
+        public int mTotalUsed = 0;
+
+        /**
+         * The total number of builds that count as big, which means they took longer than
+         * SNAPSHOT_BIG_BUILD_TIME_NS.
+         */
+        public int mBigBuilds = 0;
+
+        /**
+         * The total number of short-lived snapshots
+         */
+        public int mShortLived = 0;
+
+        /**
+         * The time taken to build snapshots.  This is cumulative over the rebuilds
+         * recorded in mRebuilds, so the average time to build a snapshot is given by
+         * mBuildTimeNs/mRebuilds.  Note that this cannot be computed from the histogram.
+         */
+        public long mTotalTimeUs = 0;
+
+        /**
+         * The maximum build time since the last log.
+         */
+        public int mMaxBuildTimeUs = 0;
+
+        /**
+         * Record the rebuild.  The parameters are the length of time it took to build the
+         * latest snapshot, and the number of times the _previous_ snapshot was used.  A
+         * negative value for used signals an invalid value, which is the case the first
+         * time a snapshot is every built.
+         */
+        private void rebuild(int duration, int used,
+                int buildBin, int useBin, boolean big, boolean quick) {
+            mTotalBuilds++;
+            mTimes[buildBin]++;
+
+            if (used >= 0) {
+                mTotalUsed += used;
+                mUsed[useBin]++;
+            }
+
+            mTotalTimeUs += duration;
+            boolean reportIt = false;
+
+            if (big) {
+                mBigBuilds++;
+            }
+            if (quick) {
+                mShortLived++;
+            }
+            if (mMaxBuildTimeUs < duration) {
+                mMaxBuildTimeUs = duration;
+            }
+        }
+
+        private Stats(long now) {
+            mStartTimeUs = now;
+            mTimes = new int[mTimeBins.count()];
+            mUsed = new int[mUseBins.count()];
+        }
+
+        /**
+         * Create a copy of the argument.  The copy is made under lock but can then be
+         * used without holding the lock.
+         */
+        private Stats(Stats orig) {
+            mStartTimeUs = orig.mStartTimeUs;
+            mStopTimeUs = orig.mStopTimeUs;
+            mTimes = Arrays.copyOf(orig.mTimes, orig.mTimes.length);
+            mUsed = Arrays.copyOf(orig.mUsed, orig.mUsed.length);
+            mTotalBuilds = orig.mTotalBuilds;
+            mTotalUsed = orig.mTotalUsed;
+            mBigBuilds = orig.mBigBuilds;
+            mShortLived = orig.mShortLived;
+            mTotalTimeUs = orig.mTotalTimeUs;
+            mMaxBuildTimeUs = orig.mMaxBuildTimeUs;
+        }
+
+        /**
+         * Set the end time for the statistics.  The end time is used only for reporting
+         * in the dump() method.
+         */
+        private void complete(long stop) {
+            mStopTimeUs = stop;
+        }
+
+        /**
+         * Format a time span into ddd:HH:MM:SS.  The input is in us.
+         */
+        private String durationToString(long us) {
+            // s has a range of several years
+            int s = (int) (us / (1000 * 1000));
+            int m = s / 60;
+            s %= 60;
+            int h = m / 60;
+            m %= 60;
+            int d = h / 24;
+            h %= 24;
+            if (d != 0) {
+                return TextUtils.formatSimple("%2d:%02d:%02d:%02d", d, h, m, s);
+            } else if (h != 0) {
+                return TextUtils.formatSimple("%2s %02d:%02d:%02d", "", h, m, s);
+            } else {
+                return TextUtils.formatSimple("%2s %2s %2d:%02d", "", "", m, s);
+            }
+        }
+
+        /**
+         * Print the prefix for dumping.  This does not generate a line to the output.
+         */
+        private void dumpPrefix(PrintWriter pw, String indent, long now, boolean header,
+                                String title) {
+            pw.print(indent + " ");
+            if (header) {
+                pw.format(Locale.US, "%-23s", title);
+            } else {
+                pw.format(Locale.US, "%11s", durationToString(now - mStartTimeUs));
+                if (mStopTimeUs != 0) {
+                    pw.format(Locale.US, " %11s", durationToString(now - mStopTimeUs));
+                } else {
+                    pw.format(Locale.US, " %11s", "now");
+                }
+            }
+        }
+
+        /**
+         * Dump the summary statistics record.  Choose the header or the data.
+         *    number of builds
+         *    number of uses
+         *    number of big builds
+         *    number of short lifetimes
+         *    cumulative build time, in seconds
+         *    maximum build time, in ms
+         */
+        private void dumpStats(PrintWriter pw, String indent, long now, boolean header) {
+            dumpPrefix(pw, indent, now, header, "Summary stats");
+            if (header) {
+                pw.format(Locale.US, "  %10s  %10s  %10s  %10s  %10s  %10s",
+                          "TotBlds", "TotUsed", "BigBlds", "ShortLvd",
+                          "TotTime", "MaxTime");
+            } else {
+                pw.format(Locale.US,
+                        "  %10d  %10d  %10d  %10d  %10d  %10d",
+                        mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived,
+                        mTotalTimeUs / 1000, mMaxBuildTimeUs / 1000);
+            }
+            pw.println();
+        }
+
+        /**
+         * Dump the build time histogram.  Choose the header or the data.
+         */
+        private void dumpTimes(PrintWriter pw, String indent, long now, boolean header) {
+            dumpPrefix(pw, indent, now, header, "Build times");
+            if (header) {
+                int[] keys = mTimeBins.userKeys();
+                for (int i = 0; i < keys.length; i++) {
+                    pw.format(Locale.US, "  %10s",
+                            TextUtils.formatSimple("<= %dms", keys[i]));
+                }
+                pw.format(Locale.US, "  %10s",
+                        TextUtils.formatSimple("> %dms", keys[keys.length - 1]));
+            } else {
+                for (int i = 0; i < mTimes.length; i++) {
+                    pw.format(Locale.US, "  %10d", mTimes[i]);
+                }
+            }
+            pw.println();
+        }
+
+        /**
+         * Dump the usage histogram.  Choose the header or the data.
+         */
+        private void dumpUsage(PrintWriter pw, String indent, long now, boolean header) {
+            dumpPrefix(pw, indent, now, header, "Use counters");
+            if (header) {
+                int[] keys = mUseBins.userKeys();
+                for (int i = 0; i < keys.length; i++) {
+                    pw.format(Locale.US, "  %10s", TextUtils.formatSimple("<= %d", keys[i]));
+                }
+                pw.format(Locale.US, "  %10s",
+                        TextUtils.formatSimple("> %d", keys[keys.length - 1]));
+            } else {
+                for (int i = 0; i < mUsed.length; i++) {
+                    pw.format(Locale.US, "  %10d", mUsed[i]);
+                }
+            }
+            pw.println();
+        }
+
+        /**
+         * Dump something, based on the "what" parameter.
+         */
+        private void dump(PrintWriter pw, String indent, long now, boolean header, String what) {
+            if (what.equals("stats")) {
+                dumpStats(pw, indent, now, header);
+            } else if (what.equals("times")) {
+                dumpTimes(pw, indent, now, header);
+            } else if (what.equals("usage")) {
+                dumpUsage(pw, indent, now, header);
+            } else {
+                throw new IllegalArgumentException("unrecognized choice: " + what);
+            }
+        }
+
+        /**
+         * Report the object via an event.  Presumably the record indicates an anomalous
+         * incident.
+         */
+        private void report() {
+            EventLogTags.writePmSnapshotStats(
+                    mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived,
+                    mMaxBuildTimeUs / US_IN_MS, mTotalTimeUs / US_IN_MS);
+        }
+    }
+
+    /**
+     * Long statistics.  These roll over approximately every week.
+     */
+    private Stats[] mLong;
+
+    /**
+     * Short statistics.  These roll over approximately every minute;
+     */
+    private Stats[] mShort;
+
+    /**
+     * The time of the last build.  This can be used to compute the length of time a
+     * snapshot existed before being replaced.
+     */
+    private long mLastBuildTime = 0;
+
+    /**
+     * Create a snapshot object.  Initialize the bin levels.  The last bin catches
+     * everything that is not caught earlier, so its value is not really important.
+     */
+    public SnapshotStatistics() {
+        // Create the bin thresholds.  The time bins are in units of us.
+        mTimeBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 });
+        mUseBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 });
+
+        // Create the raw statistics
+        final long now = SystemClock.currentTimeMicro();
+        mLong = new Stats[2];
+        mLong[0] = new Stats(now);
+        mShort = new Stats[10];
+        mShort[0] = new Stats(now);
+
+        // Create the message handler for ticks and start the ticker.
+        mHandler = new Handler(Looper.getMainLooper()) {
+                @Override
+                public void handleMessage(Message msg) {
+                    SnapshotStatistics.this.handleMessage(msg);
+                }
+            };
+        scheduleTick();
+    }
+
+    /**
+     * Handle a message.  The only messages are ticks, so the message parameter is ignored.
+     */
+    private void handleMessage(@Nullable Message msg) {
+        tick();
+        scheduleTick();
+    }
+
+    /**
+     * Schedule one tick, a tick interval in the future.
+     */
+    private void scheduleTick() {
+        mHandler.sendEmptyMessageDelayed(0, SNAPSHOT_TICK_INTERVAL_MS);
+    }
+
+    /**
+     * Record a rebuild.  Cumulative and current statistics are updated.  Events may be
+     * generated.
+     * @param now The time at which the snapshot rebuild began, in ns.
+     * @param done The time at which the snapshot rebuild completed, in ns.
+     * @param hits The number of times the previous snapshot was used.
+     */
+    public void rebuild(long now, long done, int hits) {
+        // The duration has a span of about 2000s
+        final int duration = (int) (done - now);
+        boolean reportEvent = false;
+        synchronized (mLock) {
+            mLastBuildTime = now;
+
+            final int timeBin = mTimeBins.getBin(duration / 1000);
+            final int useBin = mUseBins.getBin(hits);
+            final boolean big = duration >= SNAPSHOT_BIG_BUILD_TIME_US;
+            final boolean quick = hits <= SNAPSHOT_SHORT_LIFETIME;
+
+            mShort[0].rebuild(duration, hits, timeBin, useBin, big, quick);
+            mLong[0].rebuild(duration, hits, timeBin, useBin, big, quick);
+            if (duration >= SNAPSHOT_REPORTABLE_BUILD_TIME_US) {
+                if (mEventsReported++ < SNAPSHOT_BUILD_REPORT_LIMIT) {
+                    reportEvent = true;
+                }
+            }
+        }
+        // The IO to the logger is done outside the lock.
+        if (reportEvent) {
+            // Report the first N big builds, and every new maximum after that.
+            EventLogTags.writePmSnapshotRebuild(duration / US_IN_MS, hits);
+        }
+    }
+
+    /**
+     * Roll a stats array.  Shift the elements up an index and create a new element at
+     * index zero.  The old element zero is completed with the specified time.
+     */
+    private void shift(Stats[] s, long now) {
+        s[0].complete(now);
+        for (int i = s.length - 1; i > 0; i--) {
+            s[i] = s[i - 1];
+        }
+        s[0] = new Stats(now);
+    }
+
+    /**
+     * Roll the statistics.
+     * <ul>
+     * <li> Roll the quick statistics immediately.
+     * <li> Roll the long statistics every SNAPSHOT_LONG_TICKER ticks.  The long
+     * statistics hold a week's worth of data.
+     * <li> Roll the logging statistics every SNAPSHOT_LOGGING_TICKER ticks.  The logging
+     * statistics hold 10 minutes worth of data.
+     * </ul>
+     */
+    private void tick() {
+        synchronized (mLock) {
+            long now = SystemClock.currentTimeMicro();
+            mTicks++;
+            if (mTicks % SNAPSHOT_LONG_TICKS == 0) {
+                shift(mLong, now);
+            }
+            shift(mShort, now);
+            mEventsReported = 0;
+        }
+    }
+
+    /**
+     * Dump the statistics.  The header is dumped from l[0], so that must not be null.
+     */
+    private void dump(PrintWriter pw, String indent, long now, Stats[] l, Stats[] s, String what) {
+        l[0].dump(pw, indent, now, true, what);
+        for (int i = 0; i < s.length; i++) {
+            if (s[i] != null) {
+                s[i].dump(pw, indent, now, false, what);
+            }
+        }
+        for (int i = 0; i < l.length; i++) {
+            if (l[i] != null) {
+                l[i].dump(pw, indent, now, false, what);
+            }
+        }
+    }
+
+    /**
+     * Dump the statistics.  The format is compatible with the PackageManager dumpsys
+     * output.
+     */
+    public void dump(PrintWriter pw, String indent, long now, int unrecorded, boolean full) {
+        // Grab the raw statistics under lock, but print them outside of the lock.
+        Stats[] l;
+        Stats[] s;
+        synchronized (mLock) {
+            l = Arrays.copyOf(mLong, mLong.length);
+            l[0] = new Stats(l[0]);
+            s = Arrays.copyOf(mShort, mShort.length);
+            s[0] = new Stats(s[0]);
+        }
+        pw.format(Locale.US, "%s Unrecorded hits %d", indent, unrecorded);
+        pw.println();
+        dump(pw, indent, now, l, s, "stats");
+        if (!full) {
+            return;
+        }
+        pw.println();
+        dump(pw, indent, now, l, s, "times");
+        pw.println();
+        dump(pw, indent, now, l, s, "usage");
+    }
+}
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index e913829..81cfbf7 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -85,6 +85,15 @@
           "include-annotation": "android.platform.test.annotations.Presubmit"
         }
       ]
+    },
+    {
+      "name": "PackageManagerServiceHostTests",
+      "file_patterns": ["AppsFilter\\.java"],
+      "options": [
+        {
+          "include-filter": "com.android.server.pm.test.OverlayActorVisibilityTest"
+        }
+      ]
     }
   ],
   "postsubmit": [
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
index bf2b3c7..a8a6a72 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
@@ -24,6 +24,7 @@
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedIntentInfo;
 import android.os.Build;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Patterns;
 
@@ -32,7 +33,6 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.util.List;
-import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -251,6 +251,10 @@
      * improve the reliability of any legacy verifiers.
      */
     private boolean isValidHost(String host) {
+        if (TextUtils.isEmpty(host)) {
+            return false;
+        }
+
         mDomainMatcher.reset(host);
         return mDomainMatcher.matches();
     }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
index 44ff3eb..246810f 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -22,6 +22,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.text.TextUtils;
+import android.util.Patterns;
 
 import com.android.internal.util.CollectionUtils;
 import com.android.server.compat.PlatformCompat;
@@ -29,9 +31,13 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.util.Set;
+import java.util.regex.Matcher;
 
 public final class DomainVerificationUtils {
 
+    private static final ThreadLocal<Matcher> sCachedMatcher = ThreadLocal.withInitial(
+            () -> Patterns.DOMAIN_NAME.matcher(""));
+
     /**
      * Consolidates package exception messages. A generic unavailable message is included since the
      * caller doesn't bother to check why the package isn't available.
@@ -48,6 +54,15 @@
             return false;
         }
 
+        String host = intent.getData().getHost();
+        if (TextUtils.isEmpty(host)) {
+            return false;
+        }
+
+        if (!sCachedMatcher.get().reset(host).matches()) {
+            return false;
+        }
+
         Set<String> categories = intent.getCategories();
         int categoriesSize = CollectionUtils.size(categories);
         if (categoriesSize > 2) {
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 20c1c3c..14cab38 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -120,6 +120,7 @@
 
     @Override
     public boolean updateConfiguration(TimeConfiguration timeConfiguration) {
+        enforceManageTimeDetectorPermission();
         // TODO(b/172891783) Add actual logic
         return false;
     }
diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
index a59b368..8818023 100644
--- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
+++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
@@ -119,18 +119,18 @@
         if (!mIsQuitting) {
             mRouteSelectionCallback = new RouteSelectionCallback();
             mConnectivityManager.requestBackgroundNetwork(
-                    getRouteSelectionRequest(), mHandler, mRouteSelectionCallback);
+                    getRouteSelectionRequest(), mRouteSelectionCallback, mHandler);
 
             mWifiBringupCallback = new NetworkBringupCallback();
             mConnectivityManager.requestBackgroundNetwork(
-                    getWifiNetworkRequest(), mHandler, mWifiBringupCallback);
+                    getWifiNetworkRequest(), mWifiBringupCallback, mHandler);
 
             for (final int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
                 final NetworkBringupCallback cb = new NetworkBringupCallback();
                 mCellBringupCallbacks.add(cb);
 
                 mConnectivityManager.requestBackgroundNetwork(
-                        getCellNetworkRequestForSubId(subId), mHandler, cb);
+                        getCellNetworkRequestForSubId(subId), cb, mHandler);
             }
         } else {
             mRouteSelectionCallback = null;
@@ -154,14 +154,14 @@
      * Builds the Route selection request
      *
      * <p>This request is guaranteed to select carrier-owned, non-VCN underlying networks by virtue
-     * of a populated set of subIds as expressed in NetworkCapabilities#getSubIds(). Only carrier
-     * owned networks may be selected, as the request specifies only subIds in the VCN's
+     * of a populated set of subIds as expressed in NetworkCapabilities#getSubscriptionIds(). Only
+     * carrier owned networks may be selected, as the request specifies only subIds in the VCN's
      * subscription group, while the VCN networks are excluded by virtue of not having subIds set on
      * the VCN-exposed networks.
      */
     private NetworkRequest getRouteSelectionRequest() {
         return getBaseNetworkRequestBuilder()
-                .setSubIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
+                .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
                 .build();
     }
 
@@ -177,7 +177,7 @@
     private NetworkRequest getWifiNetworkRequest() {
         return getBaseNetworkRequestBuilder()
                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                .setSubIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
+                .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
                 .build();
     }
 
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 89b7bbd..9acbdcc 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2378,6 +2378,46 @@
         }
     }
 
+    /**
+     * Propagate a wake event to the wallpaper engine.
+     */
+    public void notifyWakingUp(int x, int y, @NonNull Bundle extras) {
+        synchronized (mLock) {
+            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
+            data.connection.forEachDisplayConnector(
+                    displayConnector -> {
+                        if (displayConnector.mEngine != null) {
+                            try {
+                                displayConnector.mEngine.dispatchWallpaperCommand(
+                                        WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
+                            } catch (RemoteException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                    });
+        }
+    }
+
+    /**
+     * Propagate a sleep event to the wallpaper engine.
+     */
+    public void notifyGoingToSleep(int x, int y, @NonNull Bundle extras) {
+        synchronized (mLock) {
+            final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
+            data.connection.forEachDisplayConnector(
+                    displayConnector -> {
+                        if (displayConnector.mEngine != null) {
+                            try {
+                                displayConnector.mEngine.dispatchWallpaperCommand(
+                                        WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1, extras);
+                            } catch (RemoteException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                    });
+        }
+    }
+
     @Override
     public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
         checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index e4dc8c2..f021072 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -96,6 +96,8 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
+import com.android.server.apphibernation.AppHibernationManagerInternal;
+import com.android.server.apphibernation.AppHibernationService;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -163,6 +165,9 @@
      */
     private final LaunchObserverRegistryImpl mLaunchObserver;
     @VisibleForTesting static final int LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE = 512;
+    private final ArrayMap<String, Boolean> mLastHibernationStates = new ArrayMap<>();
+    private AppHibernationManagerInternal mAppHibernationManagerInternal;
+    private boolean mIsAppHibernationEnabled;
 
     /**
      * The information created when an intent is incoming but we do not yet know whether it will be
@@ -789,6 +794,27 @@
         }
     }
 
+    @Nullable
+    private AppHibernationManagerInternal getAppHibernationManagerInternal() {
+        if (mAppHibernationManagerInternal == null) {
+            mIsAppHibernationEnabled = AppHibernationService.isAppHibernationEnabled();
+            mAppHibernationManagerInternal =
+                    LocalServices.getService(AppHibernationManagerInternal.class);
+        }
+        return mAppHibernationManagerInternal;
+    }
+
+    /**
+     * Notifies the tracker before the package is unstopped because of launching activity.
+     * @param packageName The package to be unstopped.
+     */
+    void notifyBeforePackageUnstopped(@NonNull String packageName) {
+        final AppHibernationManagerInternal ahmInternal = getAppHibernationManagerInternal();
+        if (ahmInternal != null && mIsAppHibernationEnabled) {
+            mLastHibernationStates.put(packageName, ahmInternal.isHibernatingGlobally(packageName));
+        }
+    }
+
     /**
      * Notifies the tracker that we called immediately before we call bindApplication on the client.
      *
@@ -823,6 +849,8 @@
         }
 
         stopLaunchTrace(info);
+        final Boolean isHibernating =
+                mLastHibernationStates.remove(info.mLastLaunchedActivity.packageName);
         if (abort) {
             mSupervisor.stopWaitingForActivityVisible(info.mLastLaunchedActivity);
             launchObserverNotifyActivityLaunchCancelled(info);
@@ -830,7 +858,7 @@
             if (info.isInterestingToLoggerAndObserver()) {
                 launchObserverNotifyActivityLaunchFinished(info, timestampNs);
             }
-            logAppTransitionFinished(info);
+            logAppTransitionFinished(info, isHibernating != null ? isHibernating : false);
         }
         info.mPendingDrawActivities.clear();
         mTransitionInfoList.remove(info);
@@ -859,7 +887,7 @@
         }
     }
 
-    private void logAppTransitionFinished(@NonNull TransitionInfo info) {
+    private void logAppTransitionFinished(@NonNull TransitionInfo info, boolean isHibernating) {
         if (DEBUG_METRICS) Slog.i(TAG, "logging finished transition " + info);
 
         // Take a snapshot of the transition info before sending it to the handler for logging.
@@ -868,7 +896,7 @@
         if (info.isInterestingToLoggerAndObserver()) {
             BackgroundThread.getHandler().post(() -> logAppTransition(
                     info.mCurrentTransitionDeviceUptime, info.mCurrentTransitionDelayMs,
-                    infoSnapshot));
+                    infoSnapshot, isHibernating));
         }
         BackgroundThread.getHandler().post(() -> logAppDisplayed(infoSnapshot));
         if (info.mPendingFullyDrawn != null) {
@@ -880,7 +908,7 @@
 
     // This gets called on a background thread without holding the activity manager lock.
     private void logAppTransition(int currentTransitionDeviceUptime, int currentTransitionDelayMs,
-            TransitionInfoSnapshot info) {
+            TransitionInfoSnapshot info, boolean isHibernating) {
         final LogMaker builder = new LogMaker(APP_TRANSITION);
         builder.setPackageName(info.packageName);
         builder.setType(info.type);
@@ -933,7 +961,8 @@
                 packageOptimizationInfo.getCompilationReason(),
                 packageOptimizationInfo.getCompilationFilter(),
                 info.sourceType,
-                info.sourceEventDelayMs);
+                info.sourceEventDelayMs,
+                isHibernating);
 
         if (DEBUG_METRICS) {
             Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)",
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f5cfc9d..aa9727a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -189,6 +189,7 @@
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
+import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
@@ -215,14 +216,8 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
-import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
-import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_SOLID_COLOR;
-import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_WALLPAPER;
-import static com.android.server.wm.WindowManagerService.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.letterboxBackgroundTypeToString;
 import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
 import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
 import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM;
@@ -268,7 +263,6 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -344,7 +338,6 @@
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.Task.ActivityState;
 import com.android.server.wm.WindowManagerService.H;
-import com.android.server.wm.WindowManagerService.LetterboxBackgroundType;
 import com.android.server.wm.utils.InsetUtils;
 
 import com.google.android.collect.Sets;
@@ -587,7 +580,10 @@
 
     AnimatingActivityRegistry mAnimatingActivityRegistry;
 
-    private Task mLastParent;
+    // Set to the previous Task parent of the ActivityRecord when it is reparented to a new Task
+    // due to picture-in-picture. This gets cleared whenever this activity or the Task
+    // it references to gets removed. This should also be cleared when we move out of pip.
+    private Task mLastParentBeforePip;
 
     boolean firstWindowDrawn;
     /** Whether the visible window(s) of this activity is drawn. */
@@ -642,7 +638,8 @@
      */
     private boolean mWillCloseOrEnterPip;
 
-    private Letterbox mLetterbox;
+    @VisibleForTesting
+    final LetterboxUiController mLetterboxUiController;
 
     /**
      * The scale to fit at least one side of the activity to its parent. If the activity uses
@@ -671,8 +668,6 @@
     @Nullable
     private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
 
-    private boolean mShowWallpaperForLetterboxBackground;
-
     // activity is not displayed?
     // TODO: rename to mNoDisplay
     @VisibleForTesting
@@ -1096,60 +1091,11 @@
                 pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges));
             }
         }
-
-        dumpLetterboxInfo(pw, prefix);
-    }
-
-    private void dumpLetterboxInfo(PrintWriter pw, String prefix) {
-        final WindowState mainWin = findMainWindow();
-        if (mainWin == null) {
-            return;
+        if (mLastParentBeforePip != null) {
+            pw.println(prefix + "lastParentTaskIdBeforePip=" + mLastParentBeforePip.mTaskId);
         }
 
-        boolean areBoundsLetterboxed = mainWin.isLetterboxedAppWindow();
-        pw.println(prefix + "areBoundsLetterboxed=" + areBoundsLetterboxed);
-        if (!areBoundsLetterboxed) {
-            return;
-        }
-
-        pw.println(prefix + "  letterboxReason=" + getLetterboxReasonString(mainWin));
-        pw.println(prefix + "  letterboxAspectRatio=" + computeAspectRatio(getBounds()));
-
-        boolean isLetterboxUiShown = isLetterboxed(mainWin);
-        pw.println(prefix + "isLetterboxUiShown=" + isLetterboxUiShown);
-
-        if (!isLetterboxUiShown) {
-            return;
-        }
-        pw.println(prefix + "  letterboxBackgroundColor=" + Integer.toHexString(
-                getLetterboxBackgroundColor().toArgb()));
-        pw.println(prefix + "  letterboxBackgroundType="
-                + letterboxBackgroundTypeToString(mWmService.getLetterboxBackgroundType()));
-        if (mWmService.getLetterboxBackgroundType() == LETTERBOX_BACKGROUND_WALLPAPER) {
-            pw.println(prefix + "  isLetterboxWallpaperBlurSupported="
-                    + isLetterboxWallpaperBlurSupported());
-            pw.println(prefix + "  letterboxBackgroundWallpaperDarkScrimAlpha="
-                    + getLetterboxWallpaperDarkScrimAlpha());
-            pw.println(prefix + "  letterboxBackgroundWallpaperBlurRadius="
-                    + getLetterboxWallpaperBlurRadius());
-        }
-    }
-
-    /**
-     * Returns a string representing the reason for letterboxing. This method assumes the activity
-     * is letterboxed.
-     */
-    private String getLetterboxReasonString(WindowState mainWin) {
-        if (inSizeCompatMode()) {
-            return "SIZE_COMPAT_MODE";
-        }
-        if (isLetterboxedForFixedOrientationAndAspectRatio()) {
-            return "FIXED_ORIENTATION";
-        }
-        if (mainWin.isLetterboxedForDisplayCutout()) {
-            return "DISPLAY_CUTOUT";
-        }
-        return "UNKNOWN_REASON";
+        mLetterboxUiController.dump(pw, prefix);
     }
 
     void setAppTimeTracker(AppTimeTracker att) {
@@ -1349,7 +1295,7 @@
             if (getDisplayContent() != null) {
                 getDisplayContent().mClosingApps.remove(this);
             }
-        } else if (mLastParent != null && mLastParent.getRootTask() != null) {
+        } else if (oldTask != null && oldTask.getRootTask() != null) {
             task.getRootTask().mExitingActivities.remove(this);
         }
         final Task rootTask = getRootTask();
@@ -1362,7 +1308,10 @@
                 ? rootTask.getAnimatingActivityRegistry()
                 : null;
 
-        mLastParent = task;
+        if (task == mLastParentBeforePip) {
+            // Activity's reparented back from pip, clear the links once established
+            clearLastParentBeforePip();
+        }
 
         updateColorTransform();
 
@@ -1381,6 +1330,26 @@
         }
     }
 
+    /**
+     * Sets {@link #mLastParentBeforePip} to the current parent Task, it's caller's job to ensure
+     * {@link #getTask()} is set before this is called.
+     */
+    void setLastParentBeforePip() {
+        mLastParentBeforePip = getTask();
+        mLastParentBeforePip.mChildPipActivity = this;
+    }
+
+    private void clearLastParentBeforePip() {
+        if (mLastParentBeforePip != null) {
+            mLastParentBeforePip.mChildPipActivity = null;
+            mLastParentBeforePip = null;
+        }
+    }
+
+    @Nullable Task getLastParentBeforePip() {
+        return mLastParentBeforePip;
+    }
+
     private void updateColorTransform() {
         if (mSurfaceControl != null && mLastAppSaturationInfo != null) {
             getPendingTransaction().setColorTransform(mSurfaceControl,
@@ -1415,183 +1384,29 @@
             }
         }
 
-        if (mLetterbox != null) {
-            mLetterbox.onMovedToDisplay(mDisplayContent.getDisplayId());
-        }
+        mLetterboxUiController.onMovedToDisplay(mDisplayContent.getDisplayId());
     }
 
-    // TODO(b/183754168): Move letterbox UI logic into a separate class.
     void layoutLetterbox(WindowState winHint) {
-        final WindowState w = findMainWindow();
-        if (w == null || winHint != null && w != winHint) {
-            return;
-        }
-        final boolean surfaceReady = w.isDrawn()  // Regular case
-                || w.isDragResizeChanged();  // Waiting for relayoutWindow to call preserveSurface.
-        final boolean needsLetterbox = surfaceReady && isLetterboxed(w);
-        updateRoundedCorners(w);
-        updateWallpaperForLetterbox(w);
-        if (needsLetterbox) {
-            if (mLetterbox == null) {
-                mLetterbox = new Letterbox(() -> makeChildSurface(null),
-                        mWmService.mTransactionFactory,
-                        mWmService::isLetterboxActivityCornersRounded,
-                        this::getLetterboxBackgroundColor,
-                        this::hasWallpaperBackgroudForLetterbox,
-                        this::getLetterboxWallpaperBlurRadius,
-                        this::getLetterboxWallpaperDarkScrimAlpha);
-                mLetterbox.attachInput(w);
-            }
-            getPosition(mTmpPoint);
-            // Get the bounds of the "space-to-fill". The transformed bounds have the highest
-            // priority because the activity is launched in a rotated environment. In multi-window
-            // mode, the task-level represents this. In fullscreen-mode, the task container does
-            // (since the orientation letterbox is also applied to the task).
-            final Rect transformedBounds = getFixedRotationTransformDisplayBounds();
-            final Rect spaceToFill = transformedBounds != null
-                    ? transformedBounds
-                    : inMultiWindowMode()
-                            ? getRootTask().getBounds()
-                            : getRootTask().getParent().getBounds();
-            mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint);
-        } else if (mLetterbox != null) {
-            mLetterbox.hide();
-        }
-    }
-
-    private Color getLetterboxBackgroundColor() {
-        final WindowState w = findMainWindow();
-        if (w == null || w.isLetterboxedForDisplayCutout()) {
-            return Color.valueOf(Color.BLACK);
-        }
-        @LetterboxBackgroundType int letterboxBackgroundType =
-                mWmService.getLetterboxBackgroundType();
-        switch (letterboxBackgroundType) {
-            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING:
-                if (taskDescription != null && taskDescription.getBackgroundColorFloating() != 0) {
-                    return Color.valueOf(taskDescription.getBackgroundColorFloating());
-                }
-                break;
-            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND:
-                if (taskDescription != null && taskDescription.getBackgroundColor() != 0) {
-                    return Color.valueOf(taskDescription.getBackgroundColor());
-                }
-                break;
-            case LETTERBOX_BACKGROUND_WALLPAPER:
-                if (hasWallpaperBackgroudForLetterbox()) {
-                    // Color is used for translucent scrim that dims wallpaper.
-                    return Color.valueOf(Color.BLACK);
-                }
-                Slog.w(TAG, "Wallpaper option is selected for letterbox background but "
-                        + "blur is not supported by a device or not supported in the current "
-                        + "window configuration or both alpha scrim and blur radius aren't "
-                        + "provided so using solid color background");
-                break;
-            case LETTERBOX_BACKGROUND_SOLID_COLOR:
-                return mWmService.getLetterboxBackgroundColor();
-            default:
-                throw new AssertionError(
-                    "Unexpected letterbox background type: " + letterboxBackgroundType);
-        }
-        // If picked option configured incorrectly or not supported then default to a solid color
-        // background.
-        return mWmService.getLetterboxBackgroundColor();
-    }
-
-    /**
-     * @return {@code true} when the main window is letterboxed, this activity isn't transparent
-     * and doesn't show a wallpaper.
-     */
-    @VisibleForTesting
-    boolean isLetterboxed(WindowState mainWindow) {
-        return mainWindow.isLetterboxedAppWindow() && fillsParent()
-                // Check for FLAG_SHOW_WALLPAPER explicitly instead of using
-                // WindowContainer#showWallpaper because the later will return true when this
-                // activity is using blurred wallpaper for letterbox backgroud.
-                && (mainWindow.mAttrs.flags & FLAG_SHOW_WALLPAPER) == 0;
-    }
-
-    private void updateRoundedCorners(WindowState mainWindow) {
-        int cornersRadius =
-                // Don't round corners if letterboxed only for display cutout.
-                isLetterboxed(mainWindow) && !mainWindow.isLetterboxedForDisplayCutout()
-                        ? Math.max(0, mWmService.getLetterboxActivityCornersRadius()) : 0;
-        setCornersRadius(mainWindow, cornersRadius);
-    }
-
-    private void setCornersRadius(WindowState mainWindow, int cornersRadius) {
-        final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface();
-        if (windowSurface != null && windowSurface.isValid()) {
-            Transaction transaction = getSyncTransaction();
-            transaction.setCornerRadius(windowSurface, cornersRadius);
-        }
+        mLetterboxUiController.layoutLetterbox(winHint);
     }
 
     boolean hasWallpaperBackgroudForLetterbox() {
-        return mShowWallpaperForLetterboxBackground;
-    }
-
-    private void updateWallpaperForLetterbox(WindowState mainWindow) {
-        @LetterboxBackgroundType int letterboxBackgroundType =
-                mWmService.getLetterboxBackgroundType();
-        boolean wallpaperShouldBeShown =
-                letterboxBackgroundType == LETTERBOX_BACKGROUND_WALLPAPER
-                        && isLetterboxed(mainWindow)
-                        // Don't use wallpaper as a background if letterboxed for display cutout.
-                        && !mainWindow.isLetterboxedForDisplayCutout()
-                        // Check that dark scrim alpha or blur radius are provided
-                        && (getLetterboxWallpaperBlurRadius() > 0
-                                || getLetterboxWallpaperDarkScrimAlpha() > 0)
-                        // Check that blur is supported by a device if blur radius is provided.
-                        && (getLetterboxWallpaperBlurRadius() <= 0
-                                || isLetterboxWallpaperBlurSupported());
-        if (mShowWallpaperForLetterboxBackground != wallpaperShouldBeShown) {
-            mShowWallpaperForLetterboxBackground = wallpaperShouldBeShown;
-            requestUpdateWallpaperIfNeeded();
-        }
-    }
-
-    private int getLetterboxWallpaperBlurRadius() {
-        int blurRadius = mWmService.getLetterboxBackgroundWallpaperBlurRadius();
-        return blurRadius < 0 ? 0 : blurRadius;
-    }
-
-    private float getLetterboxWallpaperDarkScrimAlpha() {
-        float alpha = mWmService.getLetterboxBackgroundWallpaperDarkScrimAlpha();
-        // No scrim by default.
-        return (alpha < 0 || alpha >= 1) ? 0.0f : alpha;
-    }
-
-    private boolean isLetterboxWallpaperBlurSupported() {
-        return mWmService.mContext.getSystemService(WindowManager.class).isCrossWindowBlurEnabled();
+        return mLetterboxUiController.hasWallpaperBackgroudForLetterbox();
     }
 
     void updateLetterboxSurface(WindowState winHint) {
-        final WindowState w = findMainWindow();
-        if (w != winHint && winHint != null && w != null) {
-            return;
-        }
-        layoutLetterbox(winHint);
-        if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) {
-            mLetterbox.applySurfaceChanges(getSyncTransaction());
-        }
+        mLetterboxUiController.updateLetterboxSurface(winHint);
     }
 
+    /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */
     Rect getLetterboxInsets() {
-        if (mLetterbox != null) {
-            return mLetterbox.getInsets();
-        } else {
-            return new Rect();
-        }
+        return mLetterboxUiController.getLetterboxInsets();
     }
 
     /** Gets the inner bounds of letterbox. The bounds will be empty if there is no letterbox. */
     void getLetterboxInnerBounds(Rect outBounds) {
-        if (mLetterbox != null) {
-            outBounds.set(mLetterbox.getInnerFrame());
-        } else {
-            outBounds.setEmpty();
-        }
+        mLetterboxUiController.getLetterboxInnerBounds(outBounds);
     }
 
     /**
@@ -1599,7 +1414,7 @@
      *     when the current activity is displayed.
      */
     boolean isFullyTransparentBarAllowed(Rect rect) {
-        return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect);
+        return mLetterboxUiController.isFullyTransparentBarAllowed(rect);
     }
 
     /**
@@ -1607,7 +1422,7 @@
      * the given {@code rect}.
      */
     boolean isLetterboxOverlappingWith(Rect rect) {
-        return mLetterbox != null && mLetterbox.isOverlappingWith(rect);
+        return mLetterboxUiController.isLetterboxOverlappingWith(rect);
     }
 
     static class Token extends IApplicationToken.Stub {
@@ -1856,6 +1671,9 @@
 
         mPersistentState = persistentState;
         taskDescription = _taskDescription;
+
+        mLetterboxUiController = new LetterboxUiController(mWmService, this);
+
         if (_createTime > 0) {
             createTime = _createTime;
         }
@@ -2140,7 +1958,6 @@
             ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Add starting %s: startingData=%s",
                     this, startingData);
 
-
             WindowManagerPolicy.StartingSurface surface = null;
             try {
                 surface = startingData.createStartingSurface(ActivityRecord.this);
@@ -3434,6 +3251,7 @@
      */
     void cleanUp(boolean cleanServices, boolean setState) {
         task.cleanUpActivityReferences(this);
+        clearLastParentBeforePip();
 
         deferRelaunchUntilPaused = false;
         frozenBeforeDestroy = false;
@@ -3687,10 +3505,8 @@
             dc.setFocusedApp(null);
             mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
         }
-        if (mLetterbox != null) {
-            mLetterbox.destroy();
-            mLetterbox = null;
-        }
+
+        mLetterboxUiController.destroy();
 
         if (!delayed) {
             updateReportedVisibilityLocked();
@@ -7028,11 +6844,13 @@
         // and back which can cause visible issues (see b/184078928).
         final int parentWindowingMode =
                 newParentConfiguration.windowConfiguration.getWindowingMode();
+        final boolean isFixedOrientationLetterboxAllowed =
+                isSplitScreenWindowingMode(parentWindowingMode)
+                        || parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
+                        || parentWindowingMode == WINDOWING_MODE_FULLSCREEN;
         // TODO(b/181207944): Consider removing the if condition and always run
         // resolveFixedOrientationConfiguration() since this should be applied for all cases.
-        if (isSplitScreenWindowingMode(parentWindowingMode)
-                || parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
-                || parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+        if (isFixedOrientationLetterboxAllowed) {
             resolveFixedOrientationConfiguration(newParentConfiguration);
         }
 
@@ -7053,12 +6871,16 @@
             resolveFullscreenConfiguration(newParentConfiguration);
         }
 
+        if (isFixedOrientationLetterboxAllowed || mCompatDisplayInsets != null
+                // In fullscreen, can be letterboxed for aspect ratio.
+                || !inMultiWindowMode()) {
+            updateResolvedBoundsHorizontalPosition(newParentConfiguration);
+        }
+
         if (mVisibleRequested) {
             updateCompatDisplayInsets();
         }
 
-        // TODO(b/175212232): Consolidate position logic from each "resolve" method above here.
-
         // Assign configuration sequence number into hierarchy because there is a different way than
         // ensureActivityConfiguration() in this class that uses configuration in WindowState during
         // layout traversals.
@@ -7089,6 +6911,48 @@
         }
     }
 
+
+    /**
+     * Adjusts horizontal position of resolved bounds if they doesn't fill the parent using gravity
+     * requested in the config or via an ADB command. For more context see {@link
+     * WindowManagerService#getLetterboxHorizontalPositionMultiplier}.
+     */
+    private void updateResolvedBoundsHorizontalPosition(Configuration newParentConfiguration) {
+        final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+        final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+        final Rect screenResolvedBounds =
+                mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds;
+        final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+        final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+        if (resolvedBounds.isEmpty() || parentBounds.width() == screenResolvedBounds.width()) {
+            return;
+        }
+
+        int offsetX = 0;
+        if (screenResolvedBounds.width() >= parentAppBounds.width()) {
+            // If resolved bounds overlap with insets, center within app bounds.
+            offsetX = getHorizontalCenterOffset(
+                    parentAppBounds.width(), screenResolvedBounds.width());
+        } else {
+            float positionMultiplier =
+                    mWmService.mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier();
+            positionMultiplier =
+                    (positionMultiplier < 0.0f || positionMultiplier > 1.0f)
+                            // Default to central position if invalid value is provided.
+                            ? 0.5f : positionMultiplier;
+            offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width())
+                    * positionMultiplier);
+        }
+
+        if (mSizeCompatBounds != null) {
+            mSizeCompatBounds.offset(offsetX, 0 /* offsetY */);
+            final int dx = mSizeCompatBounds.left - resolvedBounds.left;
+            offsetBounds(resolvedConfig, dx,  0 /* offsetY */);
+        } else {
+            offsetBounds(resolvedConfig, offsetX, 0 /* offsetY */);
+        }
+    }
+
     /**
      * Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed
      * orientation then aspect ratio restrictions are also already respected.
@@ -7144,7 +7008,7 @@
         // Override from config_fixedOrientationLetterboxAspectRatio or via ADB with
         // set-fixed-orientation-letterbox-aspect-ratio.
         final float letterboxAspectRatioOverride =
-                mWmService.getFixedOrientationLetterboxAspectRatio();
+                mWmService.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
         aspect = letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
                 ? letterboxAspectRatioOverride : aspect;
 
@@ -7166,7 +7030,10 @@
             resolvedBounds.set(parentBounds.left, top, parentBounds.right, top + height);
         } else {
             final int width = (int) Math.rint(parentHeight / aspect);
-            final int left = parentBounds.centerX() - width / 2;
+            final Rect parentAppBounds = newParentConfig.windowConfiguration.getAppBounds();
+            final int left = width <= parentAppBounds.width()
+                    // Avoid overlapping with the horizontal decor area when possible.
+                    ? parentAppBounds.left : parentBounds.centerX() - width / 2;
             resolvedBounds.set(left, parentBounds.top, left + width, parentBounds.bottom);
         }
 
@@ -7218,12 +7085,6 @@
             task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
                     getFixedRotationTransformDisplayInfo());
         }
-        if (needToBeCentered) {
-            // Offset to center relative to parent's app bounds.
-            final int offsetX = getHorizontalCenterOffset(
-                    parentAppBounds.width(), resolvedBounds.width());
-            offsetBounds(resolvedConfig, offsetX, 0 /* offsetY */);
-        }
     }
 
     /**
@@ -7321,8 +7182,8 @@
 
         final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
 
-        // Calculates the scale and offset to horizontal center the size compatibility bounds into
-        // the region which is available to application.
+        // Calculates the scale the size compatibility bounds into the region which is available
+        // to application.
         final int contentW = resolvedAppBounds.width();
         final int contentH = resolvedAppBounds.height();
         final int viewportW = containerAppBounds.width();
@@ -7330,8 +7191,9 @@
         // Only allow to scale down.
         mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH)
                 ? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH);
-        final int screenTopInset = containerAppBounds.top - containerBounds.top;
-        final boolean topNotAligned = screenTopInset != resolvedAppBounds.top - resolvedBounds.top;
+        final int containerTopInset = containerAppBounds.top - containerBounds.top;
+        final boolean topNotAligned =
+                containerTopInset != resolvedAppBounds.top - resolvedBounds.top;
         if (mSizeCompatScale != 1f || topNotAligned) {
             if (mSizeCompatBounds == null) {
                 mSizeCompatBounds = new Rect();
@@ -7340,18 +7202,15 @@
             mSizeCompatBounds.offsetTo(0, 0);
             mSizeCompatBounds.scale(mSizeCompatScale);
             // The insets are included in height, e.g. the area of real cutout shouldn't be scaled.
-            mSizeCompatBounds.bottom += screenTopInset;
+            mSizeCompatBounds.bottom += containerTopInset;
         } else {
             mSizeCompatBounds = null;
         }
 
-        // Center horizontally in parent (app bounds) and align to top of parent (bounds)
-        // - this is a UX choice.
-        final int offsetX = getHorizontalCenterOffset(
-                (int) viewportW, (int) (contentW * mSizeCompatScale));
+        // Align to top of parent (bounds) - this is a UX choice and exclude the horizontal decor
+        // if needed. Horizontal position is adjusted in updateResolvedBoundsHorizontalPosition.
         // Above coordinates are in "@" space, now place "*" and "#" to screen space.
-        final int screenPosX = (fillContainer
-                ? containerBounds.left : containerAppBounds.left) + offsetX;
+        final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
         final int screenPosY = containerBounds.top;
         if (screenPosX != 0 || screenPosY != 0) {
             if (mSizeCompatBounds != null) {
@@ -7667,7 +7526,7 @@
     /**
      * Returns the aspect ratio of the given {@code rect}.
      */
-    private static float computeAspectRatio(Rect rect) {
+    static float computeAspectRatio(Rect rect) {
         final int width = rect.width();
         final int height = rect.height();
         if (width == 0 || height == 0) {
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
new file mode 100644
index 0000000..eb7087c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -0,0 +1,328 @@
+/*
+ * 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.server.wm;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.graphics.Color;
+
+import com.android.internal.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Reads letterbox configs from resources and controls their overrides at runtime. */
+final class LetterboxConfiguration {
+
+    /**
+     * Override of aspect ratio for fixed orientation letterboxing that is set via ADB with
+     * set-fixed-orientation-letterbox-aspect-ratio or via {@link
+     * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored
+     * if it is <= this value.
+     */
+    static final float MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO = 1.0f;
+
+    /** Enum for Letterbox background type. */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({LETTERBOX_BACKGROUND_SOLID_COLOR, LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND,
+            LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING, LETTERBOX_BACKGROUND_WALLPAPER})
+    @interface LetterboxBackgroundType {};
+    /** Solid background using color specified in R.color.config_letterboxBackgroundColor. */
+    static final int LETTERBOX_BACKGROUND_SOLID_COLOR = 0;
+
+    /** Color specified in R.attr.colorBackground for the letterboxed application. */
+    static final int LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND = 1;
+
+    /** Color specified in R.attr.colorBackgroundFloating for the letterboxed application. */
+    static final int LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING = 2;
+
+    /** Using wallpaper as a background which can be blurred or dimmed with dark scrim. */
+    static final int LETTERBOX_BACKGROUND_WALLPAPER = 3;
+
+    final Context mContext;
+
+    // Aspect ratio of letterbox for fixed orientation, values <=
+    // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored.
+    private float mFixedOrientationLetterboxAspectRatio;
+
+    // Corners radius for activities presented in the letterbox mode, values < 0 will be ignored.
+    private int mLetterboxActivityCornersRadius;
+
+    // Color for {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} letterbox background type.
+    private Color mLetterboxBackgroundColor;
+
+    @LetterboxBackgroundType
+    private int mLetterboxBackgroundType;
+
+    // Blur radius for LETTERBOX_BACKGROUND_WALLPAPER option in mLetterboxBackgroundType.
+    // Values <= 0 are ignored and 0 is used instead.
+    private int mLetterboxBackgroundWallpaperBlurRadius;
+
+    // Alpha of a black scrim shown over wallpaper letterbox background when
+    // LETTERBOX_BACKGROUND_WALLPAPER option is selected for mLetterboxBackgroundType.
+    // Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead.
+    private float mLetterboxBackgroundWallpaperDarkScrimAlpha;
+
+    // Horizontal position of a center of the letterboxed app window. 0 corresponds to the left
+    // side of the screen and 1.0 to the right side.
+    private float mLetterboxHorizontalPositionMultiplier;
+
+    LetterboxConfiguration(Context context) {
+        mContext = context;
+        mFixedOrientationLetterboxAspectRatio = context.getResources().getFloat(
+                R.dimen.config_fixedOrientationLetterboxAspectRatio);
+        mLetterboxActivityCornersRadius = context.getResources().getInteger(
+                R.integer.config_letterboxActivityCornersRadius);
+        mLetterboxBackgroundColor = Color.valueOf(context.getResources().getColor(
+                R.color.config_letterboxBackgroundColor));
+        mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(context);
+        mLetterboxBackgroundWallpaperBlurRadius = context.getResources().getDimensionPixelSize(
+                R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
+        mLetterboxBackgroundWallpaperDarkScrimAlpha = context.getResources().getFloat(
+                R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
+        mLetterboxHorizontalPositionMultiplier = context.getResources().getFloat(
+                R.dimen.config_letterboxHorizontalPositionMultiplier);
+    }
+
+    /**
+     * Overrides the aspect ratio of letterbox for fixed orientation. If given value is <= {@link
+     * #MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO}, both it and a value of {@link
+     * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and
+     * the framework implementation will be used to determine the aspect ratio.
+     */
+    void setFixedOrientationLetterboxAspectRatio(float aspectRatio) {
+        mFixedOrientationLetterboxAspectRatio = aspectRatio;
+    }
+
+    /**
+     * Resets the aspect ratio of letterbox for fixed orientation to {@link
+     * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}.
+     */
+    void resetFixedOrientationLetterboxAspectRatio() {
+        mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
+                com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio);
+    }
+
+    /**
+     * Gets the aspect ratio of letterbox for fixed orientation.
+     */
+    float getFixedOrientationLetterboxAspectRatio() {
+        return mFixedOrientationLetterboxAspectRatio;
+    }
+
+    /**
+     * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0,
+     * both it and a value of {@link
+     * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and
+     * and corners of the activity won't be rounded.
+     */
+    void setLetterboxActivityCornersRadius(int cornersRadius) {
+        mLetterboxActivityCornersRadius = cornersRadius;
+    }
+
+    /**
+     * Resets corners raidus for activities presented in the letterbox mode to {@link
+     * com.android.internal.R.integer.config_letterboxActivityCornersRadius}.
+     */
+    void resetLetterboxActivityCornersRadius() {
+        mLetterboxActivityCornersRadius = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_letterboxActivityCornersRadius);
+    }
+
+    /**
+     * Whether corners of letterboxed activities are rounded.
+     */
+    boolean isLetterboxActivityCornersRounded() {
+        return getLetterboxActivityCornersRadius() > 0;
+    }
+
+    /**
+     * Gets corners raidus for activities presented in the letterbox mode.
+     */
+    int getLetterboxActivityCornersRadius() {
+        return mLetterboxActivityCornersRadius;
+    }
+
+    /**
+     * Gets color of letterbox background which is  used when {@link
+     * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
+     * fallback for other backfround types.
+     */
+    Color getLetterboxBackgroundColor() {
+        return mLetterboxBackgroundColor;
+    }
+
+
+    /**
+     * Sets color of letterbox background which is used when {@link
+     * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
+     * fallback for other backfround types.
+     */
+    void setLetterboxBackgroundColor(Color color) {
+        mLetterboxBackgroundColor = color;
+    }
+
+    /**
+     * Resets color of letterbox background to {@link
+     * com.android.internal.R.color.config_letterboxBackgroundColor}.
+     */
+    void resetLetterboxBackgroundColor() {
+        mLetterboxBackgroundColor = Color.valueOf(mContext.getResources().getColor(
+                com.android.internal.R.color.config_letterboxBackgroundColor));
+    }
+
+    /**
+     * Gets {@link LetterboxBackgroundType} specified in {@link
+     * com.android.internal.R.integer.config_letterboxBackgroundType} or over via ADB command.
+     */
+    @LetterboxBackgroundType
+    int getLetterboxBackgroundType() {
+        return mLetterboxBackgroundType;
+    }
+
+    /** Sets letterbox background type. */
+    void setLetterboxBackgroundType(@LetterboxBackgroundType int backgroundType) {
+        mLetterboxBackgroundType = backgroundType;
+    }
+
+    /**
+     * Resets cletterbox background type to {@link
+     * com.android.internal.R.integer.config_letterboxBackgroundType}.
+     */
+    void resetLetterboxBackgroundType() {
+        mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
+    }
+
+    /** Returns a string representing the given {@link LetterboxBackgroundType}. */
+    static String letterboxBackgroundTypeToString(
+            @LetterboxBackgroundType int backgroundType) {
+        switch (backgroundType) {
+            case LETTERBOX_BACKGROUND_SOLID_COLOR:
+                return "LETTERBOX_BACKGROUND_SOLID_COLOR";
+            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND:
+                return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND";
+            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING:
+                return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING";
+            case LETTERBOX_BACKGROUND_WALLPAPER:
+                return "LETTERBOX_BACKGROUND_WALLPAPER";
+            default:
+                return "unknown=" + backgroundType;
+        }
+    }
+
+    @LetterboxBackgroundType
+    private static int readLetterboxBackgroundTypeFromConfig(Context context) {
+        int backgroundType = context.getResources().getInteger(
+                com.android.internal.R.integer.config_letterboxBackgroundType);
+        return backgroundType == LETTERBOX_BACKGROUND_SOLID_COLOR
+                    || backgroundType == LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND
+                    || backgroundType == LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING
+                    || backgroundType == LETTERBOX_BACKGROUND_WALLPAPER
+                    ? backgroundType : LETTERBOX_BACKGROUND_SOLID_COLOR;
+    }
+
+    /**
+     * Overrides alpha of a black scrim shown over wallpaper for {@link
+     * #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link mLetterboxBackgroundType}.
+     *
+     * <p>If given value is < 0 or >= 1, both it and a value of {@link
+     * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha} are ignored
+     * and 0.0 (transparent) is instead.
+     */
+    void setLetterboxBackgroundWallpaperDarkScrimAlpha(float alpha) {
+        mLetterboxBackgroundWallpaperDarkScrimAlpha = alpha;
+    }
+
+    /**
+     * Resets alpha of a black scrim shown over wallpaper letterbox background to {@link
+     * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha}.
+     */
+    void resetLetterboxBackgroundWallpaperDarkScrimAlpha() {
+        mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat(
+                com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
+    }
+
+    /**
+     * Gets alpha of a black scrim shown over wallpaper letterbox background.
+     */
+    float getLetterboxBackgroundWallpaperDarkScrimAlpha() {
+        return mLetterboxBackgroundWallpaperDarkScrimAlpha;
+    }
+
+    /**
+     * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in
+     * {@link mLetterboxBackgroundType}.
+     *
+     * <p> If given value <= 0, both it and a value of {@link
+     * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius} are ignored
+     * and 0 is used instead.
+     */
+    void setLetterboxBackgroundWallpaperBlurRadius(int radius) {
+        mLetterboxBackgroundWallpaperBlurRadius = radius;
+    }
+
+    /**
+     * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
+     * mLetterboxBackgroundType} to {@link
+     * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius}.
+     */
+    void resetLetterboxBackgroundWallpaperBlurRadius() {
+        mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
+    }
+
+    /**
+     * Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
+     * mLetterboxBackgroundType}.
+     */
+    int getLetterboxBackgroundWallpaperBlurRadius() {
+        return mLetterboxBackgroundWallpaperBlurRadius;
+    }
+
+    /*
+     * Gets horizontal position of a center of the letterboxed app window specified
+     * in {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}
+     * or via an ADB command. 0 corresponds to the left side of the screen and 1 to the
+     * right side.
+     *
+     * <p>This value can be outside of [0, 1] range so clients need to check and default to the
+     * central position (0.5).
+     */
+    float getLetterboxHorizontalPositionMultiplier() {
+        return mLetterboxHorizontalPositionMultiplier;
+    }
+
+    /**
+     * Overrides horizontal position of a center of the letterboxed app window. If given value < 0
+     * or > 1, then it and a value of {@link
+     * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and
+     * central position (0.5) is used.
+     */
+    void setLetterboxHorizontalPositionMultiplier(float multiplier) {
+        mLetterboxHorizontalPositionMultiplier = multiplier;
+    }
+
+    /**
+     * Resets horizontal position of a center of the letterboxed app window to {@link
+     * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}.
+     */
+    void resetLetterboxHorizontalPositionMultiplier() {
+        mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat(
+                com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier);
+    }
+
+}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
new file mode 100644
index 0000000..130f680
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -0,0 +1,336 @@
+/*
+ * 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.server.wm;
+
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager.TaskDescription;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
+
+import java.io.PrintWriter;
+
+/** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */
+// TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in
+// SizeCompatTests and LetterboxTests but not all.
+// TODO(b/185264020): Consider making LetterboxUiController applicable to any level of the
+// hierarchy in addition to ActivityRecord (Task, DisplayArea, ...).
+final class LetterboxUiController {
+
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
+
+    private final Point mTmpPoint = new Point();
+
+    private final LetterboxConfiguration mLetterboxConfiguration;
+    private final ActivityRecord mActivityRecord;
+
+    private boolean mShowWallpaperForLetterboxBackground;
+
+    @Nullable
+    private Letterbox mLetterbox;
+
+    LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
+        mLetterboxConfiguration = wmService.mLetterboxConfiguration;
+        // Given activityRecord may not be fully constructed since LetterboxUiController
+        // 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;
+    }
+
+    /** Cleans up {@link Letterbox} if it exists.*/
+    void destroy() {
+        if (mLetterbox != null) {
+            mLetterbox.destroy();
+            mLetterbox = null;
+        }
+    }
+
+    void onMovedToDisplay(int displayId) {
+        if (mLetterbox != null) {
+            mLetterbox.onMovedToDisplay(displayId);
+        }
+    }
+
+    boolean hasWallpaperBackgroudForLetterbox() {
+        return mShowWallpaperForLetterboxBackground;
+    }
+
+    /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */
+    Rect getLetterboxInsets() {
+        if (mLetterbox != null) {
+            return mLetterbox.getInsets();
+        } else {
+            return new Rect();
+        }
+    }
+
+    /** Gets the inner bounds of letterbox. The bounds will be empty if there is no letterbox. */
+    void getLetterboxInnerBounds(Rect outBounds) {
+        if (mLetterbox != null) {
+            outBounds.set(mLetterbox.getInnerFrame());
+        } else {
+            outBounds.setEmpty();
+        }
+    }
+
+    /**
+     * @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent
+     *     when the current activity is displayed.
+     */
+    boolean isFullyTransparentBarAllowed(Rect rect) {
+        return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect);
+    }
+
+    /**
+     * @return {@code true} if there is a letterbox and any part of that letterbox overlaps with
+     * the given {@code rect}.
+     */
+    boolean isLetterboxOverlappingWith(Rect rect) {
+        return mLetterbox != null && mLetterbox.isOverlappingWith(rect);
+    }
+
+    void updateLetterboxSurface(WindowState winHint) {
+        final WindowState w = mActivityRecord.findMainWindow();
+        if (w != winHint && winHint != null && w != null) {
+            return;
+        }
+        layoutLetterbox(winHint);
+        if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) {
+            mLetterbox.applySurfaceChanges(mActivityRecord.getSyncTransaction());
+        }
+    }
+
+    void layoutLetterbox(WindowState winHint) {
+        final WindowState w = mActivityRecord.findMainWindow();
+        if (w == null || winHint != null && w != winHint) {
+            return;
+        }
+        final boolean surfaceReady = w.isDrawn()  // Regular case
+                || w.isDragResizeChanged();  // Waiting for relayoutWindow to call preserveSurface.
+        final boolean needsLetterbox = surfaceReady && isLetterboxed(w);
+        updateRoundedCorners(w);
+        updateWallpaperForLetterbox(w);
+        if (needsLetterbox) {
+            if (mLetterbox == null) {
+                mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null),
+                        mActivityRecord.mWmService.mTransactionFactory,
+                        mLetterboxConfiguration::isLetterboxActivityCornersRounded,
+                        this::getLetterboxBackgroundColor,
+                        this::hasWallpaperBackgroudForLetterbox,
+                        this::getLetterboxWallpaperBlurRadius,
+                        this::getLetterboxWallpaperDarkScrimAlpha);
+                mLetterbox.attachInput(w);
+            }
+            mActivityRecord.getPosition(mTmpPoint);
+            // Get the bounds of the "space-to-fill". The transformed bounds have the highest
+            // priority because the activity is launched in a rotated environment. In multi-window
+            // mode, the task-level represents this. In fullscreen-mode, the task container does
+            // (since the orientation letterbox is also applied to the task).
+            final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds();
+            final Rect spaceToFill = transformedBounds != null
+                    ? transformedBounds
+                    : mActivityRecord.inMultiWindowMode()
+                            ? mActivityRecord.getRootTask().getBounds()
+                            : mActivityRecord.getRootTask().getParent().getBounds();
+            mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint);
+        } else if (mLetterbox != null) {
+            mLetterbox.hide();
+        }
+    }
+
+    /**
+     * @return {@code true} when the main window is letterboxed, this activity isn't transparent
+     * and doesn't show a wallpaper.
+     */
+    @VisibleForTesting
+    boolean isLetterboxed(WindowState mainWindow) {
+        return mainWindow.isLetterboxedAppWindow() && mActivityRecord.fillsParent()
+                // Check for FLAG_SHOW_WALLPAPER explicitly instead of using
+                // WindowContainer#showWallpaper because the later will return true when this
+                // activity is using blurred wallpaper for letterbox backgroud.
+                && (mainWindow.mAttrs.flags & FLAG_SHOW_WALLPAPER) == 0;
+    }
+
+    private Color getLetterboxBackgroundColor() {
+        final WindowState w = mActivityRecord.findMainWindow();
+        if (w == null || w.isLetterboxedForDisplayCutout()) {
+            return Color.valueOf(Color.BLACK);
+        }
+        @LetterboxBackgroundType int letterboxBackgroundType =
+                mLetterboxConfiguration.getLetterboxBackgroundType();
+        TaskDescription taskDescription = mActivityRecord.taskDescription;
+        switch (letterboxBackgroundType) {
+            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING:
+                if (taskDescription != null && taskDescription.getBackgroundColorFloating() != 0) {
+                    return Color.valueOf(taskDescription.getBackgroundColorFloating());
+                }
+                break;
+            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND:
+                if (taskDescription != null && taskDescription.getBackgroundColor() != 0) {
+                    return Color.valueOf(taskDescription.getBackgroundColor());
+                }
+                break;
+            case LETTERBOX_BACKGROUND_WALLPAPER:
+                if (hasWallpaperBackgroudForLetterbox()) {
+                    // Color is used for translucent scrim that dims wallpaper.
+                    return Color.valueOf(Color.BLACK);
+                }
+                Slog.w(TAG, "Wallpaper option is selected for letterbox background but "
+                        + "blur is not supported by a device or not supported in the current "
+                        + "window configuration or both alpha scrim and blur radius aren't "
+                        + "provided so using solid color background");
+                break;
+            case LETTERBOX_BACKGROUND_SOLID_COLOR:
+                return mLetterboxConfiguration.getLetterboxBackgroundColor();
+            default:
+                throw new AssertionError(
+                    "Unexpected letterbox background type: " + letterboxBackgroundType);
+        }
+        // If picked option configured incorrectly or not supported then default to a solid color
+        // background.
+        return mLetterboxConfiguration.getLetterboxBackgroundColor();
+    }
+
+    private void updateRoundedCorners(WindowState mainWindow) {
+        int cornersRadius =
+                // Don't round corners if letterboxed only for display cutout.
+                isLetterboxed(mainWindow)
+                                && !mainWindow.isLetterboxedForDisplayCutout()
+                        ? Math.max(0, mLetterboxConfiguration.getLetterboxActivityCornersRadius())
+                        : 0;
+        setCornersRadius(mainWindow, cornersRadius);
+    }
+
+    private void setCornersRadius(WindowState mainWindow, int cornersRadius) {
+        final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface();
+        if (windowSurface != null && windowSurface.isValid()) {
+            Transaction transaction = mActivityRecord.getSyncTransaction();
+            transaction.setCornerRadius(windowSurface, cornersRadius);
+        }
+    }
+
+    private void updateWallpaperForLetterbox(WindowState mainWindow) {
+        @LetterboxBackgroundType int letterboxBackgroundType =
+                mLetterboxConfiguration.getLetterboxBackgroundType();
+        boolean wallpaperShouldBeShown =
+                letterboxBackgroundType == LETTERBOX_BACKGROUND_WALLPAPER
+                        && isLetterboxed(mainWindow)
+                        // Don't use wallpaper as a background if letterboxed for display cutout.
+                        && !mainWindow.isLetterboxedForDisplayCutout()
+                        // Check that dark scrim alpha or blur radius are provided
+                        && (getLetterboxWallpaperBlurRadius() > 0
+                                || getLetterboxWallpaperDarkScrimAlpha() > 0)
+                        // Check that blur is supported by a device if blur radius is provided.
+                        && (getLetterboxWallpaperBlurRadius() <= 0
+                                || isLetterboxWallpaperBlurSupported());
+        if (mShowWallpaperForLetterboxBackground != wallpaperShouldBeShown) {
+            mShowWallpaperForLetterboxBackground = wallpaperShouldBeShown;
+            mActivityRecord.requestUpdateWallpaperIfNeeded();
+        }
+    }
+
+    private int getLetterboxWallpaperBlurRadius() {
+        int blurRadius = mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius();
+        return blurRadius < 0 ? 0 : blurRadius;
+    }
+
+    private float getLetterboxWallpaperDarkScrimAlpha() {
+        float alpha = mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha();
+        // No scrim by default.
+        return (alpha < 0 || alpha >= 1) ? 0.0f : alpha;
+    }
+
+    private boolean isLetterboxWallpaperBlurSupported() {
+        return mLetterboxConfiguration.mContext.getSystemService(WindowManager.class)
+                .isCrossWindowBlurEnabled();
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        final WindowState mainWin = mActivityRecord.findMainWindow();
+        if (mainWin == null) {
+            return;
+        }
+
+        boolean areBoundsLetterboxed = mainWin.isLetterboxedAppWindow();
+        pw.println(prefix + "areBoundsLetterboxed=" + areBoundsLetterboxed);
+        if (!areBoundsLetterboxed) {
+            return;
+        }
+
+        pw.println(prefix + "  letterboxReason=" + getLetterboxReasonString(mainWin));
+        pw.println(prefix + "  letterboxAspectRatio="
+                + mActivityRecord.computeAspectRatio(mActivityRecord.getBounds()));
+
+        boolean isLetterboxUiShown = isLetterboxed(mainWin);
+        pw.println(prefix + "isLetterboxUiShown=" + isLetterboxUiShown);
+
+        if (!isLetterboxUiShown) {
+            return;
+        }
+        pw.println(prefix + "  letterboxBackgroundColor=" + Integer.toHexString(
+                getLetterboxBackgroundColor().toArgb()));
+        pw.println(prefix + "  letterboxBackgroundType="
+                + letterboxBackgroundTypeToString(
+                        mLetterboxConfiguration.getLetterboxBackgroundType()));
+        if (mLetterboxConfiguration.getLetterboxBackgroundType()
+                == LETTERBOX_BACKGROUND_WALLPAPER) {
+            pw.println(prefix + "  isLetterboxWallpaperBlurSupported="
+                    + isLetterboxWallpaperBlurSupported());
+            pw.println(prefix + "  letterboxBackgroundWallpaperDarkScrimAlpha="
+                    + getLetterboxWallpaperDarkScrimAlpha());
+            pw.println(prefix + "  letterboxBackgroundWallpaperBlurRadius="
+                    + getLetterboxWallpaperBlurRadius());
+        }
+        pw.println(prefix + "  letterboxHorizontalPositionMultiplier="
+                + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier());
+    }
+
+    /**
+     * Returns a string representing the reason for letterboxing. This method assumes the activity
+     * is letterboxed.
+     */
+    private String getLetterboxReasonString(WindowState mainWin) {
+        if (mActivityRecord.inSizeCompatMode()) {
+            return "SIZE_COMPAT_MODE";
+        }
+        if (mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio()) {
+            return "FIXED_ORIENTATION";
+        }
+        if (mainWin.isLetterboxedForDisplayCutout()) {
+            return "DISPLAY_CUTOUT";
+        }
+        return "UNKNOWN_REASON";
+    }
+
+}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 6631a3e..4bb48b0b 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1415,6 +1415,9 @@
             return true;
         }
 
+        // The given task if always treated as in visible range if it is the origin of pinned task.
+        if (task.mChildPipActivity != null) return true;
+
         if (mMaxNumVisibleTasks >= 0) {
             // Always keep up to the max number of recent tasks, but return false afterwards
             return numVisibleTasks <= mMaxNumVisibleTasks;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 52551ec..b9fc8b1 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2124,6 +2124,8 @@
                         .setDeferTaskAppear(true)
                         .setHasBeenVisible(true)
                         .build();
+                // Establish bi-directional link between the original and pinned task.
+                r.setLastParentBeforePip();
                 // It's possible the task entering PIP is in freeform, so save the last
                 // non-fullscreen bounds. Then when this new PIP task exits PIP, it can restore
                 // to its previous freeform bounds.
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 95a4f69e..fb66c04 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -36,9 +36,11 @@
 import android.animation.ArgbEvaluator;
 import android.content.Context;
 import android.graphics.Color;
+import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.os.Trace;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -210,9 +212,9 @@
             String name = "RotationLayer";
             mScreenshotLayer = displayContent.makeOverlay()
                     .setName(name)
-                    .setBufferSize(mWidth, mHeight)
                     .setSecure(isSecure)
                     .setCallsite("ScreenRotationAnimation")
+                    .setBLASTLayer()
                     .build();
             // This is the way to tell the input system to exclude this surface from occlusion
             // detection since we don't have a window for it. We do this because this window is
@@ -225,32 +227,29 @@
                     .setCallsite("ScreenRotationAnimation")
                     .build();
 
-            final Surface surface = mService.mSurfaceFactory.get();
-            // In case display bounds change, screenshot buffer and surface may mismatch so
-            // set a scaling mode.
-            surface.copyFrom(mScreenshotLayer);
-            surface.setScalingMode(Surface.SCALING_MODE_SCALE_TO_WINDOW);
-
+            HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
                     "ScreenRotationAnimation#getMedianBorderLuma");
-            mStartLuma = RotationAnimationUtils.getMedianBorderLuma(
-                    screenshotBuffer.getHardwareBuffer(), screenshotBuffer.getColorSpace());
+            mStartLuma = RotationAnimationUtils.getMedianBorderLuma(hardwareBuffer,
+                    screenshotBuffer.getColorSpace());
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-            try {
-                surface.attachAndQueueBufferWithColorSpace(screenshotBuffer.getHardwareBuffer(),
-                        screenshotBuffer.getColorSpace());
-            } catch (RuntimeException e) {
-                Slog.w(TAG, "Failed to attach screenshot - " + e.getMessage());
-            }
+
+            GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(
+                    screenshotBuffer.getHardwareBuffer());
+            // Scale the layer to the display size.
+            float dsdx = (float) mWidth / hardwareBuffer.getWidth();
+            float dsdy = (float) mHeight / hardwareBuffer.getHeight();
 
             t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
             t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
             t.setLayer(mBackColorSurface, -1);
             t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
             t.setAlpha(mBackColorSurface, 1);
+            t.setBuffer(mScreenshotLayer, buffer);
+            t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
+            t.setMatrix(mScreenshotLayer, dsdx, 0, 0, dsdy);
             t.show(mScreenshotLayer);
             t.show(mBackColorSurface);
-            surface.destroy();
 
         } catch (OutOfResourcesException e) {
             Slog.w(TAG, "Unable to allocate freeze surface", e);
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index b82a308..c6cd560 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -640,9 +640,16 @@
         }
     }
 
-    void windowAddedLocked(String packageName) {
-        mPackageName = packageName;
-        mRelayoutTag = "relayoutWindow: " + mPackageName;
+    void windowAddedLocked() {
+        if (mPackageName == null) {
+            final WindowProcessController wpc = mService.mAtmService.mProcessMap.getProcess(mPid);
+            if (wpc != null) {
+                mPackageName = wpc.mInfo.packageName;
+                mRelayoutTag = "relayoutWindow: " + mPackageName;
+            } else {
+                Slog.e(TAG_WM, "Unknown process pid=" + mPid);
+            }
+        }
         if (mSurfaceSession == null) {
             if (DEBUG) {
                 Slog.v(TAG_WM, "First window added to " + this + ", creating SurfaceSession");
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 140ae3e..603bfd1 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -58,10 +58,12 @@
                     overrideConfig, displayId);
         }
 
-        final Task task = activity.getTask();
-        if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(task,
-                activity.token, theme)) {
-            return new ShellStartingSurface(task);
+        synchronized (mService.mGlobalLock) {
+            final Task task = activity.getTask();
+            if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(
+                    task, activity.token, theme)) {
+                return new ShellStartingSurface(task);
+            }
         }
         return null;
     }
@@ -124,14 +126,13 @@
                 activity.mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(
                         topFullscreenActivity, false /* checkOpening */);
             }
+            if (DEBUG_ENABLE_SHELL_DRAWER) {
+                mService.mAtmService.mTaskOrganizerController.addStartingWindow(task,
+                        activity.token, 0 /* launchTheme */);
+                return new ShellStartingSurface(task);
+            }
         }
-        if (!DEBUG_ENABLE_SHELL_DRAWER) {
-            return mService.mTaskSnapshotController
-                    .createStartingSurface(activity, taskSnapshot);
-        }
-        mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token,
-                0 /* launchTheme */);
-        return new ShellStartingSurface(task);
+        return mService.mTaskSnapshotController.createStartingSurface(activity, taskSnapshot);
     }
 
 
@@ -144,8 +145,9 @@
 
         @Override
         public void remove(boolean animate) {
-            mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask,
-                    animate);
+            synchronized (mService.mGlobalLock) {
+                mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask, animate);
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java
index 6f434e0..e0a791e 100644
--- a/services/core/java/com/android/server/wm/SurfaceFreezer.java
+++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java
@@ -22,6 +22,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.GraphicBuffer;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
@@ -153,29 +154,24 @@
          */
         Snapshot(Supplier<Surface> surfaceFactory, SurfaceControl.Transaction t,
                 SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer, SurfaceControl parent) {
-            Surface drawSurface = surfaceFactory.get();
             // We can't use a delegating constructor since we need to
             // reference this::onAnimationFinished
-            HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
-            final int width = hardwareBuffer.getWidth();
-            final int height = hardwareBuffer.getHeight();
+            GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
+                    screenshotBuffer.getHardwareBuffer());
 
             mSurfaceControl = mAnimatable.makeAnimationLeash()
                     .setName("snapshot anim: " + mAnimatable.toString())
-                    .setBufferSize(width, height)
                     .setFormat(PixelFormat.TRANSLUCENT)
                     .setParent(parent)
                     .setSecure(screenshotBuffer.containsSecureLayers())
                     .setCallsite("SurfaceFreezer.Snapshot")
+                    .setBLASTLayer()
                     .build();
 
             ProtoLog.i(WM_SHOW_TRANSACTIONS, "  THUMBNAIL %s: CREATE", mSurfaceControl);
 
-            // Transfer the thumbnail to the surface
-            drawSurface.copyFrom(mSurfaceControl);
-            drawSurface.attachAndQueueBufferWithColorSpace(hardwareBuffer,
-                    screenshotBuffer.getColorSpace());
-            drawSurface.release();
+            t.setBuffer(mSurfaceControl, graphicBuffer);
+            t.setColorSpace(mSurfaceControl, screenshotBuffer.getColorSpace());
             t.show(mSurfaceControl);
 
             // We parent the thumbnail to the container, and just place it on top of anything else
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8690499..858d9f3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -131,6 +131,7 @@
 import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER;
 import static com.android.server.wm.TaskProto.DISPLAY_ID;
 import static com.android.server.wm.TaskProto.FILLS_PARENT;
+import static com.android.server.wm.TaskProto.HAS_CHILD_PIP_ACTIVITY;
 import static com.android.server.wm.TaskProto.LAST_NON_FULLSCREEN_BOUNDS;
 import static com.android.server.wm.TaskProto.MIN_HEIGHT;
 import static com.android.server.wm.TaskProto.MIN_WIDTH;
@@ -836,6 +837,14 @@
     // The task will be removed when TaskOrganizer, which is managing the task, is destroyed.
     boolean mRemoveWithTaskOrganizer;
 
+    /**
+     * Reference to the pinned activity that is logically parented to this task, ie.
+     * the previous top activity within this task is put into pinned mode.
+     * This always gets cleared in pair with the ActivityRecord-to-Task link as seen in
+     * {@link ActivityRecord#clearLastParentBeforePip()}.
+     */
+    ActivityRecord mChildPipActivity;
+
     private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
             Intent _affinityIntent, String _affinity, String _rootAffinity,
             ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
@@ -1841,6 +1850,10 @@
 
     /** Completely remove all activities associated with an existing task. */
     void performClearTask(String reason) {
+        // The original task is to be removed, try remove also the pinned task.
+        if (mChildPipActivity != null && mChildPipActivity.getTask() != null) {
+            mTaskSupervisor.removeRootTask(mChildPipActivity.getTask());
+        }
         // Broken down into to cases to avoid object create due to capturing mStack.
         if (getRootTask() == null) {
             forAllActivities((r) -> {
@@ -4449,6 +4462,7 @@
         }
         pw.print(prefix); pw.print("taskId=" + mTaskId);
         pw.println(" rootTaskId=" + getRootTaskId());
+        pw.print(prefix); pw.println("hasChildPipActivity=" + (mChildPipActivity != null));
         pw.print(prefix); pw.print("mHasBeenVisible="); pw.println(getHasBeenVisible());
         pw.print(prefix); pw.print("mResizeMode=");
         pw.print(ActivityInfo.resizeModeToString(mResizeMode));
@@ -5328,7 +5342,6 @@
             return;
         }
         final int currentMode = getWindowingMode();
-        final int currentOverrideMode = getRequestedOverrideWindowingMode();
         final Task topTask = getTopMostTask();
         int windowingMode = preferredWindowingMode;
 
@@ -5397,9 +5410,26 @@
                 mTaskSupervisor.mNoAnimActivities.add(topActivity);
             }
             super.setWindowingMode(windowingMode);
-            // setWindowingMode triggers an onConfigurationChanged cascade which can result in a
-            // different resolved windowing mode (usually when preferredWindowingMode is UNDEFINED).
-            windowingMode = getWindowingMode();
+
+            // Try reparent pinned activity back to its original task after onConfigurationChanged
+            // cascade finishes. This is done on Task level instead of
+            // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit PiP,
+            // we set final windowing mode on the ActivityRecord first and then on its Task when
+            // the exit PiP transition finishes. Meanwhile, the exit transition is always
+            // performed on its original task, reparent immediately in ActivityRecord breaks it.
+            if (currentMode == WINDOWING_MODE_PINNED) {
+                if (topActivity != null && topActivity.getLastParentBeforePip() != null) {
+                    // Do not reparent if the pinned task is in removal, indicated by the
+                    // force hidden flag.
+                    if (!isForceHidden()) {
+                        final Task lastParentBeforePip = topActivity.getLastParentBeforePip();
+                        topActivity.reparent(lastParentBeforePip,
+                                lastParentBeforePip.getChildCount() /* top */,
+                                "movePinnedActivityToOriginalTask");
+                        lastParentBeforePip.moveToFront("movePinnedActivityToOriginalTask");
+                    }
+                }
+            }
 
             if (creating) {
                 // Nothing else to do if we don't have a window container yet. E.g. call from ctor.
@@ -6279,6 +6309,8 @@
         // Launching this app's activity, make sure the app is no longer
         // considered stopped.
         try {
+            mTaskSupervisor.getActivityMetricsLogger()
+                    .notifyBeforePackageUnstopped(next.packageName);
             mAtmService.getPackageManager().setPackageStoppedState(
                     next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */
         } catch (RemoteException e1) {
@@ -7530,7 +7562,11 @@
             final Task task = getBottomMostTask();
             setWindowingMode(WINDOWING_MODE_UNDEFINED);
 
-            getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */);
+            // Task could have been removed from the hierarchy due to windowing mode change
+            // where its only child is reparented back to their original parent task.
+            if (isAttached()) {
+                getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */);
+            }
 
             mTaskSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, this);
         });
@@ -7831,6 +7867,7 @@
 
         proto.write(CREATED_BY_ORGANIZER, mCreatedByOrganizer);
         proto.write(AFFINITY, affinity);
+        proto.write(HAS_CHILD_PIP_ACTIVITY, mChildPipActivity != null);
 
         proto.end(token);
     }
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index dff621c..625cff3 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -439,6 +439,13 @@
             taskDisplayArea = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
         }
 
+        // Re-route to default display if the home activity doesn't support multi-display
+        if (taskDisplayArea != null && activityRecord.isActivityTypeHome()
+                && !mSupervisor.mRootWindowContainer.canStartHomeOnDisplayArea(activityRecord.info,
+                        taskDisplayArea, false /* allowInstrumenting */)) {
+            taskDisplayArea = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
+        }
+
         return (taskDisplayArea != null)
                 ? taskDisplayArea
                 : getFallbackDisplayAreaForActivity(activityRecord, request);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 5ffab48..a467d82 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -646,8 +646,12 @@
         if (shouldDisableSnapshots()) {
             return;
         }
+        final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
+        if (displayContent == null) {
+            return;
+        }
         mTmpTasks.clear();
-        mService.mRoot.getDisplayContent(displayId).forAllTasks(task -> {
+        displayContent.forAllTasks(task -> {
             // Since RecentsAnimation will handle task snapshot while switching apps with the best
             // capture timing (e.g. IME window capture), No need additional task capture while task
             // is controlled by RecentsAnimation.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c80a38f..d14a773 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -159,7 +159,6 @@
 import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
-import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Point;
@@ -977,52 +976,7 @@
     private boolean mAnimationsDisabled = false;
     boolean mPointerLocationEnabled = false;
 
-    /**
-     * Override of aspect ratio for fixed orientation letterboxing that is set via ADB with
-     * set-fixed-orientation-letterbox-aspect-ratio or via {@link
-     * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored
-     * if it is <= this value.
-     */
-    static final float MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO = 1.0f;
-
-    /** Enum for Letterbox background type. */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({LETTERBOX_BACKGROUND_SOLID_COLOR, LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND,
-            LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING, LETTERBOX_BACKGROUND_WALLPAPER})
-    @interface LetterboxBackgroundType {};
-    /** Solid background using color specified in R.color.config_letterboxBackgroundColor. */
-    static final int LETTERBOX_BACKGROUND_SOLID_COLOR = 0;
-
-    /** Color specified in R.attr.colorBackground for the letterboxed application. */
-    static final int LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND = 1;
-
-    /** Color specified in R.attr.colorBackgroundFloating for the letterboxed application. */
-    static final int LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING = 2;
-
-    /** Using wallpaper as a background which can be blurred or dimmed with dark scrim. */
-    static final int LETTERBOX_BACKGROUND_WALLPAPER = 3;
-
-    // Aspect ratio of letterbox for fixed orientation, values <=
-    // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored.
-    private float mFixedOrientationLetterboxAspectRatio;
-
-    // Corners radius for activities presented in the letterbox mode, values < 0 will be ignored.
-    private int mLetterboxActivityCornersRadius;
-
-    // Color for {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} letterbox background type.
-    private Color mLetterboxBackgroundColor;
-
-    @LetterboxBackgroundType
-    private int mLetterboxBackgroundType;
-
-    // Blur radius for LETTERBOX_BACKGROUND_WALLPAPER option in mLetterboxBackgroundType.
-    // Values <= 0 are ignored and 0 is used instead.
-    private int mLetterboxBackgroundWallpaperBlurRadius;
-
-    // Alpha of a black scrim shown over wallpaper letterbox background when
-    // LETTERBOX_BACKGROUND_WALLPAPER option is selected for mLetterboxBackgroundType.
-    // Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead.
-    private float mLetterboxBackgroundWallpaperDarkScrimAlpha;
+    final LetterboxConfiguration mLetterboxConfiguration;
 
     final InputManagerService mInputManager;
     final DisplayManagerInternal mDisplayManagerInternal;
@@ -1254,17 +1208,7 @@
         mAssistantOnTopOfDream = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_assistantOnTopOfDream);
 
-        mFixedOrientationLetterboxAspectRatio = context.getResources().getFloat(
-                com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio);
-        mLetterboxActivityCornersRadius = context.getResources().getInteger(
-                com.android.internal.R.integer.config_letterboxActivityCornersRadius);
-        mLetterboxBackgroundColor = Color.valueOf(context.getResources().getColor(
-                com.android.internal.R.color.config_letterboxBackgroundColor));
-        mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(context);
-        mLetterboxBackgroundWallpaperBlurRadius = context.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
-        mLetterboxBackgroundWallpaperDarkScrimAlpha = context.getResources().getFloat(
-                com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
+        mLetterboxConfiguration = new LetterboxConfiguration(context);
 
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
@@ -3863,201 +3807,6 @@
         }
     }
 
-    /**
-     * Overrides the aspect ratio of letterbox for fixed orientation. If given value is <= {@link
-     * #MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO}, both it and a value of {@link
-     * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and
-     * the framework implementation will be used to determine the aspect ratio.
-     */
-    void setFixedOrientationLetterboxAspectRatio(float aspectRatio) {
-        mFixedOrientationLetterboxAspectRatio = aspectRatio;
-    }
-
-    /**
-     * Resets the aspect ratio of letterbox for fixed orientation to {@link
-     * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}.
-     */
-    void resetFixedOrientationLetterboxAspectRatio() {
-        mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
-                com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio);
-    }
-
-    /**
-     * Gets the aspect ratio of letterbox for fixed orientation.
-     */
-    float getFixedOrientationLetterboxAspectRatio() {
-        return mFixedOrientationLetterboxAspectRatio;
-    }
-
-    /**
-     * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0,
-     * both it and a value of {@link
-     * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and
-     * and corners of the activity won't be rounded.
-     */
-    void setLetterboxActivityCornersRadius(int cornersRadius) {
-        mLetterboxActivityCornersRadius = cornersRadius;
-    }
-
-    /**
-     * Resets corners raidus for activities presented in the letterbox mode to {@link
-     * com.android.internal.R.integer.config_letterboxActivityCornersRadius}.
-     */
-    void resetLetterboxActivityCornersRadius() {
-        mLetterboxActivityCornersRadius = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_letterboxActivityCornersRadius);
-    }
-
-    /**
-     * Whether corners of letterboxed activities are rounded.
-     */
-    boolean isLetterboxActivityCornersRounded() {
-        return getLetterboxActivityCornersRadius() > 0;
-    }
-
-    /**
-     * Gets corners raidus for activities presented in the letterbox mode.
-     */
-    int getLetterboxActivityCornersRadius() {
-        return mLetterboxActivityCornersRadius;
-    }
-
-    /**
-     * Gets color of letterbox background which is  used when {@link
-     * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
-     * fallback for other backfround types.
-     */
-    Color getLetterboxBackgroundColor() {
-        return mLetterboxBackgroundColor;
-    }
-
-
-    /**
-     * Sets color of letterbox background which is used when {@link
-     * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
-     * fallback for other backfround types.
-     */
-    void setLetterboxBackgroundColor(Color color) {
-        mLetterboxBackgroundColor = color;
-    }
-
-    /**
-     * Resets color of letterbox background to {@link
-     * com.android.internal.R.color.config_letterboxBackgroundColor}.
-     */
-    void resetLetterboxBackgroundColor() {
-        mLetterboxBackgroundColor = Color.valueOf(mContext.getResources().getColor(
-                com.android.internal.R.color.config_letterboxBackgroundColor));
-    }
-
-    /**
-     * Gets {@link LetterboxBackgroundType} specified in {@link
-     * com.android.internal.R.integer.config_letterboxBackgroundType} or over via ADB command.
-     */
-    @LetterboxBackgroundType
-    int getLetterboxBackgroundType() {
-        return mLetterboxBackgroundType;
-    }
-
-    /** Sets letterbox background type. */
-    void setLetterboxBackgroundType(@LetterboxBackgroundType int backgroundType) {
-        mLetterboxBackgroundType = backgroundType;
-    }
-
-    /**
-     * Resets cletterbox background type to {@link
-     * com.android.internal.R.integer.config_letterboxBackgroundType}.
-     */
-    void resetLetterboxBackgroundType() {
-        mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
-    }
-
-    @LetterboxBackgroundType
-    private static int readLetterboxBackgroundTypeFromConfig(Context context) {
-        int backgroundType = context.getResources().getInteger(
-                com.android.internal.R.integer.config_letterboxBackgroundType);
-        return backgroundType == LETTERBOX_BACKGROUND_SOLID_COLOR
-                    || backgroundType == LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND
-                    || backgroundType == LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING
-                    || backgroundType == LETTERBOX_BACKGROUND_WALLPAPER
-                    ? backgroundType : LETTERBOX_BACKGROUND_SOLID_COLOR;
-    }
-
-    /** Returns a string representing the given {@link LetterboxBackgroundType}. */
-    static String letterboxBackgroundTypeToString(
-            @LetterboxBackgroundType int backgroundType) {
-        switch (backgroundType) {
-            case LETTERBOX_BACKGROUND_SOLID_COLOR:
-                return "LETTERBOX_BACKGROUND_SOLID_COLOR";
-            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND:
-                return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND";
-            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING:
-                return "LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING";
-            case LETTERBOX_BACKGROUND_WALLPAPER:
-                return "LETTERBOX_BACKGROUND_WALLPAPER";
-            default:
-                return "unknown=" + backgroundType;
-        }
-    }
-
-    /**
-     * Overrides alpha of a black scrim shown over wallpaper for {@link
-     * #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link mLetterboxBackgroundType}.
-     *
-     * <p>If given value is < 0 or >= 1, both it and a value of {@link
-     * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha} are ignored
-     * and 0.0 (transparent) is instead.
-     */
-    void setLetterboxBackgroundWallpaperDarkScrimAlpha(float alpha) {
-        mLetterboxBackgroundWallpaperDarkScrimAlpha = alpha;
-    }
-
-    /**
-     * Resets alpha of a black scrim shown over wallpaper letterbox background to {@link
-     * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha}.
-     */
-    void resetLetterboxBackgroundWallpaperDarkScrimAlpha() {
-        mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat(
-                com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
-    }
-
-    /**
-     * Gets alpha of a black scrim shown over wallpaper letterbox background.
-     */
-    float getLetterboxBackgroundWallpaperDarkScrimAlpha() {
-        return mLetterboxBackgroundWallpaperDarkScrimAlpha;
-    }
-
-    /**
-     * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in
-     * {@link mLetterboxBackgroundType}.
-     *
-     * <p> If given value <= 0, both it and a value of {@link
-     * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius} are ignored
-     * and 0 is used instead.
-     */
-    void setLetterboxBackgroundWallpaperBlurRadius(int radius) {
-        mLetterboxBackgroundWallpaperBlurRadius = radius;
-    }
-
-    /**
-     * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
-     * mLetterboxBackgroundType} to {@link
-     * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius}.
-     */
-    void resetLetterboxBackgroundWallpaperBlurRadius() {
-        mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
-    }
-
-    /**
-     * Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
-     * mLetterboxBackgroundType}.
-     */
-    int getLetterboxBackgroundWallpaperBlurRadius() {
-        return mLetterboxBackgroundWallpaperBlurRadius;
-    }
-
     @Override
     public void setIgnoreOrientationRequest(int displayId, boolean ignoreOrientationRequest) {
         if (!checkCallingPermission(
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 5942f34..1b578d1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -19,10 +19,10 @@
 import static android.os.Build.IS_USER;
 import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED;
 
-import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
-import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
-import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_SOLID_COLOR;
-import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_WALLPAPER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
 
 import android.graphics.Color;
 import android.graphics.Point;
@@ -41,7 +41,7 @@
 import com.android.internal.protolog.ProtoLogImpl;
 import com.android.server.LocalServices;
 import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.wm.WindowManagerService.LetterboxBackgroundType;
+import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -64,10 +64,12 @@
 
     // Internal service impl -- must perform security checks before touching.
     private final WindowManagerService mInternal;
+    private final LetterboxConfiguration mLetterboxConfiguration;
 
     public WindowManagerShellCommand(WindowManagerService service) {
         mInterface = service;
         mInternal = service;
+        mLetterboxConfiguration = service.mLetterboxConfiguration;
     }
 
     @Override
@@ -119,30 +121,12 @@
                     return runGetIgnoreOrientationRequest(pw);
                 case "dump-visible-window-views":
                     return runDumpVisibleWindowViews(pw);
-                case "set-fixed-orientation-letterbox-aspect-ratio":
-                    return runSetFixedOrientationLetterboxAspectRatio(pw);
-                case "get-fixed-orientation-letterbox-aspect-ratio":
-                    return runGetFixedOrientationLetterboxAspectRatio(pw);
-                case "set-letterbox-activity-corners-radius":
-                    return runSetLetterboxActivityCornersRadius(pw);
-                case "get-letterbox-activity-corners-radius":
-                    return runGetLetterboxActivityCornersRadius(pw);
-                case "set-letterbox-background-type":
-                    return runSetLetterboxBackgroundType(pw);
-                case "get-letterbox-background-type":
-                    return runGetLetterboxBackgroundType(pw);
-                case "set-letterbox-background-color":
-                    return runSetLetterboxBackgroundColor(pw);
-                case "get-letterbox-background-color":
-                    return runGetLetterboxBackgroundColor(pw);
-                case "set-letterbox-background-wallpaper-blur-radius":
-                    return runSetLetterboxBackgroundWallpaperBlurRadius(pw);
-                case "get-letterbox-background-wallpaper-blur-radius":
-                    return runGetLetterboxBackgroundWallpaperBlurRadius(pw);
-                case "set-letterbox-background-wallpaper-dark-scrim-alpha":
-                    return runSetLetterboxBackgroundWallpaperDarkScrimAlpha(pw);
-                case "get-letterbox-background-wallpaper-dark-scrim-alpha":
-                    return runGetLetterboxBackgroundWallpaperDarkScrimAlpha(pw);
+                case "set-letterbox-style":
+                    return runSetLetterboxStyle(pw);
+                case "get-letterbox-style":
+                    return runGetLetterboxStyle(pw);
+                case "reset-letterbox-style":
+                    return runResetLetterboxStyle(pw);
                 case "set-sandbox-display-apis":
                     return runSandboxDisplayApis(pw);
                 case "reset":
@@ -607,12 +591,6 @@
         final float aspectRatio;
         try {
             String arg = getNextArgRequired();
-            if ("reset".equals(arg)) {
-                synchronized (mInternal.mGlobalLock) {
-                    mInternal.resetFixedOrientationLetterboxAspectRatio();
-                }
-                return 0;
-            }
             aspectRatio = Float.parseFloat(arg);
         } catch (NumberFormatException  e) {
             getErrPrintWriter().println("Error: bad aspect ratio format " + e);
@@ -623,19 +601,7 @@
             return -1;
         }
         synchronized (mInternal.mGlobalLock) {
-            mInternal.setFixedOrientationLetterboxAspectRatio(aspectRatio);
-        }
-        return 0;
-    }
-
-    private int runGetFixedOrientationLetterboxAspectRatio(PrintWriter pw) throws RemoteException {
-        synchronized (mInternal.mGlobalLock) {
-            final float aspectRatio = mInternal.getFixedOrientationLetterboxAspectRatio();
-            if (aspectRatio <= WindowManagerService.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
-                pw.println("Letterbox aspect ratio is not set");
-            } else {
-                pw.println("Letterbox aspect ratio is " + aspectRatio);
-            }
+            mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(aspectRatio);
         }
         return 0;
     }
@@ -644,12 +610,6 @@
         final int cornersRadius;
         try {
             String arg = getNextArgRequired();
-            if ("reset".equals(arg)) {
-                synchronized (mInternal.mGlobalLock) {
-                    mInternal.resetLetterboxActivityCornersRadius();
-                }
-                return 0;
-            }
             cornersRadius = Integer.parseInt(arg);
         } catch (NumberFormatException  e) {
             getErrPrintWriter().println("Error: bad corners radius format " + e);
@@ -660,110 +620,59 @@
             return -1;
         }
         synchronized (mInternal.mGlobalLock) {
-            mInternal.setLetterboxActivityCornersRadius(cornersRadius);
-        }
-        return 0;
-    }
-
-    private int runGetLetterboxActivityCornersRadius(PrintWriter pw) throws RemoteException {
-        synchronized (mInternal.mGlobalLock) {
-            final int cornersRadius = mInternal.getLetterboxActivityCornersRadius();
-            if (cornersRadius < 0) {
-                pw.println("Letterbox corners radius is not set");
-            } else {
-                pw.println("Letterbox corners radius is " + cornersRadius);
-            }
+            mLetterboxConfiguration.setLetterboxActivityCornersRadius(cornersRadius);
         }
         return 0;
     }
 
     private int runSetLetterboxBackgroundType(PrintWriter pw) throws RemoteException {
         @LetterboxBackgroundType final int backgroundType;
-
-        String arg = getNextArgRequired();
-        if ("reset".equals(arg)) {
-            synchronized (mInternal.mGlobalLock) {
-                mInternal.resetLetterboxBackgroundType();
-            }
-            return 0;
-        }
-        switch (arg) {
-            case "solid_color":
-                backgroundType = LETTERBOX_BACKGROUND_SOLID_COLOR;
-                break;
-            case "app_color_background":
-                backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
-                break;
-            case "app_color_background_floating":
-                backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
-                break;
-            case "wallpaper":
-                backgroundType = LETTERBOX_BACKGROUND_WALLPAPER;
-                break;
-            default:
-                getErrPrintWriter().println(
-                        "Error: 'reset', 'solid_color', 'app_color_background' or "
-                        + "'wallpaper' should be provided as an argument");
-                return -1;
-        }
-        synchronized (mInternal.mGlobalLock) {
-            mInternal.setLetterboxBackgroundType(backgroundType);
-        }
-        return 0;
-    }
-
-    private int runGetLetterboxBackgroundType(PrintWriter pw) throws RemoteException {
-        synchronized (mInternal.mGlobalLock) {
-            @LetterboxBackgroundType final int backgroundType =
-                    mInternal.getLetterboxBackgroundType();
-            switch (backgroundType) {
-                case LETTERBOX_BACKGROUND_SOLID_COLOR:
-                    pw.println("Letterbox background type is 'solid_color'");
+        try {
+            String arg = getNextArgRequired();
+            switch (arg) {
+                case "solid_color":
+                    backgroundType = LETTERBOX_BACKGROUND_SOLID_COLOR;
                     break;
-                case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND:
-                    pw.println("Letterbox background type is 'app_color_background'");
+                case "app_color_background":
+                    backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
                     break;
-                case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING:
-                    pw.println("Letterbox background type is 'app_color_background_floating'");
+                case "app_color_background_floating":
+                    backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
                     break;
-                case LETTERBOX_BACKGROUND_WALLPAPER:
-                    pw.println("Letterbox background type is 'wallpaper'");
+                case "wallpaper":
+                    backgroundType = LETTERBOX_BACKGROUND_WALLPAPER;
                     break;
                 default:
-                    throw new AssertionError(
-                            "Unexpected letterbox background type: " + backgroundType);
+                    getErrPrintWriter().println(
+                            "Error: 'reset', 'solid_color', 'app_color_background' or "
+                            + "'wallpaper' should be provided as an argument");
+                    return -1;
             }
+        } catch (IllegalArgumentException  e) {
+            getErrPrintWriter().println(
+                    "Error: 'reset', 'solid_color', 'app_color_background' or "
+                        + "'wallpaper' should be provided as an argument" + e);
+            return -1;
+        }
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setLetterboxBackgroundType(backgroundType);
         }
         return 0;
     }
 
     private int runSetLetterboxBackgroundColor(PrintWriter pw) throws RemoteException {
         final Color color;
-        String arg = getNextArgRequired();
         try {
-            if ("reset".equals(arg)) {
-                synchronized (mInternal.mGlobalLock) {
-                    mInternal.resetLetterboxBackgroundColor();
-                }
-                return 0;
-            }
+            String arg = getNextArgRequired();
             color = Color.valueOf(Color.parseColor(arg));
         } catch (IllegalArgumentException  e) {
             getErrPrintWriter().println(
                     "Error: 'reset' or color in #RRGGBB format should be provided as "
-                            + "an argument " + e + " but got " + arg);
+                            + "an argument " + e);
             return -1;
         }
         synchronized (mInternal.mGlobalLock) {
-            mInternal.setLetterboxBackgroundColor(color);
-        }
-        return 0;
-    }
-
-    private int runGetLetterboxBackgroundColor(PrintWriter pw) throws RemoteException {
-        synchronized (mInternal.mGlobalLock) {
-            final Color color = mInternal.getLetterboxBackgroundColor();
-            pw.println("Letterbox background color is " + Integer.toHexString(color.toArgb()));
+            mLetterboxConfiguration.setLetterboxBackgroundColor(color);
         }
         return 0;
     }
@@ -773,12 +682,6 @@
         final int radius;
         try {
             String arg = getNextArgRequired();
-            if ("reset".equals(arg)) {
-                synchronized (mInternal.mGlobalLock) {
-                    mInternal.resetLetterboxBackgroundWallpaperBlurRadius();
-                }
-                return 0;
-            }
             radius = Integer.parseInt(arg);
         } catch (NumberFormatException  e) {
             getErrPrintWriter().println("Error: blur radius format " + e);
@@ -789,20 +692,7 @@
             return -1;
         }
         synchronized (mInternal.mGlobalLock) {
-            mInternal.setLetterboxBackgroundWallpaperBlurRadius(radius);
-        }
-        return 0;
-    }
-
-    private int runGetLetterboxBackgroundWallpaperBlurRadius(PrintWriter pw)
-            throws RemoteException {
-        synchronized (mInternal.mGlobalLock) {
-            final int radius = mInternal.getLetterboxBackgroundWallpaperBlurRadius();
-            if (radius <= 0) {
-                pw.println("Letterbox background wallpaper blur radius is not set");
-            } else {
-                pw.println("Letterbox background wallpaper blur radius is " + radius);
-            }
+            mLetterboxConfiguration.setLetterboxBackgroundWallpaperBlurRadius(radius);
         }
         return 0;
     }
@@ -812,12 +702,6 @@
         final float alpha;
         try {
             String arg = getNextArgRequired();
-            if ("reset".equals(arg)) {
-                synchronized (mInternal.mGlobalLock) {
-                    mInternal.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
-                }
-                return 0;
-            }
             alpha = Float.parseFloat(arg);
         } catch (NumberFormatException  e) {
             getErrPrintWriter().println("Error: bad alpha format " + e);
@@ -828,24 +712,140 @@
             return -1;
         }
         synchronized (mInternal.mGlobalLock) {
-            mInternal.setLetterboxBackgroundWallpaperDarkScrimAlpha(alpha);
+            mLetterboxConfiguration.setLetterboxBackgroundWallpaperDarkScrimAlpha(alpha);
         }
         return 0;
     }
 
-    private int runGetLetterboxBackgroundWallpaperDarkScrimAlpha(PrintWriter pw)
-            throws RemoteException {
+    private int runSeLetterboxHorizontalPositionMultiplier(PrintWriter pw) throws RemoteException {
+        final float multiplier;
+        try {
+            String arg = getNextArgRequired();
+            multiplier = Float.parseFloat(arg);
+        } catch (NumberFormatException  e) {
+            getErrPrintWriter().println("Error: bad multiplier format " + e);
+            return -1;
+        } catch (IllegalArgumentException  e) {
+            getErrPrintWriter().println(
+                    "Error: 'reset' or multiplier should be provided as an argument " + e);
+            return -1;
+        }
         synchronized (mInternal.mGlobalLock) {
-            final float alpha = mInternal.getLetterboxBackgroundWallpaperDarkScrimAlpha();
-            if (alpha < 0 || alpha >= 1) {
-                pw.println("Letterbox dark scrim alpha is not set");
-            } else {
-                pw.println("Letterbox dark scrim alpha is " + alpha);
+            mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier);
+        }
+        return 0;
+    }
+
+    private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {
+        if (peekNextArg() == null) {
+            getErrPrintWriter().println("Error: No arguments provided.");
+        }
+        while (peekNextArg() != null) {
+            String arg = getNextArg();
+            switch (arg) {
+                case "--aspectRatio":
+                    runSetFixedOrientationLetterboxAspectRatio(pw);
+                    break;
+                case "--cornerRadius":
+                    runSetLetterboxActivityCornersRadius(pw);
+                    break;
+                case "--backgroundType":
+                    runSetLetterboxBackgroundType(pw);
+                    break;
+                case "--backgroundColor":
+                    runSetLetterboxBackgroundColor(pw);
+                    break;
+                case "--wallpaperBlurRadius":
+                    runSetLetterboxBackgroundWallpaperBlurRadius(pw);
+                    break;
+                case "--wallpaperDarkScrimAlpha":
+                    runSetLetterboxBackgroundWallpaperDarkScrimAlpha(pw);
+                    break;
+                case "--horizontalPositionMultiplier":
+                    runSeLetterboxHorizontalPositionMultiplier(pw);
+                    break;
+                default:
+                    getErrPrintWriter().println(
+                            "Error: Unrecognized letterbox style option: " + arg);
+                    return -1;
             }
         }
         return 0;
     }
 
+    private int runResetLetterboxStyle(PrintWriter pw) throws RemoteException {
+        if (peekNextArg() == null) {
+            resetLetterboxStyle();
+        }
+        synchronized (mInternal.mGlobalLock) {
+            while (peekNextArg() != null) {
+                String arg = getNextArg();
+                switch (arg) {
+                    case "aspectRatio":
+                        mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
+                        break;
+                    case "cornerRadius":
+                        mLetterboxConfiguration.resetLetterboxActivityCornersRadius();
+                        break;
+                    case "backgroundType":
+                        mLetterboxConfiguration.resetLetterboxBackgroundType();
+                        break;
+                    case "backgroundColor":
+                        mLetterboxConfiguration.resetLetterboxBackgroundColor();
+                        break;
+                    case "wallpaperBlurRadius":
+                        mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius();
+                        break;
+                    case "wallpaperDarkScrimAlpha":
+                        mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
+                        break;
+                    case "horizontalPositionMultiplier":
+                        mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
+                        break;
+                    default:
+                        getErrPrintWriter().println(
+                                "Error: Unrecognized letterbox style option: " + arg);
+                        return -1;
+                }
+            }
+        }
+        return 0;
+    }
+
+    private void resetLetterboxStyle() {
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
+            mLetterboxConfiguration.resetLetterboxActivityCornersRadius();
+            mLetterboxConfiguration.resetLetterboxBackgroundType();
+            mLetterboxConfiguration.resetLetterboxBackgroundColor();
+            mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius();
+            mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
+            mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
+        }
+    }
+
+    private int runGetLetterboxStyle(PrintWriter pw) throws RemoteException {
+        synchronized (mInternal.mGlobalLock) {
+            pw.println("Corner radius: "
+                    + mLetterboxConfiguration.getLetterboxActivityCornersRadius());
+            pw.println("Horizontal position multiplier: "
+                    + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier());
+            pw.println("Aspect ratio: "
+                    + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
+
+            pw.println("Background type: "
+                    + LetterboxConfiguration.letterboxBackgroundTypeToString(
+                            mLetterboxConfiguration.getLetterboxBackgroundType()));
+            pw.println("    Background color: " + Integer.toHexString(
+                    mLetterboxConfiguration.getLetterboxBackgroundColor().toArgb()));
+            pw.println("    Wallpaper blur radius: "
+                    + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius());
+            pw.println("    Wallpaper dark scrim alpha: "
+                    + mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha());
+        }
+        return 0;
+    }
+
     private int runReset(PrintWriter pw) throws RemoteException {
         int displayId = getDisplayId(getNextArg());
 
@@ -870,23 +870,8 @@
         // set-ignore-orientation-request
         mInterface.setIgnoreOrientationRequest(displayId, false /* ignoreOrientationRequest */);
 
-        // set-fixed-orientation-letterbox-aspect-ratio
-        mInternal.resetFixedOrientationLetterboxAspectRatio();
-
-        // set-letterbox-activity-corners-radius
-        mInternal.resetLetterboxActivityCornersRadius();
-
-        // set-letterbox-background-type
-        mInternal.resetLetterboxBackgroundType();
-
-        // set-letterbox-background-color
-        mInternal.resetLetterboxBackgroundColor();
-
-        // set-letterbox-background-wallpaper-blur-radius
-        mInternal.resetLetterboxBackgroundWallpaperBlurRadius();
-
-        // set-letterbox-background-wallpaper-dark-scrim-alpha
-        mInternal.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
+        // set-letterbox-style
+        resetLetterboxStyle();
 
         // set-sandbox-display-apis
         mInternal.setSandboxDisplayApis(displayId, /* sandboxDisplayApis= */ true);
@@ -922,42 +907,13 @@
         pw.println("  set-ignore-orientation-request [-d DISPLAY_ID] [true|1|false|0]");
         pw.println("  get-ignore-orientation-request [-d DISPLAY_ID] ");
         pw.println("    If app requested orientation should be ignored.");
-        pw.println("  set-fixed-orientation-letterbox-aspect-ratio [reset|aspectRatio]");
-        pw.println("  get-fixed-orientation-letterbox-aspect-ratio");
-        pw.println("    Aspect ratio of letterbox for fixed orientation. If aspectRatio <= "
-                + WindowManagerService.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
-        pw.println("    both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will be");
-        pw.println("    ignored and framework implementation will determine aspect ratio.");
-        pw.println("  set-letterbox-activity-corners-radius [reset|cornersRadius]");
-        pw.println("  get-letterbox-activity-corners-radius");
-        pw.println("    Corners radius for activities in the letterbox mode. If radius < 0,");
-        pw.println("    both it and R.integer.config_letterboxActivityCornersRadius will be");
-        pw.println("    ignored and corners of the activity won't be rounded.");
-        pw.println("  set-letterbox-background-color [reset|colorName|'\\#RRGGBB']");
-        pw.println("  get-letterbox-background-color");
-        pw.println("    Color of letterbox background which is be used when letterbox background");
-        pw.println("    type is 'solid-color'. Use get(set)-letterbox-background-type to check");
-        pw.println("    and control letterbox background type. See Color#parseColor for allowed");
-        pw.println("    color formats (#RRGGBB and some colors by name, e.g. magenta or olive). ");
-        pw.println("  set-letterbox-background-type [reset|solid_color|app_color_background");
-        pw.println("    |app_color_background_floating|wallpaper]");
-        pw.println("  get-letterbox-background-type");
-        pw.println("    Type of background used in the letterbox mode.");
-        pw.println("  set-letterbox-background-wallpaper-blur-radius [reset|radius]");
-        pw.println("  get-letterbox-background-wallpaper-blur-radius");
-        pw.println("    Blur radius for 'wallpaper' letterbox background. If radius <= 0");
-        pw.println("    both it and R.dimen.config_letterboxBackgroundWallpaperBlurRadius are ");
-        pw.println("    ignored and 0 is used.");
-        pw.println("  set-letterbox-background-wallpaper-dark-scrim-alpha [reset|alpha]");
-        pw.println("  get-letterbox-background-wallpaper-dark-scrim-alpha");
-        pw.println("    Alpha of a black translucent scrim shown over 'wallpaper'");
-        pw.println("    letterbox background. If alpha < 0 or >= 1 both it and");
-        pw.println("    R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha are ignored and ");
-        pw.println("    0.0 (transparent) is used instead.");
         pw.println("  set-sandbox-display-apis [true|1|false|0]");
         pw.println("    Sets override of Display APIs getRealSize / getRealMetrics to reflect ");
         pw.println("    DisplayArea of the activity, or the window bounds if in letterbox or");
         pw.println("    Size Compat Mode.");
+
+        printLetterboxHelp(pw);
+
         pw.println("  reset [-d DISPLAY_ID]");
         pw.println("    Reset all override settings.");
         if (!IS_USER) {
@@ -967,4 +923,47 @@
             pw.println("    Logging settings.");
         }
     }
+
+    private void printLetterboxHelp(PrintWriter pw) {
+        pw.println("  set-letterbox-style");
+        pw.println("    Sets letterbox style using the following options:");
+        pw.println("      --aspectRatio aspectRatio");
+        pw.println("        Aspect ratio of letterbox for fixed orientation. If aspectRatio <= "
+                + LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
+        pw.println("        both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will");
+        pw.println("        be ignored and framework implementation will determine aspect ratio.");
+        pw.println("      --cornerRadius radius");
+        pw.println("        Corners radius for activities in the letterbox mode. If radius < 0,");
+        pw.println("        both it and R.integer.config_letterboxActivityCornersRadius will be");
+        pw.println("        ignored and corners of the activity won't be rounded.");
+        pw.println("      --backgroundType [reset|solid_color|app_color_background");
+        pw.println("          |app_color_background_floating|wallpaper]");
+        pw.println("        Type of background used in the letterbox mode.");
+        pw.println("      --backgroundColor color");
+        pw.println("        Color of letterbox which is be used when letterbox background type");
+        pw.println("        is 'solid-color'. Use (set)get-letterbox-style to check and control");
+        pw.println("        letterbox background type. See Color#parseColor for allowed color");
+        pw.println("        formats (#RRGGBB and some colors by name, e.g. magenta or olive).");
+        pw.println("      --wallpaperBlurRadius radius");
+        pw.println("        Blur radius for 'wallpaper' letterbox background. If radius <= 0");
+        pw.println("        both it and R.dimen.config_letterboxBackgroundWallpaperBlurRadius");
+        pw.println("        are ignored and 0 is used.");
+        pw.println("      --wallpaperDarkScrimAlpha alpha");
+        pw.println("        Alpha of a black translucent scrim shown over 'wallpaper'");
+        pw.println("        letterbox background. If alpha < 0 or >= 1 both it and");
+        pw.println("        R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha are ignored");
+        pw.println("        and 0.0 (transparent) is used instead.");
+        pw.println("      --horizontalPositionMultiplier multiplier");
+        pw.println("        Horizontal position of app window center. If multiplier < 0 or > 1,");
+        pw.println("        both it and R.dimen.config_letterboxHorizontalPositionMultiplier");
+        pw.println("        are ignored and central position (0.5) is used.");
+        pw.println("  reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
+        pw.println("      |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
+        pw.println("      |horizontalPositionMultiplier]");
+        pw.println("    Resets overrides to default values for specified properties separated");
+        pw.println("    by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
+        pw.println("    If no arguments provided, all values will be reset.");
+        pw.println("  get-letterbox-style");
+        pw.println("    Prints letterbox style configuration.");
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 2e37fee..9382b8e 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -36,6 +36,7 @@
 import android.app.WindowConfiguration;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.graphics.GraphicBuffer;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.Binder;
@@ -45,7 +46,6 @@
 import android.os.RemoteException;
 import android.util.ArraySet;
 import android.util.Slog;
-import android.view.Surface;
 import android.view.SurfaceControl;
 import android.window.IDisplayAreaOrganizerController;
 import android.window.ITaskOrganizerController;
@@ -766,18 +766,21 @@
             return false;
         }
 
+        GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
+                buffer.getHardwareBuffer());
         SurfaceControl screenshot = mService.mWindowManager.mSurfaceControlFactory.apply(null)
                 .setName(wc.getName() + " - Organizer Screenshot")
-                .setBufferSize(bounds.width(), bounds.height())
                 .setFormat(PixelFormat.TRANSLUCENT)
                 .setParent(wc.getParentSurfaceControl())
+                .setSecure(buffer.containsSecureLayers())
                 .setCallsite("WindowOrganizerController.takeScreenshot")
+                .setBLASTLayer()
                 .build();
 
-        Surface surface = new Surface();
-        surface.copyFrom(screenshot);
-        surface.attachAndQueueBufferWithColorSpace(buffer.getHardwareBuffer(), null);
-        surface.release();
+        SurfaceControl.Transaction transaction = mService.mWindowManager.mTransactionFactory.get();
+        transaction.setBuffer(screenshot, graphicBuffer);
+        transaction.setColorSpace(screenshot, buffer.getColorSpace());
+        transaction.apply();
 
         outSurfaceControl.copyFrom(screenshot, "WindowOrganizerController.takeScreenshot");
         return true;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 46d923b..1a5042f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1140,7 +1140,7 @@
 
     void attach() {
         if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
-        mSession.windowAddedLocked(mAttrs.packageName);
+        mSession.windowAddedLocked();
     }
 
     /**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ddcb2bf..6283b4e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -96,6 +96,7 @@
 import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED;
 import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED;
 import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_STARTING_PROFILE_FAILED;
+import static android.app.admin.DevicePolicyManager.STATE_USER_UNMANAGED;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
 import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
@@ -8517,20 +8518,16 @@
                     + " as profile owner for user " + userHandle);
             return false;
         }
-        if (who == null
-                || !isPackageInstalledForUser(who.getPackageName(), userHandle)) {
-            throw new IllegalArgumentException("Component " + who
-                    + " not installed for userId:" + userHandle);
-        }
+        Preconditions.checkArgument(who != null);
 
         final CallerIdentity caller = getCallerIdentity();
         synchronized (getLockObject()) {
             enforceCanSetProfileOwnerLocked(caller, who, userHandle);
-
+            Preconditions.checkArgument(isPackageInstalledForUser(who.getPackageName(), userHandle),
+                    "Component " + who + " not installed for userId:" + userHandle);
             final ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
-            if (admin == null || getUserData(userHandle).mRemovingAdmins.contains(who)) {
-                throw new IllegalArgumentException("Not active admin: " + who);
-            }
+            Preconditions.checkArgument(admin != null && !getUserData(
+                    userHandle).mRemovingAdmins.contains(who), "Not active admin: " + who);
 
             final int parentUserId = getProfileParentId(userHandle);
             // When trying to set a profile owner on a new user, it may be that this user is
@@ -8739,7 +8736,8 @@
 
         final CallerIdentity caller = getCallerIdentity();
         if (userHandle != mOwners.getDeviceOwnerUserId() && !mOwners.hasProfileOwner(userHandle)
-                && getManagedUserId(userHandle) == -1) {
+                && getManagedUserId(userHandle) == -1
+                && newState != STATE_USER_UNMANAGED) {
             // No managed device, user or profile, so setting provisioning state makes no sense.
             throw new IllegalStateException("Not allowed to change provisioning state unless a "
                       + "device or profile owner is set.");
@@ -8802,6 +8800,12 @@
             case DevicePolicyManager.STATE_USER_SETUP_FINALIZED:
                 // Cannot transition out of finalized.
                 break;
+            case DevicePolicyManager.STATE_USER_PROFILE_FINALIZED:
+                // Should only move to an unmanaged state after removing the work profile.
+                if (newState == DevicePolicyManager.STATE_USER_UNMANAGED) {
+                    return;
+                }
+                break;
         }
 
         // Didn't meet any of the accepted state transition checks above, throw appropriate error.
diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index b136d00..83677c2 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -36,6 +36,10 @@
     ],
     test_suites: ["general-tests"],
     java_resources: [
+        ":PackageManagerTestOverlayActor",
+        ":PackageManagerTestOverlay",
+        ":PackageManagerTestOverlayTarget",
+        ":PackageManagerTestOverlayTargetNoOverlayable",
         ":PackageManagerTestAppDeclaresStaticLibrary",
         ":PackageManagerTestAppStub",
         ":PackageManagerTestAppUsesStaticLibrary",
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayActorVisibilityTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayActorVisibilityTest.kt
new file mode 100644
index 0000000..558d01ed
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayActorVisibilityTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.server.pm.test
+
+import com.android.internal.util.test.SystemPreparer
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import java.io.File
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class OverlayActorVisibilityTest : BaseHostJUnit4Test() {
+
+    companion object {
+        private const val ACTOR_PKG_NAME = "com.android.server.pm.test.overlay.actor"
+        private const val ACTOR_APK = "PackageManagerTestOverlayActor.apk"
+        private const val TARGET_APK = "PackageManagerTestOverlayTarget.apk"
+        private const val OVERLAY_APK = "PackageManagerTestOverlay.apk"
+        private const val TARGET_NO_OVERLAYABLE_APK =
+            "PackageManagerTestOverlayTargetNoOverlayable.apk"
+
+        @get:ClassRule
+        val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
+    }
+
+    @get:Rule
+    val tempFolder = TemporaryFolder()
+
+    private val preparer: SystemPreparer = SystemPreparer(
+        tempFolder,
+        SystemPreparer.RebootStrategy.FULL,
+        deviceRebootRule
+    ) { this.device }
+
+    private val namedActorFile = File(
+        "/system/etc/sysconfig/com.android.server.pm.test.OverlayActorVisibilityTest.xml"
+    )
+
+    @Before
+    @After
+    fun uninstallPackages() {
+        device.uninstallPackages(ACTOR_APK, TARGET_APK, OVERLAY_APK)
+    }
+
+    @Before
+    fun pushSysConfigFile() {
+        // In order for the test app to be the verification agent, it needs a permission file
+        // which can be pushed onto the system and removed afterwards.
+        // language=XML
+        val file = tempFolder.newFile().apply {
+            """
+            <config>
+                <named-actor
+                    namespace="androidTest"
+                    name="OverlayActorVisibilityTest"
+                    package="$ACTOR_PKG_NAME"
+                    />
+            </config>
+            """
+                .trimIndent()
+                .let { writeText(it) }
+        }
+
+        preparer.pushFile(file, namedActorFile.toString())
+            .reboot()
+    }
+
+    @After
+    fun deleteSysConfigFile() {
+        preparer.deleteFile(namedActorFile.toString())
+            .reboot()
+    }
+
+    @Test
+    fun testVisibilityByOverlayable() {
+        assertThat(device.installJavaResourceApk(tempFolder, ACTOR_APK, false)).isNull()
+        assertThat(device.installJavaResourceApk(tempFolder, OVERLAY_APK, false)).isNull()
+        assertThat(device.installJavaResourceApk(tempFolder, TARGET_NO_OVERLAYABLE_APK, false))
+            .isNull()
+
+        runDeviceTests(
+            ACTOR_PKG_NAME, "$ACTOR_PKG_NAME.OverlayableVisibilityTest",
+            "verifyNotVisible"
+        )
+
+        assertThat(device.installJavaResourceApk(tempFolder, TARGET_APK, true)).isNull()
+
+        assertWithMessage(device.executeShellCommand("dumpsys package $OVERLAY_APK"))
+
+        runDeviceTests(
+            ACTOR_PKG_NAME, "$ACTOR_PKG_NAME.OverlayableVisibilityTest",
+            "verifyVisible"
+        )
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/Android.bp
new file mode 100644
index 0000000..92dcd34
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/Android.bp
@@ -0,0 +1,26 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestOverlay",
+    srcs: [
+        "src/**/*.kt",
+    ],
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/AndroidManifest.xml
new file mode 100644
index 0000000..21e4432
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.overlay">
+    <application/>
+    <overlay android:targetPackage="com.android.server.pm.test.overlay.target"
+        android:targetName="Testing"/>
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/res/values/values.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/res/values/values.xml
new file mode 100644
index 0000000..f0b8586
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/res/values/values.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<resources>
+    <string name="policy_public">You have been overlaid</string>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/Android.bp
new file mode 100644
index 0000000..5718474
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/Android.bp
@@ -0,0 +1,33 @@
+// 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestOverlayActor",
+    srcs: ["src/**/*.kt"],
+    static_libs: [
+        "androidx.test.runner",
+        "junit",
+        "kotlin-test",
+        "truth-prebuilt",
+    ],
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/AndroidManifest.xml
new file mode 100644
index 0000000..a92a14f
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.overlay.actor"
+    >
+
+    <application/>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.server.pm.test.overlay.actor"
+        />
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/src/com/android/server/pm/test/overlay/actor/OverlayableVisibilityTest.kt b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/src/com/android/server/pm/test/overlay/actor/OverlayableVisibilityTest.kt
new file mode 100644
index 0000000..7537247f
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/src/com/android/server/pm/test/overlay/actor/OverlayableVisibilityTest.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.server.pm.test.overlay.actor
+
+import android.content.Context
+import android.content.pm.PackageManager
+import androidx.test.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import kotlin.test.assertFailsWith
+
+class OverlayableVisibilityTest {
+
+    companion object {
+        private const val TARGET_PACKAGE = "com.android.server.pm.test.overlay.target"
+        private const val OVERLAY_PACKAGE = "com.android.server.pm.test.overlay"
+    }
+
+    private val context: Context = InstrumentationRegistry.getContext()
+    private val packageManager = context.packageManager
+
+    @Test
+    fun verifyVisible() {
+        assertThat(packageManager.getApplicationInfo(TARGET_PACKAGE, 0)).isNotNull()
+        assertThat(packageManager.getApplicationInfo(OVERLAY_PACKAGE, 0)).isNotNull()
+    }
+
+    @Test
+    fun verifyNotVisible() {
+        assertFailsWith(PackageManager.NameNotFoundException::class) {
+            packageManager.getApplicationInfo(TARGET_PACKAGE, 0)
+        }
+        assertFailsWith(PackageManager.NameNotFoundException::class) {
+            packageManager.getApplicationInfo(OVERLAY_PACKAGE, 0)
+        }
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/Android.bp
new file mode 100644
index 0000000..2bb6b82
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/Android.bp
@@ -0,0 +1,38 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestOverlayTarget",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    resource_dirs: [
+        "res",
+        "res_overlayable",
+    ],
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestOverlayTargetNoOverlayable",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    resource_dirs: [
+        "res",
+    ],
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml
new file mode 100644
index 0000000..6038cb1
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.overlay.target">
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/assets/asset.txt b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/assets/asset.txt
new file mode 100644
index 0000000..4625e3b
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/assets/asset.txt
@@ -0,0 +1 @@
+Not overlaid
\ No newline at end of file
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/public.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/public.xml
new file mode 100644
index 0000000..283f5c1
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/public.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<resources>
+    <public-group type="string" first-id="0x7f010000">
+        <public name="policy_public" />
+    </public-group>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/values.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/values.xml
new file mode 100644
index 0000000..822194f
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/values.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<resources>
+    <string name="policy_public">Not overlaid</string>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res_overlayable/values/overlayable.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res_overlayable/values/overlayable.xml
new file mode 100644
index 0000000..0100389
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res_overlayable/values/overlayable.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<resources>
+  <overlayable name="Testing" actor="overlay://androidTest/OverlayActorVisibilityTest">
+    <policy type="public">
+      <item type="string" name="policy_public" />
+    </policy>
+  </overlayable>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
index dce853a..4de8d52 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
@@ -235,9 +235,23 @@
                     <category android:name="android.intent.category.BROWSABLE"/>
                     <category android:name="android.intent.category.DEFAULT"/>
                     <data android:scheme="https"/>
-                    <data android:path="/sub5"/>
-                    <data android:host="example5.com"/>
-                    <data android:host="invalid5"/>
+                    <data android:path="/sub6"/>
+                    <data android:host="example6.com"/>
+                    <data android:host="invalid6"/>
+                </intent-filter>
+                <intent-filter android:autoVerify="$autoVerify">
+                    <category android:name="android.intent.category.BROWSABLE"/>
+                    <category android:name="android.intent.category.DEFAULT"/>
+                    <data android:scheme="example7.com"/>
+                <intent-filter android:autoVerify="$autoVerify">
+                    <category android:name="android.intent.category.BROWSABLE"/>
+                    <category android:name="android.intent.category.DEFAULT"/>
+                    <data android:scheme="https"/>
+                </intent-filter>
+                <intent-filter android:autoVerify="$autoVerify">
+                    <category android:name="android.intent.category.BROWSABLE"/>
+                    <category android:name="android.intent.category.DEFAULT"/>
+                    <data android:path="/sub7"/>
                 </intent-filter>
             </xml>
         """.trimIndent()
@@ -324,6 +338,30 @@
                                     addDataAuthority("invalid6", null)
                                 }
                         )
+                        addIntent(
+                                ParsedIntentInfo().apply {
+                                    setAutoVerify(autoVerify)
+                                    addCategory(Intent.CATEGORY_BROWSABLE)
+                                    addCategory(Intent.CATEGORY_DEFAULT)
+                                    addDataAuthority("example7.com", null)
+                                }
+                        )
+                        addIntent(
+                                ParsedIntentInfo().apply {
+                                    setAutoVerify(autoVerify)
+                                    addCategory(Intent.CATEGORY_BROWSABLE)
+                                    addCategory(Intent.CATEGORY_DEFAULT)
+                                    addDataScheme("https")
+                                }
+                        )
+                        addIntent(
+                                ParsedIntentInfo().apply {
+                                    setAutoVerify(autoVerify)
+                                    addCategory(Intent.CATEGORY_BROWSABLE)
+                                    addCategory(Intent.CATEGORY_DEFAULT)
+                                    addDataPath("/sub7", PatternMatcher.PATTERN_LITERAL)
+                                }
+                        )
                     },
             )
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt
new file mode 100644
index 0000000..98634b2
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.server.pm.test.verify.domain
+
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import com.android.server.pm.verify.domain.DomainVerificationUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class DomainVerificationValidIntentTest {
+
+    companion object {
+
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun parameters(): Array<Params> {
+            val succeeding = mutableListOf<Params>()
+            val failing = mutableListOf<Params>()
+
+            // Start with the base intent
+            val base = Params(categorySet = emptySet()).also { succeeding += it }
+
+            // Add all explicit supported categorySet
+            succeeding += base.copy(
+                categorySet = setOf(Intent.CATEGORY_BROWSABLE),
+                matchDefaultOnly = true
+            )
+
+            failing += base.copy(
+                categorySet = setOf(Intent.CATEGORY_BROWSABLE),
+                matchDefaultOnly = false
+            )
+
+            succeeding += listOf(true, false).map {
+                base.copy(
+                    categorySet = setOf(Intent.CATEGORY_DEFAULT),
+                    matchDefaultOnly = it
+                )
+            }
+
+            succeeding += listOf(true, false).map {
+                base.copy(
+                    categorySet = setOf(Intent.CATEGORY_BROWSABLE, Intent.CATEGORY_DEFAULT),
+                    matchDefaultOnly = it
+                )
+            }
+
+            // Fail on unsupported category
+            failing += listOf(
+                emptySet(),
+                setOf(Intent.CATEGORY_BROWSABLE),
+                setOf(Intent.CATEGORY_DEFAULT),
+                setOf(Intent.CATEGORY_BROWSABLE, Intent.CATEGORY_DEFAULT)
+            ).map { base.copy(categorySet = it + "invalid.CATEGORY") }
+
+            // Fail on unsupported action
+            failing += base.copy(action = Intent.ACTION_SEND)
+
+            // Fail on unsupported domain
+            failing += base.copy(domain = "invalid")
+
+            // Fail on empty domains
+            failing += base.copy(domain = "")
+
+            // Fail on missing scheme
+            failing += base.copy(
+                uriFunction = { Uri.Builder().authority("test.com").build() }
+            )
+
+            // Fail on missing host
+            failing += base.copy(
+                domain = "",
+                uriFunction = { Uri.Builder().scheme("https").build() }
+            )
+
+            succeeding.forEach { it.expected = true }
+            failing.forEach { it.expected = false }
+            return (succeeding + failing).toTypedArray()
+        }
+
+        data class Params(
+            val action: String = Intent.ACTION_VIEW,
+            val categorySet: Set<String> = mutableSetOf(),
+            val domain: String = "test.com",
+            val matchDefaultOnly: Boolean = true,
+            var expected: Boolean? = null,
+            val uriFunction: (domain: String) -> Uri = { Uri.parse("https://$it") }
+        ) {
+            val intent = Intent(action, uriFunction(domain)).apply {
+                categorySet.forEach(::addCategory)
+            }
+
+            override fun toString() = intent.toShortString(false, false, false, false) +
+                    ", matchDefaultOnly = $matchDefaultOnly, expected = $expected"
+        }
+    }
+
+    @Parameterized.Parameter(0)
+    lateinit var params: Params
+
+    @Test
+    fun verify() {
+        val flags = if (params.matchDefaultOnly) PackageManager.MATCH_DEFAULT_ONLY else 0
+        assertThat(DomainVerificationUtils.isDomainVerificationIntent(params.intent, flags))
+            .isEqualTo(params.expected)
+    }
+}
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index e4b650c..17a5dcc 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -28,6 +28,8 @@
     <uses-permission android:name="android.permission.MANAGE_APPOPS"/>
     <uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"/>
     <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
+    <uses-permission
+        android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
 
     <!-- needed by MasterClearReceiverTest to display a system dialog -->
     <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 7654093..d55bbd1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -468,8 +468,9 @@
                 TEST_CALLING_UID);
     }
 
-    private void setPrioritizedAlarm(int type, long triggerTime, IAlarmListener listener) {
-        mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test",
+    private void setPrioritizedAlarm(int type, long triggerTime, long windowLength,
+            IAlarmListener listener) {
+        mService.setImpl(type, triggerTime, windowLength, 0, null, listener, "test",
                 FLAG_STANDALONE | FLAG_PRIORITIZE, null, null, TEST_CALLING_UID,
                 TEST_CALLING_PACKAGE, null);
     }
@@ -1685,7 +1686,7 @@
         final int numAlarms = 10;
         for (int i = 0; i < numAlarms; i++) {
             setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    new IAlarmListener.Stub() {
+                    0, new IAlarmListener.Stub() {
                         @Override
                         public void doAlarm(IAlarmCompleteListener callback)
                                 throws RemoteException {
@@ -1720,7 +1721,7 @@
         final int numAlarms = 10;
         for (int i = 0; i < numAlarms; i++) {
             setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    new IAlarmListener.Stub() {
+                    0, new IAlarmListener.Stub() {
                         @Override
                         public void doAlarm(IAlarmCompleteListener callback)
                                 throws RemoteException {
@@ -1738,12 +1739,12 @@
         }
         assertEquals(numAlarms, alarmsFired.get());
 
-        setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 3, new IAlarmListener.Stub() {
+        setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 3, 0, new IAlarmListener.Stub() {
             @Override
             public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
             }
         });
-        setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 2, new IAlarmListener.Stub() {
+        setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 2, 0, new IAlarmListener.Stub() {
             @Override
             public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
             }
@@ -2263,6 +2264,28 @@
     }
 
     @Test
+    public void minWindowPriorityAlarm() {
+        doReturn(true).when(
+                () -> CompatChanges.isChangeEnabled(
+                        eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS),
+                        anyString(), any(UserHandle.class)));
+        final long minWindow = 73;
+        setDeviceConfigLong(KEY_MIN_WINDOW, minWindow);
+
+        // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC.
+        for (int window = 1; window <= minWindow; window++) {
+            setPrioritizedAlarm(ELAPSED_REALTIME, 0, window, new IAlarmListener.Stub() {
+                @Override
+                public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
+                }
+            });
+            assertEquals(1, mService.mAlarmStore.size());
+            final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0);
+            assertEquals(window, a.windowLength);
+        }
+    }
+
+    @Test
     public void denyListPackagesAdded() {
         mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"});
         setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5");
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
new file mode 100644
index 0000000..a8d8a90
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -0,0 +1,488 @@
+/*
+ * 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.server.app;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.GameManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.function.Supplier;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class GameManagerServiceTests {
+    @Mock MockContext mMockContext;
+    private static final String TAG = "GameServiceTests";
+    private static final String PACKAGE_NAME_INVALID = "com.android.app";
+    private static final int USER_ID_1 = 1001;
+    private static final int USER_ID_2 = 1002;
+
+    private MockitoSession mMockingSession;
+    private String mPackageName;
+    @Mock
+    private PackageManager mMockPackageManager;
+
+    // Stolen from ConnectivityServiceTest.MockContext
+    class MockContext extends ContextWrapper {
+        private static final String TAG = "MockContext";
+
+        // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
+        private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
+
+        MockContext(Context base) {
+            super(base);
+        }
+
+        /**
+         * Mock checks for the specified permission, and have them behave as per {@code granted}.
+         *
+         * <p>Passing null reverts to default behavior, which does a real permission check on the
+         * test package.
+         *
+         * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
+         *                {@link PackageManager#PERMISSION_DENIED}.
+         */
+        public void setPermission(String permission, Integer granted) {
+            mMockedPermissions.put(permission, granted);
+        }
+
+        private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) {
+            final Integer granted = mMockedPermissions.get(permission);
+            return granted != null ? granted : ifAbsent.get();
+        }
+
+        @Override
+        public int checkPermission(String permission, int pid, int uid) {
+            return checkMockedPermission(
+                    permission, () -> super.checkPermission(permission, pid, uid));
+        }
+
+        @Override
+        public int checkCallingOrSelfPermission(String permission) {
+            return checkMockedPermission(
+                    permission, () -> super.checkCallingOrSelfPermission(permission));
+        }
+
+        @Override
+        public void enforceCallingOrSelfPermission(String permission, String message) {
+            final Integer granted = mMockedPermissions.get(permission);
+            if (granted == null) {
+                super.enforceCallingOrSelfPermission(permission, message);
+                return;
+            }
+
+            if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
+                throw new SecurityException("[Test] permission denied: " + permission);
+            }
+        }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return mMockPackageManager;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(DeviceConfig.class)
+                .strictness(Strictness.WARN)
+                .startMocking();
+        mMockContext = new MockContext(InstrumentationRegistry.getContext());
+        mPackageName = mMockContext.getPackageName();
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
+        final PackageInfo pi = new PackageInfo();
+        pi.packageName = mPackageName;
+        final List<PackageInfo> packages = new ArrayList<>();
+        packages.add(pi);
+        when(mMockPackageManager.getInstalledPackages(anyInt())).thenReturn(packages);
+        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(applicationInfo);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    private void mockModifyGameModeGranted() {
+        mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
+                PackageManager.PERMISSION_GRANTED);
+    }
+
+    private void mockModifyGameModeDenied() {
+        mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
+                PackageManager.PERMISSION_DENIED);
+    }
+
+    private void mockDeviceConfigDefault() {
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+                DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, "").build();
+        when(DeviceConfig.getProperties(anyString(), anyString()))
+                .thenReturn(properties);
+    }
+
+    private void mockDeviceConfigNone() {
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+                DeviceConfig.NAMESPACE_GAME_OVERLAY).build();
+        when(DeviceConfig.getProperties(anyString(), anyString()))
+                .thenReturn(properties);
+    }
+
+    private void mockDeviceConfigPerformance() {
+        String configString = "mode=2,downscaleFactor=0.5";
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+                DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build();
+        when(DeviceConfig.getProperties(anyString(), anyString()))
+                .thenReturn(properties);
+    }
+
+    private void mockDeviceConfigBattery() {
+        String configString = "mode=3,downscaleFactor=0.7";
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+                DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build();
+        when(DeviceConfig.getProperties(anyString(), anyString()))
+                .thenReturn(properties);
+    }
+
+    private void mockDeviceConfigAll() {
+        String configString = "mode=3,downscaleFactor=0.7:mode=2,downscaleFactor=0.5";
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+                DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build();
+        when(DeviceConfig.getProperties(anyString(), anyString()))
+                .thenReturn(properties);
+    }
+
+    private void mockDeviceConfigInvalid() {
+        String configString = "mode=2,downscaleFactor=0.55";
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+                DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build();
+        when(DeviceConfig.getProperties(anyString(), anyString()))
+                .thenReturn(properties);
+    }
+
+    private void mockDeviceConfigMalformed() {
+        String configString = "adsljckv=nin3rn9hn1231245:8795tq=21ewuydg";
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+                DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build();
+        when(DeviceConfig.getProperties(anyString(), anyString()))
+                .thenReturn(properties);
+    }
+
+    /**
+     * By default game mode is not supported.
+     */
+    @Test
+    public void testGameModeDefaultValue() {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+
+        mockModifyGameModeGranted();
+
+        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+    }
+
+    /**
+     * Test the default behaviour for a nonexistent user.
+     */
+    @Test
+    public void testDefaultValueForNonexistentUser() {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+
+        mockModifyGameModeGranted();
+
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_2);
+        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                gameManagerService.getGameMode(mPackageName, USER_ID_2));
+    }
+
+    /**
+     * Test getter and setter of game modes.
+     */
+    @Test
+    public void testGameMode() {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+
+        mockModifyGameModeGranted();
+
+        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
+                USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+    }
+
+    /**
+     * Test permission.MANAGE_GAME_MODE is checked
+     */
+    @Test
+    public void testGetGameModeInvalidPackageName() {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        try {
+            assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                    gameManagerService.getGameMode(PACKAGE_NAME_INVALID,
+                            USER_ID_1));
+
+            fail("GameManagerService failed to generate SecurityException when "
+                    + "permission.MANAGE_GAME_MODE is not granted.");
+        } catch (SecurityException ignored) {
+        }
+
+        // The test should throw an exception, so the test is passing if we get here.
+    }
+
+    /**
+     * Test permission.MANAGE_GAME_MODE is checked
+     */
+    @Test
+    public void testSetGameModePermissionDenied() {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+
+        // Update the game mode so we can read back something valid.
+        mockModifyGameModeGranted();
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+        // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated.
+        mockModifyGameModeDenied();
+        try {
+            gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
+                    USER_ID_1);
+
+            fail("GameManagerService failed to generate SecurityException when "
+                    + "permission.MANAGE_GAME_MODE is denied.");
+        } catch (SecurityException ignored) {
+        }
+
+        // The test should throw an exception, so the test is passing if we get here.
+        mockModifyGameModeGranted();
+        // Verify that the Game Mode value wasn't updated.
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+    }
+
+    /**
+     * Test game modes are user-specific.
+     */
+    @Test
+    public void testGameModeMultipleUsers() {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.onUserStarting(USER_ID_2);
+
+        mockModifyGameModeGranted();
+
+        // Set User 1 to Standard
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+        // Set User 2 to Performance and verify User 1 is still Standard
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
+                USER_ID_2);
+        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+                gameManagerService.getGameMode(mPackageName, USER_ID_2));
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+        // Set User 1 to Battery and verify User 2 is still Performance
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY,
+                USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_BATTERY,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+                gameManagerService.getGameMode(mPackageName, USER_ID_2));
+    }
+
+    /**
+     * Phonesky device config exists, but is only propagating the default value.
+     */
+    @Test
+    public void testDeviceConfigDefault() {
+        mockDeviceConfigDefault();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.loadDeviceConfigLocked();
+
+        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+        assertEquals(modes.length, 1);
+        assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED);
+    }
+
+    /**
+     * Phonesky device config does not exists.
+     */
+    @Test
+    public void testDeviceConfigNone() {
+        mockDeviceConfigNone();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.loadDeviceConfigLocked();
+
+        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+        assertEquals(modes.length, 1);
+        assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED);
+    }
+
+    /**
+     * Phonesky device config for performance mode exists and is valid.
+     */
+    @Test
+    public void testDeviceConfigPerformance() {
+        mockDeviceConfigPerformance();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.loadDeviceConfigLocked();
+
+        boolean perfModeExists = false;
+        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+        for (int mode : modes) {
+            if (mode == GameManager.GAME_MODE_PERFORMANCE) {
+                perfModeExists = true;
+            }
+        }
+        assertEquals(modes.length, 1);
+        assertTrue(perfModeExists);
+    }
+
+    /**
+     * Phonesky device config for battery mode exists and is valid.
+     */
+    @Test
+    public void testDeviceConfigBattery() {
+        mockDeviceConfigBattery();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.loadDeviceConfigLocked();
+
+        boolean batteryModeExists = false;
+        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+        for (int mode : modes) {
+            if (mode == GameManager.GAME_MODE_BATTERY) {
+                batteryModeExists = true;
+            }
+        }
+        assertEquals(modes.length, 1);
+        assertTrue(batteryModeExists);
+    }
+
+    /**
+     * Phonesky device configs for both battery and performance modes exists and are valid.
+     */
+    @Test
+    public void testDeviceConfigAll() {
+        mockDeviceConfigAll();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.loadDeviceConfigLocked();
+
+        boolean batteryModeExists = false;
+        boolean perfModeExists = false;
+        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+        for (int mode : modes) {
+            if (mode == GameManager.GAME_MODE_BATTERY) {
+                batteryModeExists = true;
+            } else if (mode == GameManager.GAME_MODE_PERFORMANCE) {
+                perfModeExists = true;
+            }
+        }
+        assertTrue(batteryModeExists);
+        assertTrue(perfModeExists);
+    }
+
+    /**
+     * Phonesky device config contains values that parse correctly but are not valid in game mode.
+     */
+    @Test
+    public void testDeviceConfigInvalid() {
+        mockDeviceConfigInvalid();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.loadDeviceConfigLocked();
+
+        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+        assertEquals(modes.length, 1);
+        assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED);
+    }
+
+    /**
+     * Phonesky device config is garbage.
+     */
+    @Test
+    public void testDeviceConfigMalformed() {
+        mockDeviceConfigMalformed();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        gameManagerService.loadDeviceConfigLocked();
+
+        int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+        assertEquals(modes.length, 1);
+        assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
deleted file mode 100644
index edc0d46..0000000
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ /dev/null
@@ -1,271 +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.server.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.Manifest;
-import android.app.GameManager;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.PackageManager;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-
-import java.util.HashMap;
-import java.util.function.Supplier;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class GameManagerServiceTests {
-
-    private static final String TAG = "GameServiceTests";
-    private static final String PACKAGE_NAME_INVALID = "com.android.app";
-    private static final int USER_ID_1 = 1001;
-    private static final int USER_ID_2 = 1002;
-
-    // Stolen from ConnectivityServiceTest.MockContext
-    static class MockContext extends ContextWrapper {
-        private static final String TAG = "MockContext";
-
-        // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
-        private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
-
-        @Mock
-        private final MockPackageManager mMockPackageManager;
-
-        MockContext(Context base) {
-            super(base);
-            mMockPackageManager = new MockPackageManager();
-        }
-
-        /**
-         * Mock checks for the specified permission, and have them behave as per {@code granted}.
-         *
-         * <p>Passing null reverts to default behavior, which does a real permission check on the
-         * test package.
-         *
-         * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
-         *                {@link PackageManager#PERMISSION_DENIED}.
-         */
-        public void setPermission(String permission, Integer granted) {
-            mMockedPermissions.put(permission, granted);
-        }
-
-        private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) {
-            final Integer granted = mMockedPermissions.get(permission);
-            return granted != null ? granted : ifAbsent.get();
-        }
-
-        @Override
-        public int checkPermission(String permission, int pid, int uid) {
-            return checkMockedPermission(
-                    permission, () -> super.checkPermission(permission, pid, uid));
-        }
-
-        @Override
-        public int checkCallingOrSelfPermission(String permission) {
-            return checkMockedPermission(
-                    permission, () -> super.checkCallingOrSelfPermission(permission));
-        }
-
-        @Override
-        public void enforceCallingOrSelfPermission(String permission, String message) {
-            final Integer granted = mMockedPermissions.get(permission);
-            if (granted == null) {
-                super.enforceCallingOrSelfPermission(permission, message);
-                return;
-            }
-
-            if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
-                throw new SecurityException("[Test] permission denied: " + permission);
-            }
-        }
-
-        @Override
-        public PackageManager getPackageManager() {
-            return mMockPackageManager;
-        }
-    }
-
-    @Mock
-    private MockContext mMockContext;
-
-    private String mPackageName;
-
-    @Before
-    public void setUp() throws Exception {
-        mMockContext = new MockContext(InstrumentationRegistry.getContext());
-        mPackageName = mMockContext.getPackageName();
-    }
-
-    private void mockModifyGameModeGranted() {
-        mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
-                PackageManager.PERMISSION_GRANTED);
-    }
-
-    private void mockModifyGameModeDenied() {
-        mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
-                PackageManager.PERMISSION_DENIED);
-    }
-
-    /**
-     * By default game mode is not supported.
-     */
-    @Test
-    public void testGameModeDefaultValue() {
-        GameManagerService gameManagerService = new GameManagerService(mMockContext);
-        gameManagerService.onUserStarting(USER_ID_1);
-
-        mockModifyGameModeGranted();
-
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-    }
-
-    /**
-     * Test the default behaviour for a nonexistent user.
-     */
-    @Test
-    public void testDefaultValueForNonexistentUser() {
-        GameManagerService gameManagerService = new GameManagerService(mMockContext);
-        gameManagerService.onUserStarting(USER_ID_1);
-
-        mockModifyGameModeGranted();
-
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_2);
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
-                gameManagerService.getGameMode(mPackageName, USER_ID_2));
-    }
-
-    /**
-     * Test getter and setter of game modes.
-     */
-    @Test
-    public void testGameMode() {
-        GameManagerService gameManagerService = new GameManagerService(mMockContext);
-        gameManagerService.onUserStarting(USER_ID_1);
-
-        mockModifyGameModeGranted();
-
-        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
-        assertEquals(GameManager.GAME_MODE_STANDARD,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
-                USER_ID_1);
-        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-    }
-
-    /**
-     * Test permission.MANAGE_GAME_MODE is checked
-     */
-    @Test
-    public void testGetGameModeInvalidPackageName() {
-        GameManagerService gameManagerService = new GameManagerService(mMockContext);
-        gameManagerService.onUserStarting(USER_ID_1);
-
-        try {
-            assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
-                    gameManagerService.getGameMode(PACKAGE_NAME_INVALID,
-                            USER_ID_1));
-
-            fail("GameManagerService failed to generate SecurityException when "
-                    + "permission.MANAGE_GAME_MODE is not granted.");
-        } catch (SecurityException ignored) {
-        }
-
-        // The test should throw an exception, so the test is passing if we get here.
-    }
-
-    /**
-     * Test permission.MANAGE_GAME_MODE is checked
-     */
-    @Test
-    public void testSetGameModePermissionDenied() {
-        GameManagerService gameManagerService = new GameManagerService(mMockContext);
-        gameManagerService.onUserStarting(USER_ID_1);
-
-        // Update the game mode so we can read back something valid.
-        mockModifyGameModeGranted();
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
-        assertEquals(GameManager.GAME_MODE_STANDARD,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-
-        // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated.
-        mockModifyGameModeDenied();
-        try {
-            gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
-                    USER_ID_1);
-
-            fail("GameManagerService failed to generate SecurityException when "
-                    + "permission.MANAGE_GAME_MODE is denied.");
-        } catch (SecurityException ignored) {
-        }
-
-        // The test should throw an exception, so the test is passing if we get here.
-        mockModifyGameModeGranted();
-        // Verify that the Game Mode value wasn't updated.
-        assertEquals(GameManager.GAME_MODE_STANDARD,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-    }
-
-    /**
-     * Test game modes are user-specific.
-     */
-    @Test
-    public void testGameModeMultipleUsers() {
-        GameManagerService gameManagerService = new GameManagerService(mMockContext);
-        gameManagerService.onUserStarting(USER_ID_1);
-        gameManagerService.onUserStarting(USER_ID_2);
-
-        mockModifyGameModeGranted();
-
-        // Set User 1 to Standard
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
-        assertEquals(GameManager.GAME_MODE_STANDARD,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-
-        // Set User 2 to Performance and verify User 1 is still Standard
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
-                USER_ID_2);
-        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
-                gameManagerService.getGameMode(mPackageName, USER_ID_2));
-        assertEquals(GameManager.GAME_MODE_STANDARD,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-
-        // Set User 1 to Battery and verify User 2 is still Performance
-        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY,
-                USER_ID_1);
-        assertEquals(GameManager.GAME_MODE_BATTERY,
-                gameManagerService.getGameMode(mPackageName, USER_ID_1));
-        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
-                gameManagerService.getGameMode(mPackageName, USER_ID_2));
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 73ec5b8..1b42dfa 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -31,7 +31,7 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
-import static android.app.admin.PasswordMetrics.computeForPassword;
+import static android.app.admin.PasswordMetrics.computeForPasswordOrPin;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
 import static android.net.InetAddresses.parseNumericAddress;
 
@@ -1551,6 +1551,16 @@
     @Test
     public void testSetProfileOwner_failures() throws Exception {
         // TODO Test more failure cases.  Basically test all chacks in enforceCanSetProfileOwner().
+        // Package doesn't exist and caller is not system
+        assertExpectException(SecurityException.class,
+                /* messageRegex= */ "Calling identity is not authorized",
+                () -> dpm.setProfileOwner(admin1, "owner-name", UserHandle.USER_SYSTEM));
+
+        // Package exists, but caller is not system
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+        assertExpectException(SecurityException.class,
+                /* messageRegex= */ "Calling identity is not authorized",
+                () -> dpm.setProfileOwner(admin1, "owner-name", UserHandle.USER_SYSTEM));
     }
 
     @Test
@@ -3169,6 +3179,16 @@
     }
 
     @Test
+    public void testSetUserProvisioningState_profileFinalized_canTransitionToUserUnmanaged()
+            throws Exception {
+        setupProfileOwner();
+
+        exerciseUserProvisioningTransitions(CALLER_USER_HANDLE,
+                DevicePolicyManager.STATE_USER_PROFILE_FINALIZED,
+                DevicePolicyManager.STATE_USER_UNMANAGED);
+    }
+
+    @Test
     public void testSetUserProvisioningState_illegalTransitionToAnotherInProgressState()
             throws Exception {
         setupProfileOwner();
@@ -5156,7 +5176,8 @@
 
         reset(mContext.spiedContext);
 
-        PasswordMetrics passwordMetricsNoSymbols = computeForPassword("abcdXYZ5".getBytes());
+        PasswordMetrics passwordMetricsNoSymbols = computeForPasswordOrPin(
+                "abcdXYZ5".getBytes(), /* isPin */ false);
 
         setActivePasswordState(passwordMetricsNoSymbols);
         assertThat(dpm.isActivePasswordSufficient()).isTrue();
@@ -5183,7 +5204,8 @@
         reset(mContext.spiedContext);
         assertThat(dpm.isActivePasswordSufficient()).isFalse();
 
-        PasswordMetrics passwordMetricsWithSymbols = computeForPassword("abcd.XY5".getBytes());
+        PasswordMetrics passwordMetricsWithSymbols = computeForPasswordOrPin(
+                "abcd.XY5".getBytes(), /* isPin */ false);
 
         setActivePasswordState(passwordMetricsWithSymbols);
         assertThat(dpm.isActivePasswordSufficient()).isTrue();
@@ -5237,7 +5259,7 @@
         parentDpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
 
         when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
-                .thenReturn(computeForPassword("184342".getBytes()));
+                .thenReturn(computeForPasswordOrPin("184342".getBytes(), /* isPin */ true));
 
         // Numeric password is compliant with current requirement (QUALITY_NUMERIC set explicitly
         // on the parent admin)
@@ -6360,7 +6382,7 @@
                 .thenReturn(CALLER_USER_HANDLE);
         when(getServices().lockSettingsInternal
                 .getUserPasswordMetrics(CALLER_USER_HANDLE))
-                .thenReturn(computeForPassword("asdf".getBytes()));
+                .thenReturn(computeForPasswordOrPin("asdf".getBytes(), /* isPin */ false));
 
         assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_MEDIUM);
     }
@@ -6380,10 +6402,10 @@
 
         when(getServices().lockSettingsInternal
                 .getUserPasswordMetrics(CALLER_USER_HANDLE))
-                .thenReturn(computeForPassword("asdf".getBytes()));
+                .thenReturn(computeForPasswordOrPin("asdf".getBytes(), /* isPin */ false));
         when(getServices().lockSettingsInternal
                 .getUserPasswordMetrics(parentUser.id))
-                .thenReturn(computeForPassword("parentUser".getBytes()));
+                .thenReturn(computeForPasswordOrPin("parentUser".getBytes(), /* isPin */ false));
 
         assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
     }
@@ -7059,13 +7081,15 @@
         assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_NONE);
 
         reset(mContext.spiedContext);
-        PasswordMetrics passwordMetricsNoSymbols = computeForPassword("1234".getBytes());
+        PasswordMetrics passwordMetricsNoSymbols = computeForPasswordOrPin(
+                "1234".getBytes(), /* isPin */ true);
         setActivePasswordState(passwordMetricsNoSymbols);
         assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_LOW);
         assertThat(dpm.isActivePasswordSufficient()).isFalse();
 
         reset(mContext.spiedContext);
-        passwordMetricsNoSymbols = computeForPassword("84125312943a".getBytes());
+        passwordMetricsNoSymbols = computeForPasswordOrPin(
+                "84125312943a".getBytes(), /* isPin */ false);
         setActivePasswordState(passwordMetricsNoSymbols);
         assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
         // using isActivePasswordSufficient
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 4a42940..5d60a89 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -208,4 +208,9 @@
             parcel.recycle();
         }
     }
-}
+
+    @Override
+    void setKeystorePassword(byte[] password, int userHandle) {
+
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
index 2b9a05c..efa1b04 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
@@ -20,11 +20,16 @@
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
+import android.os.Handler;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
 
@@ -94,4 +99,11 @@
     public int checkCallingOrSelfPermission(String permission) {
         return PackageManager.PERMISSION_GRANTED;
     }
+
+    @Override
+    public Intent registerReceiverAsUser(BroadcastReceiver receiver,
+            UserHandle user, IntentFilter filter, String broadcastPermission,
+            Handler scheduler) {
+        return null;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
index d888b92..876c845 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
@@ -37,7 +37,7 @@
 
         @JvmStatic
         @Parameterized.Parameters(name = "deferRebuild {0}")
-        fun parameters() = arrayOf(true, false)
+        fun parameters() = arrayOf(/*true, */false)
     }
 
     private lateinit var mapper: OverlayReferenceMapper
@@ -55,11 +55,17 @@
     fun targetWithOverlay() {
         val target = mockTarget()
         val overlay = mockOverlay()
-        val existing = mapper.addInOrder(overlay)
+        val existing = mapper.addInOrder(overlay) {
+            assertThat(it).isEmpty()
+        }
         assertEmpty()
-        mapper.addInOrder(target, existing = existing)
+        mapper.addInOrder(target, existing = existing) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay))
-        mapper.remove(target)
+        mapper.remove(target) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertEmpty()
     }
 
@@ -78,22 +84,34 @@
                         )
                 )
         )
-        val existing = mapper.addInOrder(overlay0, overlay1)
+        val existing = mapper.addInOrder(overlay0, overlay1) {
+            assertThat(it).isEmpty()
+        }
         assertEmpty()
-        mapper.addInOrder(target, existing = existing)
+        mapper.addInOrder(target, existing = existing) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay0, overlay1))
-        mapper.remove(overlay0)
+        mapper.remove(overlay0) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay1))
-        mapper.remove(target)
+        mapper.remove(target) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertEmpty()
     }
 
     @Test
     fun targetWithoutOverlay() {
         val target = mockTarget()
-        mapper.addInOrder(target)
+        mapper.addInOrder(target) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
-        mapper.remove(target)
+        mapper.remove(target) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertEmpty()
     }
 
@@ -101,11 +119,17 @@
     fun overlayWithTarget() {
         val target = mockTarget()
         val overlay = mockOverlay()
-        val existing = mapper.addInOrder(target)
+        val existing = mapper.addInOrder(target) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
-        mapper.addInOrder(overlay, existing = existing)
+        mapper.addInOrder(overlay, existing = existing) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay))
-        mapper.remove(overlay)
+        mapper.remove(overlay) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
     }
 
@@ -122,34 +146,52 @@
                         )
                 )
         )
-        mapper.addInOrder(target0, target1, overlay)
+        mapper.addInOrder(target0, target1, overlay) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target0, target1, overlay))
-        mapper.remove(target0)
+        mapper.remove(target0) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertMapping(ACTOR_PACKAGE_NAME to setOf(target1, overlay))
-        mapper.remove(target1)
+        mapper.remove(target1) {
+            assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+        }
         assertEmpty()
     }
 
     @Test
     fun overlayWithoutTarget() {
         val overlay = mockOverlay()
-        mapper.addInOrder(overlay)
+        mapper.addInOrder(overlay) {
+            assertThat(it).isEmpty()
+        }
         // An overlay can only have visibility exposed through its target
         assertEmpty()
-        mapper.remove(overlay)
+        mapper.remove(overlay) {
+            assertThat(it).isEmpty()
+        }
         assertEmpty()
     }
 
     private fun OverlayReferenceMapper.addInOrder(
         vararg pkgs: AndroidPackage,
-        existing: MutableMap<String, AndroidPackage> = mutableMapOf()
-    ) = pkgs.fold(existing) { map, pkg ->
-        addPkg(pkg, map)
-        map[pkg.packageName] = pkg
-        return@fold map
+        existing: MutableMap<String, AndroidPackage> = mutableMapOf(),
+        assertion: (changedPackages: Set<String>) -> Unit
+    ): MutableMap<String, AndroidPackage> {
+        val changedPackages = mutableSetOf<String>()
+        pkgs.forEach {
+            changedPackages += addPkg(it, existing)
+            existing[it.packageName] = it
+        }
+        assertion(changedPackages)
+        return existing
     }
 
-    private fun OverlayReferenceMapper.remove(pkg: AndroidPackage) = removePkg(pkg.packageName)
+    private fun OverlayReferenceMapper.remove(
+        pkg: AndroidPackage,
+        assertion: (changedPackages: Set<String>) -> Unit
+    ) = assertion(removePkg(pkg.packageName))
 
     private fun assertMapping(vararg pairs: Pair<String, Set<AndroidPackage>>) {
         val expected = pairs.associate { it }
diff --git a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
index 8070bd1..558e259 100644
--- a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
@@ -7,6 +7,14 @@
           "include-filter": "com.android.server.om."
         }
       ]
+    },
+    {
+      "name": "PackageManagerServiceHostTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.pm.test.OverlayActorVisibilityTest"
+        }
+      ]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 9f428c7..67dd055 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -799,10 +799,18 @@
         simulateAddBasicAndroid(appsFilter);
         appsFilter.onSystemReady();
 
-        PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID);
+        // Packages must be added in actor -> overlay -> target order so that the implicit
+        // visibility of the actor into the overlay can be tested
+
+        PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_APPID);
         PackageSetting overlaySetting =
                 simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_APPID);
-        PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_APPID);
+
+        // Actor can not see overlay (yet)
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
+                overlaySetting, SYSTEM_USER));
+
+        PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID);
 
         // Actor can see both target and overlay
         assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
@@ -821,6 +829,12 @@
                 actorSetting, SYSTEM_USER));
         assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_APPID, overlaySetting,
                 actorSetting, SYSTEM_USER));
+
+        appsFilter.removePackage(targetSetting);
+
+        // Actor loses visibility to the overlay via removal of the target
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
+                overlaySetting, SYSTEM_USER));
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
index 809b6d5..182848b4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
@@ -123,6 +123,7 @@
         for (long i = cal.getTimeInMillis(); i >= 5; i--) {
             File file = mock(File.class);
             when(file.getName()).thenReturn(String.valueOf(i));
+            when(file.getAbsolutePath()).thenReturn(String.valueOf(i));
             AtomicFile af = new AtomicFile(file);
             expectedFiles.add(af);
             mDataBase.mHistoryFiles.addLast(af);
@@ -133,6 +134,7 @@
         for (int i = 5; i >= 0; i--) {
             File file = mock(File.class);
             when(file.getName()).thenReturn(String.valueOf(cal.getTimeInMillis() - i));
+            when(file.getAbsolutePath()).thenReturn(String.valueOf(cal.getTimeInMillis() - i));
             AtomicFile af = new AtomicFile(file);
             mDataBase.mHistoryFiles.addLast(af);
         }
@@ -158,6 +160,7 @@
         for (long i = cal.getTimeInMillis(); i >= 5; i--) {
             File file = mock(File.class);
             when(file.getName()).thenReturn(i + ".bak");
+            when(file.getAbsolutePath()).thenReturn(i + ".bak");
             AtomicFile af = new AtomicFile(file);
             mDataBase.mHistoryFiles.addLast(af);
         }
@@ -415,4 +418,36 @@
         assertThat(mDataBase.mBuffer).isNotEqualTo(nh);
         verify(mAlarmManager, times(1)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any());
     }
+
+    @Test
+    public void testRemoveFilePathFromHistory_hasMatch() throws Exception {
+        for (int i = 0; i < 5; i++) {
+            AtomicFile af = mock(AtomicFile.class);
+            when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i));
+            mDataBase.mHistoryFiles.addLast(af);
+        }
+        // Baseline size of history files
+        assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5);
+
+        // Remove only file number 3
+        String filePathToRemove = new File(mRootDir, "af3").getAbsolutePath();
+        mDataBase.removeFilePathFromHistory(filePathToRemove);
+        assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(4);
+    }
+
+    @Test
+    public void testRemoveFilePathFromHistory_noMatch() throws Exception {
+        for (int i = 0; i < 5; i++) {
+            AtomicFile af = mock(AtomicFile.class);
+            when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i));
+            mDataBase.mHistoryFiles.addLast(af);
+        }
+        // Baseline size of history files
+        assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5);
+
+        // Attempt to remove a filename that doesn't exist, expect nothing to break or change
+        String filePathToRemove = new File(mRootDir, "af.thisfileisfake").getAbsolutePath();
+        mDataBase.removeFilePathFromHistory(filePathToRemove);
+        assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5);
+    }
 }
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 87efaa2..37d7198 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5057,7 +5057,8 @@
     }
 
     @Test
-    public void testToastRateLimiterCanPreventShowCallForCustomToast() throws Exception {
+    public void testToastRateLimiterWontPreventShowCallForCustomToastWhenInForeground()
+            throws Exception {
         final String testPackage = "testPackageName";
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
@@ -5075,30 +5076,7 @@
         INotificationManager nmService = (INotificationManager) mService.mService;
 
         nmService.enqueueToast(testPackage, token, callback, 2000, 0);
-        verify(callback, times(0)).show(any());
-    }
-
-    @Test
-    public void testCustomToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception {
-        final String testPackage = "testPackageName";
-        assertEquals(0, mService.mToastQueue.size());
-        mService.isSystemUid = false;
-        setToastRateIsWithinQuota(false); // rate limit reached
-        // Avoids rate limiting.
-        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true);
-
-        // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
-                .thenReturn(false);
-
-        setAppInForegroundForToasts(mUid, true);
-
-        Binder token = new Binder();
-        ITransientNotification callback = mock(ITransientNotification.class);
-        INotificationManager nmService = (INotificationManager) mService.mService;
-
-        nmService.enqueueToast(testPackage, token, callback, 2000, 0);
-        verify(callback).show(any());
+        verify(callback, times(1)).show(any());
     }
 
     @Test
@@ -5206,12 +5184,14 @@
     }
 
     @Test
-    public void testToastRateLimiterCanPreventShowCallForTextToast() throws Exception {
+    public void testToastRateLimiterCanPreventShowCallForTextToast_whenInBackground()
+            throws Exception {
         final String testPackage = "testPackageName";
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(false); // rate limit reached
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
+        setAppInForegroundForToasts(mUid, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5226,12 +5206,35 @@
     }
 
     @Test
+    public void testToastRateLimiterWontPreventShowCallForTextToast_whenInForeground()
+            throws Exception {
+        final String testPackage = "testPackageName";
+        assertEquals(0, mService.mToastQueue.size());
+        mService.isSystemUid = false;
+        setToastRateIsWithinQuota(false); // rate limit reached
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
+        setAppInForegroundForToasts(mUid, true);
+
+        // package is not suspended
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+                .thenReturn(false);
+
+        Binder token = new Binder();
+        INotificationManager nmService = (INotificationManager) mService.mService;
+
+        nmService.enqueueTextToast(testPackage, token, "Text", 2000, 0, null);
+        verify(mStatusBar, times(1))
+                .showToast(anyInt(), any(), any(), any(), any(), anyInt(), any());
+    }
+
+    @Test
     public void testTextToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception {
         final String testPackage = "testPackageName";
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(false); // rate limit reached
         setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true);
+        setAppInForegroundForToasts(mUid, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 0afd39f..2f52352 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1971,7 +1971,8 @@
 
         // test misc display overrides
         assertEquals(ignoreOrientationRequests, testDisplayContent.mIgnoreOrientationRequest);
-        assertEquals(fixedOrientationLetterboxRatio, mWm.getFixedOrientationLetterboxAspectRatio(),
+        assertEquals(fixedOrientationLetterboxRatio,
+                mWm.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(),
                 0 /* delta */);
     }
 
@@ -2011,7 +2012,8 @@
 
         // test misc display overrides
         assertEquals(ignoreOrientationRequests, testDisplayContent.mIgnoreOrientationRequest);
-        assertEquals(fixedOrientationLetterboxRatio, mWm.getFixedOrientationLetterboxAspectRatio(),
+        assertEquals(fixedOrientationLetterboxRatio,
+                mWm.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(),
                 0 /* delta */);
     }
 
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 7c2cfab..95b7443 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -335,11 +335,11 @@
         final WindowState window = createWindow(null, TYPE_BASE_APPLICATION, mActivity, "window");
 
         assertEquals(window, mActivity.findMainWindow());
-        assertTrue(mActivity.isLetterboxed(mActivity.findMainWindow()));
+        assertTrue(mActivity.mLetterboxUiController.isLetterboxed(mActivity.findMainWindow()));
 
         window.mAttrs.flags |= FLAG_SHOW_WALLPAPER;
 
-        assertFalse(mActivity.isLetterboxed(mActivity.findMainWindow()));
+        assertFalse(mActivity.mLetterboxUiController.isLetterboxed(mActivity.findMainWindow()));
     }
 
     @Test
@@ -1023,7 +1023,7 @@
 
         // Portrait fixed app with min aspect ratio higher that aspect ratio override for fixed
         // orientation letterbox.
-        mActivity.mWmService.setFixedOrientationLetterboxAspectRatio(1.1f);
+        mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);
         mActivity.info.setMinAspectRatio(3);
         prepareUnresizable(mActivity, /* maxAspect= */ 0, SCREEN_ORIENTATION_PORTRAIT);
 
@@ -1054,7 +1054,7 @@
 
         // Portrait fixed app with max aspect ratio lower that aspect ratio override for fixed
         // orientation letterbox.
-        mActivity.mWmService.setFixedOrientationLetterboxAspectRatio(3);
+        mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(3);
         prepareUnresizable(mActivity, /* maxAspect= */ 2, SCREEN_ORIENTATION_PORTRAIT);
 
         final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
@@ -1085,7 +1085,7 @@
         // Portrait fixed app with min aspect ratio higher that aspect ratio override for fixed
         // orientation letterbox.
         final float fixedOrientationLetterboxAspectRatio = 1.1f;
-        mActivity.mWmService.setFixedOrientationLetterboxAspectRatio(
+        mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(
                 fixedOrientationLetterboxAspectRatio);
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
 
@@ -1515,6 +1515,129 @@
         assertEquals(primarySplitBounds, letterboxedBounds);
     }
 
+    @Test
+    public void testUpdateResolvedBoundsHorizontalPosition_left() {
+        // Display configured as (2800, 1400).
+        assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+                /* letterboxHorizontalPositionMultiplier */ 0.0f,
+                // At launch.
+                /* fixedOrientationLetterbox */ new Rect(0, 0, 700, 1400),
+                // After 90 degree rotation.
+                /* sizeCompatUnscaled */ new Rect(0, 0, 700, 1400),
+                // After the display is resized to (700, 1400).
+                /* sizeCompatScaled */ new Rect(0, 0, 350, 700));
+    }
+
+    @Test
+    public void testUpdateResolvedBoundsHorizontalPosition_center() {
+        // Display configured as (2800, 1400).
+        assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+                /* letterboxHorizontalPositionMultiplier */ 0.5f,
+                // At launch.
+                /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
+                // After 90 degree rotation.
+                /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
+                // After the display is resized to (700, 1400).
+                /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
+    }
+
+    @Test
+    public void testUpdateResolvedBoundsHorizontalPosition_invalidMultiplier_defaultToCenter() {
+        // Display configured as (2800, 1400).
+
+        // Below 0.0.
+        assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+                /* letterboxHorizontalPositionMultiplier */ -1.0f,
+                // At launch.
+                /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
+                // After 90 degree rotation.
+                /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
+                // After the display is resized to (700, 1400).
+                /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
+
+        // Above 1.0
+        assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+                /* letterboxHorizontalPositionMultiplier */ 2.0f,
+                // At launch.
+                /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
+                // After 90 degree rotation.
+                /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
+                // After the display is resized to (700, 1400).
+                /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
+    }
+
+    @Test
+    public void testUpdateResolvedBoundsHorizontalPosition_right() {
+        // Display configured as (2800, 1400).
+        assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+                /* letterboxHorizontalPositionMultiplier */ 1.0f,
+                // At launch.
+                /* fixedOrientationLetterbox */ new Rect(2100, 0, 2800, 1400),
+                // After 90 degree rotation.
+                /* sizeCompatUnscaled */ new Rect(700, 0, 1400, 1400),
+                // After the display is resized to (700, 1400).
+                /* sizeCompatScaled */ new Rect(1050, 0, 1400, 700));
+    }
+
+    private void assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+            float letterboxHorizontalPositionMultiplier, Rect fixedOrientationLetterbox,
+            Rect sizeCompatUnscaled, Rect sizeCompatScaled) {
+        // Set up a display in landscape and ignoring orientation request.
+        setUpDisplaySizeWithApp(2800, 1400);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+        mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
+                letterboxHorizontalPositionMultiplier);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+        assertEquals(fixedOrientationLetterbox, mActivity.getBounds());
+
+        // Rotate to put activity in size compat mode.
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+        assertTrue(mActivity.inSizeCompatMode());
+        // Activity is in size compat mode but not scaled.
+        assertEquals(sizeCompatUnscaled, mActivity.getBounds());
+
+        // Force activity to scaled down for size compat mode.
+        resizeDisplay(mTask.mDisplayContent, 700, 1400);
+
+        assertTrue(mActivity.inSizeCompatMode());
+        assertScaled();
+        assertEquals(sizeCompatScaled, mActivity.getBounds());
+    }
+
+    @Test
+    public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() {
+        // When activity width equals parent width, multiplier shouldn't have any effect.
+        assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
+                /* letterboxHorizontalPositionMultiplier */ 0.0f);
+        assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
+                /* letterboxHorizontalPositionMultiplier */ 0.5f);
+        assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
+                /* letterboxHorizontalPositionMultiplier */ 1.0f);
+    }
+
+    private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
+            float letterboxHorizontalPositionMultiplier) {
+        // Set up a display in landscape and ignoring orientation request.
+        setUpDisplaySizeWithApp(2800, 1400);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+        mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
+                letterboxHorizontalPositionMultiplier);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+        assertFitted();
+
+        // Rotate to put activity in size compat mode.
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+        assertTrue(mActivity.inSizeCompatMode());
+        // Activity is in size compat mode but not scaled.
+        assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds());
+    }
+
     private static WindowState addWindowToActivity(ActivityRecord activity) {
         final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
         params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index f3616da6c..619aee6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -17,6 +17,8 @@
 package com.android.server.wm;
 
 import android.annotation.NonNull;
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -253,4 +255,14 @@
     public SurfaceControl.Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) {
         return this;
     }
+
+    @Override
+    public SurfaceControl.Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) {
+        return this;
+    }
+
+    @Override
+    public SurfaceControl.Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) {
+        return this;
+    }
 }
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 5bafbbd..2d4e4ef 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -101,6 +101,7 @@
 import com.android.internal.policy.AttributeCache;
 import com.android.internal.util.ArrayUtils;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.runner.Description;
@@ -207,11 +208,26 @@
         // Ensure letterbox aspect ratio is not overridden on any device target.
         // {@link com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}, is set
         // on some device form factors.
-        mAtm.mWindowManager.setFixedOrientationLetterboxAspectRatio(0);
+        mAtm.mWindowManager.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(0);
+        // Ensure letterbox position multiplier is not overridden on any device target.
+        // {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier},
+        // may be set on some device form factors.
+        mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f);
 
         checkDeviceSpecificOverridesNotApplied();
     }
 
+    @After
+    public void tearDown() throws Exception {
+        // Revert back to device overrides.
+        mAtm.mWindowManager.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(
+                mContext.getResources().getFloat(
+                        com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio));
+        mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
+                mContext.getResources().getFloat(
+                    com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier));
+    }
+
     /**
      * Check that device-specific overrides are not applied. Only need to check once during entire
      * test run for each case: global overrides, default display, and test display.
@@ -219,7 +235,8 @@
     private void checkDeviceSpecificOverridesNotApplied() {
         // Check global overrides
         if (!sGlobalOverridesChecked) {
-            assertEquals(0, mWm.getFixedOrientationLetterboxAspectRatio(), 0 /* delta */);
+            assertEquals(0, mWm.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(),
+                    0 /* delta */);
             sGlobalOverridesChecked = true;
         }
         // Check display-specific overrides
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 2e692e6..7f24c36 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -2139,6 +2139,12 @@
                 Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
                         + "forceRestart=" + forceRestart);
             }
+            if (mCurrentGadgetHalVersion < UsbManager.GADGET_HAL_V1_2) {
+                if ((functions & UsbManager.FUNCTION_NCM) != 0) {
+                    Slog.e(TAG, "Could not set unsupported function for the GadgetHal");
+                    return;
+                }
+            }
             if (mCurrentFunctions != functions
                     || !mCurrentFunctionsApplied
                     || forceRestart) {
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index 32533cb..0d6cd5a 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -760,6 +760,7 @@
     private static void assignExclusiveSmsPermissionsToSystemApp(Context context,
             PackageManager packageManager, AppOpsManager appOps, String packageName,
             boolean sigatureMatch) {
+        if (packageName == null) return;
         // First check package signature matches the caller's package signature.
         // Since this class is only used internally by the system, this check makes sure
         // the package signature matches system signature.
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index bde62fb..ac01afa 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -134,7 +134,7 @@
      *
      * Range [0, 15] for each CQI.
      */
-    private List<Integer> mCsiCqiReport;;
+    private List<Integer> mCsiCqiReport;
     private int mSsRsrp;
     private int mSsRsrq;
     private int mSsSinr;
@@ -172,13 +172,13 @@
      * @hide
      */
     public CellSignalStrengthNr(int csiRsrp, int csiRsrq, int csiSinr, int csiCqiTableIndex,
-            List<Integer> csiCqiReport, int ssRsrp, int ssRsrq, int ssSinr) {
+            List<Byte> csiCqiReport, int ssRsrp, int ssRsrq, int ssSinr) {
         mCsiRsrp = inRangeOrUnavailable(csiRsrp, -140, -44);
         mCsiRsrq = inRangeOrUnavailable(csiRsrq, -20, -3);
         mCsiSinr = inRangeOrUnavailable(csiSinr, -23, 23);
         mCsiCqiTableIndex = inRangeOrUnavailable(csiCqiTableIndex, 1, 3);
-        mCsiCqiReport =  csiCqiReport.stream()
-                .map(cqi -> new Integer(inRangeOrUnavailable(cqi.intValue(), 1, 3)))
+        mCsiCqiReport = csiCqiReport.stream()
+                .map(cqi -> new Integer(inRangeOrUnavailable(Byte.toUnsignedInt(cqi), 1, 3)))
                 .collect(Collectors.toList());
         mSsRsrp = inRangeOrUnavailable(ssRsrp, -140, -44);
         mSsRsrq = inRangeOrUnavailable(ssRsrq, -43, 20);
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index c8ed82c..0539897 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1055,6 +1055,20 @@
      */
     public static final int HANDOVER_FAILED = 0x10006;
 
+    /**
+     * Enterprise setup failure: duplicate CID in DataCallResponse.
+     *
+     * @hide
+     */
+    public static final int DUPLICATE_CID = 0x10007;
+
+    /**
+     * Enterprise setup failure: no default data connection set up yet.
+     *
+     * @hide
+     */
+    public static final int NO_DEFAULT_DATA = 0x10008;
+
     private static final Map<Integer, String> sFailCauseMap;
     static {
         sFailCauseMap = new HashMap<>();
@@ -1481,6 +1495,9 @@
         sFailCauseMap.put(UNACCEPTABLE_NETWORK_PARAMETER,
                 "UNACCEPTABLE_NETWORK_PARAMETER");
         sFailCauseMap.put(LOST_CONNECTION, "LOST_CONNECTION");
+        sFailCauseMap.put(HANDOVER_FAILED, "HANDOVER_FAILED");
+        sFailCauseMap.put(DUPLICATE_CID, "DUPLICATE_CID");
+        sFailCauseMap.put(NO_DEFAULT_DATA, "NO_DEFAULT_DATA");
     }
 
     private DataFailCause() {
@@ -1580,6 +1597,7 @@
                             add(RADIO_NOT_AVAILABLE);
                             add(UNACCEPTABLE_NETWORK_PARAMETER);
                             add(SIGNAL_LOST);
+                            add(DUPLICATE_CID);
                         }
                     };
                 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f0771be..c8e5e56 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4885,8 +4885,9 @@
      * Return the set of IMSIs that should be considered "merged together" for data usage
      * purposes. This API merges IMSIs based on subscription grouping: IMSI of those in the same
      * group will all be returned.
-     * Return the current IMSI if there is no subscription group. See
-     * {@link SubscriptionManager#createSubscriptionGroup(List)} for the definition of a group.
+     * Return the current IMSI if there is no subscription group, see
+     * {@link SubscriptionManager#createSubscriptionGroup(List)} for the definition of a group,
+     * otherwise return an empty array if there is a failure.
      *
      * @hide
      */
diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java
index 6c1c653..5642549 100644
--- a/telephony/java/android/telephony/data/QosBearerFilter.java
+++ b/telephony/java/android/telephony/data/QosBearerFilter.java
@@ -31,7 +31,6 @@
 import java.util.List;
 import java.util.Objects;
 
-
 /**
  * Class that stores QOS filter parameters as defined in
  * 3gpp 24.008 10.5.6.12 and 3gpp 24.501 9.11.4.13.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 0593615..a540dff 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -23,7 +23,7 @@
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_LAYER_NAME
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_WINDOW_NAME
 
-val LAUNCHER_TITLE = arrayOf("Wallpaper", "Launcher", "com.google.android.googlequicksearchbox")
+val HOME_WINDOW_TITLE = arrayOf("Wallpaper", "Launcher")
 
 fun FlickerTestParameter.statusBarWindowIsAlwaysVisible() {
     assertWm {
@@ -41,23 +41,23 @@
     assertWm {
         this.showsAppWindowOnTop(testApp.getPackage())
             .then()
-            .showsAppWindowOnTop(*LAUNCHER_TITLE)
+            .showsAppWindowOnTop(*HOME_WINDOW_TITLE)
     }
 }
 
 fun FlickerTestParameter.launcherWindowBecomesVisible() {
     assertWm {
-        this.hidesBelowAppWindow(*LAUNCHER_TITLE)
+        this.hidesBelowAppWindow(*HOME_WINDOW_TITLE)
             .then()
-            .showsBelowAppWindow(*LAUNCHER_TITLE)
+            .showsBelowAppWindow(*HOME_WINDOW_TITLE)
     }
 }
 
 fun FlickerTestParameter.launcherWindowBecomesInvisible() {
     assertWm {
-        this.showsBelowAppWindow(*LAUNCHER_TITLE)
+        this.showsBelowAppWindow(*HOME_WINDOW_TITLE)
             .then()
-            .hidesBelowAppWindow(*LAUNCHER_TITLE)
+            .hidesBelowAppWindow(*HOME_WINDOW_TITLE)
     }
 }
 
@@ -179,7 +179,7 @@
 
 fun FlickerTestParameter.appLayerReplacesLauncher(appName: String) {
     assertLayers {
-        this.isVisible(*LAUNCHER_TITLE)
+        this.isVisible(*HOME_WINDOW_TITLE)
             .then()
             .isVisible(appName)
     }
@@ -190,7 +190,7 @@
         this.isVisible(testApp.getPackage())
             .then()
             .isInvisible(testApp.getPackage())
-            .isVisible(*LAUNCHER_TITLE)
+            .isVisible(*HOME_WINDOW_TITLE)
     }
 }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
index 6a7309c..01e34d9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
@@ -18,11 +18,11 @@
 
 import android.platform.helpers.IAppHelper
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.LAUNCHER_TITLE
+import com.android.server.wm.flicker.HOME_WINDOW_TITLE
 
 fun FlickerTestParameter.appWindowReplacesLauncherAsTopWindow(testApp: IAppHelper) {
     assertWm {
-        this.showsAppWindowOnTop(*LAUNCHER_TITLE)
+        this.showsAppWindowOnTop(*HOME_WINDOW_TITLE)
             .then()
             .showsAppWindowOnTop("Snapshot", testApp.getPackage())
     }
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java
index f6d9a73..487c856 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java
@@ -56,6 +56,7 @@
         CanvasProperty<Float> mY;
         CanvasProperty<Float> mRadius;
         CanvasProperty<Float> mProgress;
+        CanvasProperty<Float> mNoisePhase;
         CanvasProperty<Paint> mPaint;
         RuntimeShader mRuntimeShader;
 
@@ -99,6 +100,7 @@
             mY = CanvasProperty.createFloat(200.0f);
             mRadius = CanvasProperty.createFloat(150.0f);
             mProgress = CanvasProperty.createFloat(0.0f);
+            mNoisePhase = CanvasProperty.createFloat(0.0f);
 
             Paint p = new Paint();
             p.setAntiAlias(true);
@@ -115,7 +117,8 @@
 
             if (canvas.isHardwareAccelerated()) {
                 RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
-                recordingCanvas.drawRipple(mX, mY, mRadius, mPaint, mProgress, mRuntimeShader);
+                recordingCanvas.drawRipple(mX, mY, mRadius, mPaint, mProgress, mNoisePhase,
+                        mRuntimeShader);
             }
         }
 
@@ -141,6 +144,9 @@
                     mProgress, mToggle ? 1.0f : 0.0f));
 
             mRunningAnimations.add(new RenderNodeAnimator(
+                    mNoisePhase, DURATION));
+
+            mRunningAnimations.add(new RenderNodeAnimator(
                     mPaint, RenderNodeAnimator.PAINT_ALPHA, 64.0f));
 
             // Will be "chained" to run after the above
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 6d852bf..9b74583 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -312,7 +312,7 @@
             .addCapability(NET_CAPABILITY_EIMS)
             .addCapability(NET_CAPABILITY_NOT_METERED);
         if (isAtLeastS()) {
-            netCap.setSubIds(Set.of(TEST_SUBID1, TEST_SUBID2));
+            netCap.setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2));
             netCap.setUids(uids);
         }
         if (isAtLeastR()) {
@@ -642,16 +642,16 @@
             assertTrue(nc2.appliesToUid(22));
 
             // Verify the subscription id list can be combined only when they are equal.
-            nc1.setSubIds(Set.of(TEST_SUBID1, TEST_SUBID2));
-            nc2.setSubIds(Set.of(TEST_SUBID2));
+            nc1.setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2));
+            nc2.setSubscriptionIds(Set.of(TEST_SUBID2));
             assertThrows(IllegalStateException.class, () -> nc2.combineCapabilities(nc1));
 
-            nc2.setSubIds(Set.of());
+            nc2.setSubscriptionIds(Set.of());
             assertThrows(IllegalStateException.class, () -> nc2.combineCapabilities(nc1));
 
-            nc2.setSubIds(Set.of(TEST_SUBID2, TEST_SUBID1));
+            nc2.setSubscriptionIds(Set.of(TEST_SUBID2, TEST_SUBID1));
             nc2.combineCapabilities(nc1);
-            assertEquals(Set.of(TEST_SUBID2, TEST_SUBID1), nc2.getSubIds());
+            assertEquals(Set.of(TEST_SUBID2, TEST_SUBID1), nc2.getSubscriptionIds());
         }
     }
 
@@ -806,20 +806,20 @@
         assertEquals(nc1, nc2);
 
         if (isAtLeastS()) {
-            assertThrows(NullPointerException.class, () -> nc1.setSubIds(null));
-            nc1.setSubIds(Set.of());
+            assertThrows(NullPointerException.class, () -> nc1.setSubscriptionIds(null));
+            nc1.setSubscriptionIds(Set.of());
             nc2.set(nc1);
             assertEquals(nc1, nc2);
 
-            nc1.setSubIds(Set.of(TEST_SUBID1));
+            nc1.setSubscriptionIds(Set.of(TEST_SUBID1));
             nc2.set(nc1);
             assertEquals(nc1, nc2);
 
-            nc2.setSubIds(Set.of(TEST_SUBID2, TEST_SUBID1));
+            nc2.setSubscriptionIds(Set.of(TEST_SUBID2, TEST_SUBID1));
             nc2.set(nc1);
             assertEquals(nc1, nc2);
 
-            nc2.setSubIds(Set.of(TEST_SUBID3, TEST_SUBID2));
+            nc2.setSubscriptionIds(Set.of(TEST_SUBID3, TEST_SUBID2));
             assertNotEquals(nc1, nc2);
         }
     }
@@ -908,8 +908,8 @@
         // satisfy these requests.
         final NetworkCapabilities nc = new NetworkCapabilities.Builder()
                 .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
-                .setSubIds(new ArraySet<>(subIds)).build();
-        assertEquals(new ArraySet<>(subIds), nc.getSubIds());
+                .setSubscriptionIds(new ArraySet<>(subIds)).build();
+        assertEquals(new ArraySet<>(subIds), nc.getSubscriptionIds());
         return nc;
     }
 
@@ -921,11 +921,11 @@
         final NetworkCapabilities ncWithoutRequestedIds = capsWithSubIds(TEST_SUBID3);
 
         final NetworkRequest requestWithoutId = new NetworkRequest.Builder().build();
-        assertEmpty(requestWithoutId.networkCapabilities.getSubIds());
+        assertEmpty(requestWithoutId.networkCapabilities.getSubscriptionIds());
         final NetworkRequest requestWithIds = new NetworkRequest.Builder()
-                .setSubIds(Set.of(TEST_SUBID1, TEST_SUBID2)).build();
+                .setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2)).build();
         assertEquals(Set.of(TEST_SUBID1, TEST_SUBID2),
-                requestWithIds.networkCapabilities.getSubIds());
+                requestWithIds.networkCapabilities.getSubscriptionIds());
 
         assertFalse(requestWithIds.canBeSatisfiedBy(ncWithoutId));
         assertTrue(requestWithIds.canBeSatisfiedBy(ncWithOtherIds));
@@ -1138,8 +1138,8 @@
 
         if (isAtLeastS()) {
             final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
-                    .setSubIds(Set.of(TEST_SUBID1)).build();
-            assertEquals(Set.of(TEST_SUBID1), nc2.getSubIds());
+                    .setSubscriptionIds(Set.of(TEST_SUBID1)).build();
+            assertEquals(Set.of(TEST_SUBID1), nc2.getSubscriptionIds());
         }
     }
 }
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index 6cbdd25..19f8843 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -384,7 +384,7 @@
                 eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
                 eq(testPkgName), eq(testAttributionTag));
 
-        manager.requestBackgroundNetwork(request, handler, callback);
+        manager.requestBackgroundNetwork(request, callback, handler);
         verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
                 eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
                 eq(testPkgName), eq(testAttributionTag));
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 1b0a500..b71be59 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -2668,25 +2668,6 @@
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        // Bring up wifi with a score of 70.
-        // Cell is lingered because it would not satisfy any request, even if it validated.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.adjustScore(50);
-        mWiFiNetworkAgent.connect(false);   // Score: 70
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
-        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
-        assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
-
-        // Tear down wifi.
-        mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
-        assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
-
         // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but
         // it's arguably correct to linger it, since it was the default network before it validated.
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -2991,11 +2972,17 @@
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
-        // BUG: the network will no longer linger, even though it's validated and outscored.
-        // TODO: fix this.
         mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
         mEthernetNetworkAgent.connect(true);
+        // BUG: with the legacy int-based scoring code, the network will no longer linger, even
+        // though it's validated and outscored.
+        // The new policy-based scoring code fixes this.
+        // TODO: remove the line below and replace with the three commented lines when
+        // the policy-based scoring code is turned on.
         callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
+        // callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
+        // callback.expectCallback(CallbackEntry.LOSING, mWiFiNetworkAgent);
+        // callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
         assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         callback.assertNoCallback();
 
@@ -4313,7 +4300,7 @@
         final TestNetworkCallback cellBgCallback = new TestNetworkCallback();
         mCm.requestBackgroundNetwork(new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_CELLULAR).build(),
-                mCsHandlerThread.getThreadHandler(), cellBgCallback);
+                cellBgCallback, mCsHandlerThread.getThreadHandler());
 
         // Make callbacks for monitoring.
         final NetworkRequest request = new NetworkRequest.Builder().build();
@@ -12602,12 +12589,12 @@
     public void testSubIdsClearedWithoutNetworkFactoryPermission() throws Exception {
         mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED);
         final NetworkCapabilities nc = new NetworkCapabilities();
-        nc.setSubIds(Collections.singleton(Process.myUid()));
+        nc.setSubscriptionIds(Collections.singleton(Process.myUid()));
 
         final NetworkCapabilities result =
                 mService.networkCapabilitiesRestrictedForCallerPermissions(
                         nc, Process.myPid(), Process.myUid());
-        assertTrue(result.getSubIds().isEmpty());
+        assertTrue(result.getSubscriptionIds().isEmpty());
     }
 
     @Test
@@ -12616,17 +12603,17 @@
 
         final Set<Integer> subIds = Collections.singleton(Process.myUid());
         final NetworkCapabilities nc = new NetworkCapabilities();
-        nc.setSubIds(subIds);
+        nc.setSubscriptionIds(subIds);
 
         final NetworkCapabilities result =
                 mService.networkCapabilitiesRestrictedForCallerPermissions(
                         nc, Process.myPid(), Process.myUid());
-        assertEquals(subIds, result.getSubIds());
+        assertEquals(subIds, result.getSubscriptionIds());
     }
 
     private NetworkRequest getRequestWithSubIds() {
         return new NetworkRequest.Builder()
-                .setSubIds(Collections.singleton(Process.myUid()))
+                .setSubscriptionIds(Collections.singleton(Process.myUid()))
                 .build();
     }
 
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 9a66343..907cb46 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -696,7 +696,7 @@
                         .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                         .addTransportType(transport);
         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-            ncBuilder.setSubIds(Collections.singleton(subId));
+            ncBuilder.setSubscriptionIds(Collections.singleton(subId));
         }
 
         return ncBuilder;
diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
index b592000..8289e85 100644
--- a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
@@ -153,21 +153,19 @@
         verify(mConnectivityManager)
                 .requestBackgroundNetwork(
                         eq(getWifiRequest(expectedSubIds)),
-                        any(),
-                        any(NetworkBringupCallback.class));
+                        any(NetworkBringupCallback.class),
+                        any());
         for (final int subId : expectedSubIds) {
             verify(mConnectivityManager)
                     .requestBackgroundNetwork(
                             eq(getCellRequestForSubId(subId)),
-                            any(),
-                            any(NetworkBringupCallback.class));
+                            any(NetworkBringupCallback.class), any());
         }
 
         verify(mConnectivityManager)
                 .requestBackgroundNetwork(
                         eq(getRouteSelectionRequest(expectedSubIds)),
-                        any(),
-                        any(RouteSelectionCallback.class));
+                        any(RouteSelectionCallback.class), any());
     }
 
     @Test
@@ -191,7 +189,7 @@
     private NetworkRequest getWifiRequest(Set<Integer> netCapsSubIds) {
         return getExpectedRequestBase()
                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                .setSubIds(netCapsSubIds)
+                .setSubscriptionIds(netCapsSubIds)
                 .build();
     }
 
@@ -203,7 +201,7 @@
     }
 
     private NetworkRequest getRouteSelectionRequest(Set<Integer> netCapsSubIds) {
-        return getExpectedRequestBase().setSubIds(netCapsSubIds).build();
+        return getExpectedRequestBase().setSubscriptionIds(netCapsSubIds).build();
     }
 
     private NetworkRequest.Builder getExpectedRequestBase() {
@@ -267,8 +265,8 @@
         verify(mConnectivityManager)
                 .requestBackgroundNetwork(
                         eq(getRouteSelectionRequest(INITIAL_SUB_IDS)),
-                        any(),
-                        mRouteSelectionCallbackCaptor.capture());
+                        mRouteSelectionCallbackCaptor.capture(),
+                        any());
 
         RouteSelectionCallback cb = mRouteSelectionCallbackCaptor.getValue();
         cb.onAvailable(mNetwork);
diff --git a/tools/hiddenapi/checksorted_sha.sh b/tools/hiddenapi/checksorted_sha.sh
deleted file mode 100755
index 72fb867..0000000
--- a/tools/hiddenapi/checksorted_sha.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-set -e
-LOCAL_DIR="$( dirname ${BASH_SOURCE} )"
-git show --name-only --pretty=format: $1 | grep "hiddenapi/hiddenapi-.*txt" | while read file; do
-    diff <(git show $1:$file) <(git show $1:$file | $LOCAL_DIR/sort_api.sh )  || {
-      echo -e "\e[1m\e[31m$file $1 is not sorted or contains duplicates. To sort it correctly:\e[0m"
-      echo -e "\e[33m${LOCAL_DIR}/sort_api.sh $PWD/$file\e[0m"
-      exit 1
-    }
-done
diff --git a/tools/hiddenapi/sort_api.sh b/tools/hiddenapi/sort_api.sh
deleted file mode 100755
index 710da40..0000000
--- a/tools/hiddenapi/sort_api.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-set -e
-if [ -z "$1" ]; then
-  source_list=/dev/stdin
-  dest_list=/dev/stdout
-else
-  source_list="$1"
-  dest_list="$1"
-fi
-# Load the file
-readarray A < "$source_list"
-# Sort
-IFS=$'\n'
-# Stash away comments
-C=( $(grep -E '^#' <<< "${A[*]}" || :) )
-A=( $(grep -v -E '^#' <<< "${A[*]}" || :) )
-# Sort entries
-A=( $(LC_COLLATE=C sort -f <<< "${A[*]}") )
-A=( $(uniq <<< "${A[*]}") )
-# Concatenate comments and entries
-A=( ${C[*]} ${A[*]} )
-unset IFS
-# Dump array back into the file
-if [ ${#A[@]} -ne 0 ]; then
-  printf '%s\n' "${A[@]}" > "$dest_list"
-fi
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index da0571ba..3b75660 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -117,13 +117,13 @@
     /**
      * Interface used to listen country code event
      */
-    public interface CountryCodeChangeListener {
+    public interface CountryCodeChangedListener {
         /**
          * Called when country code changed.
          *
-         * @param countryCode A new country code which is 2-Character alphanumeric.
+         * @param countryCode An ISO-3166-alpha2 country code which is 2-Character alphanumeric.
          */
-        void onChanged(@NonNull String countryCode);
+        void onCountryCodeChanged(@NonNull String countryCode);
     }
 
     /**
@@ -163,27 +163,27 @@
     /** @hide */
     @VisibleForTesting
     public class WificondEventHandler extends IWificondEventCallback.Stub {
-        private Map<CountryCodeChangeListener, Executor> mCountryCodeChangeListenerHolder =
+        private Map<CountryCodeChangedListener, Executor> mCountryCodeChangedListenerHolder =
                 new HashMap<>();
 
         /**
-         * Register CountryCodeChangeListener with pid.
+         * Register CountryCodeChangedListener with pid.
          *
          * @param executor The Executor on which to execute the callbacks.
          * @param listener listener for country code changed events.
          */
-        public void registerCountryCodeChangeListener(Executor executor,
-                CountryCodeChangeListener listener) {
-            mCountryCodeChangeListenerHolder.put(listener, executor);
+        public void registerCountryCodeChangedListener(Executor executor,
+                CountryCodeChangedListener listener) {
+            mCountryCodeChangedListenerHolder.put(listener, executor);
         }
 
         /**
-         * Unregister CountryCodeChangeListener with pid.
+         * Unregister CountryCodeChangedListener with pid.
          *
          * @param listener listener which registered country code changed events.
          */
-        public void unregisterCountryCodeChangeListener(CountryCodeChangeListener listener) {
-            mCountryCodeChangeListenerHolder.remove(listener);
+        public void unregisterCountryCodeChangedListener(CountryCodeChangedListener listener) {
+            mCountryCodeChangedListenerHolder.remove(listener);
         }
 
         @Override
@@ -191,8 +191,8 @@
             Log.d(TAG, "OnRegDomainChanged " + countryCode);
             final long token = Binder.clearCallingIdentity();
             try {
-                mCountryCodeChangeListenerHolder.forEach((listener, executor) -> {
-                    executor.execute(() -> listener.onChanged(countryCode));
+                mCountryCodeChangedListenerHolder.forEach((listener, executor) -> {
+                    executor.execute(() -> listener.onCountryCodeChanged(countryCode));
                 });
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -1240,25 +1240,25 @@
      * @param listener listener for country code changed events.
      * @return true on success, false on failure.
      */
-    public boolean registerCountryCodeChangeListener(@NonNull @CallbackExecutor Executor executor,
-            @NonNull CountryCodeChangeListener listener) {
+    public boolean registerCountryCodeChangedListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull CountryCodeChangedListener listener) {
         if (!retrieveWificondAndRegisterForDeath()) {
             return false;
         }
         Log.d(TAG, "registerCountryCodeEventListener called");
-        mWificondEventHandler.registerCountryCodeChangeListener(executor, listener);
+        mWificondEventHandler.registerCountryCodeChangedListener(executor, listener);
         return true;
     }
 
 
     /**
-     * Unregister CountryCodeChangeListener with pid.
+     * Unregister CountryCodeChangedListener with pid.
      *
      * @param listener listener which registered country code changed events.
      */
-    public void unregisterCountryCodeChangeListener(@NonNull CountryCodeChangeListener listener) {
+    public void unregisterCountryCodeChangedListener(@NonNull CountryCodeChangedListener listener) {
         Log.d(TAG, "unregisterCountryCodeEventListener called");
-        mWificondEventHandler.unregisterCountryCodeChangeListener(listener);
+        mWificondEventHandler.unregisterCountryCodeChangedListener(listener);
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 98a0042..3fb2301 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -98,9 +98,9 @@
     @Mock
     private WifiNl80211Manager.PnoScanRequestCallback mPnoScanRequestCallback;
     @Mock
-    private WifiNl80211Manager.CountryCodeChangeListener mCountryCodeChangeListener;
+    private WifiNl80211Manager.CountryCodeChangedListener mCountryCodeChangedListener;
     @Mock
-    private WifiNl80211Manager.CountryCodeChangeListener mCountryCodeChangeListener2;
+    private WifiNl80211Manager.CountryCodeChangedListener mCountryCodeChangedListener2;
     @Mock
     private Context mContext;
     private TestLooper mLooper;
@@ -768,25 +768,25 @@
     }
 
     /**
-     * Ensures callback works after register CountryCodeChangeListener.
+     * Ensures callback works after register CountryCodeChangedListener.
      */
     @Test
-    public void testCountryCodeChangeListenerInvocation() throws Exception {
-        assertTrue(mWificondControl.registerCountryCodeChangeListener(
-                Runnable::run, mCountryCodeChangeListener));
-        assertTrue(mWificondControl.registerCountryCodeChangeListener(
-                Runnable::run, mCountryCodeChangeListener2));
+    public void testCountryCodeChangedListenerInvocation() throws Exception {
+        assertTrue(mWificondControl.registerCountryCodeChangedListener(
+                Runnable::run, mCountryCodeChangedListener));
+        assertTrue(mWificondControl.registerCountryCodeChangedListener(
+                Runnable::run, mCountryCodeChangedListener2));
 
         mWificondEventHandler.OnRegDomainChanged(TEST_COUNTRY_CODE);
-        verify(mCountryCodeChangeListener).onChanged(TEST_COUNTRY_CODE);
-        verify(mCountryCodeChangeListener2).onChanged(TEST_COUNTRY_CODE);
+        verify(mCountryCodeChangedListener).onCountryCodeChanged(TEST_COUNTRY_CODE);
+        verify(mCountryCodeChangedListener2).onCountryCodeChanged(TEST_COUNTRY_CODE);
 
-        reset(mCountryCodeChangeListener);
-        reset(mCountryCodeChangeListener2);
-        mWificondControl.unregisterCountryCodeChangeListener(mCountryCodeChangeListener2);
+        reset(mCountryCodeChangedListener);
+        reset(mCountryCodeChangedListener2);
+        mWificondControl.unregisterCountryCodeChangedListener(mCountryCodeChangedListener2);
         mWificondEventHandler.OnRegDomainChanged(TEST_COUNTRY_CODE);
-        verify(mCountryCodeChangeListener).onChanged(TEST_COUNTRY_CODE);
-        verify(mCountryCodeChangeListener2, never()).onChanged(TEST_COUNTRY_CODE);
+        verify(mCountryCodeChangedListener).onCountryCodeChanged(TEST_COUNTRY_CODE);
+        verify(mCountryCodeChangedListener2, never()).onCountryCodeChanged(TEST_COUNTRY_CODE);
     }
 
     /**