Merge "Log FGS ENTER event synchronously." into sc-dev
diff --git a/apex/appsearch/Android.bp b/apex/appsearch/Android.bp
index 9a42016..8278426 100644
--- a/apex/appsearch/Android.bp
+++ b/apex/appsearch/Android.bp
@@ -29,6 +29,7 @@
     key: "com.android.appsearch.key",
     certificate: ":com.android.appsearch.certificate",
     updatable: false,
+    generate_hashtree: false,
 }
 
 apex_key {
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 c7c1d68..e524429 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -348,19 +348,19 @@
                                 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis);
                         int totalLatencyMillis =
                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
-                        CallStats.Builder cBuilder = new CallStats.Builder(packageName,
-                                databaseName)
+                        logger.logStats(new CallStats.Builder()
+                                .setPackageName(packageName)
+                                .setDatabase(databaseName)
+                                .setStatusCode(statusCode)
+                                .setTotalLatencyMillis(totalLatencyMillis)
                                 .setCallType(CallStats.CALL_TYPE_SET_SCHEMA)
                                 // TODO(b/173532925) check the existing binder call latency chart
                                 // is good enough for us:
                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
                                 .setNumOperationsSucceeded(operationSuccessCount)
-                                .setNumOperationsFailed(operationFailureCount);
-                        cBuilder.getGeneralStatsBuilder()
-                                .setStatusCode(statusCode)
-                                .setTotalLatencyMillis(totalLatencyMillis);
-                        logger.logStats(cBuilder.build());
+                                .setNumOperationsFailed(operationFailureCount)
+                                .build());
                     }
                 }
             });
@@ -480,19 +480,19 @@
                                 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis);
                         int totalLatencyMillis =
                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
-                        CallStats.Builder cBuilder = new CallStats.Builder(packageName,
-                                databaseName)
+                        logger.logStats(new CallStats.Builder()
+                                .setPackageName(packageName)
+                                .setDatabase(databaseName)
+                                .setStatusCode(statusCode)
+                                .setTotalLatencyMillis(totalLatencyMillis)
                                 .setCallType(CallStats.CALL_TYPE_PUT_DOCUMENTS)
                                 // TODO(b/173532925) check the existing binder call latency chart
                                 // is good enough for us:
                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
                                 .setNumOperationsSucceeded(operationSuccessCount)
-                                .setNumOperationsFailed(operationFailureCount);
-                        cBuilder.getGeneralStatsBuilder()
-                                .setStatusCode(statusCode)
-                                .setTotalLatencyMillis(totalLatencyMillis);
-                        logger.logStats(cBuilder.build());
+                                .setNumOperationsFailed(operationFailureCount)
+                                .build());
                     }
                 }
             });
@@ -563,19 +563,19 @@
                                 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis);
                         int totalLatencyMillis =
                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
-                        CallStats.Builder cBuilder = new CallStats.Builder(packageName,
-                                databaseName)
+                        logger.logStats(new CallStats.Builder()
+                                .setPackageName(packageName)
+                                .setDatabase(databaseName)
+                                .setStatusCode(statusCode)
+                                .setTotalLatencyMillis(totalLatencyMillis)
                                 .setCallType(CallStats.CALL_TYPE_GET_DOCUMENTS)
                                 // TODO(b/173532925) check the existing binder call latency chart
                                 // is good enough for us:
                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
                                 .setNumOperationsSucceeded(operationSuccessCount)
-                                .setNumOperationsFailed(operationFailureCount);
-                        cBuilder.getGeneralStatsBuilder()
-                                .setStatusCode(statusCode)
-                                .setTotalLatencyMillis(totalLatencyMillis);
-                        logger.logStats(cBuilder.build());
+                                .setNumOperationsFailed(operationFailureCount)
+                                .build());
                     }
                 }
             });
@@ -631,19 +631,19 @@
                                 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis);
                         int totalLatencyMillis =
                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
-                        CallStats.Builder cBuilder = new CallStats.Builder(packageName,
-                                databaseName)
+                        logger.logStats(new CallStats.Builder()
+                                .setPackageName(packageName)
+                                .setDatabase(databaseName)
+                                .setStatusCode(statusCode)
+                                .setTotalLatencyMillis(totalLatencyMillis)
                                 .setCallType(CallStats.CALL_TYPE_SEARCH)
                                 // TODO(b/173532925) check the existing binder call latency chart
                                 // is good enough for us:
                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
                                 .setNumOperationsSucceeded(operationSuccessCount)
-                                .setNumOperationsFailed(operationFailureCount);
-                        cBuilder.getGeneralStatsBuilder()
-                                .setStatusCode(statusCode)
-                                .setTotalLatencyMillis(totalLatencyMillis);
-                        logger.logStats(cBuilder.build());
+                                .setNumOperationsFailed(operationFailureCount)
+                                .build());
                     }
                 }
             });
@@ -697,20 +697,18 @@
                                 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis);
                         int totalLatencyMillis =
                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
-                        // TODO(b/173532925) database would be nulluable once we remove generalStats
-                        CallStats.Builder cBuilder = new CallStats.Builder(packageName,
-                                /*database=*/ "")
+                        logger.logStats(new CallStats.Builder()
+                                .setPackageName(packageName)
+                                .setStatusCode(statusCode)
+                                .setTotalLatencyMillis(totalLatencyMillis)
                                 .setCallType(CallStats.CALL_TYPE_GLOBAL_SEARCH)
                                 // TODO(b/173532925) check the existing binder call latency chart
                                 // is good enough for us:
                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
                                 .setNumOperationsSucceeded(operationSuccessCount)
-                                .setNumOperationsFailed(operationFailureCount);
-                        cBuilder.getGeneralStatsBuilder()
-                                .setStatusCode(statusCode)
-                                .setTotalLatencyMillis(totalLatencyMillis);
-                        logger.logStats(cBuilder.build());
+                                .setNumOperationsFailed(operationFailureCount)
+                                .build());
                     }
                 }
             });
@@ -965,19 +963,19 @@
                                 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis);
                         int totalLatencyMillis =
                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
-                        CallStats.Builder cBuilder = new CallStats.Builder(packageName,
-                                databaseName)
+                        logger.logStats(new CallStats.Builder()
+                                .setPackageName(packageName)
+                                .setDatabase(databaseName)
+                                .setStatusCode(statusCode)
+                                .setTotalLatencyMillis(totalLatencyMillis)
                                 .setCallType(CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_ID)
                                 // TODO(b/173532925) check the existing binder call latency chart
                                 // is good enough for us:
                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
                                 .setNumOperationsSucceeded(operationSuccessCount)
-                                .setNumOperationsFailed(operationFailureCount);
-                        cBuilder.getGeneralStatsBuilder()
-                                .setStatusCode(statusCode)
-                                .setTotalLatencyMillis(totalLatencyMillis);
-                        logger.logStats(cBuilder.build());
+                                .setNumOperationsFailed(operationFailureCount)
+                                .build());
                     }
                 }
             });
@@ -1033,19 +1031,19 @@
                                 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis);
                         int totalLatencyMillis =
                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
-                        CallStats.Builder cBuilder = new CallStats.Builder(packageName,
-                                databaseName)
+                        logger.logStats(new CallStats.Builder()
+                                .setPackageName(packageName)
+                                .setDatabase(databaseName)
+                                .setStatusCode(statusCode)
+                                .setTotalLatencyMillis(totalLatencyMillis)
                                 .setCallType(CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_SEARCH)
                                 // TODO(b/173532925) check the existing binder call latency chart
                                 // is good enough for us:
                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
                                 .setNumOperationsSucceeded(operationSuccessCount)
-                                .setNumOperationsFailed(operationFailureCount);
-                        cBuilder.getGeneralStatsBuilder()
-                                .setStatusCode(statusCode)
-                                .setTotalLatencyMillis(totalLatencyMillis);
-                        logger.logStats(cBuilder.build());
+                                .setNumOperationsFailed(operationFailureCount)
+                                .build());
                     }
                 }
             });
@@ -1110,19 +1108,17 @@
                                 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis);
                         int totalLatencyMillis =
                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
-                        CallStats.Builder cBuilder = new CallStats.Builder(/*packageName=*/ "",
-                                /*databaseName=*/ "")
+                        logger.logStats(new CallStats.Builder()
+                                .setStatusCode(statusCode)
+                                .setTotalLatencyMillis(totalLatencyMillis)
                                 .setCallType(CallStats.CALL_TYPE_FLUSH)
                                 // TODO(b/173532925) check the existing binder call latency chart
                                 // is good enough for us:
                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
                                 .setNumOperationsSucceeded(operationSuccessCount)
-                                .setNumOperationsFailed(operationFailureCount);
-                        cBuilder.getGeneralStatsBuilder()
-                                .setStatusCode(statusCode)
-                                .setTotalLatencyMillis(totalLatencyMillis);
-                        logger.logStats(cBuilder.build());
+                                .setNumOperationsFailed(operationFailureCount)
+                                .build());
                     }
                 }
             });
@@ -1162,21 +1158,17 @@
                                 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis);
                         int totalLatencyMillis =
                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
-                        // TODO(b/173532925) make packageName and database nullable after
-                        //  removing generalStats
-                        CallStats.Builder cBuilder = new CallStats.Builder(/*packageName=*/"",
-                                /*database=*/ "")
+                        logger.logStats(new CallStats.Builder()
+                                .setStatusCode(statusCode)
+                                .setTotalLatencyMillis(totalLatencyMillis)
                                 .setCallType(CallStats.CALL_TYPE_INITIALIZE)
                                 // TODO(b/173532925) check the existing binder call latency chart
                                 // is good enough for us:
                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
                                 .setNumOperationsSucceeded(operationSuccessCount)
-                                .setNumOperationsFailed(operationFailureCount);
-                        cBuilder.getGeneralStatsBuilder()
-                                .setStatusCode(statusCode)
-                                .setTotalLatencyMillis(totalLatencyMillis);
-                        logger.logStats(cBuilder.build());
+                                .setNumOperationsFailed(operationFailureCount)
+                                .build());
                     }
                 }
             });
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
index e7845d5..2181dab 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
@@ -24,10 +24,12 @@
 import android.os.Environment;
 import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.appsearch.external.localstorage.AppSearchImpl;
 import com.android.server.appsearch.external.localstorage.AppSearchLogger;
+import com.android.server.appsearch.external.localstorage.FrameworkOptimizeStrategy;
 
 import java.io.File;
 import java.util.Map;
@@ -37,9 +39,10 @@
  * Manages the lifecycle of instances of {@link AppSearchImpl}.
  *
  * <p>These instances are managed per unique device-user.
+ * @hide
  */
 public final class ImplInstanceManager {
-    private static final String APP_SEARCH_DIR = "appSearch";
+    private static final String TAG = "AppSearchImplInstanceMa";
 
     private static ImplInstanceManager sImplInstanceManager;
 
@@ -70,8 +73,11 @@
      * <p>This folder should only be accessed after unlock.
      */
     public static File getAppSearchDir(@NonNull UserHandle userHandle) {
-        return new File(
-                Environment.getDataSystemCeDirectory(userHandle.getIdentifier()), APP_SEARCH_DIR);
+        // Duplicates the implementation of Environment#getDataSystemCeDirectory
+        // TODO(b/191059409): Unhide Environment#getDataSystemCeDirectory and switch to it.
+        File systemCeDir = new File(Environment.getDataDirectory(), "system_ce");
+        File systemCeUserDir = new File(systemCeDir, String.valueOf(userHandle.getIdentifier()));
+        return new File(systemCeUserDir, "appSearch");
     }
 
     /**
@@ -153,6 +159,12 @@
             @Nullable AppSearchLogger logger)
             throws AppSearchException {
         File appSearchDir = getAppSearchDir(userHandle);
-        return AppSearchImpl.create(appSearchDir, userContext, /*logger=*/ null);
+        File icingDir = new File(appSearchDir, "icing");
+        Log.i(TAG, "Creating new AppSearch instance at: " + icingDir);
+        return AppSearchImpl.create(
+                icingDir,
+                userContext,
+                /*logger=*/ null,
+                new FrameworkOptimizeStrategy());
     }
 }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index 29cb57c..4a1a9ae 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -145,14 +145,14 @@
 public final class AppSearchImpl implements Closeable {
     private static final String TAG = "AppSearchImpl";
 
-    @VisibleForTesting static final int OPTIMIZE_THRESHOLD_DOC_COUNT = 1000;
-    @VisibleForTesting static final int OPTIMIZE_THRESHOLD_BYTES = 1_000_000; // 1MB
     @VisibleForTesting static final int CHECK_OPTIMIZE_INTERVAL = 100;
 
     private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
 
     private final LogUtil mLogUtil = new LogUtil(TAG);
 
+    private final OptimizeStrategy mOptimizeStrategy;
+
     @GuardedBy("mReadWriteLock")
     @VisibleForTesting
     final IcingSearchEngine mIcingSearchEngineLocked;
@@ -201,10 +201,12 @@
     public static AppSearchImpl create(
             @NonNull File icingDir,
             @NonNull Context userContext,
-            @Nullable AppSearchLogger logger)
+            @Nullable AppSearchLogger logger,
+            @NonNull OptimizeStrategy optimizeStrategy)
             throws AppSearchException {
         Objects.requireNonNull(icingDir);
         Objects.requireNonNull(userContext);
+        Objects.requireNonNull(optimizeStrategy);
 
         long totalLatencyStartMillis = SystemClock.elapsedRealtime();
         InitializeStats.Builder initStatsBuilder = null;
@@ -212,7 +214,9 @@
             initStatsBuilder = new InitializeStats.Builder();
         }
 
-        AppSearchImpl appSearchImpl = new AppSearchImpl(icingDir, userContext, initStatsBuilder);
+        AppSearchImpl appSearchImpl =
+                new AppSearchImpl(
+                        icingDir, userContext, initStatsBuilder, optimizeStrategy);
 
         long prepareVisibilityStoreLatencyStartMillis = SystemClock.elapsedRealtime();
         appSearchImpl.initializeVisibilityStore();
@@ -236,7 +240,8 @@
     private AppSearchImpl(
             @NonNull File icingDir,
             @NonNull Context userContext,
-            @Nullable InitializeStats.Builder initStatsBuilder)
+            @Nullable InitializeStats.Builder initStatsBuilder,
+            @NonNull OptimizeStrategy optimizeStrategy)
             throws AppSearchException {
         mReadWriteLock.writeLock().lock();
 
@@ -254,6 +259,7 @@
                     Objects.hashCode(mIcingSearchEngineLocked));
 
             mVisibilityStoreLocked = new VisibilityStore(this, userContext);
+            mOptimizeStrategy = optimizeStrategy;
 
             // The core initialization procedure. If any part of this fails, we bail into
             // resetLocked(), deleting all data (but hopefully allowing AppSearchImpl to come up).
@@ -646,9 +652,7 @@
             // Logging stats
             if (pStatsBuilder != null) {
                 pStatsBuilder
-                        .getGeneralStatsBuilder()
-                        .setStatusCode(statusProtoToResultCode(putResultProto.getStatus()));
-                pStatsBuilder
+                        .setStatusCode(statusProtoToResultCode(putResultProto.getStatus()))
                         .setGenerateDocumentProtoLatencyMillis(
                                 (int)
                                         (generateDocumentProtoEndTimeMillis
@@ -667,9 +671,8 @@
 
             if (logger != null) {
                 long totalEndTimeMillis = SystemClock.elapsedRealtime();
-                pStatsBuilder
-                        .getGeneralStatsBuilder()
-                        .setTotalLatencyMillis((int) (totalEndTimeMillis - totalStartTimeMillis));
+                pStatsBuilder.setTotalLatencyMillis(
+                        (int) (totalEndTimeMillis - totalStartTimeMillis));
                 logger.logStats(pStatsBuilder.build());
             }
         }
@@ -812,7 +815,8 @@
      *
      * @param queryExpression Query String to search.
      * @param searchSpec Spec for setting filters, raw query etc.
-     * @param callerPackageName Package name of the caller, should belong to the {@code callerUid}.
+     * @param callerPackageName Package name of the caller, should belong to the {@code
+     *     userContext}.
      * @param callerUid UID of the client making the globalQuery call.
      * @param logger logger to collect globalQuery stats
      * @return The results of performing this search. It may contain an empty list of results if no
@@ -2001,7 +2005,7 @@
      * resources that could be released.
      *
      * <p>{@link IcingSearchEngine#optimize()} should be called only if {@link
-     * GetOptimizeInfoResultProto} shows there is enough resources could be released.
+     * OptimizeStrategy#shouldOptimize(GetOptimizeInfoResultProto)} return true.
      */
     public void checkForOptimize() throws AppSearchException {
         mReadWriteLock.writeLock().lock();
@@ -2009,9 +2013,7 @@
             GetOptimizeInfoResultProto optimizeInfo = getOptimizeInfoResultLocked();
             checkSuccess(optimizeInfo.getStatus());
             mOptimizeIntervalCountLocked = 0;
-            // Second threshold, decide when to call optimize().
-            if (optimizeInfo.getOptimizableDocs() >= OPTIMIZE_THRESHOLD_DOC_COUNT
-                    || optimizeInfo.getEstimatedOptimizableBytes() >= OPTIMIZE_THRESHOLD_BYTES) {
+            if (mOptimizeStrategy.shouldOptimize(optimizeInfo)) {
                 optimize();
             }
         } finally {
@@ -2022,11 +2024,7 @@
         //  go/icing-library-apis.
     }
 
-    /**
-     * Triggers {@link IcingSearchEngine#optimize()} directly.
-     *
-     * <p>This method should be only called as a scheduled task in AppSearch Platform backend.
-     */
+    /** Triggers {@link IcingSearchEngine#optimize()} directly. */
     public void optimize() throws AppSearchException {
         mReadWriteLock.writeLock().lock();
         try {
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategy.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategy.java
new file mode 100644
index 0000000..8ec30e1
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategy.java
@@ -0,0 +1,44 @@
+/*
+ * 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 com.android.server.appsearch.external.localstorage;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.android.icing.proto.GetOptimizeInfoResultProto;
+
+/**
+ * An implementation of {@link OptimizeStrategy} will determine when to trigger {@link
+ * AppSearchImpl#optimize()} in Jetpack environment.
+ *
+ * @hide
+ */
+public class FrameworkOptimizeStrategy implements OptimizeStrategy {
+
+    @VisibleForTesting static final int DOC_COUNT_OPTIMIZE_THRESHOLD = 100_000;
+    @VisibleForTesting static final int BYTES_OPTIMIZE_THRESHOLD = 1 * 1024 * 1024 * 1024; // 1GB
+
+    @VisibleForTesting
+    static final long TIME_OPTIMIZE_THRESHOLD_MILLIS = 7 * 24 * 60 * 60 * 1000; // 1 week
+
+    @Override
+    public boolean shouldOptimize(@NonNull GetOptimizeInfoResultProto optimizeInfo) {
+        return optimizeInfo.getOptimizableDocs() >= DOC_COUNT_OPTIMIZE_THRESHOLD
+                || optimizeInfo.getEstimatedOptimizableBytes() >= BYTES_OPTIMIZE_THRESHOLD
+                || optimizeInfo.getTimeSinceLastOptimizeMs() >= TIME_OPTIMIZE_THRESHOLD_MILLIS;
+    }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/OptimizeStrategy.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/OptimizeStrategy.java
new file mode 100644
index 0000000..6cb84bc
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/OptimizeStrategy.java
@@ -0,0 +1,39 @@
+/*
+ * 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 com.android.server.appsearch.external.localstorage;
+
+import android.annotation.NonNull;
+
+import com.google.android.icing.proto.GetOptimizeInfoResultProto;
+
+/**
+ * An interface class for implementing a strategy to determine when to trigger {@link
+ * AppSearchImpl#optimize()}.
+ *
+ * @hide
+ */
+public interface OptimizeStrategy {
+
+    /**
+     * Determines whether {@link AppSearchImpl#optimize()} need to be triggered to release garbage
+     * resources in AppSearch base on the given information.
+     *
+     * @param optimizeInfo The proto object indicates the number of garbage resources in AppSearch.
+     * @return {@code true} if {@link AppSearchImpl#optimize()} need to be triggered.
+     */
+    boolean shouldOptimize(@NonNull GetOptimizeInfoResultProto optimizeInfo);
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java
index ea5263a..81fb418 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java
@@ -18,6 +18,8 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.appsearch.AppSearchResult;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -29,9 +31,9 @@
  * <p>This class can set which stats to log for both batch and non-batch {@link
  * android.app.appsearch.AppSearchSession} calls.
  *
- * <p>Some function calls like {@link android.app.appsearch.AppSearchSession#setSchema} have their
- * own detailed stats class {@link placeholder}. However, {@link CallStats} can still be used along
- * with the detailed stats class for easy aggregation/analysis with other function calls.
+ * <p>Some function calls may have their own detailed stats class like {@link PutDocumentStats}.
+ * However, {@link CallStats} can still be used along with the detailed stats class for easy
+ * aggregation/analysis with other function calls.
  *
  * @hide
  */
@@ -73,7 +75,16 @@
     public static final int CALL_TYPE_REMOVE_DOCUMENTS_BY_SEARCH = 13;
     public static final int CALL_TYPE_REMOVE_DOCUMENT_BY_SEARCH = 14;
 
-    @NonNull private final GeneralStats mGeneralStats;
+    @Nullable private final String mPackageName;
+    @Nullable private final String mDatabase;
+    /**
+     * The status code returned by {@link AppSearchResult#getResultCode()} for the call or internal
+     * state.
+     */
+    @AppSearchResult.ResultCode private final int mStatusCode;
+
+    private final int mTotalLatencyMillis;
+
     @CallType private final int mCallType;
     private final int mEstimatedBinderLatencyMillis;
     private final int mNumOperationsSucceeded;
@@ -81,17 +92,37 @@
 
     CallStats(@NonNull Builder builder) {
         Objects.requireNonNull(builder);
-        mGeneralStats = Objects.requireNonNull(builder.mGeneralStatsBuilder).build();
+        mPackageName = builder.mPackageName;
+        mDatabase = builder.mDatabase;
+        mStatusCode = builder.mStatusCode;
+        mTotalLatencyMillis = builder.mTotalLatencyMillis;
         mCallType = builder.mCallType;
         mEstimatedBinderLatencyMillis = builder.mEstimatedBinderLatencyMillis;
         mNumOperationsSucceeded = builder.mNumOperationsSucceeded;
         mNumOperationsFailed = builder.mNumOperationsFailed;
     }
 
-    /** Returns general information for the call. */
-    @NonNull
-    public GeneralStats getGeneralStats() {
-        return mGeneralStats;
+    /** Returns calling package name. */
+    @Nullable
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /** Returns calling database name. */
+    @Nullable
+    public String getDatabase() {
+        return mDatabase;
+    }
+
+    /** Returns status code for this api call. */
+    @AppSearchResult.ResultCode
+    public int getStatusCode() {
+        return mStatusCode;
+    }
+
+    /** Returns total latency of this api call in millis. */
+    public int getTotalLatencyMillis() {
+        return mTotalLatencyMillis;
     }
 
     /** Returns type of the call. */
@@ -137,23 +168,41 @@
 
     /** Builder for {@link CallStats}. */
     public static class Builder {
-        @NonNull final GeneralStats.Builder mGeneralStatsBuilder;
+        @Nullable String mPackageName;
+        @Nullable String mDatabase;
+        @AppSearchResult.ResultCode int mStatusCode;
+        int mTotalLatencyMillis;
         @CallType int mCallType;
         int mEstimatedBinderLatencyMillis;
         int mNumOperationsSucceeded;
         int mNumOperationsFailed;
 
-        /** Builder takes {@link GeneralStats.Builder}. */
-        public Builder(@NonNull String packageName, @NonNull String database) {
-            Objects.requireNonNull(packageName);
-            Objects.requireNonNull(database);
-            mGeneralStatsBuilder = new GeneralStats.Builder(packageName, database);
+        /** Sets the PackageName used by the session. */
+        @NonNull
+        public Builder setPackageName(@NonNull String packageName) {
+            mPackageName = Objects.requireNonNull(packageName);
+            return this;
         }
 
-        /** Returns {@link GeneralStats.Builder}. */
+        /** Sets the database used by the session. */
         @NonNull
-        public GeneralStats.Builder getGeneralStatsBuilder() {
-            return mGeneralStatsBuilder;
+        public Builder setDatabase(@NonNull String database) {
+            mDatabase = Objects.requireNonNull(database);
+            return this;
+        }
+
+        /** Sets the status code. */
+        @NonNull
+        public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) {
+            mStatusCode = statusCode;
+            return this;
+        }
+
+        /** Sets total latency in millis. */
+        @NonNull
+        public Builder setTotalLatencyMillis(int totalLatencyMillis) {
+            mTotalLatencyMillis = totalLatencyMillis;
+            return this;
         }
 
         /** Sets type of the call. */
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java
deleted file mode 100644
index 53c1ee3..0000000
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java
+++ /dev/null
@@ -1,122 +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 com.android.server.appsearch.external.localstorage.stats;
-
-import android.annotation.NonNull;
-import android.app.appsearch.AppSearchResult;
-
-import java.util.Objects;
-
-/**
- * A class for holding general logging information.
- *
- * <p>This class cannot be logged by {@link
- * com.android.server.appsearch.external.localstorage.AppSearchLogger} directly. It is used for
- * defining general logging information that is shared across different stats classes.
- *
- * @see PutDocumentStats
- * @see CallStats
- * @hide
- */
-public final class GeneralStats {
-    /** Package name of the application. */
-    @NonNull private final String mPackageName;
-
-    /** Database name within AppSearch. */
-    @NonNull private final String mDatabase;
-
-    /**
-     * The status code returned by {@link AppSearchResult#getResultCode()} for the call or internal
-     * state.
-     */
-    @AppSearchResult.ResultCode private final int mStatusCode;
-
-    private final int mTotalLatencyMillis;
-
-    GeneralStats(@NonNull Builder builder) {
-        Objects.requireNonNull(builder);
-        mPackageName = Objects.requireNonNull(builder.mPackageName);
-        mDatabase = Objects.requireNonNull(builder.mDatabase);
-        mStatusCode = builder.mStatusCode;
-        mTotalLatencyMillis = builder.mTotalLatencyMillis;
-    }
-
-    /** Returns package name. */
-    @NonNull
-    public String getPackageName() {
-        return mPackageName;
-    }
-
-    /** Returns database name. */
-    @NonNull
-    public String getDatabase() {
-        return mDatabase;
-    }
-
-    /** Returns result code from {@link AppSearchResult#getResultCode()} */
-    @AppSearchResult.ResultCode
-    public int getStatusCode() {
-        return mStatusCode;
-    }
-
-    /** Returns total latency, in milliseconds. */
-    public int getTotalLatencyMillis() {
-        return mTotalLatencyMillis;
-    }
-
-    /** Builder for {@link GeneralStats}. */
-    public static class Builder {
-        @NonNull final String mPackageName;
-        @NonNull final String mDatabase;
-        @AppSearchResult.ResultCode int mStatusCode = AppSearchResult.RESULT_UNKNOWN_ERROR;
-        int mTotalLatencyMillis;
-
-        /**
-         * Constructor
-         *
-         * @param packageName name of the package logging stats
-         * @param database name of the database logging stats
-         */
-        public Builder(@NonNull String packageName, @NonNull String database) {
-            mPackageName = Objects.requireNonNull(packageName);
-            mDatabase = Objects.requireNonNull(database);
-        }
-
-        /** Sets status code returned from {@link AppSearchResult#getResultCode()} */
-        @NonNull
-        public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) {
-            mStatusCode = statusCode;
-            return this;
-        }
-
-        /** Sets total latency, in milliseconds. */
-        @NonNull
-        public Builder setTotalLatencyMillis(int totalLatencyMillis) {
-            mTotalLatencyMillis = totalLatencyMillis;
-            return this;
-        }
-
-        /**
-         * Creates a new {@link GeneralStats} object from the contents of this {@link Builder}
-         * instance.
-         */
-        @NonNull
-        public GeneralStats build() {
-            return new GeneralStats(/* builder= */ this);
-        }
-    }
-}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java
index d031172..7ba1816 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java
@@ -17,6 +17,7 @@
 package com.android.server.appsearch.external.localstorage.stats;
 
 import android.annotation.NonNull;
+import android.app.appsearch.AppSearchResult;
 
 import java.util.Objects;
 
@@ -27,8 +28,15 @@
  * @hide
  */
 public final class PutDocumentStats {
-    /** {@link GeneralStats} holds the general stats. */
-    @NonNull private final GeneralStats mGeneralStats;
+    @NonNull private final String mPackageName;
+    @NonNull private final String mDatabase;
+    /**
+     * The status code returned by {@link AppSearchResult#getResultCode()} for the call or internal
+     * state.
+     */
+    @AppSearchResult.ResultCode private final int mStatusCode;
+
+    private final int mTotalLatencyMillis;
 
     /** Time used to generate a document proto from a Bundle. */
     private final int mGenerateDocumentProtoLatencyMillis;
@@ -61,7 +69,10 @@
 
     PutDocumentStats(@NonNull Builder builder) {
         Objects.requireNonNull(builder);
-        mGeneralStats = Objects.requireNonNull(builder.mGeneralStatsBuilder).build();
+        mPackageName = builder.mPackageName;
+        mDatabase = builder.mDatabase;
+        mStatusCode = builder.mStatusCode;
+        mTotalLatencyMillis = builder.mTotalLatencyMillis;
         mGenerateDocumentProtoLatencyMillis = builder.mGenerateDocumentProtoLatencyMillis;
         mRewriteDocumentTypesLatencyMillis = builder.mRewriteDocumentTypesLatencyMillis;
         mNativeLatencyMillis = builder.mNativeLatencyMillis;
@@ -73,10 +84,27 @@
         mNativeExceededMaxNumTokens = builder.mNativeExceededMaxNumTokens;
     }
 
-    /** Returns the {@link GeneralStats} object attached to this instance. */
+    /** Returns calling package name. */
     @NonNull
-    public GeneralStats getGeneralStats() {
-        return mGeneralStats;
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /** Returns calling database name. */
+    @NonNull
+    public String getDatabase() {
+        return mDatabase;
+    }
+
+    /** Returns status code for this putDocument. */
+    @AppSearchResult.ResultCode
+    public int getStatusCode() {
+        return mStatusCode;
+    }
+
+    /** Returns total latency of this putDocument in millis. */
+    public int getTotalLatencyMillis() {
+        return mTotalLatencyMillis;
     }
 
     /** Returns time spent on generating document proto, in milliseconds. */
@@ -129,7 +157,10 @@
 
     /** Builder for {@link PutDocumentStats}. */
     public static class Builder {
-        @NonNull final GeneralStats.Builder mGeneralStatsBuilder;
+        @NonNull final String mPackageName;
+        @NonNull final String mDatabase;
+        @AppSearchResult.ResultCode int mStatusCode;
+        int mTotalLatencyMillis;
         int mGenerateDocumentProtoLatencyMillis;
         int mRewriteDocumentTypesLatencyMillis;
         int mNativeLatencyMillis;
@@ -140,17 +171,24 @@
         int mNativeNumTokensIndexed;
         boolean mNativeExceededMaxNumTokens;
 
-        /** Builder takes {@link GeneralStats.Builder}. */
+        /** Builder for {@link PutDocumentStats} */
         public Builder(@NonNull String packageName, @NonNull String database) {
-            Objects.requireNonNull(packageName);
-            Objects.requireNonNull(database);
-            mGeneralStatsBuilder = new GeneralStats.Builder(packageName, database);
+            mPackageName = Objects.requireNonNull(packageName);
+            mDatabase = Objects.requireNonNull(database);
         }
 
-        /** Returns {@link GeneralStats.Builder}. */
+        /** Sets the status code. */
         @NonNull
-        public GeneralStats.Builder getGeneralStatsBuilder() {
-            return mGeneralStatsBuilder;
+        public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) {
+            mStatusCode = statusCode;
+            return this;
+        }
+
+        /** Sets total latency in millis. */
+        @NonNull
+        public Builder setTotalLatencyMillis(int totalLatencyMillis) {
+            mTotalLatencyMillis = totalLatencyMillis;
+            return this;
         }
 
         /** Sets how much time we spend for generating document proto, in milliseconds. */
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 5a0199f..31fead5e 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
@@ -192,9 +192,8 @@
     @GuardedBy("mLock")
     private void logStatsImplLocked(@NonNull CallStats stats) {
         mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
-        ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(),
-                stats.getCallType());
-        String database = stats.getGeneralStats().getDatabase();
+        ExtraStats extraStats = createExtraStatsLocked(stats.getPackageName(), stats.getCallType());
+        String database = stats.getDatabase();
         try {
             int hashCodeForDatabase = calculateHashCodeMd5(database);
             AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_CALL_STATS_REPORTED,
@@ -202,8 +201,8 @@
                     extraStats.mSkippedSampleCount,
                     extraStats.mPackageUid,
                     hashCodeForDatabase,
-                    stats.getGeneralStats().getStatusCode(),
-                    stats.getGeneralStats().getTotalLatencyMillis(),
+                    stats.getStatusCode(),
+                    stats.getTotalLatencyMillis(),
                     stats.getCallType(),
                     stats.getEstimatedBinderLatencyMillis(),
                     stats.getNumOperationsSucceeded(),
@@ -224,9 +223,9 @@
     @GuardedBy("mLock")
     private void logStatsImplLocked(@NonNull PutDocumentStats stats) {
         mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
-        ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(),
-                CallStats.CALL_TYPE_PUT_DOCUMENT);
-        String database = stats.getGeneralStats().getDatabase();
+        ExtraStats extraStats = createExtraStatsLocked(
+                stats.getPackageName(), CallStats.CALL_TYPE_PUT_DOCUMENT);
+        String database = stats.getDatabase();
         try {
             int hashCodeForDatabase = calculateHashCodeMd5(database);
             AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_PUT_DOCUMENT_STATS_REPORTED,
@@ -234,8 +233,8 @@
                     extraStats.mSkippedSampleCount,
                     extraStats.mPackageUid,
                     hashCodeForDatabase,
-                    stats.getGeneralStats().getStatusCode(),
-                    stats.getGeneralStats().getTotalLatencyMillis(),
+                    stats.getStatusCode(),
+                    stats.getTotalLatencyMillis(),
                     stats.getGenerateDocumentProtoLatencyMillis(),
                     stats.getRewriteDocumentTypesLatencyMillis(),
                     stats.getNativeLatencyMillis(),
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStore.java
index 95ed368..af09210 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStore.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStore.java
@@ -66,8 +66,8 @@
  * @hide
  */
 public class VisibilityStore {
-    /** No-op user id that won't have any visibility settings. */
-    public static final int NO_OP_USER_ID = -1;
+    /** No-op uid that won't have any visibility settings. */
+    public static final int NO_OP_UID = -1;
 
     /** Version for the visibility schema */
     private static final int SCHEMA_VERSION = 0;
@@ -106,7 +106,7 @@
      * @param userContext Context of the user that the call is being made as
      */
     public VisibilityStore(@NonNull AppSearchImpl appSearchImpl, @NonNull Context userContext) {
-        mAppSearchImpl = appSearchImpl;
+        mAppSearchImpl = Objects.requireNonNull(appSearchImpl);
         mUserContext = Objects.requireNonNull(userContext);
     }
 
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index 395292d..78d39cc 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-c35ced970a63a6c7b1d17f9706160579540850d6
+31a54dba5bda4d0109ea91eb1ac047c937cbaae3
diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp
index 0eff83c..a157517 100644
--- a/cmds/app_process/Android.bp
+++ b/cmds/app_process/Android.bp
@@ -29,7 +29,16 @@
         },
     },
 
-    ldflags: ["-Wl,--export-dynamic"],
+    // Symbols exported from the executable in .dynsym interpose symbols in every
+    // linker namespace, including an app's classloader namespace. Provide this
+    // version script to prevent unwanted interposition.
+    //
+    // By default, the static linker doesn't export most of an executable's symbols,
+    // but it will export a symbol that appears to override a symbol in a needed DSO.
+    // This commonly happens with C++ vaguely-linked entities, such as template
+    // functions or type_info variables. Hence, a version script is needed even for
+    // an executable.
+    version_script: "version-script.txt",
 
     shared_libs: [
         "libandroid_runtime",
diff --git a/cmds/app_process/version-script.txt b/cmds/app_process/version-script.txt
new file mode 100644
index 0000000..a98066a
--- /dev/null
+++ b/cmds/app_process/version-script.txt
@@ -0,0 +1,4 @@
+{
+  local:
+    *;
+};
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0bb12c8..80664ed 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2723,6 +2723,7 @@
   public final class InputDevice implements android.os.Parcelable {
     method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void disable();
     method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void enable();
+    field public static final int ACCESSIBILITY_DEVICE_ID = -2; // 0xfffffffe
   }
 
   public class KeyEvent extends android.view.InputEvent implements android.os.Parcelable {
@@ -3189,6 +3190,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.app.ActivityManager.RunningTaskInfo> getChildTasks(@NonNull android.window.WindowContainerToken, @NonNull int[]);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public android.window.WindowContainerToken getImeTarget(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.app.ActivityManager.RunningTaskInfo> getRootTasks(int, @NonNull int[]);
+    method @BinderThread public void onAppSplashScreenViewRemoved(int);
     method @BinderThread public void onBackPressedOnTaskRoot(@NonNull android.app.ActivityManager.RunningTaskInfo);
     method @BinderThread public void onTaskAppeared(@NonNull android.app.ActivityManager.RunningTaskInfo, @NonNull android.view.SurfaceControl);
     method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo);
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 1ce598b..8e1f263 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -419,7 +419,7 @@
     private IBinder mLaunchCookie;
     private IRemoteTransition mRemoteTransition;
     private boolean mOverrideTaskTransition;
-    private int mSplashScreenThemeResId;
+    private String mSplashScreenThemeResName;
     @SplashScreen.SplashScreenStyle
     private int mSplashScreenStyle;
     private boolean mRemoveWithTaskOrganizer;
@@ -1174,7 +1174,7 @@
         mRemoteTransition = IRemoteTransition.Stub.asInterface(opts.getBinder(
                 KEY_REMOTE_TRANSITION));
         mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION);
-        mSplashScreenThemeResId = opts.getInt(KEY_SPLASH_SCREEN_THEME);
+        mSplashScreenThemeResName = opts.getString(KEY_SPLASH_SCREEN_THEME);
         mRemoveWithTaskOrganizer = opts.getBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER);
         mLaunchedFromBubble = opts.getBoolean(KEY_LAUNCHED_FROM_BUBBLE);
         mTransientLaunch = opts.getBoolean(KEY_TRANSIENT_LAUNCH);
@@ -1368,8 +1368,9 @@
      * Gets whether the activity want to be launched as other theme for the splash screen.
      * @hide
      */
-    public int getSplashScreenThemeResId() {
-        return mSplashScreenThemeResId;
+    @Nullable
+    public String getSplashScreenThemeResName() {
+        return mSplashScreenThemeResName;
     }
 
     /**
@@ -1945,8 +1946,8 @@
         if (mOverrideTaskTransition) {
             b.putBoolean(KEY_OVERRIDE_TASK_TRANSITION, mOverrideTaskTransition);
         }
-        if (mSplashScreenThemeResId != 0) {
-            b.putInt(KEY_SPLASH_SCREEN_THEME, mSplashScreenThemeResId);
+        if (mSplashScreenThemeResName != null && !mSplashScreenThemeResName.isEmpty()) {
+            b.putString(KEY_SPLASH_SCREEN_THEME, mSplashScreenThemeResName);
         }
         if (mRemoveWithTaskOrganizer) {
             b.putBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER, mRemoveWithTaskOrganizer);
diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java
index 63f93bf..806091e 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -423,7 +423,7 @@
                 shortLabelResName, longLabel, longLabelResId, longLabelResName, disabledMessage,
                 disabledMessageResId, disabledMessageResName, categoriesSet, intents, rank, extras,
                 getCreationTimestampMillis(), flags, iconResId, iconResName, bitmapPath, iconUri,
-                disabledReason, persons, locusId, 0);
+                disabledReason, persons, locusId, null);
         si.setImplicitRank(implicitRank);
         if ((implicitRank & ShortcutInfo.RANK_CHANGED_BIT) != 0) {
             si.setRankChanged();
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 32fc74f..b0ce6a5 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -63,4 +63,5 @@
     void bypassNextStagedInstallerCheck(boolean value);
 
     void setAllowUnlimitedSilentUpdates(String installerPackageName);
+    void setSilentUpdatesThrottleTime(long throttleTimeInSeconds);
 }
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 76712b5..a264beb 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -449,7 +449,7 @@
 
     private int mDisabledReason;
 
-    private int mStartingThemeResId;
+    @Nullable private String mStartingThemeResName;
 
     private ShortcutInfo(Builder b) {
         mUserId = b.mContext.getUserId();
@@ -478,8 +478,9 @@
         mExtras = b.mExtras;
         mLocusId = b.mLocusId;
 
+        mStartingThemeResName = b.mStartingThemeResId != 0
+                ? b.mContext.getResources().getResourceName(b.mStartingThemeResId) : null;
         updateTimestamp();
-        mStartingThemeResId = b.mStartingThemeResId;
     }
 
     /**
@@ -626,7 +627,7 @@
             // Set this bit.
             mFlags |= FLAG_KEY_FIELDS_ONLY;
         }
-        mStartingThemeResId = source.mStartingThemeResId;
+        mStartingThemeResName = source.mStartingThemeResName;
     }
 
     /**
@@ -950,8 +951,8 @@
         if (source.mLocusId != null) {
             mLocusId = source.mLocusId;
         }
-        if (source.mStartingThemeResId != 0) {
-            mStartingThemeResId = source.mStartingThemeResId;
+        if (source.mStartingThemeResName != null && !source.mStartingThemeResName.isEmpty()) {
+            mStartingThemeResName = source.mStartingThemeResName;
         }
     }
 
@@ -1454,11 +1455,12 @@
     }
 
     /**
-     * Returns the theme resource id used for the splash screen.
+     * Returns the theme resource name used for the splash screen.
      * @hide
      */
-    public int getStartingThemeResId() {
-        return mStartingThemeResId;
+    @Nullable
+    public String getStartingThemeResName() {
+        return mStartingThemeResName;
     }
 
     /** @hide -- old signature, the internal code still uses it. */
@@ -2182,7 +2184,7 @@
         mPersons = source.readParcelableArray(cl, Person.class);
         mLocusId = source.readParcelable(cl);
         mIconUri = source.readString8();
-        mStartingThemeResId = source.readInt();
+        mStartingThemeResName = source.readString8();
     }
 
     @Override
@@ -2234,7 +2236,7 @@
         dest.writeParcelableArray(mPersons, flags);
         dest.writeParcelable(mLocusId, flags);
         dest.writeString8(mIconUri);
-        dest.writeInt(mStartingThemeResId);
+        dest.writeString8(mStartingThemeResName);
     }
 
     public static final @NonNull Creator<ShortcutInfo> CREATOR =
@@ -2391,10 +2393,10 @@
         sb.append("disabledReason=");
         sb.append(getDisabledReasonDebugString(mDisabledReason));
 
-        if (mStartingThemeResId != 0) {
+        if (mStartingThemeResName != null && !mStartingThemeResName.isEmpty()) {
             addIndentOrComma(sb, indent);
-            sb.append("SplashScreenThemeResId=");
-            sb.append(Integer.toHexString(mStartingThemeResId));
+            sb.append("SplashScreenThemeResName=");
+            sb.append(mStartingThemeResName);
         }
 
         addIndentOrComma(sb, indent);
@@ -2482,7 +2484,8 @@
             Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras,
             long lastChangedTimestamp,
             int flags, int iconResId, String iconResName, String bitmapPath, String iconUri,
-            int disabledReason, Person[] persons, LocusId locusId, int startingThemeResId) {
+            int disabledReason, Person[] persons, LocusId locusId,
+            @Nullable String startingThemeResName) {
         mUserId = userId;
         mId = id;
         mPackageName = packageName;
@@ -2511,6 +2514,6 @@
         mDisabledReason = disabledReason;
         mPersons = persons;
         mLocusId = locusId;
-        mStartingThemeResId = startingThemeResId;
+        mStartingThemeResName = startingThemeResName;
     }
 }
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index 233abf3..3ed5c64 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -74,7 +74,7 @@
     /**
      * Get the theme res ID of the starting window, it can be 0 if not specified.
      */
-    public abstract int getShortcutStartingThemeResId(int launcherUserId,
+    public abstract @Nullable String getShortcutStartingThemeResName(int launcherUserId,
             @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId,
             int userId);
 
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 1e650a8..154d923 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -493,6 +493,7 @@
                 if (targetResult.isError()) {
                     return input.error(targetResult);
                 }
+                targetSdkVersion = targetResult.getResult();
 
                 ParseResult<Integer> minResult = ParsingPackageUtils.computeMinSdkVersion(
                         minVer, minCode, ParsingPackageUtils.SDK_VERSION,
@@ -500,8 +501,6 @@
                 if (minResult.isError()) {
                     return input.error(minResult);
                 }
-
-                targetSdkVersion = targetResult.getResult();
                 minSdkVersion = minResult.getResult();
             }
         }
diff --git a/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java b/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java
new file mode 100644
index 0000000..4ec6f0d
--- /dev/null
+++ b/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java
@@ -0,0 +1,97 @@
+/*
+ * 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 android.hardware.biometrics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Build;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+/**
+ * "Base" functionality. For settings-specific functionality (which may rely on this base
+ * functionality), see {@link com.android.settings.biometrics.ParentalControlsUtils}
+ * @hide
+ */
+public class ParentalControlsUtilsInternal {
+
+    private static final String TEST_ALWAYS_REQUIRE_CONSENT =
+            "android.hardware.biometrics.ParentalControlsUtilsInternal.always_require_consent";
+
+    public static boolean isTestModeEnabled(@NonNull Context context) {
+        if (Build.IS_USERDEBUG || Build.IS_ENG) {
+            return Settings.Secure.getInt(context.getContentResolver(),
+                    TEST_ALWAYS_REQUIRE_CONSENT, 0) != 0;
+        }
+        return false;
+    }
+
+    public static boolean parentConsentRequired(@NonNull Context context,
+            @NonNull DevicePolicyManager dpm, @BiometricAuthenticator.Modality int modality,
+            @NonNull UserHandle userHandle) {
+        if (isTestModeEnabled(context)) {
+            return true;
+        }
+
+        return parentConsentRequired(dpm, modality, userHandle);
+    }
+
+    /**
+     * @return true if parental consent is required in order for biometric sensors to be used.
+     */
+    public static boolean parentConsentRequired(@NonNull DevicePolicyManager dpm,
+            @BiometricAuthenticator.Modality int modality, @NonNull UserHandle userHandle) {
+        final ComponentName cn = getSupervisionComponentName(dpm, userHandle);
+        if (cn == null) {
+            return false;
+        }
+
+        final int keyguardDisabledFeatures = dpm.getKeyguardDisabledFeatures(cn);
+        final boolean dpmFpDisabled = containsFlag(keyguardDisabledFeatures,
+                DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+        final boolean dpmFaceDisabled = containsFlag(keyguardDisabledFeatures,
+                DevicePolicyManager.KEYGUARD_DISABLE_FACE);
+        final boolean dpmIrisDisabled = containsFlag(keyguardDisabledFeatures,
+                DevicePolicyManager.KEYGUARD_DISABLE_IRIS);
+
+        final boolean consentRequired;
+        if (containsFlag(modality, BiometricAuthenticator.TYPE_FINGERPRINT) && dpmFpDisabled) {
+            consentRequired = true;
+        } else if (containsFlag(modality, BiometricAuthenticator.TYPE_FACE) && dpmFaceDisabled) {
+            consentRequired = true;
+        } else if (containsFlag(modality, BiometricAuthenticator.TYPE_IRIS) && dpmIrisDisabled) {
+            consentRequired = true;
+        } else {
+            consentRequired = false;
+        }
+
+        return consentRequired;
+    }
+
+    @Nullable
+    public static ComponentName getSupervisionComponentName(@NonNull DevicePolicyManager dpm,
+            @NonNull UserHandle userHandle) {
+        return dpm.getProfileOwnerOrDeviceOwnerSupervisionComponent(userHandle);
+    }
+
+    private static boolean containsFlag(int haystack, int needle) {
+        return (haystack & needle) != 0;
+    }
+}
diff --git a/core/java/android/os/PackageTagsList.java b/core/java/android/os/PackageTagsList.java
index c94d3de..4a81dc6 100644
--- a/core/java/android/os/PackageTagsList.java
+++ b/core/java/android/os/PackageTagsList.java
@@ -23,20 +23,26 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
+import com.android.internal.annotations.Immutable;
+
 import java.io.PrintWriter;
+import java.util.Collection;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
 /**
- * A list of packages and associated attribution tags that supports easy membership checks.
+ * A list of packages and associated attribution tags that supports easy membership checks. Supports
+ * "wildcard" attribution tags (ie, matching any attribution tag under a package) in additional to
+ * standard checks.
  *
  * @hide
  */
 @TestApi
+@Immutable
 public final class PackageTagsList implements Parcelable {
 
-    // an empty set value matches any attribution tag
+    // an empty set value matches any attribution tag (ie, wildcard)
     private final ArrayMap<String, ArraySet<String>> mPackageTags;
 
     private PackageTagsList(@NonNull ArrayMap<String, ArraySet<String>> packageTags) {
@@ -51,15 +57,34 @@
     }
 
     /**
-     * Returns true if the given package is represented within this instance. If this returns true
-     * this does not imply anything about whether any given attribution tag under the given package
-     * name is present.
+     * Returns true if the given package is found within this instance. If this returns true this
+     * does not imply anything about whether any given attribution tag under the given package name
+     * is present.
      */
     public boolean includes(@NonNull String packageName) {
         return mPackageTags.containsKey(packageName);
     }
 
     /**
+     * Returns true if the given attribution tag is found within this instance under any package.
+     * Only returns true if the attribution tag literal is found, not if any package contains the
+     * set of all attribution tags.
+     *
+     * @hide
+     */
+    public boolean includesTag(@NonNull String attributionTag) {
+        final int size = mPackageTags.size();
+        for (int i = 0; i < size; i++) {
+            ArraySet<String> tags = mPackageTags.valueAt(i);
+            if (tags.contains(attributionTag)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
      * Returns true if all attribution tags under the given package are contained within this
      * instance.
      */
@@ -76,6 +101,7 @@
         if (tags == null) {
             return false;
         } else if (tags.isEmpty()) {
+            // our tags are the full set, so we contain any attribution tag
             return true;
         } else {
             return tags.contains(attributionTag);
@@ -98,10 +124,12 @@
                 return false;
             }
             if (tags.isEmpty()) {
+                // our tags are the full set, so we contain whatever the other tags are
                 continue;
             }
             ArraySet<String> otherTags = packageTagsList.mPackageTags.valueAt(i);
             if (otherTags.isEmpty()) {
+                // other tags are the full set, so we can't contain them
                 return false;
             }
             if (!tags.containsAll(otherTags)) {
@@ -248,6 +276,31 @@
         }
 
         /**
+         * Adds the specified package and set of attribution tags to the builder.
+         *
+         * @hide
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public @NonNull Builder add(@NonNull String packageName,
+                @NonNull Collection<String> attributionTags) {
+            if (attributionTags.isEmpty()) {
+                // the input is not allowed to specify a full set by passing in an empty collection
+                return this;
+            }
+
+            ArraySet<String> tags = mPackageTags.get(packageName);
+            if (tags == null) {
+                tags = new ArraySet<>(attributionTags);
+                mPackageTags.put(packageName, tags);
+            } else if (!tags.isEmpty()) {
+                // if we contain the full set, already done, otherwise add all the tags
+                tags.addAll(attributionTags);
+            }
+
+            return this;
+        }
+
+        /**
          * Adds the specified {@link PackageTagsList} to the builder.
          */
         @SuppressLint("MissingGetterMatchingBuilder")
@@ -267,13 +320,92 @@
                 if (newTags.isEmpty()) {
                     add(entry.getKey());
                 } else {
-                    ArraySet<String> tags = mPackageTags.get(entry.getKey());
-                    if (tags == null) {
-                        tags = new ArraySet<>(newTags);
-                        mPackageTags.put(entry.getKey(), tags);
-                    } else if (!tags.isEmpty()) {
-                        tags.addAll(newTags);
-                    }
+                    add(entry.getKey(), newTags);
+                }
+            }
+
+            return this;
+        }
+
+        /**
+         * Removes all attribution tags under the specified package from the builder.
+         *
+         * @hide
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public @NonNull Builder remove(@NonNull String packageName) {
+            mPackageTags.remove(packageName);
+            return this;
+        }
+
+        /**
+         * Removes the specified package and attribution tag from the builder if and only if the
+         * specified attribution tag is listed explicitly under the package. If the package contains
+         * all possible attribution tags, then nothing will be removed.
+         *
+         * @hide
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public @NonNull Builder remove(@NonNull String packageName,
+                @Nullable String attributionTag) {
+            ArraySet<String> tags = mPackageTags.get(packageName);
+            if (tags != null && tags.remove(attributionTag) && tags.isEmpty()) {
+                mPackageTags.remove(packageName);
+            }
+            return this;
+        }
+
+        /**
+         * Removes the specified package and set of attribution tags from the builder if and only if
+         * the specified set of attribution tags are listed explicitly under the package. If the
+         * package contains all possible attribution tags, then nothing will be removed.
+         *
+         * @hide
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public @NonNull Builder remove(@NonNull String packageName,
+                @NonNull Collection<String> attributionTags) {
+            if (attributionTags.isEmpty()) {
+                // the input is not allowed to specify a full set by passing in an empty collection
+                return this;
+            }
+
+            ArraySet<String> tags = mPackageTags.get(packageName);
+            if (tags != null && tags.removeAll(attributionTags) && tags.isEmpty()) {
+                mPackageTags.remove(packageName);
+            }
+            return this;
+        }
+
+        /**
+         * Removes the specified {@link PackageTagsList} from the builder. If a package contains all
+         * possible attribution tags, it will only be removed if the package in the removed
+         * {@link PackageTagsList} also contains all possible attribution tags.
+         *
+         * @hide
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public @NonNull Builder remove(@NonNull PackageTagsList packageTagsList) {
+            return remove(packageTagsList.mPackageTags);
+        }
+
+        /**
+         * Removes the given map of package to attribution tags to the builder. An empty set of
+         * attribution tags is interpreted to imply all attribution tags under that package. If a
+         * package contains all possible attribution tags, it will only be removed if the package in
+         * the removed map also contains all possible attribution tags.
+         *
+         * @hide
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public @NonNull Builder remove(@NonNull Map<String, ? extends Set<String>> packageTagsMap) {
+            for (Map.Entry<String, ? extends Set<String>> entry : packageTagsMap.entrySet()) {
+                Set<String> removedTags = entry.getValue();
+                if (removedTags.isEmpty()) {
+                    // if removing the full set, drop the package completely
+                    remove(entry.getKey());
+                } else {
+                    remove(entry.getKey(), removedTags);
                 }
             }
 
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 055e71f..8b4c0d9 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -229,6 +229,7 @@
     protected abstract void onStopListening(Callback listener);
 
     @Override
+    @SuppressLint("MissingNullability")
     public Context createContext(@NonNull ContextParams contextParams) {
         if (contextParams.getNextAttributionSource() != null) {
             if (mHandler.getLooper().equals(Looper.myLooper())) {
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 4f1354d..782a992 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -444,6 +444,13 @@
 
     private static final int VIBRATOR_ID_ALL = -1;
 
+    /**
+     * The device id of input events generated inside accessibility service.
+     * @hide
+     */
+    @TestApi
+    public static final int ACCESSIBILITY_DEVICE_ID = -2;
+
     public static final @android.annotation.NonNull Parcelable.Creator<InputDevice> CREATOR =
             new Parcelable.Creator<InputDevice>() {
         public InputDevice createFromParcel(Parcel in) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c1e394d..5964f63 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2371,6 +2371,14 @@
         public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000;
 
         /**
+         * Flag to prevent the window from being magnified by the accessibility magnifier.
+         *
+         * TODO(b/190623172): This is a temporary solution and need to find out another way instead.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 0x00400000;
+
+        /**
          * Flag to indicate that the status bar window is in a state such that it forces showing
          * the navigation bar unless the navigation bar window is explicitly set to
          * {@link View#GONE}.
@@ -2473,6 +2481,7 @@
                 PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
                 SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
                 PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
+                PRIVATE_FLAG_NOT_MAGNIFIABLE,
                 PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
                 PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
                 PRIVATE_FLAG_USE_BLAST,
@@ -2553,6 +2562,10 @@
                         equals = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
                         name = "IS_ROUNDED_CORNERS_OVERLAY"),
                 @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_NOT_MAGNIFIABLE,
+                        equals = PRIVATE_FLAG_NOT_MAGNIFIABLE,
+                        name = "NOT_MAGNIFIABLE"),
+                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
                         equals = PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
                         name = "STATUS_FORCE_SHOW_NAVIGATION"),
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index 7668d80..81c934d 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -16,6 +16,9 @@
 
 package android.view;
 
+import static android.os.IInputConstants.POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY;
+import static android.os.IInputConstants.POLICY_FLAG_INPUTFILTER_TRUSTED;
+
 import android.annotation.IntDef;
 import android.os.PowerManager;
 
@@ -27,10 +30,13 @@
  * @hide
  */
 public interface WindowManagerPolicyConstants {
-    // Policy flags.  These flags are also defined in frameworks/base/include/ui/Input.h.
+    // Policy flags.  These flags are also defined in frameworks/base/include/ui/Input.h and
+    // frameworks/native/libs/input/android/os/IInputConstants.aidl
     int FLAG_WAKE = 0x00000001;
     int FLAG_VIRTUAL = 0x00000002;
 
+    int FLAG_INPUTFILTER_TRUSTED = POLICY_FLAG_INPUTFILTER_TRUSTED;
+    int FLAG_INJECTED_FROM_ACCESSIBILITY = POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY;
     int FLAG_INJECTED = 0x01000000;
     int FLAG_TRUSTED = 0x02000000;
     int FLAG_FILTERED = 0x04000000;
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index 1b1dc4a..10ae691 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -16,6 +16,7 @@
 package android.view.contentcapture;
 
 import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
+import static android.view.contentcapture.ContentCaptureManager.DEBUG;
 import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID;
 
 import android.annotation.IntDef;
@@ -509,11 +510,13 @@
             string.append(", class=").append(className);
             string.append(", id=").append(mNode.getAutofillId());
             if (mNode.getText() != null) {
-                string.append(", text=").append(getSanitizedString(mNode.getText()));
+                string.append(", text=")
+                        .append(DEBUG ? mNode.getText() : getSanitizedString(mNode.getText()));
             }
         }
         if (mText != null) {
-            string.append(", text=").append(getSanitizedString(mText));
+            string.append(", text=")
+                    .append(DEBUG ? mText : getSanitizedString(mText));
         }
         if (mClientContext != null) {
             string.append(", context=").append(mClientContext);
@@ -522,10 +525,13 @@
             string.append(", insets=").append(mInsets);
         }
         if (mComposingStart > MAX_INVALID_VALUE) {
-            string.append(", hasComposing");
+            string.append(", composing=[")
+                    .append(mComposingStart).append(",").append(mComposingEnd).append("]");
         }
         if (mSelectionStartIndex > MAX_INVALID_VALUE) {
-            string.append(", hasSelection");
+            string.append(", selection=[")
+                    .append(mSelectionStartIndex).append(",")
+                    .append(mSelectionEndIndex).append("]");
         }
         return string.append(']').toString();
     }
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index ed840ce..9241c30 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -212,6 +212,9 @@
 
     private static final String TAG = ContentCaptureManager.class.getSimpleName();
 
+    /** @hide */
+    public static final boolean DEBUG = false;
+
     /** Error happened during the data sharing session. */
     public static final int DATA_SHARE_ERROR_UNKNOWN = 1;
 
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index 3eb35c2..8b8dba8 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -52,6 +52,11 @@
     void copySplashScreenView(int taskId);
 
     /**
+     * Called when the Task removed the splash screen.
+     */
+    void onAppSplashScreenViewRemoved(int taskId);
+
+    /**
      * A callback when the Task is available for the registered organizer. The client is responsible
      * for releasing the SurfaceControl in the callback. For non-root tasks, the leash may initially
      * be hidden so it is up to the organizer to show this task.
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index 6772afe..4a3bf91 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -34,8 +34,10 @@
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.RemoteCallback;
 import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -76,7 +78,7 @@
  */
 public final class SplashScreenView extends FrameLayout {
     private static final String TAG = SplashScreenView.class.getSimpleName();
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = Build.IS_DEBUGGABLE;
 
     private static final int LIGHT_BARS_MASK =
             WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
@@ -85,6 +87,7 @@
                     | FLAG_TRANSLUCENT_NAVIGATION | FLAG_TRANSLUCENT_STATUS;
 
     private boolean mNotCopyable;
+    private boolean mIsCopied;
     private int mInitBackgroundColor;
     private int mInitIconBackgroundColor;
     private View mIconView;
@@ -103,6 +106,10 @@
     private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
     @Nullable
     private SurfaceView mSurfaceView;
+    @Nullable
+    private SurfaceControlViewHost mSurfaceHost;
+    @Nullable
+    private RemoteCallback mClientCallback;
 
     // cache original window and status
     private Window mWindow;
@@ -127,6 +134,7 @@
         private Bitmap mParceledIconBitmap;
         private Drawable mIconDrawable;
         private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
+        private RemoteCallback mClientCallback;
         private int mBrandingImageWidth;
         private int mBrandingImageHeight;
         private Drawable mBrandingDrawable;
@@ -161,6 +169,7 @@
             }
             mIconAnimationStart = Instant.ofEpochMilli(parcelable.mIconAnimationStartMillis);
             mIconAnimationDuration = Duration.ofMillis(parcelable.mIconAnimationDurationMillis);
+            mClientCallback = parcelable.mClientCallback;
             if (DEBUG) {
                 Log.d(TAG, String.format("Building from parcel drawable: %s", mIconDrawable));
             }
@@ -228,6 +237,7 @@
             view.mInitBackgroundColor = mBackgroundColor;
             view.mInitIconBackgroundColor = mIconBackground;
             view.setBackgroundColor(mBackgroundColor);
+            view.mClientCallback = mClientCallback;
 
             view.mBrandingImageView = view.findViewById(R.id.splashscreen_branding_view);
 
@@ -285,7 +295,8 @@
             if (mSurfacePackage == null) {
                 if (DEBUG) {
                     Log.d(TAG,
-                            "Creating Original SurfacePackage. SurfaceView: " + surfaceView);
+                            "SurfaceControlViewHost created on thread "
+                                    + Thread.currentThread().getId());
                 }
 
                 SurfaceControlViewHost viewHost = new SurfaceControlViewHost(mContext,
@@ -297,6 +308,7 @@
                 SurfaceControlViewHost.SurfacePackage surfacePackage = viewHost.getSurfacePackage();
                 surfaceView.setChildSurfacePackage(surfacePackage);
                 view.mSurfacePackage = surfacePackage;
+                view.mSurfaceHost = viewHost;
                 view.mSurfacePackageCopy = new SurfaceControlViewHost.SurfacePackage(
                         surfacePackage);
             } else {
@@ -357,6 +369,7 @@
      * @hide
      */
     public void onCopied() {
+        mIsCopied = true;
         if (mSurfaceView == null) {
             return;
         }
@@ -369,6 +382,12 @@
         mSurfacePackage = null;
     }
 
+    /** @hide **/
+    @Nullable
+    public SurfaceControlViewHost getSurfaceHost() {
+        return mSurfaceHost;
+    }
+
     @Override
     public void setAlpha(float alpha) {
         super.setAlpha(alpha);
@@ -407,8 +426,7 @@
         if (DEBUG) {
             mSurfacePackage.getSurfaceControl().addOnReparentListener(
                     (transaction, parent) -> Log.e(TAG,
-                            String.format("SurfacePackage'surface reparented.\n Parent: %s",
-                                    parent), new Throwable()));
+                            String.format("SurfacePackage'surface reparented to %s", parent)));
             Log.d(TAG, "Transferring surface " + mSurfaceView.toString());
         }
         mSurfaceView.setChildSurfacePackage(mSurfacePackage);
@@ -466,9 +484,36 @@
         mHasRemoved = true;
     }
 
+    /** @hide **/
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        releaseAnimationSurfaceHost();
+    }
+
+    private void releaseAnimationSurfaceHost() {
+        if (mSurfaceHost != null && !mIsCopied) {
+            final SurfaceControlViewHost finalSurfaceHost = mSurfaceHost;
+            mSurfaceHost = null;
+            finalSurfaceHost.getView().post(() -> {
+                if (DEBUG) {
+                    Log.d(TAG,
+                            "Shell removed splash screen."
+                                    + " Releasing SurfaceControlViewHost on thread #"
+                                    + Thread.currentThread().getId());
+                }
+                finalSurfaceHost.release();
+            });
+        } else if (mSurfacePackage != null && mSurfaceHost == null) {
+            mSurfacePackage = null;
+            mClientCallback.sendResult(null);
+        }
+    }
+
     /**
      * Called when this view is attached to an activity. This also makes SystemUI colors
      * transparent so the content of splash screen view can draw fully.
+     *
      * @hide
      */
     public void attachHostActivityAndSetSystemUIColors(Activity activity, Window window) {
@@ -585,6 +630,7 @@
         private long mIconAnimationDurationMillis;
 
         private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
+        private RemoteCallback mClientCallback;
 
         public SplashScreenViewParcelable(SplashScreenView view) {
             mIconSize = view.mIconView.getWidth();
@@ -641,6 +687,7 @@
             mIconAnimationDurationMillis = source.readLong();
             mIconBackground = source.readInt();
             mSurfacePackage = source.readTypedObject(SurfaceControlViewHost.SurfacePackage.CREATOR);
+            mClientCallback = source.readTypedObject(RemoteCallback.CREATOR);
         }
 
         @Override
@@ -660,6 +707,7 @@
             dest.writeLong(mIconAnimationDurationMillis);
             dest.writeInt(mIconBackground);
             dest.writeTypedObject(mSurfacePackage, flags);
+            dest.writeTypedObject(mClientCallback, flags);
         }
 
         public static final @NonNull Parcelable.Creator<SplashScreenViewParcelable> CREATOR =
@@ -697,5 +745,13 @@
         int getIconBackground() {
             return mIconBackground;
         }
+
+        /**
+         * Sets the {@link RemoteCallback} that will be called by the client to notify the shell
+         * of the removal of the {@link SplashScreenView}.
+         */
+        public void setClientCallback(@NonNull RemoteCallback clientCallback) {
+            mClientCallback = clientCallback;
+        }
     }
 }
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index 3340cf4..7399549 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -117,6 +117,16 @@
     public void copySplashScreenView(int taskId) {}
 
     /**
+     * Notify the shell ({@link com.android.wm.shell.ShellTaskOrganizer} that the client has
+     * removed the splash screen view.
+     * @see com.android.wm.shell.ShellTaskOrganizer#onAppSplashScreenViewRemoved(int)
+     * @see SplashScreenView#remove()
+     */
+    @BinderThread
+    public void onAppSplashScreenViewRemoved(int taskId) {
+    }
+
+    /**
      * Called when a task with the registered windowing mode can be controlled by this task
      * organizer. For non-root tasks, the leash may initially be hidden so it is up to the organizer
      * to show this task.
@@ -236,11 +246,16 @@
         }
 
         @Override
-        public void copySplashScreenView(int taskId) {
+        public void copySplashScreenView(int taskId)  {
             mExecutor.execute(() -> TaskOrganizer.this.copySplashScreenView(taskId));
         }
 
         @Override
+        public void onAppSplashScreenViewRemoved(int taskId) {
+            mExecutor.execute(() -> TaskOrganizer.this.onAppSplashScreenViewRemoved(taskId));
+        }
+
+        @Override
         public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
             mExecutor.execute(() -> TaskOrganizer.this.onTaskAppeared(taskInfo, leash));
         }
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 26d6a0c..aabcd7f 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -43,6 +43,10 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
@@ -53,6 +57,7 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -153,6 +158,11 @@
     public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET = 27;
     public static final int CUJ_SETTINGS_PAGE_SCROLL = 28;
     public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = 29;
+    public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = 30;
+    public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = 31;
+    public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32;
+    public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33;
+    public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34;
 
     private static final int NO_STATSD_LOGGING = -1;
 
@@ -191,6 +201,11 @@
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
     };
 
     private static volatile InteractionJankMonitor sInstance;
@@ -240,6 +255,11 @@
             CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET,
             CUJ_SETTINGS_PAGE_SCROLL,
             CUJ_LOCKSCREEN_UNLOCK_ANIMATION,
+            CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON,
+            CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER,
+            CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
+            CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
+            CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -578,6 +598,16 @@
                 return "SETTINGS_PAGE_SCROLL";
             case CUJ_LOCKSCREEN_UNLOCK_ANIMATION:
                 return "LOCKSCREEN_UNLOCK_ANIMATION";
+            case CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON:
+                return "SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON";
+            case CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER:
+                return "SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER";
+            case CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE:
+                return "SHADE_APP_LAUNCH_FROM_QS_TILE";
+            case CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON:
+                return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON";
+            case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP:
+                return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/view/inline/InlineTooltipUi.java b/core/java/com/android/internal/view/inline/InlineTooltipUi.java
index 5ec8b30..25fa678 100644
--- a/core/java/com/android/internal/view/inline/InlineTooltipUi.java
+++ b/core/java/com/android/internal/view/inline/InlineTooltipUi.java
@@ -213,6 +213,8 @@
             if (!mShowing) {
                 params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+                params.privateFlags |=
+                        WindowManager.LayoutParams.PRIVATE_FLAG_NOT_MAGNIFIABLE;
                 mContentContainer.addOnLayoutChangeListener(mAnchoredOnLayoutChangeListener);
                 mWm.addView(mContentContainer, params);
                 mShowing = true;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5ac2336..ee33d48 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3354,6 +3354,15 @@
      (e.g. accessibility, alarms). This is mainly for Wear devices that don't have speakers. -->
     <bool name="config_allowPriorityVibrationsInLowPowerMode">false</bool>
 
+    <!-- The duration (in milliseconds) that should be used to convert vibration ramps to a sequence
+         of fixed amplitude steps on devices without PWLE support. -->
+    <integer name="config_vibrationWaveformRampStepDuration">5</integer>
+
+    <!-- The duration (in milliseconds) that should be applied to waveform vibrations that ends in
+         non-zero amplitudes, . The waveform will
+         be played as a PWLE instead of on/off calls if this value is set. -->
+    <integer name="config_vibrationWaveformRampDownDuration">0</integer>
+
     <!-- Number of retries Cell Data should attempt for a given error code before
          restarting the modem.
          Error codes not listed will not lead to modem restarts.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b574415..7d685a2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2012,6 +2012,8 @@
   <java-symbol type="integer" name="config_notificationServiceArchiveSize" />
   <java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
   <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
+  <java-symbol type="integer" name="config_vibrationWaveformRampStepDuration" />
+  <java-symbol type="integer" name="config_vibrationWaveformRampDownDuration" />
   <java-symbol type="integer" name="config_radioScanningTimeout" />
   <java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
   <java-symbol type="integer" name="config_screenBrightnessSettingMaximum" />
diff --git a/core/tests/coretests/src/android/os/PackageTagsListTest.java b/core/tests/coretests/src/android/os/PackageTagsListTest.java
index 518e02e..9034a5c 100644
--- a/core/tests/coretests/src/android/os/PackageTagsListTest.java
+++ b/core/tests/coretests/src/android/os/PackageTagsListTest.java
@@ -30,6 +30,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
+import java.util.Collections;
 
 @Presubmit
 @RunWith(AndroidJUnit4.class)
@@ -40,7 +41,8 @@
         PackageTagsList.Builder builder = new PackageTagsList.Builder()
                 .add("package1", "attr1")
                 .add("package1", "attr2")
-                .add("package2");
+                .add("package2")
+                .add("package4", Arrays.asList("attr1", "attr2"));
         PackageTagsList list = builder.build();
 
         assertTrue(list.contains(builder.build()));
@@ -49,10 +51,13 @@
         assertTrue(list.contains("package2", "attr1"));
         assertTrue(list.contains("package2", "attr2"));
         assertTrue(list.contains("package2", "attr3"));
+        assertTrue(list.contains("package4", "attr1"));
+        assertTrue(list.contains("package4", "attr2"));
         assertTrue(list.containsAll("package2"));
         assertTrue(list.includes("package1"));
         assertTrue(list.includes("package2"));
         assertFalse(list.contains("package1", "attr3"));
+        assertFalse(list.contains("package4", "attr3"));
         assertFalse(list.containsAll("package1"));
         assertFalse(list.includes("package3"));
 
@@ -92,6 +97,51 @@
     }
 
     @Test
+    public void testPackageTagsList_Remove() {
+        PackageTagsList.Builder builder = new PackageTagsList.Builder()
+                .add("package1", "attr1")
+                .add("package1", "attr2")
+                .add("package2")
+                .add("package4", Arrays.asList("attr1", "attr2", "attr3"))
+                .add("package3", "attr1")
+                .remove("package1", "attr1")
+                .remove("package1", "attr2")
+                .remove("package2", "attr1")
+                .remove("package4", Arrays.asList("attr1", "attr2"))
+                .remove("package3");
+        PackageTagsList list = builder.build();
+
+        assertTrue(list.contains(builder.build()));
+        assertFalse(list.contains("package1", "attr1"));
+        assertFalse(list.contains("package1", "attr2"));
+        assertTrue(list.contains("package2", "attr1"));
+        assertTrue(list.contains("package2", "attr2"));
+        assertTrue(list.contains("package2", "attr3"));
+        assertFalse(list.contains("package3", "attr1"));
+        assertFalse(list.contains("package4", "attr1"));
+        assertFalse(list.contains("package4", "attr2"));
+        assertTrue(list.contains("package4", "attr3"));
+        assertTrue(list.containsAll("package2"));
+        assertFalse(list.includes("package1"));
+        assertTrue(list.includes("package2"));
+        assertFalse(list.includes("package3"));
+        assertTrue(list.includes("package4"));
+    }
+
+    @Test
+    public void testPackageTagsList_EmptyCollections() {
+        PackageTagsList.Builder builder = new PackageTagsList.Builder()
+                .add("package1", Collections.emptyList())
+                .add("package2")
+                .remove("package2", Collections.emptyList());
+        PackageTagsList list = builder.build();
+
+        assertTrue(list.contains(builder.build()));
+        assertFalse(list.contains("package1", "attr1"));
+        assertTrue(list.contains("package2", "attr2"));
+    }
+
+    @Test
     public void testWriteToParcel() {
         PackageTagsList list = new PackageTagsList.Builder()
                 .add("package1", "attr1")
diff --git a/data/etc/car/com.android.car.cluster.home.xml b/data/etc/car/com.android.car.cluster.home.xml
index e1d2b18..a3d0fcf 100644
--- a/data/etc/car/com.android.car.cluster.home.xml
+++ b/data/etc/car/com.android.car.cluster.home.xml
@@ -18,5 +18,6 @@
     <privapp-permissions package="com.android.car.cluster.home">
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"/>
+        <permission name="android.car.permission.CAR_MONITOR_INPUT"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 3c9086d..ac5e2d0 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -961,6 +961,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
+    "-1003678883": {
+      "message": "Cleaning splash screen token=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1003060523": {
       "message": "Finish needs to pause: %s",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index f7fb63d..4b1955e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -325,6 +325,13 @@
     }
 
     @Override
+    public void onAppSplashScreenViewRemoved(int taskId) {
+        if (mStartingWindow != null) {
+            mStartingWindow.onAppSplashScreenViewRemoved(taskId);
+        }
+    }
+
+    @Override
     public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
         synchronized (mLock) {
             onTaskAppeared(new TaskAppearedInfo(taskInfo, leash));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 4d33cb0..46db35a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -35,6 +35,7 @@
 import android.graphics.drawable.ColorDrawable;
 import android.hardware.display.DisplayManager;
 import android.os.IBinder;
+import android.os.RemoteCallback;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.util.Slog;
@@ -42,6 +43,7 @@
 import android.view.Choreographer;
 import android.view.Display;
 import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
@@ -121,6 +123,13 @@
 
     private final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>();
 
+    /**
+     * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is
+     * rendered and that have not yet been removed by their client.
+     */
+    private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts =
+            new SparseArray<>(1);
+
     /** Obtain proper context for showing splash screen on the provided display. */
     private Context getDisplayContext(Context context, int displayId) {
         if (displayId == DEFAULT_DISPLAY) {
@@ -386,25 +395,58 @@
 
     /**
      * Called when the Task wants to copy the splash screen.
-     * @param taskId
      */
     public void copySplashScreenView(int taskId) {
         final StartingWindowRecord preView = mStartingWindowRecords.get(taskId);
         SplashScreenViewParcelable parcelable;
-        if (preView != null && preView.mContentView != null
-                && preView.mContentView.isCopyable()) {
-            parcelable = new SplashScreenViewParcelable(preView.mContentView);
-            preView.mContentView.onCopied();
+        SplashScreenView splashScreenView = preView != null ? preView.mContentView : null;
+        if (splashScreenView != null && splashScreenView.isCopyable()) {
+            parcelable = new SplashScreenViewParcelable(splashScreenView);
+            parcelable.setClientCallback(
+                    new RemoteCallback((bundle) -> mSplashScreenExecutor.execute(
+                            () -> onAppSplashScreenViewRemoved(taskId, false))));
+            splashScreenView.onCopied();
+            mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost());
         } else {
             parcelable = null;
         }
         if (DEBUG_SPLASH_SCREEN) {
             Slog.v(TAG, "Copying splash screen window view for task: " + taskId
-                    + " parcelable? " + parcelable);
+                    + " parcelable: " + parcelable);
         }
         ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
     }
 
+    /**
+     * Called when the {@link SplashScreenView} is removed from the client Activity view's hierarchy
+     * or when the Activity is clean up.
+     *
+     * @param taskId The Task id on which the splash screen was attached
+     */
+    public void onAppSplashScreenViewRemoved(int taskId) {
+        onAppSplashScreenViewRemoved(taskId, true /* fromServer */);
+    }
+
+    /**
+     * @param fromServer If true, this means the removal was notified by the server. This is only
+     *                   used for debugging purposes.
+     * @see #onAppSplashScreenViewRemoved(int)
+     */
+    private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) {
+        SurfaceControlViewHost viewHost =
+                mAnimatedSplashScreenSurfaceHosts.get(taskId);
+        if (viewHost == null) {
+            return;
+        }
+        mAnimatedSplashScreenSurfaceHosts.remove(taskId);
+        if (DEBUG_SPLASH_SCREEN) {
+            String reason = fromServer ? "Server cleaned up" : "App removed";
+            Slog.v(TAG, reason + "the splash screen. Releasing SurfaceControlViewHost for task:"
+                    + taskId);
+        }
+        viewHost.getView().post(viewHost::release);
+    }
+
     protected boolean addWindow(int taskId, IBinder appToken, View view, WindowManager wm,
             WindowManager.LayoutParams params) {
         boolean shouldSaveView = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index cffc789..9c1dde9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -150,6 +150,14 @@
     }
 
     /**
+     * @see StartingSurfaceDrawer#onAppSplashScreenViewRemoved(int)
+     */
+    public void onAppSplashScreenViewRemoved(int taskId) {
+        mSplashScreenExecutor.execute(
+                () -> mStartingSurfaceDrawer.onAppSplashScreenViewRemoved(taskId));
+    }
+
+    /**
      * Called when the content of a task is ready to show, starting window can be removed.
      */
     public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java
index 763835c..d59756d 100644
--- a/location/java/android/location/LocationManagerInternal.java
+++ b/location/java/android/location/LocationManagerInternal.java
@@ -16,14 +16,10 @@
 
 package android.location;
 
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.location.util.identity.CallerIdentity;
-
-import com.android.internal.annotations.Immutable;
-
-import java.util.Set;
+import android.os.PackageTagsList;
 
 /**
  * Location manager local system service interface.
@@ -43,18 +39,14 @@
     }
 
     /**
-     * Interface for getting callbacks when a location provider's location tags change.
-     *
-     * @see LocationTagInfo
+     * Interface for getting callbacks when an app id's location provider package tags change.
      */
-    public interface OnProviderLocationTagsChangeListener {
+    public interface LocationPackageTagsListener {
 
         /**
-         * Called when the location tags for a provider change.
-         *
-         * @param providerLocationTagInfo The tag info for a provider.
+         * Called when the package tags for a location provider change for a uid.
          */
-        void onLocationTagsChanged(@NonNull LocationTagInfo providerLocationTagInfo);
+        void onLocationPackageTagsChanged(int uid, @NonNull PackageTagsList packageTagsList);
     }
 
     /**
@@ -109,58 +101,9 @@
     public abstract @Nullable LocationTime getGnssTimeMillis();
 
     /**
-     * Sets a listener for changes in the location providers' tags. Passing
+     * Sets a listener for changes in an app id's location provider package tags. Passing
      * {@code null} clears the current listener.
-     *
-     * @param listener The listener.
      */
-    public abstract void setOnProviderLocationTagsChangeListener(
-            @Nullable OnProviderLocationTagsChangeListener listener);
-
-    /**
-     * This class represents the location permission tags used by the location provider
-     * packages in a given UID. These tags are strictly used for accessing state guarded
-     * by the location permission(s) by a location provider which are required for the
-     * provider to fulfill its function as being a location provider.
-     */
-    @Immutable
-    public static class LocationTagInfo {
-        private final int mUid;
-
-        @NonNull
-        private final String mPackageName;
-
-        @Nullable
-        private final Set<String> mLocationTags;
-
-        public LocationTagInfo(int uid, @NonNull String packageName,
-                @Nullable Set<String> locationTags) {
-            mUid = uid;
-            mPackageName = packageName;
-            mLocationTags = locationTags;
-        }
-
-        /**
-         * @return The UID for which tags are related.
-         */
-        public int getUid() {
-            return mUid;
-        }
-
-        /**
-         * @return The package for which tags are related.
-         */
-        @NonNull
-        public String getPackageName() {
-            return mPackageName;
-        }
-
-        /**
-         * @return The tags for the package used for location related accesses.
-         */
-        @Nullable
-        public Set<String> getTags() {
-            return mLocationTags;
-        }
-    }
+    public abstract void setLocationPackageTagsListener(
+            @Nullable LocationPackageTagsListener listener);
 }
diff --git a/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml b/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml
index 120b085..73163fc 100644
--- a/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml
+++ b/packages/SettingsLib/IllustrationPreference/AndroidManifest.xml
@@ -18,6 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.settingslib.widget">
 
-    <uses-sdk android:minSdkVersion="21" />
+    <uses-sdk android:minSdkVersion="28" />
 
 </manifest>
diff --git a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
index 3f8439c..efcd41c 100644
--- a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
+++ b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
@@ -20,35 +20,38 @@
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
     android:background="?android:attr/colorBackground"
+    android:importantForAccessibility="noHideDescendants"
     android:gravity="center"
     android:orientation="horizontal">
 
-    <LinearLayout
-        android:layout_width="match_parent"
+    <FrameLayout
+        android:id="@+id/illustration_frame"
+        android:layout_width="wrap_content"
         android:layout_height="@dimen/settingslib_illustration_height"
         android:layout_gravity="center"
         android:gravity="center_vertical"
         android:padding="@dimen/settingslib_illustration_padding"
         android:orientation="vertical">
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@drawable/protection_background"/>
+
         <com.airbnb.lottie.LottieAnimationView
             android:id="@+id/lottie_view"
-            android:adjustViewBounds="true"
-            android:maxWidth="@dimen/settingslib_illustration_width"
-            android:layout_width="wrap_content"
+            android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:layout_gravity="center"
-            android:clipToOutline="true"
-            android:background="@drawable/protection_background"
-            android:importantForAccessibility="no"/>
-    </LinearLayout>
+            android:layout_gravity="center" />
 
-    <FrameLayout
-        android:id="@+id/middleground_layout"
-        android:layout_width="@dimen/settingslib_illustration_width"
-        android:layout_height="@dimen/settingslib_illustration_height"
-        android:padding="@dimen/settingslib_illustration_padding"
-        android:background="@android:color/transparent"
-        android:layout_gravity="center"
-        android:visibility="gone"/>
+        <FrameLayout
+            android:id="@+id/middleground_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@android:color/transparent"
+            android:layout_gravity="center"
+            android:visibility="gone"/>
+    </FrameLayout>
+
 </FrameLayout>
 
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/ColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/ColorUtils.java
index cd2ddeb..07102d5 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/ColorUtils.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/ColorUtils.java
@@ -39,13 +39,6 @@
 public class ColorUtils {
 
     private static HashMap<String, Integer> sSysColors;
-    static {
-        sSysColors = new HashMap<>();
-        sSysColors.put(".colorAccent", android.R.attr.colorAccent);
-        sSysColors.put(".colorPrimary", android.R.attr.colorPrimary);
-        sSysColors.put(".colorPrimaryDark", android.R.attr.colorPrimaryDark);
-        sSysColors.put(".colorSecondary", android.R.attr.colorSecondary);
-    }
 
     private static HashMap<String, Pair<Integer, Integer>> sFixedColors;
     static {
@@ -110,19 +103,6 @@
      * Apply the color of tags to the animation.
      */
     public static void applyDynamicColors(Context context, LottieAnimationView animationView) {
-        for (String key : sSysColors.keySet()) {
-            final int color = sSysColors.get(key);
-            animationView.addValueCallback(
-                    new KeyPath("**", key, "**"),
-                    LottieProperty.COLOR_FILTER,
-                    new SimpleLottieValueCallback<ColorFilter>() {
-                        @Override
-                        public ColorFilter getValue(LottieFrameInfo<ColorFilter> frameInfo) {
-                            return new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN);
-                        }
-                    }
-            );
-        }
         for (String key : sFixedColors.keySet()) {
             final Pair<Integer, Integer> fixedColorPair = sFixedColors.get(key);
             final int color = isDarkMode(context) ? fixedColorPair.second : fixedColorPair.first;
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index 1370eef..e91dd94 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -21,6 +21,7 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
+import android.view.ViewGroup.LayoutParams;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
@@ -35,7 +36,9 @@
  */
 public class IllustrationPreference extends Preference {
 
-    static final String TAG = "IllustrationPreference";
+    private static final String TAG = "IllustrationPreference";
+
+    private static final boolean IS_ENABLED_LOTTIE_ADAPTIVE_COLOR = false;
 
     private int mAnimationId;
     private boolean mIsAutoScale;
@@ -66,11 +69,22 @@
             Log.w(TAG, "Invalid illustration resource id.");
             return;
         }
+
+        // To solve the problem of non-compliant illustrations, we set the frame height
+        // to 300dp and set the length of the short side of the screen to
+        // the width of the frame.
+        final int screenWidth = getContext().getResources().getDisplayMetrics().widthPixels;
+        final int screenHeight = getContext().getResources().getDisplayMetrics().heightPixels;
+        final FrameLayout illustrationFrame = (FrameLayout) holder.findViewById(
+                R.id.illustration_frame);
+        final LayoutParams lp = (LayoutParams) illustrationFrame.getLayoutParams();
+        lp.width = screenWidth < screenHeight ? screenWidth : screenHeight;
+        illustrationFrame.setLayoutParams(lp);
+
         mMiddleGroundLayout = (FrameLayout) holder.findViewById(R.id.middleground_layout);
         mIllustrationView = (LottieAnimationView) holder.findViewById(R.id.lottie_view);
         mIllustrationView.setAnimation(mAnimationId);
         mIllustrationView.loop(true);
-        ColorUtils.applyDynamicColors(getContext(), mIllustrationView);
         mIllustrationView.playAnimation();
         if (mIsAutoScale) {
             enableAnimationAutoScale(mIsAutoScale);
@@ -78,6 +92,9 @@
         if (mMiddleGroundView != null) {
             enableMiddleGroundView();
         }
+        if (IS_ENABLED_LOTTIE_ADAPTIVE_COLOR) {
+            ColorUtils.applyDynamicColors(getContext(), mIllustrationView);
+        }
     }
 
     @VisibleForTesting
@@ -120,6 +137,13 @@
                 mIsAutoScale ? ImageView.ScaleType.CENTER_CROP : ImageView.ScaleType.CENTER_INSIDE);
     }
 
+    /**
+     * Set the lottie illustration resource id.
+     */
+    public void setLottieAnimationResId(int resId) {
+        mAnimationId = resId;
+    }
+
     private void enableMiddleGroundView() {
         mMiddleGroundLayout.removeAllViews();
         mMiddleGroundLayout.addView(mMiddleGroundView);
diff --git a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
index b299061..906ff2c 100644
--- a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
+++ b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
@@ -104,7 +104,7 @@
         android:gravity="center_vertical">
         <View
             android:layout_width=".75dp"
-            android:layout_height="match_parent"
+            android:layout_height="32dp"
             android:layout_marginTop="16dp"
             android:layout_marginBottom="16dp"
             android:background="?android:attr/dividerVertical" />
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 8bc3d22..2579e70 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -238,7 +238,9 @@
              * during the animation.
              */
             @JvmStatic
-            fun fromView(view: View): Controller = GhostedViewLaunchAnimatorController(view)
+            fun fromView(view: View, cujType: Int? = null): Controller {
+                return GhostedViewLaunchAnimatorController(view, cujType)
+            }
         }
 
         /**
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 4b655a1..ffb7ab4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -14,6 +14,7 @@
 import android.view.ViewGroup
 import android.view.ViewGroupOverlay
 import android.widget.FrameLayout
+import com.android.internal.jank.InteractionJankMonitor
 import kotlin.math.min
 
 /**
@@ -29,7 +30,10 @@
  */
 open class GhostedViewLaunchAnimatorController(
     /** The view that will be ghosted and from which the background will be extracted. */
-    private val ghostedView: View
+    private val ghostedView: View,
+
+    /** The [InteractionJankMonitor.CujType] associated to this animation. */
+    private val cujType: Int? = null
 ) : ActivityLaunchAnimator.Controller {
     /** The container to which we will add the ghost view and expanding background. */
     override var launchContainer = ghostedView.rootView as ViewGroup
@@ -125,6 +129,8 @@
 
         val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
         matrix.getValues(initialGhostViewMatrixValues)
+
+        cujType?.let { InteractionJankMonitor.getInstance().begin(ghostedView, it) }
     }
 
     override fun onLaunchAnimationProgress(
@@ -167,6 +173,8 @@
     }
 
     override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+        cujType?.let { InteractionJankMonitor.getInstance().end(it) }
+
         backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
 
         GhostView.removeGhost(ghostedView)
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 298b7c3..7c81325 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -92,5 +92,12 @@
          *         *after* returning to start hiding the keyguard.
          */
         boolean onDismiss();
+
+        /**
+         * Whether running this action when we are locked will start an animation on the keyguard.
+         */
+        default boolean willRunAnimationOnKeyguard() {
+            return false;
+        }
     }
 }
diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml
index 4f97ca4..21b177b 100644
--- a/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml
+++ b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml
@@ -32,7 +32,7 @@
         android:gravity="center_vertical|right"
         android:height="@dimen/rounded_slider_icon_size"
         android:width="@dimen/rounded_slider_icon_size"
-        android:right="@dimen/rounded_slider_icon_inset">
+        android:right="@dimen/volume_slider_icon_inset">
         <rotate
             android:fromDegrees="-270"
             android:toDegrees="-270">
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index 1d93f5d..536b042 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -33,6 +33,7 @@
         android:gravity="start"
         android:textDirection="locale"
         android:ellipsize="marquee"
+        android:marqueeRepeatLimit="1"
         android:singleLine="true"
         android:textAppearance="@style/TextAppearance.QS.TileLabel"/>
 
@@ -43,6 +44,7 @@
         android:gravity="start"
         android:textDirection="locale"
         android:ellipsize="marquee"
+        android:marqueeRepeatLimit="1"
         android:singleLine="true"
         android:visibility="gone"
         android:textAppearance="@style/TextAppearance.QS.TileLabel.Secondary"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index b4deaa0..82ce881 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -396,10 +396,6 @@
     <!-- Whether or not the notifications should always fade as they are dismissed. -->
     <bool name="config_fadeNotificationsOnDismiss">false</bool>
 
-    <!-- Whether or not the parent of the notification row itself is being translated when swiped or
-         its children views. If true, then the contents are translated and vice versa. -->
-    <bool name="config_translateNotificationContentsOnSwipe">true</bool>
-
     <!-- Whether or not the fade on the notification is based on the amount that it has been swiped
          off-screen. -->
     <bool name="config_fadeDependingOnAmountSwiped">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 76a4c5f..9b860c7 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -347,7 +347,7 @@
     <!-- Padding to make tappable chip height 48dp (18+11+11+4+4) -->
     <dimen name="screenshot_action_chip_margin_vertical">4dp</dimen>
     <dimen name="screenshot_action_chip_padding_vertical">11dp</dimen>
-    <dimen name="screenshot_action_chip_icon_size">18dp</dimen>
+    <dimen name="screenshot_action_chip_icon_size">18sp</dimen>
     <!-- Padding on each side of the icon for icon-only chips -->
     <dimen name="screenshot_action_chip_icon_only_padding_horizontal">14dp</dimen>
     <!-- Padding at the edges of the chip for icon-and-text chips -->
@@ -486,6 +486,9 @@
 
     <dimen name="volume_dialog_slider_height">116dp</dimen>
 
+    <!-- (volume_dialog_panel_width - rounded_slider_icon_size) / 2 -->
+    <dimen name="volume_slider_icon_inset">11dp</dimen>
+
     <dimen name="volume_dialog_track_width">4dp</dimen>
 
     <dimen name="volume_dialog_track_corner_radius">2dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7f4e475..91301df 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2705,6 +2705,8 @@
     <!-- Accessibility floating menu strings -->
     <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user the accessibility gesture had been replaced by accessibility floating button. [CHAR LIMIT=100] -->
     <string name="accessibility_floating_button_migration_tooltip">Accessibility button replaced the accessibility gesture\n\n<annotation id="link">View settings</annotation></string>
+    <!-- Message for the accessibility floating button settings tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user to have another option to switch from the accessibility gesture to a button. [CHAR LIMIT=100] -->
+    <string name="accessibility_floating_button_switch_migration_tooltip">You can switch from the accessibility gesture to a button\n\n<annotation id="link">Settings</annotation></string>
     <!-- Message for the accessibility floating button docking tooltip. It shows when the user first time drag the button. It will tell the user about docking behavior. [CHAR LIMIT=70] -->
     <string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string>
     <!-- Action in accessibility menu to move the accessibility floating button to the top left of the screen. [CHAR LIMIT=30] -->
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index b0f4da2..affad7a 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -391,9 +391,9 @@
         boolean animateLeft = (Math.abs(velocity) > getEscapeVelocity() && velocity < 0) ||
                 (getTranslation(animView) < 0 && !isDismissAll);
         if (animateLeft || animateLeftForRtl || animateUpForMenu) {
-            newPos = -getSize(animView);
+            newPos = -getTotalTranslationLength(animView);
         } else {
-            newPos = getSize(animView);
+            newPos = getTotalTranslationLength(animView);
         }
         long duration;
         if (fixedDuration == 0) {
@@ -470,6 +470,15 @@
     }
 
     /**
+     * Get the total translation length where we want to swipe to when dismissing the view. By
+     * default this is the size of the view, but can also be larger.
+     * @param animView the view to ask about
+     */
+    protected float getTotalTranslationLength(View animView) {
+        return getSize(animView);
+    }
+
+    /**
      * Called to update the dismiss animation.
      */
     protected void prepareDismissAnimation(View view, Animator anim) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index bfb7105..17178fa 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -75,7 +75,6 @@
     private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
     private int mMagnificationMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
     private final LayoutParams mParams;
-    private int mWindowHeight;
     @VisibleForTesting
     final Rect mDraggableWindowBounds = new Rect();
     private boolean mIsVisible = false;
@@ -95,7 +94,6 @@
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mSfVsyncFrameProvider = sfVsyncFrameProvider;
         mParams = createLayoutParams(context);
-        mWindowHeight = mWindowManager.getCurrentWindowMetrics().getBounds().height();
         mImageView = imageView;
         mImageView.setOnTouchListener(this::onTouch);
         mImageView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
@@ -313,12 +311,14 @@
 
     void onConfigurationChanged(int configDiff) {
         if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+            final Rect previousDraggableBounds = new Rect(mDraggableWindowBounds);
             mDraggableWindowBounds.set(getDraggableWindowBounds());
-            // Keep the Y position with the same height ratio before the window height is changed.
-            final int windowHeight = mWindowManager.getCurrentWindowMetrics().getBounds().height();
-            final float windowHeightFraction = (float) mParams.y / mWindowHeight;
-            mParams.y = (int) (windowHeight * windowHeightFraction);
-            mWindowHeight = windowHeight;
+            // Keep the Y position with the same height ratio before the window bounds and
+            // draggable bounds are changed.
+            final float windowHeightFraction = (float) (mParams.y - previousDraggableBounds.top)
+                    / previousDraggableBounds.height();
+            mParams.y = (int) (windowHeightFraction * mDraggableWindowBounds.height())
+                    + mDraggableWindowBounds.top;
             stickToScreenEdge(mToLeftScreenEdge);
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
index 47f3739..05256e6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
@@ -53,7 +53,7 @@
     @FloatRange(from = 0.0, to = 1.0)
     private static final float DEFAULT_POSITION_X_PERCENT = 1.0f;
     @FloatRange(from = 0.0, to = 1.0)
-    private static final float DEFAULT_POSITION_Y_PERCENT = 0.8f;
+    private static final float DEFAULT_POSITION_Y_PERCENT = 0.9f;
 
     private final Context mContext;
     private final AccessibilityFloatingMenuView mMenuView;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
index 63cfd51..ee09c62 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
@@ -143,7 +143,7 @@
         }
 
         void updateItemPadding(int padding, int size) {
-            itemView.setPaddingRelative(padding, padding, padding, padding);
+            itemView.setPaddingRelative(padding, padding, padding, 0);
         }
     }
 
@@ -154,7 +154,7 @@
 
         @Override
         void updateItemPadding(int padding, int size) {
-            final int paddingBottom = size <= 2 ? padding : 0;
+            final int paddingBottom = size <= 1 ? padding : 0;
             itemView.setPaddingRelative(padding, padding, padding, paddingBottom);
         }
     }
@@ -166,7 +166,7 @@
 
         @Override
         void updateItemPadding(int padding, int size) {
-            itemView.setPaddingRelative(padding, 0, padding, padding);
+            itemView.setPaddingRelative(padding, padding, padding, padding);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index f975a80..ec930b0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -378,6 +378,7 @@
                 return true;
             case MotionEvent.ACTION_DOWN:
             case MotionEvent.ACTION_HOVER_ENTER:
+                Trace.beginSection("UdfpsController.onTouch.ACTION_DOWN");
                 // To simplify the lifecycle of the velocity tracker, make sure it's never null
                 // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP.
                 if (mVelocityTracker == null) {
@@ -388,8 +389,7 @@
                     mVelocityTracker.clear();
                 }
                 if (isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView)) {
-                    Trace.beginAsyncSection(
-                            "UdfpsController#ACTION_DOWN", 1);
+                    Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0);
                     // The pointer that causes ACTION_DOWN is always at index 0.
                     // We need to persist its ID to track it during ACTION_MOVE that could include
                     // data for many other pointers because of multi-touch support.
@@ -397,10 +397,12 @@
                     mVelocityTracker.addMovement(event);
                     handled = true;
                 }
+                Trace.endSection();
                 break;
 
             case MotionEvent.ACTION_MOVE:
             case MotionEvent.ACTION_HOVER_MOVE:
+                Trace.beginSection("UdfpsController.onTouch.ACTION_MOVE");
                 final int idx = mActivePointerId == -1
                         ? event.getPointerId(0)
                         : event.findPointerIndex(mActivePointerId);
@@ -466,11 +468,13 @@
                         onFingerUp();
                     }
                 }
+                Trace.endSection();
                 break;
 
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_HOVER_EXIT:
+                Trace.beginSection("UdfpsController.onTouch.ACTION_UP");
                 mActivePointerId = -1;
                 if (mVelocityTracker != null) {
                     mVelocityTracker.recycle();
@@ -479,7 +483,7 @@
                 Log.v(TAG, "onTouch | finger up");
                 onFingerUp();
                 mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION);
-
+                Trace.endSection();
                 break;
 
             default:
@@ -818,12 +822,11 @@
             return;
         }
         mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major);
-        Trace.endAsyncSection(
-                "UdfpsController#ACTION_DOWN", 1);
-        Trace.beginAsyncSection("UdfpsController#startIllumination", 1);
+        Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);
+        Trace.beginAsyncSection("UdfpsController.e2e.startIllumination", 0);
         mView.startIllumination(() -> {
             mFingerprintManager.onUiReady(mSensorProps.sensorId);
-            Trace.endAsyncSection("UdfpsController#startIllumination", 1);
+            Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0);
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index dfd85fe..1fb36d7 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -72,6 +72,7 @@
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -133,7 +134,8 @@
             IWindowManager iWindowManager,
             @Background Executor backgroundExecutor,
             UiEventLogger uiEventLogger,
-            RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) {
+            RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler,
+            StatusBar statusBar) {
 
         super(context, windowManagerFuncs,
                 audioManager, iDreamManager,
@@ -152,7 +154,7 @@
                 backgroundExecutor,
                 uiEventLogger,
                 null,
-                ringerModeTracker, sysUiState, handler);
+                ringerModeTracker, sysUiState, handler, statusBar);
 
         mLockPatternUtils = lockPatternUtils;
         mKeyguardStateController = keyguardStateController;
@@ -227,7 +229,8 @@
         ActionsDialog dialog = new ActionsDialog(getContext(), mAdapter, mOverflowAdapter,
                 this::getWalletViewController, mDepthController, mSysuiColorExtractor,
                 mStatusBarService, mNotificationShadeWindowController,
-                mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger());
+                mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger(),
+                getStatusBar());
 
         if (shouldShowLockMessage(dialog)) {
             dialog.showLockMessage();
@@ -295,11 +298,13 @@
                 SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
                 NotificationShadeWindowController notificationShadeWindowController,
                 SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
-                MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger) {
+                MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
+                StatusBar statusBar) {
             super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions,
                     adapter, overflowAdapter, depthController, sysuiColorExtractor,
                     statusBarService, notificationShadeWindowController, sysuiState,
-                    onRotateCallback, keyguardShowing, powerAdapter, uiEventLogger, null);
+                    onRotateCallback, keyguardShowing, powerAdapter, uiEventLogger, null,
+                    statusBar);
             mWalletFactory = walletFactory;
 
             // Update window attributes
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 8e15283..ad920cb 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -74,8 +74,10 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
+import android.view.GestureDetector;
 import android.view.IWindowManager;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
@@ -119,6 +121,7 @@
 import com.android.systemui.scrim.ScrimDrawable;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -228,6 +231,7 @@
     private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
     protected Handler mMainHandler;
     private int mSmallestScreenWidthDp;
+    private final StatusBar mStatusBar;
 
     @VisibleForTesting
     public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
@@ -322,7 +326,8 @@
             @Background Executor backgroundExecutor,
             UiEventLogger uiEventLogger,
             GlobalActionsInfoProvider infoProvider,
-            RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) {
+            RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler,
+            StatusBar statusBar) {
         mContext = context;
         mWindowManagerFuncs = windowManagerFuncs;
         mAudioManager = audioManager;
@@ -352,6 +357,7 @@
         mSysUiState = sysUiState;
         mMainHandler = handler;
         mSmallestScreenWidthDp = mContext.getResources().getConfiguration().smallestScreenWidthDp;
+        mStatusBar = statusBar;
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -392,6 +398,10 @@
         return mUiEventLogger;
     }
 
+    protected StatusBar getStatusBar() {
+        return mStatusBar;
+    }
+
     /**
      * Show the global actions dialog (creating if necessary)
      *
@@ -625,7 +635,7 @@
                 mDepthController, mSysuiColorExtractor,
                 mStatusBarService, mNotificationShadeWindowController,
                 mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger,
-                mInfoProvider);
+                mInfoProvider, mStatusBar);
 
         dialog.setOnDismissListener(this);
         dialog.setOnShowListener(this);
@@ -2100,9 +2110,53 @@
         protected final Runnable mOnRotateCallback;
         private UiEventLogger mUiEventLogger;
         private GlobalActionsInfoProvider mInfoProvider;
+        private GestureDetector mGestureDetector;
+        private StatusBar mStatusBar;
 
         protected ViewGroup mContainer;
 
+        @VisibleForTesting
+        protected GestureDetector.SimpleOnGestureListener mGestureListener =
+                new GestureDetector.SimpleOnGestureListener() {
+                    @Override
+                    public boolean onDown(MotionEvent e) {
+                        // All gestures begin with this message, so continue listening
+                        return true;
+                    }
+
+                    @Override
+                    public boolean onSingleTapConfirmed(MotionEvent e) {
+                        // Close without opening shade
+                        mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
+                        cancel();
+                        return false;
+                    }
+
+                    @Override
+                    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+                            float distanceY) {
+                        if (distanceY < 0 && distanceY > distanceX
+                                && e1.getY() <= mStatusBar.getStatusBarHeight()) {
+                            // Downwards scroll from top
+                            openShadeAndDismiss();
+                            return true;
+                        }
+                        return false;
+                    }
+
+                    @Override
+                    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+                            float velocityY) {
+                        if (velocityY > 0 && Math.abs(velocityY) > Math.abs(velocityX)
+                                && e1.getY() <= mStatusBar.getStatusBarHeight()) {
+                            // Downwards fling from top
+                            openShadeAndDismiss();
+                            return true;
+                        }
+                        return false;
+                    }
+                };
+
         ActionsDialogLite(Context context, int themeRes, MyAdapter adapter,
                 MyOverflowAdapter overflowAdapter,
                 NotificationShadeDepthController depthController,
@@ -2110,7 +2164,7 @@
                 NotificationShadeWindowController notificationShadeWindowController,
                 SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
                 MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
-                @Nullable GlobalActionsInfoProvider infoProvider) {
+                @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar) {
             super(context, themeRes);
             mContext = context;
             mAdapter = adapter;
@@ -2125,6 +2179,9 @@
             mKeyguardShowing = keyguardShowing;
             mUiEventLogger = uiEventLogger;
             mInfoProvider = infoProvider;
+            mStatusBar = statusBar;
+
+            mGestureDetector = new GestureDetector(mContext, mGestureListener);
 
             // Window initialization
             Window window = getWindow();
@@ -2146,6 +2203,23 @@
             initializeLayout();
         }
 
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            return mGestureDetector.onTouchEvent(event) || super.onTouchEvent(event);
+        }
+
+        private void openShadeAndDismiss() {
+            mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
+            if (mStatusBar.isKeyguardShowing()) {
+                // match existing lockscreen behavior to open QS when swiping from status bar
+                mStatusBar.animateExpandSettingsPanel(null);
+            } else {
+                // otherwise, swiping down should expand notification shade
+                mStatusBar.animateExpandNotificationsPanel();
+            }
+            dismiss();
+        }
+
         private ListPopupWindow createPowerOverflowPopup() {
             GlobalActionsPopupMenu popup = new GlobalActionsPopupMenu(
                     new ContextThemeWrapper(
@@ -2194,9 +2268,9 @@
             mGlobalActionsLayout.setRotationListener(this::onRotate);
             mGlobalActionsLayout.setAdapter(mAdapter);
             mContainer = findViewById(com.android.systemui.R.id.global_actions_container);
-            mContainer.setOnClickListener(v -> {
-                mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
-                cancel();
+            mContainer.setOnTouchListener((v, event) -> {
+                mGestureDetector.onTouchEvent(event);
+                return v.onTouchEvent(event);
             });
 
             View overflowButton = findViewById(
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index c2b5807..19190cd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -46,6 +46,7 @@
 import androidx.annotation.UiThread;
 import androidx.constraintlayout.widget.ConstraintSet;
 
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.settingslib.widget.AdaptiveIcon;
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -468,7 +469,8 @@
             TransitionLayout player) {
         // TODO(b/174236650): Make sure that the carousel indicator also fades out.
         // TODO(b/174236650): Instrument the animation to measure jank.
-        return new GhostedViewLaunchAnimatorController(player) {
+        return new GhostedViewLaunchAnimatorController(player,
+                InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER) {
             @Override
             protected float getCurrentTopCornerRadius() {
                 return ((IlluminationDrawable) player.getBackground()).getCornerRadius();
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 075bc70..edbf187 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -187,6 +187,12 @@
     private var currentAttachmentLocation = -1
 
     /**
+     * Is there any active media in the carousel?
+     */
+    private var hasActiveMedia: Boolean = false
+        get() = mediaHosts.get(LOCATION_QQS)?.visible == true
+
+    /**
      * Are we currently waiting on an animation to start?
      */
     private var animationPending: Boolean = false
@@ -476,8 +482,12 @@
         val viewHost = createUniqueObjectHost()
         mediaObject.hostView = viewHost
         mediaObject.addVisibilityChangeListener {
+            // If QQS changes visibility, we need to force an update to ensure the transition
+            // goes into the correct state
+            val stateUpdate = mediaObject.location == LOCATION_QQS
+
             // Never animate because of a visibility change, only state changes should do that
-            updateDesiredLocation(forceNoAnimation = true)
+            updateDesiredLocation(forceNoAnimation = true, forceStateUpdate = stateUpdate)
         }
         mediaHosts[mediaObject.location] = mediaObject
         if (mediaObject.location == desiredLocation) {
@@ -521,10 +531,15 @@
      * going from the old desired location to the new one.
      *
      * @param forceNoAnimation optional parameter telling the system not to animate
+     * @param forceStateUpdate optional parameter telling the system to update transition state
+     *                         even if location did not change
      */
-    private fun updateDesiredLocation(forceNoAnimation: Boolean = false) {
+    private fun updateDesiredLocation(
+        forceNoAnimation: Boolean = false,
+        forceStateUpdate: Boolean = false
+    ) {
         val desiredLocation = calculateLocation()
-        if (desiredLocation != this.desiredLocation) {
+        if (desiredLocation != this.desiredLocation || forceStateUpdate) {
             if (this.desiredLocation >= 0) {
                 previousLocation = this.desiredLocation
             }
@@ -784,7 +799,7 @@
     private fun getQSTransformationProgress(): Float {
         val currentHost = getHost(desiredLocation)
         val previousHost = getHost(previousLocation)
-        if (currentHost?.location == LOCATION_QS) {
+        if (hasActiveMedia && currentHost?.location == LOCATION_QS) {
             if (previousHost?.location == LOCATION_QQS) {
                 if (previousHost.visible || statusbarState != StatusBarState.KEYGUARD) {
                     return qsExpansion
@@ -917,6 +932,7 @@
         val location = when {
             qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS
             qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
+            !hasActiveMedia -> LOCATION_QS
             onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
             onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN
             else -> LOCATION_QQS
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index f6d9389..a981b62 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -28,6 +28,7 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto;
@@ -272,7 +273,8 @@
     private void startSettingsActivity() {
         ActivityLaunchAnimator.Controller animationController =
                 mSettingsButtonContainer != null ? ActivityLaunchAnimator.Controller.fromView(
-                        mSettingsButtonContainer) : null;
+                        mSettingsButtonContainer,
+                        InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON) : null;
         mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
                 true /* dismissShade */, animationController);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index e1a66b2..7b8a6a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -539,8 +539,9 @@
 
     private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded) {
         View hostView = mediaHost.getHostView();
-        // on keyguard we cross-fade to expanded, so no need to pin it.
-        if (mLastQSExpansion > 0 && !isKeyguardState()) {
+        // On keyguard we cross-fade to expanded, so no need to pin it.
+        // If the collapsed qs isn't visible, we also just keep it at the laid out position.
+        if (mLastQSExpansion > 0 && !isKeyguardState() && mQqsMediaHost.getVisible()) {
             float targetPosition = absoluteBottomPosition - getTotalBottomMargin(hostView)
                     - hostView.getHeight();
             float currentPosition = mediaHost.getCurrentBounds().top
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index a938821..894ab52 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -50,6 +50,7 @@
 import androidx.lifecycle.LifecycleRegistry;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
@@ -389,7 +390,8 @@
      */
     protected void handleLongClick(@Nullable View view) {
         ActivityLaunchAnimator.Controller animationController =
-                view != null ? ActivityLaunchAnimator.Controller.fromView(view) : null;
+                view != null ? ActivityLaunchAnimator.Controller.fromView(view,
+                        InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) : null;
         mActivityStarter.postStartActivityDismissingKeyguard(getLongClickIntent(), 0,
                 animationController);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
index 69d49d4..73d1370 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
@@ -11,6 +11,7 @@
 import android.text.format.DateFormat
 import android.view.View
 import androidx.annotation.VisibleForTesting
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
@@ -70,7 +71,10 @@
     }
 
     override fun handleClick(view: View?) {
-        val animationController = view?.let { ActivityLaunchAnimator.Controller.fromView(it) }
+        val animationController = view?.let {
+            ActivityLaunchAnimator.Controller.fromView(
+                    it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE)
+        }
         val pendingIntent = lastAlarmInfo?.showIntent
         if (pendingIntent != null) {
             mActivityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index 6d3190f..f66b722 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -22,6 +22,7 @@
 import android.os.Looper
 import android.service.quicksettings.Tile
 import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
@@ -106,7 +107,8 @@
             putExtra(ControlsUiController.EXTRA_ANIMATE, true)
         }
         val animationController = view?.let {
-            ActivityLaunchAnimator.Controller.fromView(it)
+            ActivityLaunchAnimator.Controller.fromView(
+                    it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE)
         }
 
         mUiHandler.post {
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 0e4434b..98cd88a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -37,6 +37,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -120,7 +121,8 @@
     @Override
     protected void handleClick(@Nullable View view) {
         ActivityLaunchAnimator.Controller animationController =
-                view == null ? null : ActivityLaunchAnimator.Controller.fromView(view);
+                view == null ? null : ActivityLaunchAnimator.Controller.fromView(view,
+                        InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE);
 
         mUiHandler.post(() -> {
             if (mSelectedCard != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
index 17e94c4..f140446 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.screenshot;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_EDIT;
 import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_SHARE;
 import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT;
@@ -30,6 +32,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.util.Log;
+import android.view.RemoteAnimationAdapter;
+import android.view.WindowManagerGlobal;
 
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -69,6 +73,16 @@
                     intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
             try {
                 actionIntent.send(context, 0, null, null, null, null, opts.toBundle());
+                if (intent.getBooleanExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, false)) {
+                    RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
+                            ScreenshotController.SCREENSHOT_REMOTE_RUNNER, 0, 0);
+                    try {
+                        WindowManagerGlobal.getWindowManagerService()
+                                .overridePendingAppTransitionRemote(runner, DEFAULT_DISPLAY);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Error overriding screenshot app transition", e);
+                    }
+                }
             } catch (PendingIntent.CanceledException e) {
                 Log.e(TAG, "Pending intent canceled", e);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index b5e51c6..e9dea65 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -319,6 +319,7 @@
                             .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
                             .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
                                     mSmartActionsEnabled)
+                            .putExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, true)
                             .setAction(Intent.ACTION_EDIT)
                             .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
                     PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index bda198e..5efa1b2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -53,14 +53,18 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Pair;
 import android.view.Display;
 import android.view.DisplayAddress;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
+import android.view.RemoteAnimationTarget;
 import android.view.ScrollCaptureResponse;
 import android.view.SurfaceControl;
 import android.view.View;
@@ -106,6 +110,31 @@
     private ListenableFuture<ScrollCaptureResponse> mLastScrollCaptureRequest;
 
     /**
+     * This is effectively a no-op, but we need something non-null to pass in, in order to
+     * successfully override the pending activity entrance animation.
+     */
+    static final IRemoteAnimationRunner.Stub SCREENSHOT_REMOTE_RUNNER =
+            new IRemoteAnimationRunner.Stub() {
+                @Override
+                public void onAnimationStart(
+                        @WindowManager.TransitionOldType int transit,
+                        RemoteAnimationTarget[] apps,
+                        RemoteAnimationTarget[] wallpapers,
+                        RemoteAnimationTarget[] nonApps,
+                        final IRemoteAnimationFinishedCallback finishedCallback) {
+                    try {
+                        finishedCallback.onAnimationFinished();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error finishing screenshot remote animation", e);
+                    }
+                }
+
+                @Override
+                public void onAnimationCancelled() {
+                }
+            };
+
+    /**
      * POD used in the AsyncTask which saves an image in the background.
      */
     static class SaveImageInBackgroundData {
@@ -182,6 +211,7 @@
     static final String ACTION_TYPE_SHARE = "Share";
     static final String ACTION_TYPE_EDIT = "Edit";
     static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
+    static final String EXTRA_OVERRIDE_TRANSITION = "android:screenshot_override_transition";
     static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
 
     static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
@@ -542,7 +572,7 @@
                             mScreenshotHandler.postDelayed(this::requestScrollCapture, 150);
                             mScreenshotView.updateDisplayCutoutMargins(
                                     mWindowManager.getCurrentWindowMetrics().getWindowInsets()
-                                        .getDisplayCutout());
+                                            .getDisplayCutout());
                         }
                     });
         });
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 9f59023..f8a1ff8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -145,11 +145,11 @@
     }
 
     @Override
-    public boolean setState(int state) {
+    public boolean setState(int state, boolean force) {
         if (state > MAX_STATE || state < MIN_STATE) {
             throw new IllegalArgumentException("Invalid state " + state);
         }
-        if (state == mState) {
+        if (!force && state == mState) {
             return false;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index b6d6ed5..73f3d90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -59,7 +59,19 @@
      * @param state see {@link StatusBarState} for valid options
      * @return {@code true} if the state changed, else {@code false}
      */
-    boolean setState(int state);
+    default boolean setState(int state) {
+        return setState(state, false /* force */);
+    }
+
+    /**
+     * Update the status bar state
+     * @param state see {@link StatusBarState} for valid options
+     * @param force whether to set the state even if it's the same as the current state. This will
+     *              dispatch the state to all StatusBarStateListeners, ensuring that all listening
+     *              components are reset to this state.
+     * @return {@code true} if the state was changed or set forcefully
+     */
+    boolean setState(int state, boolean force);
 
     /**
      * Update the dozing state from {@link StatusBar}'s perspective
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
index 8479b30..f30010c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
@@ -383,10 +383,18 @@
 const val SHOWING_PERSISTENT_DOT = 4
 
 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 DELAY = 0L
+
+/**
+ * The total time spent animation should be 1500ms. The entrance animation is how much time
+ * we give to the system to animate system elements out of the way. Total chip animation length
+ * will be equivalent to 2*chip_anim_length + display_length
+ */
+private const val ENTRANCE_ANIM_LENGTH = 250L
+private const val CHIP_ANIM_LENGTH = 250L
+// 1s + entrance time + chip anim_length
+private const val DISPLAY_LENGTH = 1500L
+
 private const val MIN_UPTIME: Long = 5 * 1000
 
 private const val DEBUG = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index aef01e9..0fb1c54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -71,11 +71,11 @@
         // Check if the notification is displaying the menu, if so slide notification back
         if (isMenuVisible(row)) {
             mLogger.logMenuVisible(entry);
-            row.animateTranslateNotification(0);
+            row.animateResetTranslation();
             return;
         } else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) {
             mLogger.logParentMenuVisible(entry);
-            row.getNotificationParent().animateTranslateNotification(0);
+            row.getNotificationParent().animateResetTranslation();
             return;
         } else if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
             // We never want to open the app directly if the user clicks in between
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 4136624..c24c2be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -330,30 +330,6 @@
         }
     }
 
-    @Override
-    public void setDistanceToTopRoundness(float distanceToTopRoundness) {
-        super.setDistanceToTopRoundness(distanceToTopRoundness);
-        mBackgroundNormal.setDistanceToTopRoundness(distanceToTopRoundness);
-    }
-
-    /** Sets whether this view is the last notification in a section. */
-    @Override
-    public void setLastInSection(boolean lastInSection) {
-        if (lastInSection != mLastInSection) {
-            super.setLastInSection(lastInSection);
-            mBackgroundNormal.setLastInSection(lastInSection);
-        }
-    }
-
-    /** Sets whether this view is the first notification in a section. */
-    @Override
-    public void setFirstInSection(boolean firstInSection) {
-        if (firstInSection != mFirstInSection) {
-            super.setFirstInSection(firstInSection);
-            mBackgroundNormal.setFirstInSection(firstInSection);
-        }
-    }
-
     /**
      * Set an override tint color that is used for the background.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 6fd5567..ba28dc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -846,8 +846,6 @@
         updateClickAndFocus();
         if (mNotificationParent != null) {
             setOverrideTintColor(NO_COLOR, 0.0f);
-            // Let's reset the distance to top roundness, as this isn't applied to group children
-            setDistanceToTopRoundness(NO_ROUNDNESS);
             mNotificationParent.updateBackgroundForGroupState();
         }
         updateBackgroundClipping();
@@ -876,7 +874,7 @@
     @Override
     protected boolean handleSlideBack() {
         if (mMenuRow != null && mMenuRow.isMenuVisible()) {
-            animateTranslateNotification(0 /* targetLeft */);
+            animateResetTranslation();
             return true;
         }
         return false;
@@ -1713,21 +1711,17 @@
             mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
             mChildrenContainer.onNotificationUpdated();
 
-            if (mShouldTranslateContents) {
-                mTranslateableViews.add(mChildrenContainer);
-            }
+            mTranslateableViews.add(mChildrenContainer);
         });
 
-        if (mShouldTranslateContents) {
-            // Add the views that we translate to reveal the menu
-            mTranslateableViews = new ArrayList<>();
-            for (int i = 0; i < getChildCount(); i++) {
-                mTranslateableViews.add(getChildAt(i));
-            }
-            // Remove views that don't translate
-            mTranslateableViews.remove(mChildrenContainerStub);
-            mTranslateableViews.remove(mGutsStub);
+        // Add the views that we translate to reveal the menu
+        mTranslateableViews = new ArrayList<>();
+        for (int i = 0; i < getChildCount(); i++) {
+            mTranslateableViews.add(getChildAt(i));
         }
+        // Remove views that don't translate
+        mTranslateableViews.remove(mChildrenContainerStub);
+        mTranslateableViews.remove(mGutsStub);
     }
 
     private void doLongClickCallback() {
@@ -1805,7 +1799,7 @@
             mTranslateAnim.cancel();
         }
 
-        if (!mShouldTranslateContents) {
+        if (mDismissUsingRowTranslationX) {
             setTranslationX(0);
         } else if (mTranslateableViews != null) {
             for (int i = 0; i < mTranslateableViews.size(); i++) {
@@ -1867,23 +1861,47 @@
         return mPrivateLayout.getActiveRemoteInputText();
     }
 
-    public void animateTranslateNotification(final float leftTarget) {
+    /**
+     * Reset the translation with an animation.
+     */
+    public void animateResetTranslation() {
         if (mTranslateAnim != null) {
             mTranslateAnim.cancel();
         }
-        mTranslateAnim = getTranslateViewAnimator(leftTarget, null /* updateListener */);
+        mTranslateAnim = getTranslateViewAnimator(0, null /* updateListener */);
         if (mTranslateAnim != null) {
             mTranslateAnim.start();
         }
     }
 
+    /**
+     * Set the dismiss behavior of the view.
+     * @param usingRowTranslationX {@code true} if the view should translate using regular
+     *                                          translationX, otherwise the contents will be
+     *                                          translated.
+     */
+    @Override
+    public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) {
+        if (usingRowTranslationX != mDismissUsingRowTranslationX) {
+            // In case we were already transitioning, let's switch over!
+            float previousTranslation = getTranslation();
+            if (previousTranslation != 0) {
+                setTranslation(0);
+            }
+            super.setDismissUsingRowTranslationX(usingRowTranslationX);
+            if (previousTranslation != 0) {
+                setTranslation(previousTranslation);
+            }
+        }
+    }
+
     @Override
     public void setTranslation(float translationX) {
         invalidate();
         if (isBlockingHelperShowingAndTranslationFinished()) {
             mGuts.setTranslationX(translationX);
             return;
-        } else if (!mShouldTranslateContents) {
+        } else if (mDismissUsingRowTranslationX) {
             setTranslationX(translationX);
         } else if (mTranslateableViews != null) {
             // Translate the group of views
@@ -1907,7 +1925,7 @@
 
     @Override
     public float getTranslation() {
-        if (!mShouldTranslateContents) {
+        if (mDismissUsingRowTranslationX) {
             return getTranslationX();
         }
 
@@ -2898,7 +2916,11 @@
         float y = event.getY();
         NotificationViewWrapper wrapper = getVisibleNotificationViewWrapper();
         NotificationHeaderView header = wrapper == null ? null : wrapper.getNotificationHeader();
-        if (header != null && header.isInTouchRect(x - getTranslation(), y)) {
+        // the extra translation only needs to be added, if we're translating the notification
+        // contents, otherwise the motionEvent is already at the right place due to the
+        // touch event system.
+        float translation = !mDismissUsingRowTranslationX ? getTranslation() : 0;
+        if (header != null && header.isInTouchRect(x - translation, y)) {
             return true;
         }
         if ((!mIsSummaryWithChildren || shouldShowPublic())
@@ -3037,24 +3059,6 @@
     }
 
     @Override
-    public boolean topAmountNeedsClipping() {
-        if (isGroupExpanded()) {
-            return true;
-        }
-        if (isGroupExpansionChanging()) {
-            return true;
-        }
-        if (getShowingLayout().shouldClipToRounding(true /* topRounded */,
-                false /* bottomRounded */)) {
-            return true;
-        }
-        if (mGuts != null && mGuts.getAlpha() != 0.0f) {
-            return true;
-        }
-        return false;
-    }
-
-    @Override
     protected boolean childNeedsClipping(View child) {
         if (child instanceof NotificationContentView) {
             NotificationContentView contentView = (NotificationContentView) child;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 5134c62..d58fe3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -71,21 +71,19 @@
     private int mBackgroundTop;
 
     /**
-     * {@code true} if the children views of the {@link ExpandableOutlineView} are translated when
+     * {@code false} if the children views of the {@link ExpandableOutlineView} are translated when
      * it is moved. Otherwise, the translation is set on the {@code ExpandableOutlineView} itself.
      */
-    protected boolean mShouldTranslateContents;
-    private boolean mTopAmountRounded;
-    private float mDistanceToTopRoundness = -1;
+    protected boolean mDismissUsingRowTranslationX = true;
     private float[] mTmpCornerRadii = new float[8];
 
     private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
         @Override
         public void getOutline(View view, Outline outline) {
             if (!mCustomOutline && getCurrentTopRoundness() == 0.0f
-                    && getCurrentBottomRoundness() == 0.0f && !mAlwaysRoundBothCorners
-                    && !mTopAmountRounded) {
-                int translation = mShouldTranslateContents ? (int) getTranslation() : 0;
+                    && getCurrentBottomRoundness() == 0.0f && !mAlwaysRoundBothCorners) {
+                // Only when translating just the contents, does the outline need to be shifted.
+                int translation = !mDismissUsingRowTranslationX ? (int) getTranslation() : 0;
                 int left = Math.max(translation, 0);
                 int top = mClipTopAmount + mBackgroundTop;
                 int right = getWidth() + Math.min(translation, 0);
@@ -110,7 +108,9 @@
         float topRoundness = mAlwaysRoundBothCorners
                 ? mOutlineRadius : getCurrentBackgroundRadiusTop();
         if (!mCustomOutline) {
-            int translation = mShouldTranslateContents && !ignoreTranslation
+            // The outline just needs to be shifted if we're translating the contents. Otherwise
+            // it's already in the right place.
+            int translation = !mDismissUsingRowTranslationX && !ignoreTranslation
                     ? (int) getTranslation() : 0;
             int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f);
             left = Math.max(translation, 0) - halfExtraWidth;
@@ -168,33 +168,15 @@
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         canvas.save();
-        Path intersectPath = null;
-        if (mTopAmountRounded && topAmountNeedsClipping()) {
-            int left = (int) (- mExtraWidthForClipping / 2.0f);
-            int top = (int) (mClipTopAmount - mDistanceToTopRoundness);
-            int right = getWidth() + (int) (mExtraWidthForClipping + left);
-            int bottom = (int) Math.max(mMinimumHeightForClipping,
-                    Math.max(getActualHeight() - mClipBottomAmount, top + mOutlineRadius));
-            getRoundedRectPath(left, top, right, bottom, mOutlineRadius, 0.0f, mClipPath);
-            intersectPath = mClipPath;
-        }
-        boolean clipped = false;
         if (childNeedsClipping(child)) {
             Path clipPath = getCustomClipPath(child);
             if (clipPath == null) {
                 clipPath = getClipPath(false /* ignoreTranslation */);
             }
             if (clipPath != null) {
-                if (intersectPath != null) {
-                    clipPath.op(intersectPath, Path.Op.INTERSECT);
-                }
                 canvas.clipPath(clipPath);
-                clipped = true;
             }
         }
-        if (!clipped && intersectPath != null) {
-            canvas.clipPath(intersectPath);
-        }
         boolean result = super.drawChild(canvas, child, drawingTime);
         canvas.restore();
         return result;
@@ -212,32 +194,19 @@
         invalidate();
     }
 
-    @Override
-    public void setDistanceToTopRoundness(float distanceToTopRoundness) {
-        super.setDistanceToTopRoundness(distanceToTopRoundness);
-        if (distanceToTopRoundness != mDistanceToTopRoundness) {
-            mTopAmountRounded = distanceToTopRoundness >= 0;
-            mDistanceToTopRoundness = distanceToTopRoundness;
-            applyRoundness();
-        }
-    }
-
     protected boolean childNeedsClipping(View child) {
         return false;
     }
 
-    public boolean topAmountNeedsClipping() {
-        return true;
-    }
-
     protected boolean isClippingNeeded() {
-        return mAlwaysRoundBothCorners || mCustomOutline || getTranslation() != 0 ;
+        // When translating the contents instead of the overall view, we need to make sure we clip
+        // rounded to the contents.
+        boolean forTranslation = getTranslation() != 0 && !mDismissUsingRowTranslationX;
+        return mAlwaysRoundBothCorners || mCustomOutline || forTranslation;
     }
 
     private void initDimens() {
         Resources res = getResources();
-        mShouldTranslateContents =
-                res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe);
         mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius);
         mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline);
         if (!mAlwaysRoundBothCorners) {
@@ -272,11 +241,6 @@
     }
 
     public float getCurrentBackgroundRadiusTop() {
-        // If this view is top amount notification view, it should always has round corners on top.
-        // It will be applied with applyRoundness()
-        if (mTopAmountRounded) {
-            return mOutlineRadius;
-        }
         return getCurrentTopRoundness() * mOutlineRadius;
     }
 
@@ -382,9 +346,25 @@
         }
     }
 
+    /**
+     * Set the dismiss behavior of the view.
+     * @param usingRowTranslationX {@code true} if the view should translate using regular
+     *                                          translationX, otherwise the contents will be
+     *                                          translated.
+     */
+    public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) {
+        mDismissUsingRowTranslationX = usingRowTranslationX;
+    }
+
     @Override
     public int getOutlineTranslation() {
-        return mCustomOutline ? mOutlineRect.left : (int) getTranslation();
+        if (mCustomOutline) {
+            return mOutlineRect.left;
+        }
+        if (mDismissUsingRowTranslationX) {
+            return 0;
+        }
+        return (int) getTranslation();
     }
 
     public void updateOutline() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 763d197..8b0764b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -46,7 +46,6 @@
 public abstract class ExpandableView extends FrameLayout implements Dumpable {
     private static final String TAG = "ExpandableView";
 
-    public static final float NO_ROUNDNESS = -1;
     protected OnHeightChangedListener mOnHeightChangedListener;
     private int mActualHeight;
     protected int mClipTopAmount;
@@ -192,14 +191,6 @@
         }
     }
 
-    /**
-     * Set the distance to the top roundness, from where we should start clipping a value above
-     * or equal to 0 is the effective distance, and if a value below 0 is received, there should
-     * be no clipping.
-     */
-    public void setDistanceToTopRoundness(float distanceToTopRoundness) {
-    }
-
     public void setActualHeight(int actualHeight) {
         setActualHeight(actualHeight, true /* notifyListeners */);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index 4b1f679..754de58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -42,10 +42,8 @@
     private int mActualHeight;
     private int mClipBottomAmount;
     private int mTintColor;
-    private float[] mCornerRadii = new float[8];
+    private final float[] mCornerRadii = new float[8];
     private boolean mBottomIsRounded;
-    private boolean mLastInSection;
-    private boolean mFirstInSection;
     private int mBackgroundTop;
     private boolean mBottomAmountClips = true;
     private boolean mExpandAnimationRunning;
@@ -53,9 +51,6 @@
     private int mDrawableAlpha = 255;
     private boolean mIsPressedAllowed;
 
-    private boolean mTopAmountRounded;
-    private float mDistanceToTopRoundness;
-
     public NotificationBackgroundView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mDontModifyCorners = getResources().getBoolean(
@@ -90,15 +85,6 @@
                 left = (int) ((getWidth() - mActualWidth) / 2.0f);
                 right = (int) (left + mActualWidth);
             }
-            if (mTopAmountRounded) {
-                int clipTop = (int) (mClipTopAmount - mDistanceToTopRoundness);
-                if (clipTop >= 0 || !mFirstInSection) {
-                    top += clipTop;
-                }
-                if (clipTop >= 0 && !mLastInSection) {
-                    bottom += clipTop;
-                }
-            }
             drawable.setBounds(left, top, right, bottom);
             drawable.draw(canvas);
         }
@@ -180,14 +166,6 @@
         invalidate();
     }
 
-    public void setDistanceToTopRoundness(float distanceToTopRoundness) {
-        if (distanceToTopRoundness != mDistanceToTopRoundness) {
-            mTopAmountRounded = distanceToTopRoundness >= 0;
-            mDistanceToTopRoundness = distanceToTopRoundness;
-            invalidate();
-        }
-    }
-
     @Override
     public boolean hasOverlappingRendering() {
 
@@ -246,18 +224,6 @@
         }
     }
 
-    /** Sets whether this background belongs to the last notification in a section. */
-    public void setLastInSection(boolean lastInSection) {
-        mLastInSection = lastInSection;
-        invalidate();
-    }
-
-    /** Sets whether this background belongs to the first notification in a section. */
-    public void setFirstInSection(boolean firstInSection) {
-        mFirstInSection = firstInSection;
-        invalidate();
-    }
-
     private void updateBackgroundRadii() {
         if (mDontModifyCorners) {
             return;
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 0c86262..6822d24 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
@@ -75,7 +75,6 @@
     private int mExpandAnimationTopChange;
     private ExpandableNotificationRow mExpandingNotification;
     private float mHideAmount;
-    private float mNotificationScrimTop;
     private boolean mAppearing;
     private float mPulseHeight = MAX_PULSE_HEIGHT;
     private float mDozeAmount = 0.0f;
@@ -256,20 +255,6 @@
         return mHideAmount;
     }
 
-    /**
-     * Set y position of top of notifications background scrim, relative to top of screen.
-     */
-    public void setNotificationScrimTop(float notificationScrimTop) {
-        mNotificationScrimTop = notificationScrimTop;
-    }
-
-    /**
-     * @return Y position of top of notifications background scrim, relative to top of screen.
-     */
-    public float getNotificationScrimTop() {
-        return mNotificationScrimTop;
-    }
-
     public void setHideSensitive(boolean hideSensitive) {
         mHideSensitive = hideSensitive;
     }
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 f90b4c0..d79c575 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
@@ -40,6 +40,7 @@
 import android.graphics.Color;
 import android.graphics.Outline;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -424,13 +425,19 @@
     private final Rect mBackgroundAnimationRect = new Rect();
     private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>();
     private int mHeadsUpInset;
+
+    /**
+     * The position of the scroll boundary relative to this view. This is where the notifications
+     * stop scrolling and will start to clip instead.
+     */
+    private int mQsScrollBoundaryPosition;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
     private final Rect mTmpRect = new Rect();
     private DismissListener mDismissListener;
     private DismissAllAnimationListener mDismissAllAnimationListener;
     private NotificationRemoteInputManager mRemoteInputManager;
     private ShadeController mShadeController;
-    private Runnable mOnStackYChanged;
+    private Consumer<Boolean> mOnStackYChanged;
 
     protected boolean mClearAllEnabled;
 
@@ -453,6 +460,38 @@
     private NotificationStackScrollLayoutController mController;
 
     private boolean mKeyguardMediaControllorVisible;
+
+    /**
+     * The clip path used to clip the view in a rounded way.
+     */
+    private final Path mRoundedClipPath = new Path();
+
+    /**
+     * Should we use rounded rect clipping right now
+     */
+    private boolean mShouldUseRoundedRectClipping = false;
+
+    private int mRoundedRectClippingLeft;
+    private int mRoundedRectClippingTop;
+    private int mRoundedRectClippingBottom;
+    private int mRoundedRectClippingRight;
+    private float[] mBgCornerRadii = new float[8];
+
+    /**
+     * Whether stackY should be animated in case the view is getting shorter than the scroll
+     * position and this scrolling will lead to the top scroll inset getting smaller.
+     */
+    private boolean mAnimateStackYForContentHeightChange = false;
+
+    /**
+     * Are we launching a notification right now
+     */
+    private boolean mLaunchingNotification;
+
+    /**
+     * Do notifications dismiss with normal transitioning
+     */
+    private boolean mDismissUsingRowTranslationX = true;
     private NotificationEntry mTopHeadsUpEntry;
     private long mNumHeadsUp;
     private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
@@ -506,7 +545,7 @@
         mSectionsManager = notificationSectionsManager;
         mFeatureFlags = featureFlags;
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
-        mShouldUseSplitNotificationShade = shouldUseSplitNotificationShade(mFeatureFlags, res);
+        updateSplitNotificationShade();
         mSectionsManager.initialize(this, LayoutInflater.from(context));
         mSections = mSectionsManager.createSectionsForBuckets();
 
@@ -862,6 +901,8 @@
         mCornerRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius);
         mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize(
                 R.dimen.heads_up_status_bar_padding);
+        mQsScrollBoundaryPosition = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.quick_qs_offset_height);
     }
 
     void updateCornerRadius() {
@@ -961,6 +1002,9 @@
         updateFirstAndLastBackgroundViews();
         updateAlgorithmLayoutMinHeight();
         updateOwnTranslationZ();
+
+        // Once the layout has finished, we don't need to animate any scrolling clampings anymore.
+        mAnimateStackYForContentHeightChange = false;
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -1017,33 +1061,11 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void onPreDrawDuringAnimation() {
         mShelf.updateAppearance();
-        updateClippingToTopRoundedCorner();
         if (!mNeedsAnimation && !mChildrenUpdateRequested) {
             updateBackground();
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    private void updateClippingToTopRoundedCorner() {
-        Float clipStart = mAmbientState.getNotificationScrimTop();
-        Float clipEnd = clipStart + mCornerRadius;
-        boolean first = true;
-        for (int i = 0; i < getChildCount(); i++) {
-            ExpandableView child = (ExpandableView) getChildAt(i);
-            if (child.getVisibility() == GONE) {
-                continue;
-            }
-            float start = child.getTranslationY();
-            float end = start + child.getActualHeight();
-            boolean clip = clipStart > start && clipStart < end
-                    || clipEnd >= start && clipEnd <= end;
-            clip &= !(first && mScrollAdapter.isScrolledToTop());
-            child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0)
-                    : ExpandableView.NO_ROUNDNESS);
-            first = false;
-        }
-    }
-
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void updateScrollStateForAddedChildren() {
         if (mChildrenToAddAnimated.isEmpty()) {
@@ -1117,7 +1139,13 @@
     private void clampScrollPosition() {
         int scrollRange = getScrollRange();
         if (scrollRange < mOwnScrollY) {
-            setOwnScrollY(scrollRange);
+            boolean animateStackY = false;
+            if (scrollRange < getScrollAmountToScrollBoundary()
+                    && mAnimateStackYForContentHeightChange) {
+                // if the scroll boundary updates the position of the stack,
+                animateStackY = true;
+            }
+            setOwnScrollY(scrollRange, animateStackY);
         }
     }
 
@@ -1146,6 +1174,14 @@
      * Apply expansion fraction to the y position and height of the notifications panel.
      */
     private void updateStackPosition() {
+        updateStackPosition(false /* listenerNeedsAnimation */);
+    }
+
+    /**
+     * Apply expansion fraction to the y position and height of the notifications panel.
+     * @param listenerNeedsAnimation does the listener need to animate?
+     */
+    private void updateStackPosition(boolean listenerNeedsAnimation) {
         // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD
         float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition
                 + mAmbientState.getOverExpansion();
@@ -1153,7 +1189,7 @@
         final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
         mAmbientState.setStackY(stackY);
         if (mOnStackYChanged != null) {
-            mOnStackYChanged.run();
+            mOnStackYChanged.accept(listenerNeedsAnimation);
         }
         if (mQsExpansionFraction <= 0) {
             final float stackEndHeight = Math.max(0f,
@@ -1165,7 +1201,11 @@
         }
     }
 
-    void setOnStackYChanged(Runnable onStackYChanged) {
+    /**
+     * Add a listener when the StackY changes. The argument signifies whether an animation is
+     * needed.
+     */
+    void setOnStackYChanged(Consumer<Boolean> onStackYChanged) {
         mOnStackYChanged = onStackYChanged;
     }
 
@@ -1600,7 +1640,7 @@
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         Resources res = getResources();
-        mShouldUseSplitNotificationShade = shouldUseSplitNotificationShade(mFeatureFlags, res);
+        updateSplitNotificationShade();
         mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
         float densityScale = res.getDisplayMetrics().density;
         mSwipeHelper.setDensityScale(densityScale);
@@ -2527,8 +2567,7 @@
         updateScrollStateForRemovedChild(child);
         boolean animationGenerated = generateRemoveAnimation(child);
         if (animationGenerated) {
-            if (!mSwipedOutViews.contains(child)
-                    || Math.abs(child.getTranslation()) != child.getWidth()) {
+            if (!mSwipedOutViews.contains(child) || !isFullySwipedOut(child)) {
                 container.addTransientView(child, 0);
                 child.setTransientContainer(container);
             }
@@ -2540,6 +2579,13 @@
         focusNextViewIfFocused(child);
     }
 
+    /**
+     * Has this view been fully swiped out such that it's not visible anymore.
+     */
+    public boolean isFullySwipedOut(ExpandableView child) {
+        return Math.abs(child.getTranslation()) >= Math.abs(getTotalTranslationLength(child));
+    }
+
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void focusNextViewIfFocused(View view) {
         if (view instanceof ExpandableNotificationRow) {
@@ -2659,17 +2705,27 @@
         final int startingPosition = getPositionInLinearLayout(removedChild);
         final int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements;
         final int endPosition = startingPosition + childHeight;
-        if (endPosition <= mOwnScrollY) {
+        final int scrollBoundaryStart = getScrollAmountToScrollBoundary();
+        mAnimateStackYForContentHeightChange = true;
+        // This is reset onLayout
+        if (endPosition <= mOwnScrollY - scrollBoundaryStart) {
             // This child is fully scrolled of the top, so we have to deduct its height from the
             // scrollPosition
             setOwnScrollY(mOwnScrollY - childHeight);
-        } else if (startingPosition < mOwnScrollY) {
+        } else if (startingPosition < mOwnScrollY - scrollBoundaryStart) {
             // This child is currently being scrolled into, set the scroll position to the
             // start of this child
-            setOwnScrollY(startingPosition);
+            setOwnScrollY(startingPosition + scrollBoundaryStart);
         }
     }
 
+    /**
+     * @return the amount of scrolling needed to start clipping notifications.
+     */
+    private int getScrollAmountToScrollBoundary() {
+        return mTopPadding - mQsScrollBoundaryPosition;
+    }
+
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private int getIntrinsicHeight(View view) {
         if (view instanceof ExpandableView) {
@@ -2758,7 +2814,10 @@
         updateAnimationState(child);
         updateChronometerForChild(child);
         if (child instanceof ExpandableNotificationRow) {
-            ((ExpandableNotificationRow) child).setDismissRtl(mDismissRtl);
+            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            row.setDismissRtl(mDismissRtl);
+            row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX);
+
         }
     }
 
@@ -2818,6 +2877,9 @@
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void applyExpandAnimationParams(ExpandAnimationParameters params) {
         mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange());
+
+        // Disable clipping for launches
+        setLaunchingNotification(params != null);
         requestChildrenUpdate();
     }
 
@@ -2901,7 +2963,6 @@
             mAnimationEvents.clear();
             updateBackground();
             updateViewShadows();
-            updateClippingToTopRoundedCorner();
         } else {
             applyCurrentState();
         }
@@ -3030,7 +3091,7 @@
                     removedTranslation = row.getTranslationWhenRemoved();
                     ignoreChildren = false;
                 }
-                childWasSwipedOut |= Math.abs(row.getTranslation()) == row.getWidth();
+                childWasSwipedOut |= isFullySwipedOut(row);
             } else if (child instanceof MediaHeaderView) {
                 childWasSwipedOut = true;
             }
@@ -3038,11 +3099,11 @@
                 Rect clipBounds = child.getClipBounds();
                 childWasSwipedOut = clipBounds != null && clipBounds.height() == 0;
 
-                if (childWasSwipedOut && child instanceof ExpandableView) {
+                if (childWasSwipedOut) {
                     // Clean up any potential transient views if the child has already been swiped
                     // out, as we won't be animating it further (due to its height already being
                     // clipped to 0.
-                    ViewGroup transientContainer = ((ExpandableView) child).getTransientContainer();
+                    ViewGroup transientContainer = child.getTransientContainer();
                     if (transientContainer != null) {
                         transientContainer.removeTransientView(child);
                     }
@@ -3795,6 +3856,7 @@
             updateNotificationAnimationStates();
             updateChronometers();
             requestChildrenUpdate();
+            updateUseRoundedRectClipping();
         }
     }
 
@@ -3815,6 +3877,10 @@
     }
 
     void onChildHeightChanged(ExpandableView view, boolean needsAnimation) {
+        boolean previouslyNeededAnimation = mAnimateStackYForContentHeightChange;
+        if (needsAnimation) {
+            mAnimateStackYForContentHeightChange = true;
+        }
         updateContentHeight();
         updateScrollPositionOnExpandInBottom(view);
         clampScrollPosition();
@@ -3835,6 +3901,7 @@
             requestAnimationOnViewResize(row);
         }
         requestChildrenUpdate();
+        mAnimateStackYForContentHeightChange = previouslyNeededAnimation;
     }
 
     void onChildHeightReset(ExpandableView view) {
@@ -4015,7 +4082,6 @@
         setAnimationRunning(false);
         updateBackground();
         updateViewShadows();
-        updateClippingToTopRoundedCorner();
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -4048,7 +4114,7 @@
                 expandableView.setFakeShadowIntensity(
                         diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
                         previous.getOutlineAlpha(), (int) yLocation,
-                        previous.getOutlineTranslation());
+                        (int) (previous.getOutlineTranslation() + previous.getTranslation()));
             }
             previous = expandableView;
         }
@@ -4550,6 +4616,7 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setQsExpansionFraction(float qsExpansionFraction) {
         mQsExpansionFraction = qsExpansionFraction;
+        updateUseRoundedRectClipping();
 
         // If notifications are scrolled,
         // clear out scrollY by the time we push notifications offscreen
@@ -4560,13 +4627,18 @@
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void setOwnScrollY(int ownScrollY) {
+        setOwnScrollY(ownScrollY, false /* animateScrollChangeListener */);
+    }
+
+    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
+    private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) {
         if (ownScrollY != mOwnScrollY) {
             // We still want to call the normal scrolled changed for accessibility reasons
             onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY);
             mOwnScrollY = ownScrollY;
             mAmbientState.setScrollY(mOwnScrollY);
             updateOnScrollChange();
-            updateStackPosition();
+            updateStackPosition(animateStackYChangeListener);
         }
     }
 
@@ -4632,6 +4704,7 @@
         mStatusBarState = statusBarState;
         mAmbientState.setStatusBarState(statusBarState);
         updateSpeedBumpIndex();
+        updateDismissBehavior();
     }
 
     void onStatePostChange(boolean fromShadeLocked) {
@@ -5192,6 +5265,108 @@
     }
 
     /**
+     * Set rounded rect clipping bounds on this view.
+     */
+    public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
+            int bottomRadius) {
+        if (mRoundedRectClippingLeft == left && mRoundedRectClippingRight == right
+                && mRoundedRectClippingBottom == bottom && mRoundedRectClippingTop == top
+                && mBgCornerRadii[0] == topRadius && mBgCornerRadii[5] == bottomRadius) {
+            return;
+        }
+        mRoundedRectClippingLeft = left;
+        mRoundedRectClippingTop = top;
+        mRoundedRectClippingBottom = bottom;
+        mRoundedRectClippingRight = right;
+        mBgCornerRadii[0] = topRadius;
+        mBgCornerRadii[1] = topRadius;
+        mBgCornerRadii[2] = topRadius;
+        mBgCornerRadii[3] = topRadius;
+        mBgCornerRadii[4] = bottomRadius;
+        mBgCornerRadii[5] = bottomRadius;
+        mBgCornerRadii[6] = bottomRadius;
+        mBgCornerRadii[7] = bottomRadius;
+        mRoundedClipPath.reset();
+        mRoundedClipPath.addRoundRect(left, top, right, bottom, mBgCornerRadii, Path.Direction.CW);
+        if (mShouldUseRoundedRectClipping) {
+            invalidate();
+        }
+    }
+
+    private void updateSplitNotificationShade() {
+        boolean split = shouldUseSplitNotificationShade(mFeatureFlags, getResources());
+        if (split != mShouldUseSplitNotificationShade) {
+            mShouldUseSplitNotificationShade = split;
+            updateDismissBehavior();
+            updateUseRoundedRectClipping();
+        }
+    }
+
+    private void updateDismissBehavior() {
+        // On the split keyguard, dismissing with clipping without a visual boundary looks odd,
+        // so let's use the content dismiss behavior instead.
+        boolean dismissUsingRowTranslationX = !mShouldUseSplitNotificationShade
+                || mStatusBarState != StatusBarState.KEYGUARD;
+        if (mDismissUsingRowTranslationX != dismissUsingRowTranslationX) {
+            mDismissUsingRowTranslationX = dismissUsingRowTranslationX;
+            for (int i = 0; i < getChildCount(); i++) {
+                View child = getChildAt(i);
+                if (child instanceof ExpandableNotificationRow) {
+                    ((ExpandableNotificationRow) child).setDismissUsingRowTranslationX(
+                            dismissUsingRowTranslationX);
+                }
+            }
+        }
+    }
+
+    /**
+     * Set if we're launching a notification right now.
+     */
+    private void setLaunchingNotification(boolean launching) {
+        if (launching == mLaunchingNotification) {
+            return;
+        }
+        mLaunchingNotification = launching;
+        updateUseRoundedRectClipping();
+    }
+
+    /**
+     * Should we use rounded rect clipping
+     */
+    private void updateUseRoundedRectClipping() {
+        // We don't want to clip notifications when QS is expanded, because incoming heads up on
+        // the bottom would be clipped otherwise
+        boolean qsAllowsClipping = mQsExpansionFraction < 0.5f || mShouldUseSplitNotificationShade;
+        boolean clip = !mLaunchingNotification && mIsExpanded && qsAllowsClipping;
+        if (clip != mShouldUseRoundedRectClipping) {
+            mShouldUseRoundedRectClipping = clip;
+            invalidate();
+        }
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        if (mShouldUseRoundedRectClipping) {
+            // Let's clip rounded.
+            canvas.clipPath(mRoundedClipPath);
+        }
+        super.dispatchDraw(canvas);
+    }
+
+    /**
+     * Calculate the total translation needed when dismissing.
+     */
+    public float getTotalTranslationLength(View animView) {
+        if (!mDismissUsingRowTranslationX) {
+            return animView.getMeasuredWidth();
+        }
+        float notificationWidth = animView.getMeasuredWidth();
+        int containerWidth = getMeasuredWidth();
+        float padding = (containerWidth - notificationWidth) / 2.0f;
+        return containerWidth - padding;
+    }
+
+    /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
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 dec9888..fb4f559 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
@@ -384,6 +384,11 @@
                 }
 
                 @Override
+                public float getTotalTranslationLength(View animView) {
+                    return mView.getTotalTranslationLength(animView);
+                }
+
+                @Override
                 public void onSnooze(StatusBarNotification sbn,
                         NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
                     mStatusBar.setNotificationSnoozed(sbn, snoozeOption);
@@ -822,8 +827,18 @@
         return mView.isLayoutRtl();
     }
 
+    /**
+     * @return the left of the view.
+     */
     public int getLeft() {
-        return  mView.getLeft();
+        return mView.getLeft();
+    }
+
+    /**
+     * @return the top of the view.
+     */
+    public int getTop() {
+        return mView.getTop();
     }
 
     public float getTranslationX() {
@@ -1008,7 +1023,7 @@
         mView.setQsExpansionFraction(expansionFraction);
     }
 
-    public void setOnStackYChanged(Runnable onStackYChanged) {
+    public void setOnStackYChanged(Consumer<Boolean> onStackYChanged) {
         mView.setOnStackYChanged(onStackYChanged);
     }
 
@@ -1440,6 +1455,14 @@
     }
 
     /**
+     * Set rounded rect clipping bounds on this view.
+     */
+    public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
+            int bottomRadius) {
+        mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius);
+    }
+
+    /**
      * Enum for UiEvent logged from this class
      */
     enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index f4c4d44..6647769 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -325,6 +325,11 @@
     }
 
     @Override
+    protected float getTotalTranslationLength(View animView) {
+        return mCallback.getTotalTranslationLength(animView);
+    }
+
+    @Override
     public void setTranslation(View v, float translate) {
         if (v instanceof SwipeableView) {
             ((SwipeableView) v).setTranslation(translate);
@@ -466,6 +471,13 @@
         void onSnooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
 
         void onDismiss();
+
+        /**
+         * Get the total translation length where we want to swipe to when dismissing the view. By
+         * default this is the size of the view, but can also be larger.
+         * @param animView the view to ask about
+         */
+        float getTotalTranslationLength(View animView);
     }
 
     static class Builder {
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 e5fd103..74e8de4 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
@@ -158,7 +158,7 @@
             AmbientState ambientState) {
         float drawStart = ambientState.isOnKeyguard() ? 0
                 : ambientState.getStackY() - ambientState.getScrollY();
-        float clipStart = ambientState.getNotificationScrimTop();
+        float clipStart = 0;
         int childCount = algorithmState.visibleChildren.size();
         boolean firstHeadsUp = true;
         for (int i = 0; i < childCount; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 4fd2064..ee12b4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -392,7 +392,7 @@
                         0, () -> removeTransientView(changingView), null);
             } else if (event.animationType ==
                 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
-                if (Math.abs(changingView.getTranslation()) == changingView.getWidth()
+                if (mHostLayout.isFullySwipedOut(changingView)
                         && changingView.getTransientContainer() != null) {
                     changingView.getTransientContainer().removeTransientView(changingView);
                 }
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 528827f..35d1526 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -830,6 +830,7 @@
         mNotificationStackScrollLayoutController.setOverscrollTopChangedListener(
                 mOnOverscrollTopChangedListener);
         mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled);
+        mNotificationStackScrollLayoutController.setOnStackYChanged(this::onStackYChanged);
         mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
                 mOnEmptySpaceClickListener);
         addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
@@ -2198,15 +2199,17 @@
         mDepthController.setQsPanelExpansion(qsExpansionFraction);
     }
 
-    private Runnable mOnStackYChanged = () -> {
+    private void onStackYChanged(boolean shouldAnimate) {
         if (mQs != null) {
+            if (shouldAnimate) {
+                mAnimateNextNotificationBounds = true;
+                mNotificationBoundsAnimationDelay = 0;
+            }
             setQSClippingBounds();
         }
     };
 
     private void onNotificationScrolled(int newScrollPosition) {
-        // Since this is an overscroller, sometimes the scrollY can be temporarily negative
-        // (when overscrollng on the top and flinging). Let's
         updateQSExpansionEnabledAmbient();
     }
 
@@ -2228,14 +2231,13 @@
      * and QS state.
      */
     private void setQSClippingBounds() {
-        int top = 0;
-        int bottom = 0;
-        int left = 0;
-        int right = 0;
+        int top;
+        int bottom;
+        int left;
+        int right;
 
         final int qsPanelBottomY = calculateQsBottomPosition(computeQsExpansionFraction());
-        final boolean visible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0)
-                && !mShouldUseSplitNotificationShade;
+        final boolean qsVisible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0);
 
         if (!mShouldUseSplitNotificationShade) {
             if (mTransitioningToFullShadeProgress > 0.0f) {
@@ -2244,7 +2246,6 @@
                 top = mTransitionToFullShadeQSPosition;
             } else {
                 final float notificationTop = getQSEdgePosition();
-                mAmbientState.setNotificationScrimTop(notificationTop);
                 top = (int) (isOnKeyguard() ? Math.min(qsPanelBottomY, notificationTop)
                         : notificationTop);
             }
@@ -2252,8 +2253,7 @@
             // notification bounds should take full screen width regardless of insets
             left = 0;
             right = getView().getRight() + mDisplayRightInset;
-        } else if (qsPanelBottomY > 0) { // so bounds are empty on lockscreen
-            mAmbientState.setNotificationScrimTop(mSplitShadeNotificationsTopPadding);
+        } else {
             top = Math.min(qsPanelBottomY, mSplitShadeNotificationsTopPadding);
             bottom = mNotificationStackScrollLayoutController.getHeight();
             left = mNotificationStackScrollLayoutController.getLeft();
@@ -2261,17 +2261,17 @@
         }
         // top should never be lower than bottom, otherwise it will be invisible.
         top = Math.min(top, bottom);
-        applyQSClippingBounds(left, top, right, bottom, visible);
+        applyQSClippingBounds(left, top, right, bottom, qsVisible);
     }
 
     private void applyQSClippingBounds(int left, int top, int right, int bottom,
-            boolean visible) {
+            boolean qsVisible) {
         if (!mAnimateNextNotificationBounds || mKeyguardStatusAreaClipBounds.isEmpty()) {
             if (mQsClippingAnimation != null) {
                 // update the end position of the animator
                 mQsClippingAnimationEndBounds.set(left, top, right, bottom);
             } else {
-                applyQSClippingImmediately(left, top, right, bottom, visible);
+                applyQSClippingImmediately(left, top, right, bottom, qsVisible);
             }
         } else {
             mQsClippingAnimationEndBounds.set(left, top, right, bottom);
@@ -2295,7 +2295,7 @@
                 int animBottom = (int) MathUtils.lerp(startBottom,
                         mQsClippingAnimationEndBounds.bottom, fraction);
                 applyQSClippingImmediately(animLeft, animTop, animRight, animBottom,
-                        visible /* visible */);
+                        qsVisible /* qsVisible */);
             });
             mQsClippingAnimation.addListener(new AnimatorListenerAdapter() {
                 @Override
@@ -2310,7 +2310,7 @@
     }
 
     private void applyQSClippingImmediately(int left, int top, int right, int bottom,
-            boolean visible) {
+            boolean qsVisible) {
         // Fancy clipping for quick settings
         int radius = mScrimCornerRadius;
         int statusBarClipTop = 0;
@@ -2318,19 +2318,34 @@
         if (!mShouldUseSplitNotificationShade) {
             // The padding on this area is large enough that we can use a cheaper clipping strategy
             mKeyguardStatusAreaClipBounds.set(left, top, right, bottom);
-            clipStatusView = visible;
+            clipStatusView = qsVisible;
             radius = (int) MathUtils.lerp(mScreenCornerRadius, mScrimCornerRadius,
                     Math.min(top / (float) mScrimCornerRadius, 1f));
             statusBarClipTop = top - mKeyguardStatusBar.getTop();
         }
         if (mQs != null) {
-            mQs.setFancyClipping(top, bottom, radius, visible);
+            mQs.setFancyClipping(top, bottom, radius, qsVisible
+                    && !mShouldUseSplitNotificationShade);
         }
         mKeyguardStatusViewController.setClipBounds(
                 clipStatusView ? mKeyguardStatusAreaClipBounds : null);
-        mScrimController.setNotificationsBounds(left, top, right, bottom);
+        if (!qsVisible && mShouldUseSplitNotificationShade) {
+            // On the lockscreen when qs isn't visible, we don't want the bounds of the shade to
+            // be visible, otherwise you can see the bounds once swiping up to see bouncer
+            mScrimController.setNotificationsBounds(0, 0, 0, 0);
+        } else {
+            mScrimController.setNotificationsBounds(left, top, right, bottom);
+        }
+
         mScrimController.setScrimCornerRadius(radius);
         mKeyguardStatusBar.setTopClipping(statusBarClipTop);
+        int nsslLeft = left - mNotificationStackScrollLayoutController.getLeft();
+        int nsslRight = right - mNotificationStackScrollLayoutController.getLeft();
+        int nsslTop = top - mNotificationStackScrollLayoutController.getTop();
+        int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop();
+        int bottomRadius = mShouldUseSplitNotificationShade ? radius : 0;
+        mNotificationStackScrollLayoutController.setRoundedClippingBounds(
+                nsslLeft, nsslTop, nsslRight, nsslBottom, radius, bottomRadius);
     }
 
     private float getQSEdgePosition() {
@@ -3323,7 +3338,6 @@
             // The expandedHeight is always the full panel Height when bypassing
             expandedHeight = getMaxPanelHeightNonBypass();
         }
-        mNotificationStackScrollLayoutController.setOnStackYChanged(mOnStackYChanged);
         mNotificationStackScrollLayoutController.setExpandedHeight(expandedHeight);
         updateKeyguardBottomAreaAlpha();
         updateBigClockAlpha();
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 5ee5e48..07ad136 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -2841,9 +2841,11 @@
                 mActivityIntentHelper.wouldLaunchResolverActivity(intent,
                         mLockscreenUserManager.getCurrentUserId());
 
+        boolean animate =
+                animationController != null && !willLaunchResolverActivity && shouldAnimateLaunch(
+                        true /* isActivityIntent */);
         ActivityLaunchAnimator.Controller animController =
-                !willLaunchResolverActivity && shouldAnimateLaunch(true /* isActivityIntent */)
-                        ? wrapAnimationController(animationController, dismissShade) : null;
+                animate ? wrapAnimationController(animationController, dismissShade) : null;
 
         // If we animate, we will dismiss the shade only once the animation is done. This is taken
         // care of by the StatusBarLaunchAnimationController.
@@ -2857,7 +2859,7 @@
             int[] result = new int[]{ActivityManager.START_CANCELED};
 
             mActivityLaunchAnimator.startIntentWithAnimation(animController,
-                    true /* animate */, intent.getPackage(), (adapter) -> {
+                    animate, intent.getPackage(), (adapter) -> {
                         ActivityOptions options = new ActivityOptions(
                                 getActivityOptions(mDisplayId, adapter));
                         options.setDisallowEnterPictureInPictureWhileLaunching(
@@ -2907,16 +2909,12 @@
             }
         };
         executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShadeDirectly,
-                willLaunchResolverActivity, true /* deferred */);
+                willLaunchResolverActivity, true /* deferred */, animate);
     }
 
     @Nullable
     private ActivityLaunchAnimator.Controller wrapAnimationController(
-            @Nullable ActivityLaunchAnimator.Controller animationController, boolean dismissShade) {
-        if (animationController == null) {
-            return null;
-        }
-
+            ActivityLaunchAnimator.Controller animationController, boolean dismissShade) {
         View rootView = animationController.getLaunchContainer().getRootView();
         if (rootView == mSuperStatusBarViewFactory.getStatusBarWindowView()) {
             // We are animating a view in the status bar. We have to make sure that the status bar
@@ -2959,34 +2957,56 @@
             final boolean dismissShade,
             final boolean afterKeyguardGone,
             final boolean deferred) {
-        dismissKeyguardThenExecute(() -> {
-            if (runnable != null) {
-                if (mStatusBarKeyguardViewManager.isShowing()
-                        && mStatusBarKeyguardViewManager.isOccluded()) {
-                    mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
-                } else {
-                    AsyncTask.execute(runnable);
-                }
-            }
-            if (dismissShade) {
-                if (mExpandedVisible && !mBouncerShowing) {
-                    mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                            true /* force */, true /* delayed*/);
-                } else {
+        executeRunnableDismissingKeyguard(runnable, cancelAction, dismissShade, afterKeyguardGone,
+                deferred, false /* willAnimateOnKeyguard */);
+    }
 
-                    // Do it after DismissAction has been processed to conserve the needed ordering.
-                    mHandler.post(mShadeController::runPostCollapseRunnables);
+    public void executeRunnableDismissingKeyguard(final Runnable runnable,
+            final Runnable cancelAction,
+            final boolean dismissShade,
+            final boolean afterKeyguardGone,
+            final boolean deferred,
+            final boolean willAnimateOnKeyguard) {
+        OnDismissAction onDismissAction = new OnDismissAction() {
+            @Override
+            public boolean onDismiss() {
+                if (runnable != null) {
+                    if (mStatusBarKeyguardViewManager.isShowing()
+                            && mStatusBarKeyguardViewManager.isOccluded()) {
+                        mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
+                    } else {
+                        AsyncTask.execute(runnable);
+                    }
                 }
-            } else if (isInLaunchTransition()
-                    && mNotificationPanelViewController.isLaunchTransitionFinished()) {
+                if (dismissShade) {
+                    if (mExpandedVisible && !mBouncerShowing) {
+                        mShadeController.animateCollapsePanels(
+                                CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+                                true /* force */, true /* delayed*/);
+                    } else {
 
-                // We are not dismissing the shade, but the launch transition is already finished,
-                // so nobody will call readyForKeyguardDone anymore. Post it such that
-                // keyguardDonePending gets called first.
-                mHandler.post(mStatusBarKeyguardViewManager::readyForKeyguardDone);
+                        // Do it after DismissAction has been processed to conserve the needed
+                        // ordering.
+                        mHandler.post(mShadeController::runPostCollapseRunnables);
+                    }
+                } else if (StatusBar.this.isInLaunchTransition()
+                        && mNotificationPanelViewController.isLaunchTransitionFinished()) {
+
+                    // We are not dismissing the shade, but the launch transition is already
+                    // finished,
+                    // so nobody will call readyForKeyguardDone anymore. Post it such that
+                    // keyguardDonePending gets called first.
+                    mHandler.post(mStatusBarKeyguardViewManager::readyForKeyguardDone);
+                }
+                return deferred;
             }
-            return deferred;
-        }, cancelAction, afterKeyguardGone);
+
+            @Override
+            public boolean willRunAnimationOnKeyguard() {
+                return willAnimateOnKeyguard;
+            }
+        };
+        dismissKeyguardThenExecute(onDismissAction, cancelAction, afterKeyguardGone);
     }
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -3408,6 +3428,10 @@
     }
 
     boolean updateIsKeyguard() {
+        return updateIsKeyguard(false /* force */);
+    }
+
+    boolean updateIsKeyguard(boolean force) {
         boolean wakeAndUnlocking = mBiometricUnlockController.getMode()
                 == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 
@@ -3431,7 +3455,7 @@
                 showKeyguardImpl();
             }
         } else {
-            return hideKeyguardImpl();
+            return hideKeyguardImpl(force);
         }
         return false;
     }
@@ -3560,11 +3584,11 @@
     /**
      * @return true if we would like to stay in the shade, false if it should go away entirely
      */
-    public boolean hideKeyguardImpl() {
+    public boolean hideKeyguardImpl(boolean force) {
         mIsKeyguard = false;
         Trace.beginSection("StatusBar#hideKeyguard");
         boolean staying = mStatusBarStateController.leaveOpenOnKeyguardHide();
-        if (!(mStatusBarStateController.setState(StatusBarState.SHADE))) {
+        if (!(mStatusBarStateController.setState(StatusBarState.SHADE, force))) {
             //TODO: StatusBarStateController should probably know about hiding the keyguard and
             // notify listeners.
 
@@ -4609,28 +4633,37 @@
      *
      * @param action The action to execute after dismissing the keyguard.
      * @param collapsePanel Whether we should collapse the panel after dismissing the keyguard.
-     * @param deferKeyguardDismiss Whether we should defer the keyguard actual dismissal, for
-     *                             instance to run animations on the keyguard before hiding it.
+     * @param willAnimateOnKeyguard Whether {@param action} will run an animation on the keyguard if
+     *                              we are locked.
      */
     private void executeActionDismissingKeyguard(Runnable action, boolean afterKeyguardGone,
-            boolean collapsePanel, boolean deferKeyguardDismiss) {
+            boolean collapsePanel, boolean willAnimateOnKeyguard) {
         if (!mDeviceProvisionedController.isDeviceProvisioned()) return;
 
-        dismissKeyguardThenExecute(() -> {
-            new Thread(() -> {
-                try {
-                    // The intent we are sending is for the application, which
-                    // won't have permission to immediately start an activity after
-                    // the user switches to home.  We know it is safe to do at this
-                    // point, so make sure new activity switches are now allowed.
-                    ActivityManager.getService().resumeAppSwitches();
-                } catch (RemoteException e) {
-                }
-                action.run();
-            }).start();
+        OnDismissAction onDismissAction = new OnDismissAction() {
+            @Override
+            public boolean onDismiss() {
+                new Thread(() -> {
+                    try {
+                        // The intent we are sending is for the application, which
+                        // won't have permission to immediately start an activity after
+                        // the user switches to home.  We know it is safe to do at this
+                        // point, so make sure new activity switches are now allowed.
+                        ActivityManager.getService().resumeAppSwitches();
+                    } catch (RemoteException e) {
+                    }
+                    action.run();
+                }).start();
 
-            return collapsePanel ? mShadeController.collapsePanel() : deferKeyguardDismiss;
-        }, afterKeyguardGone);
+                return collapsePanel ? mShadeController.collapsePanel() : willAnimateOnKeyguard;
+            }
+
+            @Override
+            public boolean willRunAnimationOnKeyguard() {
+                return willAnimateOnKeyguard;
+            }
+        };
+        dismissKeyguardThenExecute(onDismissAction, afterKeyguardGone);
     }
 
     @Override
@@ -4674,7 +4707,6 @@
         // the animation on the keyguard). The animation will take care of (instantly) collapsing
         // the shade and hiding the keyguard once it is done.
         boolean collapse = !animate;
-        boolean deferKeyguardDismiss = animate;
         executeActionDismissingKeyguard(() -> {
             try {
                 // We wrap animationCallback with a StatusBarLaunchAnimatorController so that the
@@ -4703,7 +4735,7 @@
             if (intentSentUiThreadCallback != null) {
                 postOnUiThread(intentSentUiThreadCallback);
             }
-        }, willLaunchResolverActivity, collapse, deferKeyguardDismiss);
+        }, willLaunchResolverActivity, collapse, animate);
     }
 
     private void postOnUiThread(Runnable runnable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index c7efcb2..2601b8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -192,6 +192,7 @@
 
     private OnDismissAction mAfterKeyguardGoneAction;
     private Runnable mKeyguardGoneCancelAction;
+    private boolean mDismissActionWillAnimateOnKeyguard;
     private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();
 
     // Dismiss action to be launched when we stop dozing or the keyguard is gone.
@@ -447,6 +448,7 @@
 
             mAfterKeyguardGoneAction = r;
             mKeyguardGoneCancelAction = cancelAction;
+            mDismissActionWillAnimateOnKeyguard = r != null && r.willRunAnimationOnKeyguard();
 
             // If there is an an alternate auth interceptor (like the UDFPS), show that one instead
             // of the bouncer.
@@ -625,9 +627,12 @@
             mBouncer.startPreHideAnimation(finishRunnable);
             mStatusBar.onBouncerPreHideAnimation();
 
-            // startPreHideAnimation() will change the visibility of the bouncer, so we have to
-            // make sure to update its state.
-            updateStates();
+            // We update the state (which will show the keyguard) only if an animation will run on
+            // the keyguard. If there is no animation, we wait before updating the state so that we
+            // go directly from bouncer to launcher/app.
+            if (mDismissActionWillAnimateOnKeyguard) {
+                updateStates();
+            }
         } else if (finishRunnable != null) {
             finishRunnable.run();
         }
@@ -798,6 +803,7 @@
             mAfterKeyguardGoneAction = null;
         }
         mKeyguardGoneCancelAction = null;
+        mDismissActionWillAnimateOnKeyguard = false;
         for (int i = 0; i < mAfterKeyguardGoneRunnables.size(); i++) {
             mAfterKeyguardGoneRunnables.get(i).run();
         }
@@ -866,6 +872,7 @@
             return; // allow bouncer to trigger saved actions
         }
         mAfterKeyguardGoneAction = null;
+        mDismissActionWillAnimateOnKeyguard = false;
         if (mKeyguardGoneCancelAction != null) {
             mKeyguardGoneCancelAction.run();
             mKeyguardGoneCancelAction = null;
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 d93b766..98b9cc9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -41,6 +41,7 @@
 import android.util.EventLog;
 import android.view.View;
 
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.widget.LockPatternUtils;
@@ -260,10 +261,19 @@
         boolean showOverLockscreen = mKeyguardStateController.isShowing() && intent != null
                 && mActivityIntentHelper.wouldShowOverLockscreen(intent.getIntent(),
                 mLockscreenUserManager.getCurrentUserId());
-        ActivityStarter.OnDismissAction postKeyguardAction =
-                () -> handleNotificationClickAfterKeyguardDismissed(
+        ActivityStarter.OnDismissAction postKeyguardAction = new ActivityStarter.OnDismissAction() {
+            @Override
+            public boolean onDismiss() {
+                return handleNotificationClickAfterKeyguardDismissed(
                         entry, row, controller, intent,
                         isActivityIntent, animate, showOverLockscreen);
+            }
+
+            @Override
+            public boolean willRunAnimationOnKeyguard() {
+                return animate;
+            }
+        };
         if (showOverLockscreen) {
             mIsCollapsingToShowActivityOverLockscreen = true;
             postKeyguardAction.onDismiss();
@@ -453,53 +463,76 @@
     public void startNotificationGutsIntent(final Intent intent, final int appUid,
             ExpandableNotificationRow row) {
         boolean animate = mStatusBar.shouldAnimateLaunch(true /* isActivityIntent */);
-        mActivityStarter.dismissKeyguardThenExecute(() -> {
-            AsyncTask.execute(() -> {
-                ActivityLaunchAnimator.Controller animationController =
-                        new StatusBarLaunchAnimatorController(
-                                mNotificationAnimationProvider.getAnimatorController(row),
-                                mStatusBar, true /* isActivityIntent */);
+        ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
+            @Override
+            public boolean onDismiss() {
+                AsyncTask.execute(() -> {
+                    ActivityLaunchAnimator.Controller animationController =
+                            new StatusBarLaunchAnimatorController(
+                                    mNotificationAnimationProvider.getAnimatorController(row),
+                                    mStatusBar, true /* isActivityIntent */);
 
-                mActivityLaunchAnimator.startIntentWithAnimation(
-                        animationController, animate, intent.getPackage(),
-                        (adapter) -> TaskStackBuilder.create(mContext)
-                                .addNextIntentWithParentStack(intent)
-                                .startActivities(getActivityOptions(
-                                        mStatusBar.getDisplayId(),
-                                        adapter),
-                                        new UserHandle(UserHandle.getUserId(appUid))));
-            });
-            return true;
-        }, null, false /* afterKeyguardGone */);
+                    mActivityLaunchAnimator.startIntentWithAnimation(
+                            animationController, animate, intent.getPackage(),
+                            (adapter) -> TaskStackBuilder.create(mContext)
+                                    .addNextIntentWithParentStack(intent)
+                                    .startActivities(getActivityOptions(
+                                            mStatusBar.getDisplayId(),
+                                            adapter),
+                                            new UserHandle(UserHandle.getUserId(appUid))));
+                });
+                return true;
+            }
+
+            @Override
+            public boolean willRunAnimationOnKeyguard() {
+                return animate;
+            }
+        };
+        mActivityStarter.dismissKeyguardThenExecute(onDismissAction, null,
+                false /* afterKeyguardGone */);
     }
 
     @Override
     public void startHistoryIntent(View view, boolean showHistory) {
         boolean animate = mStatusBar.shouldAnimateLaunch(true /* isActivityIntent */);
-        mActivityStarter.dismissKeyguardThenExecute(() -> {
-            AsyncTask.execute(() -> {
-                Intent intent = showHistory ? new Intent(
-                        Settings.ACTION_NOTIFICATION_HISTORY) : new Intent(
-                        Settings.ACTION_NOTIFICATION_SETTINGS);
-                TaskStackBuilder tsb = TaskStackBuilder.create(mContext)
-                        .addNextIntent(new Intent(Settings.ACTION_NOTIFICATION_SETTINGS));
-                if (showHistory) {
-                    tsb.addNextIntent(intent);
-                }
+        ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
+            @Override
+            public boolean onDismiss() {
+                AsyncTask.execute(() -> {
+                    Intent intent = showHistory ? new Intent(
+                            Settings.ACTION_NOTIFICATION_HISTORY) : new Intent(
+                            Settings.ACTION_NOTIFICATION_SETTINGS);
+                    TaskStackBuilder tsb = TaskStackBuilder.create(mContext)
+                            .addNextIntent(new Intent(Settings.ACTION_NOTIFICATION_SETTINGS));
+                    if (showHistory) {
+                        tsb.addNextIntent(intent);
+                    }
 
-                ActivityLaunchAnimator.Controller animationController =
-                        new StatusBarLaunchAnimatorController(
-                                ActivityLaunchAnimator.Controller.fromView(view), mStatusBar,
-                                true /* isActivityIntent */);
+                    ActivityLaunchAnimator.Controller viewController =
+                            ActivityLaunchAnimator.Controller.fromView(view,
+                                    InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON
+                            );
+                    ActivityLaunchAnimator.Controller animationController =
+                            new StatusBarLaunchAnimatorController(viewController, mStatusBar,
+                                    true /* isActivityIntent */);
 
-                mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate,
-                        intent.getPackage(),
-                        (adapter) -> tsb.startActivities(
-                                getActivityOptions(mStatusBar.getDisplayId(), adapter),
-                                UserHandle.CURRENT));
-            });
-            return true;
-        }, null, false /* afterKeyguardGone */);
+                    mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate,
+                            intent.getPackage(),
+                            (adapter) -> tsb.startActivities(
+                                    getActivityOptions(mStatusBar.getDisplayId(), adapter),
+                                    UserHandle.CURRENT));
+                });
+                return true;
+            }
+
+            @Override
+            public boolean willRunAnimationOnKeyguard() {
+                return animate;
+            }
+        };
+        mActivityStarter.dismissKeyguardThenExecute(onDismissAction, null,
+                false /* afterKeyguardGone */);
     }
 
     private void removeHunAfterClick(ExpandableNotificationRow row) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index e135cc5..a2d0672 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -131,9 +131,14 @@
         lightRevealAnimationPlaying = false
         aodUiAnimationPlaying = false
 
-        // Make sure the status bar is in the correct keyguard state, since we might have left it in
-        // the KEYGUARD state if this wakeup cancelled the screen off animation.
-        statusBar.updateIsKeyguard()
+        // Make sure the status bar is in the correct keyguard state, forcing it if necessary. This
+        // is required if the screen off animation is cancelled, since it might be incorrectly left
+        // in the KEYGUARD or SHADE states depending on when it was cancelled and whether 'lock
+        // instantly' is enabled. We need to force it so that the state is set even if we're going
+        // from SHADE to SHADE or KEYGUARD to KEYGUARD, since we might have changed parts of the UI
+        // (such as showing AOD in the shade) without actually changing the StatusBarState. This
+        // ensures that the UI definitely reflects the desired state.
+        statusBar.updateIsKeyguard(true /* force */)
     }
 
     override fun onStartedGoingToSleep() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index ef7fac3..b295f66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -25,6 +25,7 @@
 import android.util.Log
 import android.view.View
 import android.widget.Chronometer
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dagger.SysUISingleton
@@ -179,7 +180,10 @@
                 logger.logChipClicked()
                 activityStarter.postStartActivityDismissingKeyguard(
                         currentCallNotificationInfo.intent, 0,
-                        ActivityLaunchAnimator.Controller.fromView(backgroundView))
+                        ActivityLaunchAnimator.Controller.fromView(
+                                backgroundView,
+                                InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
+                )
             }
 
             setUpUidObserver(currentCallNotificationInfo)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 4ab07af..2460484 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -796,6 +796,9 @@
         for (int i = 0; i < mMobileSignalControllers.size(); i++) {
             MobileSignalController controller = mMobileSignalControllers.valueAt(i);
             controller.setConfiguration(mConfig);
+            if (mProviderModel) {
+                controller.refreshCallIndicator(mCallbackHandler);
+            }
         }
         refreshLocale();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index 485df21..5617f1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -59,7 +59,6 @@
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.util.MathUtils;
 import android.view.Choreographer;
 import android.view.MotionEvent;
 import android.view.View;
@@ -489,20 +488,20 @@
     @Test
     public void onRotationChanged_buttonIsShowing_expectedYPosition() {
         final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        final int oldWindowHeight = windowBounds.height();
         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        final Rect oldDraggableBounds = new Rect(mMagnificationModeSwitch.mDraggableWindowBounds);
         final float windowHeightFraction =
-                (float) mWindowManager.getLayoutParamsFromAttachedView().y / oldWindowHeight;
+                (float) (mWindowManager.getLayoutParamsFromAttachedView().y
+                        - oldDraggableBounds.top) / oldDraggableBounds.height();
 
-        // The window bounds are changed due to the rotation change.
+        // The window bounds and the draggable bounds are changed due to the rotation change.
         final Rect newWindowBounds = new Rect(0, 0, windowBounds.height(), windowBounds.width());
         mWindowManager.setWindowBounds(newWindowBounds);
         mMagnificationModeSwitch.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
 
-        int expectedY = (int) (newWindowBounds.height() * windowHeightFraction);
-        expectedY = MathUtils.constrain(expectedY,
-                mMagnificationModeSwitch.mDraggableWindowBounds.top,
-                mMagnificationModeSwitch.mDraggableWindowBounds.bottom);
+        int expectedY = (int) (windowHeightFraction
+                * mMagnificationModeSwitch.mDraggableWindowBounds.height())
+                + mMagnificationModeSwitch.mDraggableWindowBounds.top;
         assertEquals(
                 "The Y position does not keep the same height ratio after the rotation changed.",
                 expectedY, mWindowManager.getLayoutParamsFromAttachedView().y);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 83e7b17..f0f5420 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -38,8 +38,9 @@
 import android.service.dreams.IDreamManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.GestureDetector;
 import android.view.IWindowManager;
-import android.view.View;
+import android.view.MotionEvent;
 import android.view.WindowManagerPolicyConstants;
 
 import androidx.test.filters.SmallTest;
@@ -57,6 +58,7 @@
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -109,6 +111,7 @@
     @Mock private SysUiState mSysUiState;
     @Mock private Handler mHandler;
     @Mock private UserContextProvider mUserContextProvider;
+    @Mock private StatusBar mStatusBar;
 
     private TestableLooper mTestableLooper;
 
@@ -150,7 +153,8 @@
                 mInfoProvider,
                 mRingerModeTracker,
                 mSysUiState,
-                mHandler
+                mHandler,
+                mStatusBar
         );
         mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
 
@@ -194,7 +198,7 @@
     }
 
     @Test
-    public void testShouldLogOnTapOutside() {
+    public void testSingleTap_logAndDismiss() {
         mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
         doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
         doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
@@ -207,12 +211,61 @@
         };
         doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
         GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog();
-        View container = dialog.findViewById(com.android.systemui.R.id.global_actions_container);
-        container.callOnClick();
+
+        GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener);
+        gestureListener.onSingleTapConfirmed(null);
         verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
     }
 
     @Test
+    public void testSwipeDownLockscreen_logAndOpenQS() {
+        mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+        doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+        doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+        doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+        doReturn(true).when(mStatusBar).isKeyguardShowing();
+        String[] actions = {
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+        };
+        doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+        GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog();
+
+        GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener);
+        MotionEvent start = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0);
+        gestureListener.onFling(start, end, 0, 1000);
+        verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
+        verify(mStatusBar).animateExpandSettingsPanel(null);
+    }
+
+    @Test
+    public void testSwipeDown_logAndOpenNotificationShade() {
+        mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+        doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+        doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+        doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+        doReturn(false).when(mStatusBar).isKeyguardShowing();
+        String[] actions = {
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+        };
+        doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+        GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog();
+
+        GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener);
+        MotionEvent start = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0);
+        gestureListener.onFling(start, end, 0, 1000);
+        verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
+        verify(mStatusBar).animateExpandNotificationsPanel();
+    }
+
+    @Test
     public void testShouldLogBugreportPress() throws InterruptedException {
         GlobalActionsDialog.BugReportAction bugReportAction =
                 mGlobalActionsDialogLite.makeBugReportActionForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 3130e97..c543470 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -65,6 +65,7 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -124,6 +125,7 @@
     @Mock private Handler mHandler;
     @Mock private UserTracker mUserTracker;
     @Mock private SecureSettings mSecureSettings;
+    @Mock private StatusBar mStatusBar;
 
     private TestableLooper mTestableLooper;
 
@@ -164,7 +166,8 @@
                 mUiEventLogger,
                 mRingerModeTracker,
                 mSysUiState,
-                mHandler
+                mHandler,
+                mStatusBar
         );
         mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
 
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 5f4d90b..f6264ff 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
@@ -49,6 +49,7 @@
 import com.android.wm.shell.bubbles.Bubble;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -213,6 +214,7 @@
         verify(mBubblesManager, never()).expandStackAndSelectBubble(any(NotificationEntry.class));
     }
 
+    @Ignore
     @Test
     public void testBubbleWithNoNotifOpensBubble() throws Exception {
         Bubble bubble = mock(Bubble.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 2e0827f..fa25c3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -289,6 +289,7 @@
 
     @Test
     public void testIconScrollXAfterTranslationAndReset() throws Exception {
+        mGroupRow.setDismissUsingRowTranslationX(false);
         mGroupRow.setTranslation(50);
         assertEquals(50, -mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX());
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 9e939ee..2d51683 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -84,7 +84,6 @@
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.FeatureFlags;
@@ -675,21 +674,6 @@
         verify(mTapAgainViewController).show();
     }
 
-    @Test
-    public void testNotificationClipping_isAlignedWithNotificationScrimInSplitShade() {
-        mStatusBarStateController.setState(SHADE);
-        QS qs = mock(QS.class);
-        when(qs.getHeader()).thenReturn(mock(View.class));
-        mNotificationPanelViewController.mQs = qs;
-        enableSplitShade();
-
-        // hacky way to refresh notification scrim top with non-zero qsPanelBottom value
-        mNotificationPanelViewController.setTransitionToFullShadeAmount(200, false, 0);
-
-        verify(mAmbientState)
-                .setNotificationScrimTop(NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE);
-    }
-
     private FalsingManager.FalsingTapListener getFalsingTapListener() {
         for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) {
             listener.onViewAttachedToWindow(mView);
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 b2efd68..154972e 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
@@ -843,12 +843,14 @@
 
         // By default, showKeyguardImpl sets state to KEYGUARD.
         mStatusBar.showKeyguardImpl();
-        verify(mStatusBarStateController).setState(eq(StatusBarState.KEYGUARD));
+        verify(mStatusBarStateController).setState(
+                eq(StatusBarState.KEYGUARD), eq(false) /* force */);
 
         // If useFullscreenUserSwitcher is true, state is set to FULLSCREEN_USER_SWITCHER.
         when(mUserSwitcherController.useFullscreenUserSwitcher()).thenReturn(true);
         mStatusBar.showKeyguardImpl();
-        verify(mStatusBarStateController).setState(eq(StatusBarState.FULLSCREEN_USER_SWITCHER));
+        verify(mStatusBarStateController).setState(
+                eq(StatusBarState.FULLSCREEN_USER_SWITCHER), eq(false) /* force */);
     }
 
     @Test
diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml
index b9c5f1d..8d0227e 100644
--- a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml
@@ -25,9 +25,9 @@
     <!-- Max((28 + 20), 0) = 48 -->
     <dimen name="status_bar_height_landscape">48dp</dimen>
     <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) -->
-    <dimen name="quick_qs_offset_height">28dp</dimen>
+    <dimen name="quick_qs_offset_height">48dp</dimen>
     <!-- Total height of QQS (quick_qs_offset_height + 128) -->
-    <dimen name="quick_qs_total_height">156dp</dimen>
+    <dimen name="quick_qs_total_height">176dp</dimen>
 
     <dimen name="waterfall_display_left_edge_size">20dp</dimen>
     <dimen name="waterfall_display_top_edge_size">0dp</dimen>
diff --git a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
index 3310cb4..ea2c7d2 100644
--- a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
+++ b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
@@ -31,9 +31,9 @@
 import android.util.SparseIntArray;
 import android.view.InputDevice;
 import android.view.MotionEvent;
+import android.view.WindowManagerPolicyConstants;
 
 import com.android.internal.os.SomeArgs;
-import com.android.server.policy.WindowManagerPolicy;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -122,6 +122,12 @@
             return;
         }
         cancelAnyPendingInjectedEvents();
+        // The events injected from outside of system_server are not trusted. Remove the flag to
+        // prevent accessibility service from impersonating a real input device.
+        policyFlags &= ~WindowManagerPolicyConstants.FLAG_INPUTFILTER_TRUSTED;
+        // Indicate that the input event is injected from accessibility, to let applications
+        // distinguish it from events injected by other means.
+        policyFlags |= WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY;
         sendMotionEventToNext(event, rawEvent, policyFlags);
     }
 
@@ -156,7 +162,9 @@
             return false;
         }
         MotionEvent motionEvent = (MotionEvent) message.obj;
-        sendMotionEventToNext(motionEvent, motionEvent, WindowManagerPolicy.FLAG_PASS_TO_USER);
+        sendMotionEventToNext(motionEvent, motionEvent,
+                WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+                | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY);
         boolean isEndOfSequence = message.arg1 != 0;
         if (isEndOfSequence) {
             notifyService(mServiceInterfaceForCurrentGesture, mSequencesInProgress.get(0), true);
@@ -308,7 +316,8 @@
             MotionEvent cancelEvent =
                     obtainMotionEvent(now, now, MotionEvent.ACTION_CANCEL, getLastTouchPoints(), 1);
             sendMotionEventToNext(cancelEvent, cancelEvent,
-                    WindowManagerPolicy.FLAG_PASS_TO_USER);
+                    WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+                    | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY);
             mOpenGesturesInProgress.put(source, false);
         }
     }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 83dfe8e..05131d4 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -454,19 +454,13 @@
                 }).cancelTimeout();
 
             }, FgThread.getExecutor()).whenComplete(uncheckExceptions((association, err) -> {
-
-                final long callingIdentity = Binder.clearCallingIdentity();
-                try {
-                    if (err == null) {
-                        addAssociation(association);
-                    } else {
-                        Slog.e(LOG_TAG, "Failed to discover device(s)", err);
-                        callback.onFailure("No devices found: " + err.getMessage());
-                    }
-                    cleanup();
-                } finally {
-                    Binder.restoreCallingIdentity(callingIdentity);
+                if (err == null) {
+                    addAssociation(association);
+                } else {
+                    Slog.e(LOG_TAG, "Failed to discover device(s)", err);
+                    callback.onFailure("No devices found: " + err.getMessage());
                 }
+                cleanup();
             }));
         }
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 2eafc61..5e388d9 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1791,6 +1791,7 @@
                         if (!r.fgRequired) {
                             final long delayMs = SystemClock.elapsedRealtime() - r.createRealTime;
                             if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) {
+                                resetFgsRestrictionLocked(r);
                                 setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
                                         r.appInfo.uid, r.intent.getIntent(), r, r.userId,false);
                                 final String temp = "startForegroundDelayMs:" + delayMs;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index d3a7752..99a33e4 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -848,7 +848,7 @@
             return mAttributionFlags;
         }
 
-        /** @return attributoin chiang id for the access */
+        /** @return attribution chain id for the access */
         public int getAttributionChainId() {
             return mAttributionChainId;
         }
@@ -912,7 +912,8 @@
                     proxyAttributionTag, uidState, flags);
 
             mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName,
-                    tag, uidState, flags, accessTime);
+                    tag, uidState, flags, accessTime, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+                    AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
         }
 
         /**
@@ -1053,9 +1054,9 @@
             event.numUnfinishedStarts++;
 
             if (isStarted) {
-                // TODO: Consider storing the attribution chain flags and id
                 mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
-                        parent.packageName, tag, uidState, flags, startTime);
+                        parent.packageName, tag, uidState, flags, startTime, attributionFlags,
+                        attributionChainId);
             }
         }
 
@@ -1112,7 +1113,8 @@
 
                 mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
                         parent.packageName, tag, event.getUidState(),
-                        event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration());
+                        event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
+                        event.getAttributionFlags(), event.getAttributionChainId());
 
                 if (!isPausing) {
                     mInProgressStartOpEventPool.release(event);
@@ -1215,7 +1217,8 @@
                 event.mStartElapsedTime = SystemClock.elapsedRealtime();
                 event.mStartTime = startTime;
                 mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
-                        parent.packageName, tag, event.mUidState, event.mFlags, startTime);
+                        parent.packageName, tag, event.mUidState, event.mFlags, startTime,
+                        event.getAttributionFlags(), event.getAttributionChainId());
                 if (shouldSendActive) {
                     scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName,
                             tag, true, event.getAttributionFlags(), event.getAttributionChainId());
@@ -4558,15 +4561,15 @@
                 }
 
                 try {
-                    if (mPlatformCompat.isChangeEnabledByPackageName(
+                    if (!mPlatformCompat.isChangeEnabledByPackageName(
                             SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
-                            userId) && mPlatformCompat.isChangeEnabledByUid(
+                            userId) || !mPlatformCompat.isChangeEnabledByUid(
                                     SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
-                            callingUid) && !isAttributionTagValid) {
-                        Slog.e(TAG, msg);
-                    } else {
-                        Slog.e(TAG, msg);
+                            callingUid)) {
+                        // Do not override tags if overriding is not enabled for this package
+                        isAttributionTagValid = true;
                     }
+                    Slog.e(TAG, msg);
                 } catch (RemoteException neverHappens) {
                 }
             }
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
index 10cfddf..49469cc 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -26,6 +26,7 @@
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
 import static android.app.AppOpsManager.OP_FLAG_SELF;
 import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
+import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY;
 import static android.app.AppOpsManager.OP_NONE;
 import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA;
 import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE;
@@ -156,8 +157,11 @@
     private static final String ATTR_NOTE_DURATION = "nd";
     private static final String ATTR_UID_STATE = "us";
     private static final String ATTR_FLAGS = "f";
+    private static final String ATTR_ATTRIBUTION_FLAGS = "af";
+    private static final String ATTR_CHAIN_ID = "ci";
 
-    private static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED;
+    private static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED
+            | OP_FLAG_TRUSTED_PROXY;
 
     // Lock for read/write access to on disk state
     private final Object mOnDiskLock = new Object();
@@ -227,13 +231,14 @@
 
     void recordDiscreteAccess(int uid, String packageName, int op, @Nullable String attributionTag,
             @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime,
-            long accessDuration) {
+            long accessDuration, @AppOpsManager.AttributionFlags int attributionFlags,
+            int attributionChainId) {
         if (!isDiscreteOp(op, flags)) {
             return;
         }
         synchronized (mInMemoryLock) {
             mDiscreteOps.addDiscreteAccess(op, uid, packageName, attributionTag, flags, uidState,
-                    accessTime, accessDuration);
+                    accessTime, accessDuration, attributionFlags, attributionChainId);
         }
     }
 
@@ -383,9 +388,10 @@
 
         void addDiscreteAccess(int op, int uid, @NonNull String packageName,
                 @Nullable String attributionTag, @AppOpsManager.OpFlags int flags,
-                @AppOpsManager.UidState int uidState, long accessTime, long accessDuration) {
+                @AppOpsManager.UidState int uidState, long accessTime, long accessDuration,
+                @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
             getOrCreateDiscreteUidOps(uid).addDiscreteAccess(op, packageName, attributionTag, flags,
-                    uidState, accessTime, accessDuration);
+                    uidState, accessTime, accessDuration, attributionFlags, attributionChainId);
         }
 
         private void filter(long beginTimeMillis, long endTimeMillis,
@@ -613,9 +619,10 @@
 
         void addDiscreteAccess(int op, @NonNull String packageName, @Nullable String attributionTag,
                 @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
-                long accessTime, long accessDuration) {
+                long accessTime, long accessDuration,
+                @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
             getOrCreateDiscretePackageOps(packageName).addDiscreteAccess(op, attributionTag, flags,
-                    uidState, accessTime, accessDuration);
+                    uidState, accessTime, accessDuration, attributionFlags, attributionChainId);
         }
 
         private DiscretePackageOps getOrCreateDiscretePackageOps(String packageName) {
@@ -680,9 +687,10 @@
 
         void addDiscreteAccess(int op, @Nullable String attributionTag,
                 @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
-                long accessTime, long accessDuration) {
+                long accessTime, long accessDuration,
+                @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
             getOrCreateDiscreteOp(op).addDiscreteAccess(attributionTag, flags, uidState, accessTime,
-                    accessDuration);
+                    accessDuration, attributionFlags, attributionChainId);
         }
 
         void merge(DiscretePackageOps other) {
@@ -823,37 +831,39 @@
                 for (int j = 0; j < n; j++) {
                     DiscreteOpEvent event = list.get(j);
                     list.set(j, new DiscreteOpEvent(event.mNoteTime - offset, event.mNoteDuration,
-                            event.mUidState, event.mOpFlag));
+                            event.mUidState, event.mOpFlag, event.mAttributionFlags,
+                            event.mAttributionChainId));
                 }
             }
         }
 
         void addDiscreteAccess(@Nullable String attributionTag,
                 @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
-                long accessTime, long accessDuration) {
+                long accessTime, long accessDuration,
+                @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
             List<DiscreteOpEvent> attributedOps = getOrCreateDiscreteOpEventsList(
                     attributionTag);
-            accessTime = accessTime / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
-            accessDuration = accessDuration == -1 ? -1
-                    : (accessDuration + sDiscreteHistoryQuantization - 1)
-                            / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
 
             int nAttributedOps = attributedOps.size();
             int i = nAttributedOps;
             for (; i > 0; i--) {
                 DiscreteOpEvent previousOp = attributedOps.get(i - 1);
-                if (previousOp.mNoteTime < accessTime) {
+                if (discretizeTimeStamp(previousOp.mNoteTime) < discretizeTimeStamp(accessTime)) {
                     break;
                 }
-                if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState) {
-                    if (accessDuration != previousOp.mNoteDuration) {
+                if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState
+                        && previousOp.mAttributionFlags == attributionFlags
+                        && previousOp.mAttributionChainId == attributionChainId) {
+                    if (discretizeDuration(accessDuration) != discretizeDuration(
+                            previousOp.mNoteDuration)) {
                         break;
                     } else {
                         return;
                     }
                 }
             }
-            attributedOps.add(i, new DiscreteOpEvent(accessTime, accessDuration, uidState, flags));
+            attributedOps.add(i, new DiscreteOpEvent(accessTime, accessDuration, uidState, flags,
+                    attributionFlags, attributionChainId));
         }
 
         private List<DiscreteOpEvent> getOrCreateDiscreteOpEventsList(String attributionTag) {
@@ -875,7 +885,8 @@
                 for (int j = 0; j < nEvents; j++) {
                     DiscreteOpEvent event = events.get(j);
                     result.addDiscreteAccess(op, uid, packageName, tag, event.mUidState,
-                            event.mOpFlag, event.mNoteTime, event.mNoteDuration);
+                            event.mOpFlag, discretizeTimeStamp(event.mNoteTime),
+                            discretizeDuration(event.mNoteDuration));
                 }
             }
         }
@@ -932,11 +943,15 @@
                                     -1);
                             int uidState = parser.getAttributeInt(null, ATTR_UID_STATE);
                             int opFlags = parser.getAttributeInt(null, ATTR_FLAGS);
+                            int attributionFlags = parser.getAttributeInt(null,
+                                    ATTR_ATTRIBUTION_FLAGS, AppOpsManager.ATTRIBUTION_FLAGS_NONE);
+                            int attributionChainId = parser.getAttributeInt(null, ATTR_CHAIN_ID,
+                                    AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
                             if (noteTime + noteDuration < beginTimeMillis) {
                                 continue;
                             }
                             DiscreteOpEvent event = new DiscreteOpEvent(noteTime, noteDuration,
-                                    uidState, opFlags);
+                                    uidState, opFlags, attributionFlags, attributionChainId);
                             events.add(event);
                         }
                     }
@@ -952,13 +967,18 @@
         final long mNoteDuration;
         final @AppOpsManager.UidState int mUidState;
         final @AppOpsManager.OpFlags int mOpFlag;
+        final @AppOpsManager.AttributionFlags int mAttributionFlags;
+        final int mAttributionChainId;
 
         DiscreteOpEvent(long noteTime, long noteDuration, @AppOpsManager.UidState int uidState,
-                @AppOpsManager.OpFlags int opFlag) {
+                @AppOpsManager.OpFlags int opFlag,
+                @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
             mNoteTime = noteTime;
             mNoteDuration = noteDuration;
             mUidState = uidState;
             mOpFlag = opFlag;
+            mAttributionFlags = attributionFlags;
+            mAttributionChainId = attributionChainId;
         }
 
         private void dump(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf,
@@ -969,13 +989,19 @@
             pw.print("-");
             pw.print(flagsToString(mOpFlag));
             pw.print("] at ");
-            date.setTime(mNoteTime);
+            date.setTime(discretizeTimeStamp(mNoteTime));
             pw.print(sdf.format(date));
             if (mNoteDuration != -1) {
                 pw.print(" for ");
-                pw.print(mNoteDuration);
+                pw.print(discretizeDuration(mNoteDuration));
                 pw.print(" milliseconds ");
             }
+            if (mAttributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE) {
+                pw.print(" attribution flags=");
+                pw.print(mAttributionFlags);
+                pw.print(" with chainId=");
+                pw.print(mAttributionChainId);
+            }
             pw.println();
         }
 
@@ -984,6 +1010,12 @@
             if (mNoteDuration != -1) {
                 out.attributeLong(null, ATTR_NOTE_DURATION, mNoteDuration);
             }
+            if (mAttributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE) {
+                out.attributeInt(null, ATTR_ATTRIBUTION_FLAGS, mAttributionFlags);
+            }
+            if (mAttributionChainId != AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE) {
+                out.attributeInt(null, ATTR_CHAIN_ID, mAttributionChainId);
+            }
             out.attributeInt(null, ATTR_UID_STATE, mUidState);
             out.attributeInt(null, ATTR_FLAGS, mOpFlag);
         }
@@ -1055,6 +1087,16 @@
         return true;
     }
 
+    private static long discretizeTimeStamp(long timeStamp) {
+        return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
+
+    }
+
+    private static long discretizeDuration(long duration) {
+        return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1)
+                        / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
+    }
+
     void setDebugMode(boolean debugMode) {
         this.mDebugMode = debugMode;
     }
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 8b72be7..dd5df50 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -487,7 +487,8 @@
 
     void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
             @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags,
-            long accessTime) {
+            long accessTime, @AppOpsManager.AttributionFlags int attributionFlags,
+            int attributionChainId) {
         synchronized (mInMemoryLock) {
             if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
                 if (!isPersistenceInitializedMLocked()) {
@@ -499,7 +500,7 @@
                         attributionTag, uidState, flags, 1);
 
                 mDiscreteRegistry.recordDiscreteAccess(uid, packageName, op, attributionTag,
-                        flags, uidState, accessTime, -1);
+                        flags, uidState, accessTime, -1, attributionFlags, attributionChainId);
             }
         }
     }
@@ -521,7 +522,8 @@
 
     void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
             @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags,
-            long eventStartTime, long increment) {
+            long eventStartTime, long increment,
+            @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
         synchronized (mInMemoryLock) {
             if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
                 if (!isPersistenceInitializedMLocked()) {
@@ -532,7 +534,8 @@
                         System.currentTimeMillis()).increaseAccessDuration(op, uid, packageName,
                         attributionTag, uidState, flags, increment);
                 mDiscreteRegistry.recordDiscreteAccess(uid, packageName, op, attributionTag,
-                        flags, uidState, eventStartTime, increment);
+                        flags, uidState, eventStartTime, increment, attributionFlags,
+                        attributionChainId);
             }
         }
     }
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index b9e97e8..aeb1893 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -65,7 +65,7 @@
 import android.location.Location;
 import android.location.LocationManager;
 import android.location.LocationManagerInternal;
-import android.location.LocationManagerInternal.OnProviderLocationTagsChangeListener;
+import android.location.LocationManagerInternal.LocationPackageTagsListener;
 import android.location.LocationProvider;
 import android.location.LocationRequest;
 import android.location.LocationTime;
@@ -93,6 +93,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.location.eventlog.LocationEventLog;
@@ -259,7 +260,7 @@
             new CopyOnWriteArrayList<>();
 
     @GuardedBy("mLock")
-    @Nullable OnProviderLocationTagsChangeListener mOnProviderLocationTagsChangeListener;
+    @Nullable LocationPackageTagsListener mLocationTagsChangedListener;
 
     LocationManagerService(Context context, Injector injector) {
         mContext = context.createAttributionContext(ATTRIBUTION_TAG);
@@ -1363,32 +1364,28 @@
             refreshAppOpsRestrictions(UserHandle.USER_ALL);
         }
 
-        OnProviderLocationTagsChangeListener listener;
-        synchronized (mLock) {
-            listener = mOnProviderLocationTagsChangeListener;
-        }
-
-        if (listener != null) {
-            if (!oldState.extraAttributionTags.equals(newState.extraAttributionTags)
-                    || !Objects.equals(oldState.identity, newState.identity)) {
-                if (oldState.identity != null) {
-                    listener.onLocationTagsChanged(
-                            new LocationManagerInternal.LocationTagInfo(
-                                    oldState.identity.getUid(),
-                                    oldState.identity.getPackageName(),
-                                    Collections.emptySet()));
-                }
-                if (newState.identity != null) {
-                    ArraySet<String> attributionTags = new ArraySet<>(
-                            newState.extraAttributionTags.size() + 1);
-                    attributionTags.addAll(newState.extraAttributionTags);
-                    attributionTags.add(newState.identity.getAttributionTag());
-
-                    listener.onLocationTagsChanged(
-                            new LocationManagerInternal.LocationTagInfo(
-                                    newState.identity.getUid(),
-                                    newState.identity.getPackageName(),
-                                    attributionTags));
+        if (!oldState.extraAttributionTags.equals(newState.extraAttributionTags)
+                || !Objects.equals(oldState.identity, newState.identity)) {
+            // since we're potentially affecting the tag lists for two different uids, acquire the
+            // lock to ensure providers cannot change while we're looping over the providers
+            // multiple times, which could lead to inconsistent results.
+            synchronized (mLock) {
+                LocationPackageTagsListener listener = mLocationTagsChangedListener;
+                if (listener != null) {
+                    int oldUid = oldState.identity != null ? oldState.identity.getUid() : -1;
+                    int newUid = newState.identity != null ? newState.identity.getUid() : -1;
+                    if (oldUid != -1) {
+                        PackageTagsList tags = calculateAppOpsLocationSourceTags(oldUid);
+                        FgThread.getHandler().post(
+                                () -> listener.onLocationPackageTagsChanged(oldUid, tags));
+                    }
+                    // if the new app id is the same as the old app id, no need to invoke the
+                    // listener twice, it's already been taken care of
+                    if (newUid != -1 && newUid != oldUid) {
+                        PackageTagsList tags = calculateAppOpsLocationSourceTags(newUid);
+                        FgThread.getHandler().post(
+                                () -> listener.onLocationPackageTagsChanged(newUid, tags));
+                    }
                 }
             }
         }
@@ -1436,6 +1433,31 @@
                 userId);
     }
 
+    PackageTagsList calculateAppOpsLocationSourceTags(int uid) {
+        PackageTagsList.Builder builder = new PackageTagsList.Builder();
+        for (LocationProviderManager manager : mProviderManagers) {
+            AbstractLocationProvider.State managerState = manager.getState();
+            if (managerState.identity == null) {
+                continue;
+            }
+            if (managerState.identity.getUid() != uid) {
+                continue;
+            }
+
+            builder.add(managerState.identity.getPackageName(), managerState.extraAttributionTags);
+            if (managerState.extraAttributionTags.isEmpty()
+                    || managerState.identity.getAttributionTag() != null) {
+                builder.add(managerState.identity.getPackageName(),
+                        managerState.identity.getAttributionTag());
+            } else {
+                Log.e(TAG, manager.getName() + " provider has specified a null attribution tag and "
+                        + "a non-empty set of extra attribution tags - dropping the null "
+                        + "attribution tag");
+            }
+        }
+        return builder.build();
+    }
+
     private class LocalService extends LocationManagerInternal {
 
         LocalService() {}
@@ -1506,10 +1528,29 @@
         }
 
         @Override
-        public void setOnProviderLocationTagsChangeListener(
-                @Nullable OnProviderLocationTagsChangeListener listener) {
+        public void setLocationPackageTagsListener(
+                @Nullable LocationPackageTagsListener listener) {
             synchronized (mLock) {
-                mOnProviderLocationTagsChangeListener = listener;
+                mLocationTagsChangedListener = listener;
+
+                // calculate initial tag list and send to listener
+                if (listener != null) {
+                    ArraySet<Integer> uids = new ArraySet<>(mProviderManagers.size());
+                    for (LocationProviderManager manager : mProviderManagers) {
+                        CallerIdentity identity = manager.getIdentity();
+                        if (identity != null) {
+                            uids.add(identity.getUid());
+                        }
+                    }
+
+                    for (int uid : uids) {
+                        PackageTagsList tags = calculateAppOpsLocationSourceTags(uid);
+                        if (!tags.isEmpty()) {
+                            FgThread.getHandler().post(
+                                    () -> listener.onLocationPackageTagsChanged(uid, tags));
+                        }
+                    }
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
index 1da45bd..eb7b77a 100644
--- a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
@@ -263,10 +263,10 @@
     }
 
     /**
-     * The current allowed state of this provider.
+     * The current state of the provider.
      */
-    public final boolean isAllowed() {
-        return mInternalState.get().state.allowed;
+    public final State getState() {
+        return mInternalState.get().state;
     }
 
     /**
@@ -277,13 +277,6 @@
     }
 
     /**
-     * The current provider properties of this provider.
-     */
-    public final @Nullable ProviderProperties getProperties() {
-        return mInternalState.get().state.properties;
-    }
-
-    /**
      * Call this method to report a change in provider properties.
      */
     protected void setProperties(@Nullable ProviderProperties properties) {
@@ -291,13 +284,6 @@
     }
 
     /**
-     * The current identity of this provider.
-     */
-    public final @Nullable CallerIdentity getIdentity() {
-        return mInternalState.get().state.identity;
-    }
-
-    /**
      * Call this method to report a change in the provider's identity.
      */
     protected void setIdentity(@Nullable CallerIdentity identity) {
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 4f8b87b..8d335b8 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -1407,12 +1407,16 @@
         return mName;
     }
 
+    public AbstractLocationProvider.State getState() {
+        return mProvider.getState();
+    }
+
     public @Nullable CallerIdentity getIdentity() {
-        return mProvider.getIdentity();
+        return mProvider.getState().identity;
     }
 
     public @Nullable ProviderProperties getProperties() {
-        return mProvider.getProperties();
+        return mProvider.getState().properties;
     }
 
     public boolean hasProvider() {
@@ -2403,7 +2407,7 @@
         Preconditions.checkArgument(userId >= 0);
 
         boolean enabled = mState == STATE_STARTED
-                && mProvider.isAllowed()
+                && mProvider.getState().allowed
                 && mSettingsHelper.isLocationEnabled(userId);
 
         int index = mEnabled.indexOfKey(userId);
diff --git a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java
index 021e8db..22295fe 100644
--- a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java
@@ -21,9 +21,7 @@
 import android.annotation.Nullable;
 import android.location.Location;
 import android.location.LocationResult;
-import android.location.provider.ProviderProperties;
 import android.location.provider.ProviderRequest;
-import android.location.util.identity.CallerIdentity;
 import android.os.Bundle;
 
 import com.android.internal.annotations.GuardedBy;
@@ -32,7 +30,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Collections;
-import java.util.Set;
 
 /**
  * Represents a location provider that may switch between a mock implementation and a real
@@ -290,21 +287,21 @@
         Preconditions.checkState(!Thread.holdsLock(mOwnerLock));
 
         AbstractLocationProvider provider;
+        State providerState;
         synchronized (mOwnerLock) {
             provider = mProvider;
-            pw.println("allowed=" + isAllowed());
-            CallerIdentity identity = getIdentity();
-            if (identity != null) {
-                pw.println("identity=" + identity);
-            }
-            Set<String> extraAttributionTags = getExtraAttributionTags();
-            if (!extraAttributionTags.isEmpty()) {
-                pw.println("extra attribution tags=" + extraAttributionTags);
-            }
-            ProviderProperties properties = getProperties();
-            if (properties != null) {
-                pw.println("properties=" + properties);
-            }
+            providerState = getState();
+        }
+
+        pw.println("allowed=" + providerState.allowed);
+        if (providerState.identity != null) {
+            pw.println("identity=" + providerState.identity);
+        }
+        if (!providerState.extraAttributionTags.isEmpty()) {
+            pw.println("extra attribution tags=" + providerState.extraAttributionTags);
+        }
+        if (providerState.properties != null) {
+            pw.println("properties=" + providerState.properties);
         }
 
         if (provider != null) {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 9370b14..5b2c809 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1000,16 +1000,15 @@
             intents[0].setSourceBounds(sourceBounds);
 
             // Replace theme for splash screen
-            final int splashScreenThemeResId =
-                    mShortcutServiceInternal.getShortcutStartingThemeResId(getCallingUserId(),
+            final String splashScreenThemeResName =
+                    mShortcutServiceInternal.getShortcutStartingThemeResName(getCallingUserId(),
                             callingPackage, packageName, shortcutId, targetUserId);
-            if (splashScreenThemeResId != 0) {
+            if (splashScreenThemeResName != null && !splashScreenThemeResName.isEmpty()) {
                 if (startActivityOptions == null) {
                     startActivityOptions = new Bundle();
                 }
-                startActivityOptions.putInt(KEY_SPLASH_SCREEN_THEME, splashScreenThemeResId);
+                startActivityOptions.putString(KEY_SPLASH_SCREEN_THEME, splashScreenThemeResName);
             }
-
             return startShortcutIntentsAsPublisher(
                     intents, packageName, featureId, startActivityOptions, targetUserId);
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 0b63b7d..60a7571 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -626,7 +626,14 @@
         }
 
         boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0;
-        if (params.isStaged || isApex) {
+        if (isApex) {
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGE_UPDATES)
+                    == PackageManager.PERMISSION_DENIED
+                    && mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
+                    == PackageManager.PERMISSION_DENIED) {
+                throw new SecurityException("Not allowed to perform APEX updates");
+            }
+        } else if (params.isStaged) {
             mContext.enforceCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES, TAG);
         }
 
@@ -1110,6 +1117,17 @@
         mSilentUpdatePolicy.setAllowUnlimitedSilentUpdates(installerPackageName);
     }
 
+    /**
+     * Set the silent updates throttle time in seconds.
+     */
+    @Override
+    public void setSilentUpdatesThrottleTime(long throttleTimeInSeconds) {
+        if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
+            throw new SecurityException("Caller not allowed to set silent updates throttle time");
+        }
+        mSilentUpdatePolicy.setSilentUpdatesThrottleTime(throttleTimeInSeconds);
+    }
+
     private static int getSessionCount(SparseArray<PackageInstallerSession> sessions,
             int installerUid) {
         int count = 0;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index f9c63a9..49559f29 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -307,8 +307,8 @@
                     return runLogVisibility();
                 case "bypass-staged-installer-check":
                     return runBypassStagedInstallerCheck();
-                case "allow-unlimited-silent-updates":
-                    return runAllowUnlimitedSilentUpdates();
+                case "set-silent-updates-policy":
+                    return runSetSilentUpdatesPolicy();
                 default: {
                     Boolean domainVerificationResult =
                             mDomainVerificationShell.runCommand(this, cmd);
@@ -3041,12 +3041,20 @@
         }
     }
 
-    private int runAllowUnlimitedSilentUpdates() {
+    private int runSetSilentUpdatesPolicy() {
         final PrintWriter pw = getOutPrintWriter();
         String opt;
+        String installerPackageName = null;
+        Long throttleTimeInSeconds = null;
         boolean reset = false;
         while ((opt = getNextOption()) != null) {
             switch (opt) {
+                case "--allow-unlimited-silent-updates":
+                    installerPackageName = getNextArgRequired();
+                    break;
+                case "--throttle-time":
+                    throttleTimeInSeconds = Long.parseLong(getNextArgRequired());
+                    break;
                 case "--reset":
                     reset = true;
                     break;
@@ -3055,10 +3063,24 @@
                     return -1;
             }
         }
+        if (throttleTimeInSeconds != null && throttleTimeInSeconds < 0) {
+            pw.println("Error: Invalid value for \"--throttle-time\":" + throttleTimeInSeconds);
+            return -1;
+        }
 
-        final String installerPackageName = reset ? null : getNextArgRequired();
         try {
-            mInterface.getPackageInstaller().setAllowUnlimitedSilentUpdates(installerPackageName);
+            final IPackageInstaller installer = mInterface.getPackageInstaller();
+            if (reset) {
+                installer.setAllowUnlimitedSilentUpdates(null /* installerPackageName */);
+                installer.setSilentUpdatesThrottleTime(-1 /* restore to the default */);
+            } else {
+                if (installerPackageName != null) {
+                    installer.setAllowUnlimitedSilentUpdates(installerPackageName);
+                }
+                if (throttleTimeInSeconds != null) {
+                    installer.setSilentUpdatesThrottleTime(throttleTimeInSeconds);
+                }
+            }
         } catch (RemoteException e) {
             pw.println("Failure ["
                     + e.getClass().getName() + " - "
@@ -3889,11 +3911,14 @@
         pw.println("      --enable: turn on debug logging (default)");
         pw.println("      --disable: turn off debug logging");
         pw.println("");
-        pw.println("  allow-unlimited-silent-updates (--reset | <INSTALLER>)");
-        pw.println("    Allows unlimited silent updated installation requests from the installer");
-        pw.println("    without the throttle time.");
-        pw.println("      --reset: clear the allowed installer and tracks of silent updates in");
-        pw.println("        the system.");
+        pw.println("  set-silent-updates-policy [--allow-unlimited-silent-updates <INSTALLER>]");
+        pw.println("                            [--throttle-time <SECONDS>] [--reset]");
+        pw.println("    Sets the policies of the silent updates.");
+        pw.println("      --allow-unlimited-silent-updates: allows unlimited silent updated");
+        pw.println("        installation requests from the installer without the throttle time.");
+        pw.println("      --throttle-time: update the silent updates throttle time in seconds.");
+        pw.println("      --reset: restore the installer and throttle time to the default, and");
+        pw.println("        clear tracks of silent updates in the system.");
         pw.println("");
         mDomainVerificationShell.printHelp(pw);
         pw.println("");
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 1bd9e5e..b4bd086 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -139,7 +139,7 @@
     private static final String ATTR_BITMAP_PATH = "bitmap-path";
     private static final String ATTR_ICON_URI = "icon-uri";
     private static final String ATTR_LOCUS_ID = "locus-id";
-    private static final String ATTR_SPLASH_SCREEN_THEME_ID = "splash-screen-theme-id";
+    private static final String ATTR_SPLASH_SCREEN_THEME_NAME = "splash-screen-theme-name";
 
     private static final String ATTR_PERSON_NAME = "name";
     private static final String ATTR_PERSON_URI = "uri";
@@ -1799,7 +1799,7 @@
         ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
         ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId());
         ShortcutService.writeAttr(out, ATTR_TITLE_RES_NAME, si.getTitleResName());
-        ShortcutService.writeAttr(out, ATTR_SPLASH_SCREEN_THEME_ID, si.getStartingThemeResId());
+        ShortcutService.writeAttr(out, ATTR_SPLASH_SCREEN_THEME_NAME, si.getStartingThemeResName());
         ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
         ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId());
         ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName());
@@ -2010,7 +2010,7 @@
         String bitmapPath;
         String iconUri;
         final String locusIdString;
-        int splashScreenThemeResId;
+        String splashScreenThemeResName;
         int backupVersionCode;
         ArraySet<String> categories = null;
         ArrayList<Person> persons = new ArrayList<>();
@@ -2021,8 +2021,8 @@
         title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE);
         titleResId = ShortcutService.parseIntAttribute(parser, ATTR_TITLE_RES_ID);
         titleResName = ShortcutService.parseStringAttribute(parser, ATTR_TITLE_RES_NAME);
-        splashScreenThemeResId = ShortcutService.parseIntAttribute(parser,
-                ATTR_SPLASH_SCREEN_THEME_ID);
+        splashScreenThemeResName = ShortcutService.parseStringAttribute(parser,
+                ATTR_SPLASH_SCREEN_THEME_NAME);
         text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT);
         textResId = ShortcutService.parseIntAttribute(parser, ATTR_TEXT_RES_ID);
         textResName = ShortcutService.parseStringAttribute(parser, ATTR_TEXT_RES_NAME);
@@ -2117,7 +2117,7 @@
                 rank, extras, lastChangedTimestamp, flags,
                 iconResId, iconResName, bitmapPath, iconUri,
                 disabledReason, persons.toArray(new Person[persons.size()]), locusId,
-                splashScreenThemeResId);
+                splashScreenThemeResName);
     }
 
     private static Intent parseIntent(TypedXmlPullParser parser)
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
index c06f01a..b86c50b 100644
--- a/services/core/java/com/android/server/pm/ShortcutParser.java
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -383,8 +383,11 @@
             final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutLongLabel, 0);
             final int disabledMessageResId = sa.getResourceId(
                     R.styleable.Shortcut_shortcutDisabledMessage, 0);
-            final int splashScreenTheme = sa.getResourceId(
+            final int splashScreenThemeResId = sa.getResourceId(
                     R.styleable.Shortcut_splashScreenTheme, 0);
+            final String splashScreenThemeResName = splashScreenThemeResId != 0
+                    ? service.mContext.getResources().getResourceName(splashScreenThemeResId)
+                    : null;
 
             if (TextUtils.isEmpty(id)) {
                 Log.w(TAG, "android:shortcutId must be provided. activity=" + activity);
@@ -407,7 +410,7 @@
                     rank,
                     iconResId,
                     enabled,
-                    splashScreenTheme);
+                    splashScreenThemeResName);
         } finally {
             sa.recycle();
         }
@@ -416,7 +419,7 @@
     private static ShortcutInfo createShortcutFromManifest(ShortcutService service,
             @UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
             int titleResId, int textResId, int disabledMessageResId,
-            int rank, int iconResId, boolean enabled, int splashScreenTheme) {
+            int rank, int iconResId, boolean enabled, @Nullable String splashScreenThemeResName) {
 
         final int flags =
                 (enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED)
@@ -456,7 +459,7 @@
                 disabledReason,
                 null /* persons */,
                 null /* locusId */,
-                splashScreenTheme);
+                splashScreenThemeResName);
     }
 
     private static String parseCategory(ShortcutService service, AttributeSet attrs) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 5f10277..fcbf40e 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -3536,7 +3536,8 @@
         }
 
         @Override
-        public int getShortcutStartingThemeResId(int launcherUserId,
+        @Nullable
+        public String getShortcutStartingThemeResName(int launcherUserId,
                 @NonNull String callingPackage, @NonNull String packageName,
                 @NonNull String shortcutId, int userId) {
             Objects.requireNonNull(callingPackage, "callingPackage");
@@ -3553,11 +3554,11 @@
                 final ShortcutPackage p = getUserShortcutsLocked(userId)
                         .getPackageShortcutsIfExists(packageName);
                 if (p == null) {
-                    return 0;
+                    return null;
                 }
 
                 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
-                return shortcutInfo != null ? shortcutInfo.getStartingThemeResId() : 0;
+                return shortcutInfo != null ? shortcutInfo.getStartingThemeResName() : null;
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/SilentUpdatePolicy.java b/services/core/java/com/android/server/pm/SilentUpdatePolicy.java
index 117acab..700f72cf 100644
--- a/services/core/java/com/android/server/pm/SilentUpdatePolicy.java
+++ b/services/core/java/com/android/server/pm/SilentUpdatePolicy.java
@@ -33,7 +33,8 @@
  * in the {@link PackageInstallerSession}.
  */
 public class SilentUpdatePolicy {
-    // A throttle time to prevent the installer from silently updating the same app repeatedly.
+    // The default throttle time to prevent the installer from silently updating the same app
+    // repeatedly.
     private static final long SILENT_UPDATE_THROTTLE_TIME_MS = TimeUnit.SECONDS.toMillis(30);
 
     // Map to the uptime timestamp for each installer and app of the silent update.
@@ -44,6 +45,9 @@
     @GuardedBy("mSilentUpdateInfos")
     private String mAllowUnlimitedSilentUpdatesInstaller;
 
+    @GuardedBy("mSilentUpdateInfos")
+    private long mSilentUpdateThrottleTimeMs = SILENT_UPDATE_THROTTLE_TIME_MS;
+
     /**
      * Checks if the silent update is allowed by the given installer and app package name.
      *
@@ -58,7 +62,11 @@
             return true;
         }
         final long lastSilentUpdatedMs = getTimestampMs(installerPackageName, packageName);
-        return SystemClock.uptimeMillis() - lastSilentUpdatedMs > SILENT_UPDATE_THROTTLE_TIME_MS;
+        final long throttleTimeMs;
+        synchronized (mSilentUpdateInfos) {
+            throttleTimeMs = mSilentUpdateThrottleTimeMs;
+        }
+        return SystemClock.uptimeMillis() - lastSilentUpdatedMs > throttleTimeMs;
     }
 
     /**
@@ -99,11 +107,25 @@
         }
     }
 
+    /**
+     * Set the silent updates throttle time in seconds.
+     *
+     * @param throttleTimeInSeconds The throttle time to set, or <code>-1</code> to restore the
+     *        value to the default.
+     */
+    void setSilentUpdatesThrottleTime(long throttleTimeInSeconds) {
+        synchronized (mSilentUpdateInfos) {
+            mSilentUpdateThrottleTimeMs = throttleTimeInSeconds >= 0
+                    ? TimeUnit.SECONDS.toMillis(throttleTimeInSeconds)
+                    : SILENT_UPDATE_THROTTLE_TIME_MS;
+        }
+    }
+
     private void pruneLocked(long uptime) {
         final int size = mSilentUpdateInfos.size();
         for (int i = size - 1; i >= 0; i--) {
             final long lastSilentUpdatedMs = mSilentUpdateInfos.valueAt(i);
-            if (uptime - lastSilentUpdatedMs > SILENT_UPDATE_THROTTLE_TIME_MS) {
+            if (uptime - lastSilentUpdatedMs > mSilentUpdateThrottleTimeMs) {
                 mSilentUpdateInfos.removeAt(i);
             }
         }
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 94005b2..22c370e 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -33,14 +33,15 @@
 import android.location.LocationManagerInternal;
 import android.net.Uri;
 import android.os.IBinder;
+import android.os.PackageTagsList;
 import android.os.Process;
 import android.os.UserHandle;
 import android.service.voice.VoiceInteractionManagerInternal;
 import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.function.DecFunction;
@@ -52,9 +53,9 @@
 import com.android.internal.util.function.UndecFunction;
 import com.android.server.LocalServices;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -67,11 +68,10 @@
             "android:activity_recognition_allow_listed_tags";
     private static final String ACTIVITY_RECOGNITION_TAGS_SEPARATOR = ";";
 
-    private static ArraySet<String> sExpectedTags = new ArraySet<>(new String[] {
+    private static final ArraySet<String> sExpectedTags = new ArraySet<>(new String[] {
             "awareness_provider", "activity_recognition_provider", "network_location_provider",
             "network_location_calibration", "fused_location_provider", "geofencer_provider"});
 
-
     @NonNull
     private final Object mLock = new Object();
 
@@ -96,13 +96,22 @@
      */
     @GuardedBy("mLock - writes only - see above")
     @NonNull
-    private final ConcurrentHashMap<Integer, ArrayMap<String, ArraySet<String>>> mLocationTags =
+    private final ConcurrentHashMap<Integer, PackageTagsList> mLocationTags =
             new ConcurrentHashMap<>();
 
+    // location tags can vary per uid - but we merge all tags under an app id into the final data
+    // structure above
+    @GuardedBy("mLock")
+    private final SparseArray<PackageTagsList> mPerUidLocationTags = new SparseArray<>();
+
+    // activity recognition currently only grabs tags from the APK manifest. we know that the
+    // manifest is the same for all users, so there's no need to track variations in tags across
+    // different users. if that logic ever changes, this might need to behave more like location
+    // tags above.
     @GuardedBy("mLock - writes only - see above")
     @NonNull
-    private final ConcurrentHashMap<Integer, ArrayMap<String, ArraySet<String>>>
-            mActivityRecognitionTags = new ConcurrentHashMap<>();
+    private final ConcurrentHashMap<Integer, PackageTagsList> mActivityRecognitionTags =
+            new ConcurrentHashMap<>();
 
     public AppOpsPolicy(@NonNull Context context) {
         mContext = context;
@@ -112,13 +121,28 @@
 
         final LocationManagerInternal locationManagerInternal = LocalServices.getService(
                 LocationManagerInternal.class);
-        locationManagerInternal.setOnProviderLocationTagsChangeListener((providerTagInfo) -> {
-            synchronized (mLock) {
-                updateAllowListedTagsForPackageLocked(providerTagInfo.getUid(),
-                        providerTagInfo.getPackageName(), providerTagInfo.getTags(),
-                        mLocationTags);
-            }
-        });
+        locationManagerInternal.setLocationPackageTagsListener(
+                (uid, packageTagsList) -> {
+                    synchronized (mLock) {
+                        if (packageTagsList.isEmpty()) {
+                            mPerUidLocationTags.remove(uid);
+                        } else {
+                            mPerUidLocationTags.set(uid, packageTagsList);
+                        }
+
+                        int appId = UserHandle.getAppId(uid);
+                        PackageTagsList.Builder appIdTags = new PackageTagsList.Builder(1);
+                        int size = mPerUidLocationTags.size();
+                        for (int i = 0; i < size; i++) {
+                            if (UserHandle.getAppId(mPerUidLocationTags.keyAt(i)) == appId) {
+                                appIdTags.add(mPerUidLocationTags.valueAt(i));
+                            }
+                        }
+
+                        updateAllowListedTagsForPackageLocked(appId, appIdTags.build(),
+                                mLocationTags);
+                    }
+                });
 
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
@@ -306,95 +330,30 @@
         }
         final String tagsList = resolvedService.serviceInfo.metaData.getString(
                 ACTIVITY_RECOGNITION_TAGS);
-        if (tagsList != null) {
-            final String[] tags = tagsList.split(ACTIVITY_RECOGNITION_TAGS_SEPARATOR);
+        if (!TextUtils.isEmpty(tagsList)) {
+            PackageTagsList packageTagsList = new PackageTagsList.Builder(1).add(
+                    resolvedService.serviceInfo.packageName,
+                    Arrays.asList(tagsList.split(ACTIVITY_RECOGNITION_TAGS_SEPARATOR))).build();
             synchronized (mLock) {
                 updateAllowListedTagsForPackageLocked(
-                        resolvedService.serviceInfo.applicationInfo.uid,
-                        resolvedService.serviceInfo.packageName, new ArraySet<>(tags),
+                        UserHandle.getAppId(resolvedService.serviceInfo.applicationInfo.uid),
+                        packageTagsList,
                         mActivityRecognitionTags);
             }
         }
     }
 
-    private static void updateAllowListedTagsForPackageLocked(int uid, String packageName,
-            Set<String> allowListedTags, ConcurrentHashMap<Integer, ArrayMap<String,
-            ArraySet<String>>> datastore) {
-        final int appId = UserHandle.getAppId(uid);
-        // We make a copy of the per UID state to limit our mutation to one
-        // operation in the underlying concurrent data structure.
-        ArrayMap<String, ArraySet<String>> appIdTags = datastore.get(appId);
-        if (appIdTags != null) {
-            appIdTags = new ArrayMap<>(appIdTags);
-        }
-
-        ArraySet<String> packageTags = (appIdTags != null) ? appIdTags.get(packageName) : null;
-        if (packageTags != null) {
-            packageTags = new ArraySet<>(packageTags);
-        }
-
-        if (allowListedTags != null && !allowListedTags.isEmpty()) {
-            if (packageTags != null) {
-                packageTags.clear();
-                packageTags.addAll(allowListedTags);
-            } else {
-                packageTags = new ArraySet<>(allowListedTags);
-            }
-            if (appIdTags == null) {
-                appIdTags = new ArrayMap<>();
-            }
-
-            // Remove any invalid tags
-            boolean nullRemoved = packageTags.remove(null);
-            boolean nullStrRemoved = packageTags.remove("null");
-            boolean emptyRemoved = packageTags.remove("");
-            if (nullRemoved || nullStrRemoved || emptyRemoved) {
-                Log.e(LOG_TAG, "Attempted to add invalid source attribution tag, removed "
-                        + "null: " + nullRemoved + " removed \"null\": " + nullStrRemoved
-                        + " removed empty string: " + emptyRemoved);
-            }
-
-            appIdTags.put(packageName, packageTags);
-            datastore.put(appId, appIdTags);
-        } else if (appIdTags != null) {
-            appIdTags.remove(packageName);
-            if (!appIdTags.isEmpty()) {
-                datastore.put(appId, appIdTags);
-            } else {
-                datastore.remove(appId);
-            }
-        }
+    private static void updateAllowListedTagsForPackageLocked(int appId,
+            PackageTagsList packageTagsList,
+            ConcurrentHashMap<Integer, PackageTagsList> datastore) {
+        datastore.put(appId, packageTagsList);
     }
 
     private static boolean isDatasourceAttributionTag(int uid, @NonNull String packageName,
-            @NonNull String attributionTag, @NonNull Map<Integer, ArrayMap<String,
-            ArraySet<String>>> mappedOps) {
+            @NonNull String attributionTag, @NonNull Map<Integer, PackageTagsList> mappedOps) {
         // Only a single lookup from the underlying concurrent data structure
-        final int appId = UserHandle.getAppId(uid);
-        final ArrayMap<String, ArraySet<String>> appIdTags = mappedOps.get(appId);
-        if (appIdTags != null) {
-            final ArraySet<String> packageTags = appIdTags.get(packageName);
-            if (packageTags != null && packageTags.contains(attributionTag)) {
-                if (packageName.equals("com.google.android.gms")
-                        && !sExpectedTags.contains(attributionTag)) {
-                    Log.i("AppOpsDebugRemapping", packageName + " tag "
-                            + attributionTag + " in " + packageTags);
-                }
-                return true;
-            }
-            if (packageName.equals("com.google.android.gms")
-                    && sExpectedTags.contains(attributionTag)) {
-                Log.i("AppOpsDebugRemapping", packageName + " tag " + attributionTag
-                        + " NOT in " + packageTags);
-            }
-        } else {
-            if (packageName.equals("com.google.android.gms")) {
-                Log.i("AppOpsDebugRemapping", "no package tags for uid " + uid
-                        + " package " + packageName);
-            }
-
-        }
-        return false;
+        final PackageTagsList appIdTags = mappedOps.get(UserHandle.getAppId(uid));
+        return appIdTags != null && appIdTags.contains(packageName, attributionTag);
     }
 
     private static int resolveLocationOp(int code) {
diff --git a/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java b/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java
index b695150..e8ce4f3 100644
--- a/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java
+++ b/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import android.content.Context;
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
 
@@ -26,17 +27,16 @@
 final class DeviceVibrationEffectAdapter
         implements VibrationEffectAdapters.EffectAdapter<VibratorInfo> {
 
-    /** Duration of each step created to simulate a ramp segment. */
-    private static final int RAMP_STEP_DURATION_MILLIS = 5;
-
     private final List<VibrationEffectAdapters.SegmentsAdapter<VibratorInfo>> mSegmentAdapters;
 
-    DeviceVibrationEffectAdapter() {
+    DeviceVibrationEffectAdapter(Context context) {
         mSegmentAdapters = Arrays.asList(
                 // TODO(b/167947076): add filter that removes unsupported primitives
                 // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback
-                new RampToStepAdapter(RAMP_STEP_DURATION_MILLIS),
-                new StepToRampAdapter(),
+                new RampToStepAdapter(context.getResources().getInteger(
+                        com.android.internal.R.integer.config_vibrationWaveformRampStepDuration)),
+                new StepToRampAdapter(context.getResources().getInteger(
+                        com.android.internal.R.integer.config_vibrationWaveformRampDownDuration)),
                 new ClippingAmplitudeAndFrequencyAdapter()
         );
     }
diff --git a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
index 1e05bdb..64624a2 100644
--- a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
+++ b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
@@ -29,7 +29,7 @@
 /**
  * Adapter that converts ramp segments that to a sequence of fixed step segments.
  *
- * <p>This leaves the list unchanged if the device have compose PWLE capability.
+ * <p>This leaves the list unchanged if the device has compose PWLE capability.
  */
 final class RampToStepAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {
 
diff --git a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
index f78df92..d439b94 100644
--- a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
+++ b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
@@ -27,39 +27,221 @@
 /**
  * Adapter that converts step segments that should be handled as PWLEs to ramp segments.
  *
- * <p>This leaves the list unchanged if the device do not have compose PWLE capability.
+ * <p>Each replaced {@link StepSegment} will be represented by a {@link RampSegment} with same
+ * start and end amplitudes/frequencies, which can then be converted to PWLE compositions. This
+ * adapter leaves the segments unchanged if the device doesn't have the PWLE composition capability.
+ *
+ * <p>This adapter also applies the ramp down duration config on devices with PWLE support. This
+ * prevents the device from ringing when it cannot handle abrupt changes between ON and OFF states.
+ * This will not change other types of abrupt amplitude changes in the original effect.
+ *
+ * <p>The effect overall duration is preserved by this transformation. Waveforms with ON/OFF
+ * segments are handled gracefully by the ramp down changes. Each OFF segment preceded by an ON
+ * segment will be shortened, and a ramp down will be added to the transition between ON and OFF.
+ * The ramps can be shorter than the configured duration in order to preserve the waveform timings,
+ * but they will still soften the ringing effect.
  */
 final class StepToRampAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {
+
+    private final int mRampDownDuration;
+
+    StepToRampAdapter(int rampDownDuration) {
+        mRampDownDuration = rampDownDuration;
+    }
+
     @Override
     public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
             VibratorInfo info) {
         if (!info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
-            // The vibrator do not have PWLE capability, so keep the segments unchanged.
+            // The vibrator does not have PWLE capability, so keep the segments unchanged.
             return repeatIndex;
         }
+        convertStepsToRamps(segments);
+        int newRepeatIndex = addRampDownToZeroAmplitudeSegments(segments, repeatIndex);
+        newRepeatIndex = addRampDownToLoop(segments, newRepeatIndex);
+        return newRepeatIndex;
+    }
+
+    private void convertStepsToRamps(List<VibrationEffectSegment> segments) {
         int segmentCount = segments.size();
+        if (mRampDownDuration > 0) {
+            // Convert all steps to ramps if the device requires ramp down.
+            for (int i = 0; i < segmentCount; i++) {
+                if (isStep(segments.get(i))) {
+                    segments.set(i, apply((StepSegment) segments.get(i)));
+                }
+            }
+            return;
+        }
         // Convert steps that require frequency control to ramps.
         for (int i = 0; i < segmentCount; i++) {
             VibrationEffectSegment segment = segments.get(i);
-            if ((segment instanceof StepSegment)
-                    && ((StepSegment) segment).getFrequency() != 0) {
+            if (isStep(segment) && ((StepSegment) segment).getFrequency() != 0) {
                 segments.set(i, apply((StepSegment) segment));
             }
         }
         // Convert steps that are next to ramps to also become ramps, so they can be composed
         // together in the same PWLE waveform.
-        for (int i = 1; i < segmentCount; i++) {
+        for (int i = 0; i < segmentCount; i++) {
             if (segments.get(i) instanceof RampSegment) {
-                for (int j = i - 1; j >= 0 && (segments.get(j) instanceof StepSegment); j--) {
+                for (int j = i - 1; j >= 0 && isStep(segments.get(j)); j--) {
+                    segments.set(j, apply((StepSegment) segments.get(j)));
+                }
+                for (int j = i + 1; j < segmentCount && isStep(segments.get(j)); j++) {
                     segments.set(j, apply((StepSegment) segments.get(j)));
                 }
             }
         }
+    }
+
+    /**
+     * This will add a ramp to zero as follows:
+     *
+     * <ol>
+     *     <li>Remove the {@link VibrationEffectSegment} that starts and ends at zero amplitude
+     *         and follows a segment that ends at non-zero amplitude;
+     *     <li>Add a ramp down to zero starting at the previous segment end amplitude and frequency,
+     *         with min between the removed segment duration and the configured ramp down duration;
+     *     <li>Add a zero amplitude segment following the ramp with the remaining duration, if
+     *         necessary;
+     * </ol>
+     */
+    private int addRampDownToZeroAmplitudeSegments(List<VibrationEffectSegment> segments,
+            int repeatIndex) {
+        if (mRampDownDuration <= 0) {
+            // Nothing to do, no ramp down duration configured.
+            return repeatIndex;
+        }
+        int newRepeatIndex = repeatIndex;
+        int newSegmentCount = segments.size();
+        for (int i = 1; i < newSegmentCount; i++) {
+            if (!isOffRampSegment(segments.get(i))
+                    || !endsWithNonZeroAmplitude(segments.get(i - 1))) {
+                continue;
+            }
+
+            // We know the previous segment is a ramp that ends at non-zero amplitude.
+            float previousAmplitude = ((RampSegment) segments.get(i - 1)).getEndAmplitude();
+            float previousFrequency = ((RampSegment) segments.get(i - 1)).getEndFrequency();
+            RampSegment ramp = (RampSegment) segments.get(i);
+
+            if (ramp.getDuration() <= mRampDownDuration) {
+                // Replace the zero amplitude segment with a ramp down of same duration, to
+                // preserve waveform timings and still soften the transition to zero.
+                segments.set(i, createRampDown(previousAmplitude, previousFrequency,
+                        ramp.getDuration()));
+            } else {
+                // Make the zero amplitude segment shorter, to preserve waveform timings, and add a
+                // ramp down to zero segment right before it.
+                segments.set(i, updateDuration(ramp, ramp.getDuration() - mRampDownDuration));
+                segments.add(i, createRampDown(previousAmplitude, previousFrequency,
+                        mRampDownDuration));
+                if (repeatIndex > i) {
+                    newRepeatIndex++;
+                }
+                i++;
+                newSegmentCount++;
+            }
+        }
+        return newRepeatIndex;
+    }
+
+    /**
+     * This will add a ramp to zero at the repeating index of the given effect, if set, only if
+     * the last segment ends at a non-zero amplitude and the repeating segment starts and ends at
+     * zero amplitude. The update is described as:
+     *
+     * <ol>
+     *     <li>Add a ramp down to zero following the last segment, with the min between the
+     *         removed segment duration and the configured ramp down duration;
+     *     <li>Skip the zero-amplitude segment by incrementing the repeat index, splitting it if
+     *         necessary to skip the correct amount;
+     * </ol>
+     */
+    private int addRampDownToLoop(List<VibrationEffectSegment> segments, int repeatIndex) {
+        if (repeatIndex < 0) {
+            // Non-repeating compositions should remain unchanged so duration will be preserved.
+            return repeatIndex;
+        }
+
+        int segmentCount = segments.size();
+        if (mRampDownDuration <= 0 || !endsWithNonZeroAmplitude(segments.get(segmentCount - 1))) {
+            // Nothing to do, no ramp down duration configured or composition already ends at zero.
+            return repeatIndex;
+        }
+
+        // We know the last segment is a ramp that ends at non-zero amplitude.
+        RampSegment lastRamp = (RampSegment) segments.get(segmentCount - 1);
+        float previousAmplitude = lastRamp.getEndAmplitude();
+        float previousFrequency = lastRamp.getEndFrequency();
+
+        if (isOffRampSegment(segments.get(repeatIndex))) {
+            // Repeating from a non-zero to a zero amplitude segment, we know the next segment is a
+            // ramp with zero amplitudes.
+            RampSegment nextRamp = (RampSegment) segments.get(repeatIndex);
+
+            if (nextRamp.getDuration() <= mRampDownDuration) {
+                // Skip the zero amplitude segment and append a ramp down of same duration to the
+                // end of the composition, to preserve waveform timings and still soften the
+                // transition to zero.
+                // This will update the waveform as follows:
+                //  R               R+1
+                //  |  ____          | ____
+                // _|_/       =>   __|/    \
+                segments.add(createRampDown(previousAmplitude, previousFrequency,
+                        nextRamp.getDuration()));
+                repeatIndex++;
+            } else {
+                // Append a ramp down to the end of the composition, split the zero amplitude
+                // segment and start repeating from the second half, to preserve waveform timings.
+                // This will update the waveform as follows:
+                //  R              R+1
+                //  |   ____        |  ____
+                // _|__/       => __|_/    \
+                segments.add(createRampDown(previousAmplitude, previousFrequency,
+                        mRampDownDuration));
+                segments.set(repeatIndex, updateDuration(nextRamp,
+                        nextRamp.getDuration() - mRampDownDuration));
+                segments.add(repeatIndex, updateDuration(nextRamp, mRampDownDuration));
+                repeatIndex++;
+            }
+        }
+
         return repeatIndex;
     }
 
-    private RampSegment apply(StepSegment segment) {
+    private static RampSegment apply(StepSegment segment) {
         return new RampSegment(segment.getAmplitude(), segment.getAmplitude(),
                 segment.getFrequency(), segment.getFrequency(), (int) segment.getDuration());
     }
+
+    private static RampSegment createRampDown(float amplitude, float frequency, long duration) {
+        return new RampSegment(amplitude, /* endAmplitude= */ 0, frequency, frequency,
+                (int) duration);
+    }
+
+    private static RampSegment updateDuration(RampSegment ramp, long newDuration) {
+        return new RampSegment(ramp.getStartAmplitude(), ramp.getEndAmplitude(),
+                ramp.getStartFrequency(), ramp.getEndFrequency(), (int) newDuration);
+    }
+
+    private static boolean isStep(VibrationEffectSegment segment) {
+        return segment instanceof StepSegment;
+    }
+
+    /** Returns true if the segment is a ramp that starts and ends at zero amplitude. */
+    private static boolean isOffRampSegment(VibrationEffectSegment segment) {
+        if (segment instanceof RampSegment) {
+            RampSegment ramp = (RampSegment) segment;
+            return ramp.getStartAmplitude() == 0 && ramp.getEndAmplitude() == 0;
+        }
+        return false;
+    }
+
+    private static boolean endsWithNonZeroAmplitude(VibrationEffectSegment segment) {
+        if (segment instanceof RampSegment) {
+            return ((RampSegment) segment).getEndAmplitude() != 0;
+        }
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index e3672f4..150fde9 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -95,8 +95,7 @@
     private final WorkSource mWorkSource = new WorkSource();
     private final PowerManager.WakeLock mWakeLock;
     private final IBatteryStats mBatteryStatsService;
-    private final DeviceVibrationEffectAdapter mDeviceEffectAdapter =
-            new DeviceVibrationEffectAdapter();
+    private final DeviceVibrationEffectAdapter mDeviceEffectAdapter;
     private final Vibration mVibration;
     private final VibrationCallbacks mCallbacks;
     private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
@@ -104,10 +103,11 @@
 
     private volatile boolean mForceStop;
 
-    VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
-            PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
-            VibrationCallbacks callbacks) {
+    VibrationThread(Vibration vib, DeviceVibrationEffectAdapter effectAdapter,
+            SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock,
+            IBatteryStats batteryStatsService, VibrationCallbacks callbacks) {
         mVibration = vib;
+        mDeviceEffectAdapter = effectAdapter;
         mCallbacks = callbacks;
         mWakeLock = wakeLock;
         mWorkSource.set(vib.uid);
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 06a5077..2f0ed19 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -132,6 +132,7 @@
     private final VibrationSettings mVibrationSettings;
     private final VibrationScaler mVibrationScaler;
     private final InputDeviceDelegate mInputDeviceDelegate;
+    private final DeviceVibrationEffectAdapter mDeviceVibrationEffectAdapter;
 
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
@@ -175,6 +176,7 @@
         mVibrationSettings = new VibrationSettings(mContext, mHandler);
         mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
         mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
+        mDeviceVibrationEffectAdapter = new DeviceVibrationEffectAdapter(mContext);
 
         VibrationCompleteListener listener = new VibrationCompleteListener(this);
         mNativeWrapper = injector.getNativeWrapper();
@@ -512,8 +514,8 @@
                 return Vibration.Status.FORWARDED_TO_INPUT_DEVICES;
             }
 
-            VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock,
-                    mBatteryStatsService, mVibrationCallbacks);
+            VibrationThread vibThread = new VibrationThread(vib, mDeviceVibrationEffectAdapter,
+                    mVibrators, mWakeLock, mBatteryStatsService, mVibrationCallbacks);
 
             if (mCurrentVibration == null) {
                 return startVibrationThreadLocked(vibThread);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 45da45a..8cc6a42 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -309,6 +309,7 @@
 import android.window.IRemoteTransition;
 import android.window.SizeConfigurationBuckets;
 import android.window.SplashScreen;
+import android.window.SplashScreenView;
 import android.window.SplashScreenView.SplashScreenViewParcelable;
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
@@ -716,25 +717,29 @@
     boolean startingMoved;
 
     boolean mHandleExitSplashScreen;
-    @TransferSplashScreenState int mTransferringSplashScreenState =
-            TRANSFER_SPLASH_SCREEN_IDLE;
+    @TransferSplashScreenState
+    int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
 
-    // Idle, can be triggered to do transfer if needed.
+    /** Idle, can be triggered to do transfer if needed. */
     static final int TRANSFER_SPLASH_SCREEN_IDLE = 0;
-    // requesting a copy from shell.
+
+    /** Requesting a copy from shell. */
     static final int TRANSFER_SPLASH_SCREEN_COPYING = 1;
-    // attach the splash screen view to activity.
+
+    /** Attach the splash screen view to activity. */
     static final int TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT = 2;
-    // client has taken over splash screen view.
+
+    /** Client has taken over splash screen view. */
     static final int TRANSFER_SPLASH_SCREEN_FINISH = 3;
 
-    @IntDef(prefix = { "TRANSFER_SPLASH_SCREEN_" }, value = {
+    @IntDef(prefix = {"TRANSFER_SPLASH_SCREEN_"}, value = {
             TRANSFER_SPLASH_SCREEN_IDLE,
             TRANSFER_SPLASH_SCREEN_COPYING,
             TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT,
             TRANSFER_SPLASH_SCREEN_FINISH,
     })
-    @interface TransferSplashScreenState {}
+    @interface TransferSplashScreenState {
+    }
 
     // How long we wait until giving up transfer splash screen.
     private static final int TRANSFER_SPLASH_SCREEN_TIMEOUT = 2000;
@@ -2196,6 +2201,23 @@
         removeStartingWindowAnimation(false /* prepareAnimation */);
     }
 
+    /**
+     * Notify the shell ({@link com.android.wm.shell.ShellTaskOrganizer} it should clean up any
+     * remaining reference to this {@link ActivityRecord}'s splash screen.
+     * @see com.android.wm.shell.ShellTaskOrganizer#onAppSplashScreenViewRemoved(int)
+     * @see SplashScreenView#remove()
+     */
+    void cleanUpSplashScreen() {
+        // We only clean up the splash screen if we were supposed to handle it. If it was
+        // transferred to another activity, the next one will handle the clean up.
+        if (mHandleExitSplashScreen && !startingMoved
+                && (mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH
+                || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_IDLE)) {
+            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Cleaning splash screen token=%s", this);
+            mAtmService.mTaskOrganizerController.onAppSplashScreenViewRemoved(getTask());
+        }
+    }
+
     void removeStartingWindow() {
         removeStartingWindowAnimation(true /* prepareAnimation */);
     }
@@ -3343,6 +3365,9 @@
         task.cleanUpActivityReferences(this);
         clearLastParentBeforePip();
 
+        // Clean up the splash screen if it was still displayed.
+        cleanUpSplashScreen();
+
         deferRelaunchUntilPaused = false;
         frozenBeforeDestroy = false;
 
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index eaebb6f..4acadb2 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -623,7 +623,11 @@
             siblings.add(current);
             boolean canPromote = true;
 
-            if (parent == null || !parent.canCreateRemoteAnimationTarget()) {
+            if (parent == null || !parent.canCreateRemoteAnimationTarget()
+                    // We cannot promote the animation on Task's parent when the task is in
+                    // clearing task in case the animating get stuck when performing the opening
+                    // task that behind it.
+                    || (current.asTask() != null && current.asTask().mInRemoveTask)) {
                 canPromote = false;
             } else {
                 // In case a descendant of the parent belongs to the other group, we cannot promote
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7af8d8b..565f99f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -6689,24 +6689,26 @@
                     }
                 }
 
-                // TODO(185200798): Persist theme name instead of theme if
-                int splashScreenThemeResId = options != null
-                        ? options.getSplashScreenThemeResId() : 0;
-
-                // User can override the splashscreen theme. The theme name is used to persist
-                // the setting, so if no theme is set in the ActivityOptions, we check if has
-                // been persisted here.
-                if (splashScreenThemeResId == 0) {
+                // Find the splash screen theme. User can override the persisted theme by
+                // ActivityOptions.
+                String splashScreenThemeResName = options != null
+                        ? options.getSplashScreenThemeResName() : null;
+                if (splashScreenThemeResName == null || splashScreenThemeResName.isEmpty()) {
                     try {
-                        String themeName = mAtmService.getPackageManager()
+                        splashScreenThemeResName = mAtmService.getPackageManager()
                                 .getSplashScreenTheme(r.packageName, r.mUserId);
-                        if (themeName != null) {
-                            Context packageContext = mAtmService.mContext
-                                    .createPackageContext(r.packageName, 0);
-                            splashScreenThemeResId = packageContext.getResources()
-                                    .getIdentifier(themeName, null, null);
-                        }
-                    } catch (RemoteException | PackageManager.NameNotFoundException
+                    } catch (RemoteException ignore) {
+                        // Just use the default theme
+                    }
+                }
+                int splashScreenThemeResId = 0;
+                if (splashScreenThemeResName != null && !splashScreenThemeResName.isEmpty()) {
+                    try {
+                        final Context packageContext = mAtmService.mContext
+                                .createPackageContext(r.packageName, 0);
+                        splashScreenThemeResId = packageContext.getResources()
+                                .getIdentifier(splashScreenThemeResName, null, null);
+                    } catch (PackageManager.NameNotFoundException
                             | Resources.NotFoundException ignore) {
                         // Just use the default theme
                     }
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index f23028f..3a2ca80 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -43,6 +43,7 @@
 import android.view.SurfaceControl;
 import android.window.ITaskOrganizer;
 import android.window.ITaskOrganizerController;
+import android.window.SplashScreenView;
 import android.window.StartingWindowInfo;
 import android.window.TaskAppearedInfo;
 import android.window.TaskSnapshot;
@@ -215,6 +216,14 @@
             }
         }
 
+        void onAppSplashScreenViewRemoved(Task task) {
+            try {
+                mTaskOrganizer.onAppSplashScreenViewRemoved(task.mTaskId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Exception sending onAppSplashScreenViewRemoved callback", e);
+            }
+        }
+
         SurfaceControl prepareLeash(Task task, String reason) {
             return new SurfaceControl(task.getSurfaceControl(), reason);
         }
@@ -314,6 +323,10 @@
             mOrganizer.copySplashScreenView(t);
         }
 
+        public void onAppSplashScreenViewRemoved(Task t) {
+            mOrganizer.onAppSplashScreenViewRemoved(t);
+        }
+
         /**
          * Register this task with this state, but doesn't trigger the task appeared callback to
          * the organizer.
@@ -566,6 +579,22 @@
         return true;
     }
 
+    /**
+     * Notify the shell ({@link com.android.wm.shell.ShellTaskOrganizer} that the client has
+     * removed the splash screen view.
+     * @see com.android.wm.shell.ShellTaskOrganizer#onAppSplashScreenViewRemoved(int)
+     * @see SplashScreenView#remove()
+     */
+    public void onAppSplashScreenViewRemoved(Task task) {
+        final Task rootTask = task.getRootTask();
+        if (rootTask == null || rootTask.mTaskOrganizer == null) {
+            return;
+        }
+        final TaskOrganizerState state =
+                mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder());
+        state.onAppSplashScreenViewRemoved(task);
+    }
+
     void onTaskAppeared(ITaskOrganizer organizer, Task task) {
         final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
         if (state != null && state.addTask(task)) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 03762b3..1a468d9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -61,6 +61,7 @@
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NOT_MAGNIFIABLE;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
@@ -5385,6 +5386,9 @@
                 || mAttrs.type == TYPE_NAVIGATION_BAR_PANEL) {
             return false;
         }
+        if ((mAttrs.privateFlags & PRIVATE_FLAG_NOT_MAGNIFIABLE) != 0) {
+            return false;
+        }
         return true;
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0128d35..fa24e52 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -6832,7 +6832,7 @@
     @Override
     public void wipeDataWithReason(int flags, String wipeReasonForUser,
             boolean calledOnParentInstance) {
-        if (!mHasFeature) {
+        if (!mHasFeature && !hasCallingOrSelfPermission(permission.MASTER_CLEAR)) {
             return;
         }
         final CallerIdentity caller = getCallerIdentity();
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java
index cf5db2e..8532dbb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java
@@ -158,7 +158,7 @@
 
     @Test
     public void testSetState() {
-        assertThat(mProvider.isAllowed()).isFalse();
+        assertThat(mProvider.getState().allowed).isFalse();
 
         AbstractLocationProvider.State newState;
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
index 1ac4a8e..a71b481 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
@@ -19,6 +19,7 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_HOVER_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
+import static android.view.WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY;
 import static android.view.WindowManagerPolicyConstants.FLAG_PASS_TO_USER;
 
 import static org.hamcrest.CoreMatchers.allOf;
@@ -186,9 +187,9 @@
         verifyNoMoreInteractions(next);
         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
 
-        verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), eq(FLAG_PASS_TO_USER));
-        verify(next).onMotionEvent(argThat(mIsLineStart), argThat(mIsLineStart),
-                eq(FLAG_PASS_TO_USER));
+        final int expectedFlags = FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY;
+        verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), eq(expectedFlags));
+        verify(next).onMotionEvent(argThat(mIsLineStart), argThat(mIsLineStart), eq(expectedFlags));
         verifyNoMoreInteractions(next);
         reset(next);
 
@@ -196,7 +197,7 @@
 
         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
         verify(next).onMotionEvent(argThat(allOf(mIsLineMiddle, hasRightDownTime)),
-                argThat(allOf(mIsLineMiddle, hasRightDownTime)), eq(FLAG_PASS_TO_USER));
+                argThat(allOf(mIsLineMiddle, hasRightDownTime)), eq(expectedFlags));
         verifyNoMoreInteractions(next);
         reset(next);
 
@@ -204,7 +205,7 @@
 
         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
         verify(next).onMotionEvent(argThat(allOf(mIsLineEnd, hasRightDownTime)),
-                argThat(allOf(mIsLineEnd, hasRightDownTime)), eq(FLAG_PASS_TO_USER));
+                argThat(allOf(mIsLineEnd, hasRightDownTime)), eq(expectedFlags));
         verifyNoMoreInteractions(next);
 
         verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, true);
@@ -242,7 +243,8 @@
         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
         reset(next);
         mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
-        verify(next).onMotionEvent(argThat(mIsClickDown), argThat(mIsClickDown), eq(0));
+        verify(next).onMotionEvent(argThat(mIsClickDown), argThat(mIsClickDown),
+                eq(FLAG_INJECTED_FROM_ACCESSIBILITY));
     }
 
     @Test
@@ -258,7 +260,8 @@
 
         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
         verify(next).onMotionEvent(
-                argThat(mIsLineStart), argThat(mIsLineStart), eq(FLAG_PASS_TO_USER));
+                argThat(mIsLineStart), argThat(mIsLineStart),
+                eq(FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY));
     }
 
     @Test
@@ -289,9 +292,11 @@
         reset(next);
 
         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
-        verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), eq(FLAG_PASS_TO_USER));
+        verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(),
+                eq(FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY));
         verify(next).onMotionEvent(
-                argThat(mIsLineStart), argThat(mIsLineStart), eq(FLAG_PASS_TO_USER));
+                argThat(mIsLineStart), argThat(mIsLineStart),
+                eq(FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
index 3bcbcbdb..8666fa6 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
@@ -57,6 +57,11 @@
 
 /** This tests AppSearchImpl when it's running with a platform-backed VisibilityStore. */
 public class AppSearchImplPlatformTest {
+    /**
+     * Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class.
+     */
+    private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true;
+
     @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
     private final Map<UserHandle, PackageManager> mMockPackageManagers = new ArrayMap<>();
     private Context mContext;
@@ -88,7 +93,8 @@
                 AppSearchImpl.create(
                         mTemporaryFolder.newFolder(),
                         mContext,
-                        /*logger=*/ null);
+                        /*logger=*/ null,
+                        ALWAYS_OPTIMIZE);
 
         mGlobalQuerierUid =
                 mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0);
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java b/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java
index 6ac4d13..4faffc0 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java
@@ -56,6 +56,11 @@
 import java.util.Map;
 
 public class VisibilityStoreTest {
+    /**
+     * Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class.
+     */
+    private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true;
+
     @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
     private final Map<UserHandle, PackageManager> mMockPackageManagers = new ArrayMap<>();
     private Context mContext;
@@ -87,7 +92,8 @@
                 AppSearchImpl.create(
                         mTemporaryFolder.newFolder(),
                         mContext,
-                        /*logger=*/ null);
+                        /*logger=*/ null,
+                        ALWAYS_OPTIMIZE);
 
         mUid = mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0);
         mVisibilityStore = appSearchImpl.getVisibilityStoreLocked();
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
index 5a8c44c..e26cfea 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
@@ -76,17 +76,22 @@
 public class AppSearchImplTest {
     @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
     private AppSearchImpl mAppSearchImpl;
+    /**
+     * Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class.
+     */
+    private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true;
 
     @Before
     public void setUp() throws Exception {
         Context context = ApplicationProvider.getApplicationContext();
 
-        // Give ourselves global query permissions
+        // Give ourselves global query permissions.
         mAppSearchImpl =
                 AppSearchImpl.create(
                         mTemporaryFolder.newFolder(),
                         context,
-                        /*logger=*/ null);
+                        /*logger=*/ null,
+                        ALWAYS_OPTIMIZE);
     }
 
     /**
@@ -423,7 +428,7 @@
     }
 
     @Test
-    public void testOptimize() throws Exception {
+    public void testTriggerCheckOptimizeByMutationSize() throws Exception {
         // Insert schema
         List<AppSearchSchema> schemas =
                 Collections.singletonList(new AppSearchSchema.Builder("type").build());
@@ -436,56 +441,30 @@
                 /*forceOverride=*/ false,
                 /*version=*/ 0);
 
-        // Insert enough documents.
-        for (int i = 0;
-                i
-                        < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
-                                + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL;
-                i++) {
-            GenericDocument document =
-                    new GenericDocument.Builder<>("namespace", "id" + i, "type").build();
-            mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
-        }
+        // Insert a document and then remove it to generate garbage.
+        GenericDocument document = new GenericDocument.Builder<>("namespace", "id", "type").build();
+        mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
+        mAppSearchImpl.remove(
+                "package", "database", "namespace", "id", /*removeStatsBuilder=*/ null);
 
-        // Check optimize() will release 0 docs since there is no deletion.
+        // Verify there is garbage documents.
         GetOptimizeInfoResultProto optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
+        assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(1);
+
+        // Increase mutation counter and stop before reach the threshold
+        mAppSearchImpl.checkForOptimize(AppSearchImpl.CHECK_OPTIMIZE_INTERVAL - 1);
+
+        // Verify the optimize() isn't triggered.
+        optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
+        assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(1);
+
+        // Increase the counter and reach the threshold, optimize() should be triggered.
+        mAppSearchImpl.checkForOptimize(/*mutateBatchSize=*/ 1);
+
+        // Verify optimize() is triggered.
+        optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
         assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(0);
-
-        // delete 999 documents, we will reach the threshold to trigger optimize() in next
-        // deletion.
-        for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1; i++) {
-            mAppSearchImpl.remove(
-                    "package", "database", "namespace", "id" + i, /*removeStatsBuilder=*/ null);
-        }
-
-        // Updates the check for optimize counter, checkForOptimize() will be triggered since
-        // CHECK_OPTIMIZE_INTERVAL is reached but optimize() won't since
-        // OPTIMIZE_THRESHOLD_DOC_COUNT is not.
-        mAppSearchImpl.checkForOptimize(
-                /*mutateBatchSize=*/ AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1);
-
-        // Verify optimize() still not be triggered.
-        optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
-        assertThat(optimizeInfo.getOptimizableDocs())
-                .isEqualTo(AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1);
-
-        // Keep delete docs
-        for (int i = AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT;
-                i
-                        < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
-                                + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL;
-                i++) {
-            mAppSearchImpl.remove(
-                    "package", "database", "namespace", "id" + i, /*removeStatsBuilder=*/ null);
-        }
-        // updates the check for optimize counter, will reach both CHECK_OPTIMIZE_INTERVAL and
-        // OPTIMIZE_THRESHOLD_DOC_COUNT this time and trigger a optimize().
-        mAppSearchImpl.checkForOptimize(/*mutateBatchSize*/ AppSearchImpl.CHECK_OPTIMIZE_INTERVAL);
-
-        // Verify optimize() is triggered
-        optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
-        assertThat(optimizeInfo.getOptimizableDocs())
-                .isLessThan(AppSearchImpl.CHECK_OPTIMIZE_INTERVAL);
+        assertThat(optimizeInfo.getEstimatedOptimizableBytes()).isEqualTo(0);
     }
 
     @Test
@@ -493,7 +472,12 @@
         // Setup the index
         Context context = ApplicationProvider.getApplicationContext();
         File appsearchDir = mTemporaryFolder.newFolder();
-        AppSearchImpl appSearchImpl = AppSearchImpl.create(appsearchDir, context, /*logger=*/ null);
+        AppSearchImpl appSearchImpl =
+                AppSearchImpl.create(
+                        appsearchDir,
+                        context,
+                        /*logger=*/ null,
+                        ALWAYS_OPTIMIZE);
 
         // Insert schema
         List<AppSearchSchema> schemas =
@@ -522,7 +506,7 @@
                         /*queryExpression=*/ "",
                         new SearchSpec.Builder().addFilterSchemas("Type1").build(),
                         context.getPackageName(),
-                        VisibilityStore.NO_OP_USER_ID,
+                        VisibilityStore.NO_OP_UID,
                         /*logger=*/ null);
         assertThat(results.getResults()).hasSize(1);
         assertThat(results.getResults().get(0).getGenericDocument()).isEqualTo(validDoc);
@@ -554,7 +538,9 @@
 
         // Initialize AppSearchImpl. This should cause a reset.
         appSearchImpl.close();
-        appSearchImpl = AppSearchImpl.create(appsearchDir, context, testLogger);
+        appSearchImpl =
+                AppSearchImpl.create(
+                        appsearchDir, context, testLogger, ALWAYS_OPTIMIZE);
 
         // Check recovery state
         InitializeStats initStats = testLogger.mInitializeStats;
@@ -582,7 +568,7 @@
                         /*queryExpression=*/ "",
                         new SearchSpec.Builder().addFilterSchemas("Type1").build(),
                         context.getPackageName(),
-                        VisibilityStore.NO_OP_USER_ID,
+                        VisibilityStore.NO_OP_UID,
                         /*logger=*/ null);
         assertThat(results.getResults()).isEmpty();
 
@@ -606,7 +592,7 @@
                         /*queryExpression=*/ "",
                         new SearchSpec.Builder().addFilterSchemas("Type1").build(),
                         context.getPackageName(),
-                        VisibilityStore.NO_OP_USER_ID,
+                        VisibilityStore.NO_OP_UID,
                         /*logger=*/ null);
         assertThat(results.getResults()).hasSize(1);
         assertThat(results.getResults().get(0).getGenericDocument()).isEqualTo(validDoc);
@@ -862,7 +848,7 @@
                         "",
                         searchSpec,
                         /*callerPackageName=*/ "",
-                        /*callerUid=*/ 0,
+                        VisibilityStore.NO_OP_UID,
                         /*logger=*/ null);
         assertThat(searchResultPage.getResults()).isEmpty();
     }
@@ -1683,7 +1669,8 @@
                 AppSearchImpl.create(
                         mTemporaryFolder.newFolder(),
                         context,
-                        /*logger=*/ null);
+                        /*logger=*/ null,
+                        ALWAYS_OPTIMIZE);
 
         // Initial check that we could do something at first.
         List<AppSearchSchema> schemas =
@@ -1758,7 +1745,7 @@
                                     .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
                                     .build(),
                             "package",
-                            /*callerUid=*/ 1,
+                            VisibilityStore.NO_OP_UID,
                             /*logger=*/ null);
                 });
 
@@ -1830,7 +1817,12 @@
         // Setup the index
         Context context = ApplicationProvider.getApplicationContext();
         File appsearchDir = mTemporaryFolder.newFolder();
-        AppSearchImpl appSearchImpl = AppSearchImpl.create(appsearchDir, context, /*logger=*/ null);
+        AppSearchImpl appSearchImpl =
+                AppSearchImpl.create(
+                        appsearchDir,
+                        context,
+                        /*logger=*/ null,
+                        ALWAYS_OPTIMIZE);
 
         List<AppSearchSchema> schemas =
                 Collections.singletonList(new AppSearchSchema.Builder("type").build());
@@ -1856,7 +1848,11 @@
 
         // That document should be visible even from another instance.
         AppSearchImpl appSearchImpl2 =
-                AppSearchImpl.create(appsearchDir, context, /*logger=*/ null);
+                AppSearchImpl.create(
+                        appsearchDir,
+                        context,
+                        /*logger=*/ null,
+                        ALWAYS_OPTIMIZE);
         getResult =
                 appSearchImpl2.getDocument(
                         "package", "database", "namespace1", "id1", Collections.emptyMap());
@@ -1868,7 +1864,12 @@
         // Setup the index
         Context context = ApplicationProvider.getApplicationContext();
         File appsearchDir = mTemporaryFolder.newFolder();
-        AppSearchImpl appSearchImpl = AppSearchImpl.create(appsearchDir, context, /*logger=*/ null);
+        AppSearchImpl appSearchImpl =
+                AppSearchImpl.create(
+                        appsearchDir,
+                        context,
+                        /*logger=*/ null,
+                        ALWAYS_OPTIMIZE);
 
         List<AppSearchSchema> schemas =
                 Collections.singletonList(new AppSearchSchema.Builder("type").build());
@@ -1918,7 +1919,11 @@
 
         // Only the second document should be retrievable from another instance.
         AppSearchImpl appSearchImpl2 =
-                AppSearchImpl.create(appsearchDir, context, /*logger=*/ null);
+                AppSearchImpl.create(
+                        appsearchDir,
+                        context,
+                        /*logger=*/ null,
+                        ALWAYS_OPTIMIZE);
         expectThrows(
                 AppSearchException.class,
                 () ->
@@ -1939,7 +1944,12 @@
         // Setup the index
         Context context = ApplicationProvider.getApplicationContext();
         File appsearchDir = mTemporaryFolder.newFolder();
-        AppSearchImpl appSearchImpl = AppSearchImpl.create(appsearchDir, context, /*logger=*/ null);
+        AppSearchImpl appSearchImpl =
+                AppSearchImpl.create(
+                        appsearchDir,
+                        context,
+                        /*logger=*/ null,
+                        ALWAYS_OPTIMIZE);
 
         List<AppSearchSchema> schemas =
                 Collections.singletonList(new AppSearchSchema.Builder("type").build());
@@ -1997,7 +2007,11 @@
 
         // Only the second document should be retrievable from another instance.
         AppSearchImpl appSearchImpl2 =
-                AppSearchImpl.create(appsearchDir, context, /*logger=*/ null);
+                AppSearchImpl.create(
+                        appsearchDir,
+                        context,
+                        /*logger=*/ null,
+                        ALWAYS_OPTIMIZE);
         expectThrows(
                 AppSearchException.class,
                 () ->
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
index f0a6ef1..c6726c6 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
@@ -53,6 +53,10 @@
     @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
     private AppSearchImpl mAppSearchImpl;
     private TestLogger mLogger;
+    /**
+     * Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class.
+     */
+    private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true;
 
     @Before
     public void setUp() throws Exception {
@@ -63,7 +67,8 @@
                 AppSearchImpl.create(
                         mTemporaryFolder.newFolder(),
                         context,
-                        /*logger=*/ null);
+                        /*logger=*/ null,
+                        ALWAYS_OPTIMIZE);
         mLogger = new TestLogger();
     }
 
@@ -286,11 +291,13 @@
     public void testLoggingStats_initialize() throws Exception {
         Context context = ApplicationProvider.getApplicationContext();
 
+        // Create an unused AppSearchImpl to generated an InitializeStats.
         AppSearchImpl appSearchImpl =
                 AppSearchImpl.create(
                         mTemporaryFolder.newFolder(),
                         context,
-                        mLogger);
+                        mLogger,
+                        ALWAYS_OPTIMIZE);
 
         InitializeStats iStats = mLogger.mInitializeStats;
         assertThat(iStats).isNotNull();
@@ -325,9 +332,9 @@
 
         PutDocumentStats pStats = mLogger.mPutDocumentStats;
         assertThat(pStats).isNotNull();
-        assertThat(pStats.getGeneralStats().getPackageName()).isEqualTo(testPackageName);
-        assertThat(pStats.getGeneralStats().getDatabase()).isEqualTo(testDatabase);
-        assertThat(pStats.getGeneralStats().getStatusCode()).isEqualTo(AppSearchResult.RESULT_OK);
+        assertThat(pStats.getPackageName()).isEqualTo(testPackageName);
+        assertThat(pStats.getDatabase()).isEqualTo(testDatabase);
+        assertThat(pStats.getStatusCode()).isEqualTo(AppSearchResult.RESULT_OK);
         // The rest of native stats have been tested in testCopyNativeStats
         assertThat(pStats.getNativeDocumentSizeBytes()).isGreaterThan(0);
     }
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategyTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategyTest.java
new file mode 100644
index 0000000..f30cbb8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategyTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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 com.android.server.appsearch.external.localstorage;
+
+import static com.android.server.appsearch.external.localstorage.FrameworkOptimizeStrategy.BYTES_OPTIMIZE_THRESHOLD;
+import static com.android.server.appsearch.external.localstorage.FrameworkOptimizeStrategy.DOC_COUNT_OPTIMIZE_THRESHOLD;
+import static com.android.server.appsearch.external.localstorage.FrameworkOptimizeStrategy.TIME_OPTIMIZE_THRESHOLD_MILLIS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.server.appsearch.proto.GetOptimizeInfoResultProto;
+import com.android.server.appsearch.proto.StatusProto;
+
+import org.junit.Test;
+
+public class FrameworkOptimizeStrategyTest {
+    FrameworkOptimizeStrategy mFrameworkOptimizeStrategy = new FrameworkOptimizeStrategy();
+
+    @Test
+    public void testShouldOptimize_docCountThreshold() {
+        GetOptimizeInfoResultProto optimizeInfo =
+                GetOptimizeInfoResultProto.newBuilder()
+                        .setTimeSinceLastOptimizeMs(0)
+                        .setEstimatedOptimizableBytes(BYTES_OPTIMIZE_THRESHOLD)
+                        .setOptimizableDocs(0)
+                        .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK).build())
+                        .build();
+        assertThat(mFrameworkOptimizeStrategy.shouldOptimize(optimizeInfo)).isTrue();
+    }
+
+    @Test
+    public void testShouldOptimize_byteThreshold() {
+        GetOptimizeInfoResultProto optimizeInfo =
+                GetOptimizeInfoResultProto.newBuilder()
+                        .setTimeSinceLastOptimizeMs(TIME_OPTIMIZE_THRESHOLD_MILLIS)
+                        .setEstimatedOptimizableBytes(0)
+                        .setOptimizableDocs(0)
+                        .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK).build())
+                        .build();
+        assertThat(mFrameworkOptimizeStrategy.shouldOptimize(optimizeInfo)).isTrue();
+    }
+
+    @Test
+    public void testShouldNotOptimize_timeThreshold() {
+        GetOptimizeInfoResultProto optimizeInfo =
+                GetOptimizeInfoResultProto.newBuilder()
+                        .setTimeSinceLastOptimizeMs(0)
+                        .setEstimatedOptimizableBytes(0)
+                        .setOptimizableDocs(DOC_COUNT_OPTIMIZE_THRESHOLD)
+                        .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK).build())
+                        .build();
+        assertThat(mFrameworkOptimizeStrategy.shouldOptimize(optimizeInfo)).isTrue();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java
index a71e532..6d90686 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java
@@ -29,57 +29,28 @@
     static final int TEST_TOTAL_LATENCY_MILLIS = 20;
 
     @Test
-    public void testAppSearchStats_GeneralStats() {
-        final GeneralStats gStats =
-                new GeneralStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE)
-                        .setStatusCode(TEST_STATUS_CODE)
-                        .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS)
-                        .build();
-
-        assertThat(gStats.getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
-        assertThat(gStats.getDatabase()).isEqualTo(TEST_DATA_BASE);
-        assertThat(gStats.getStatusCode()).isEqualTo(TEST_STATUS_CODE);
-        assertThat(gStats.getTotalLatencyMillis()).isEqualTo(TEST_TOTAL_LATENCY_MILLIS);
-    }
-
-    /** Make sure status code is UNKNOWN if not set in {@link GeneralStats} */
-    @Test
-    public void testAppSearchStats_GeneralStats_defaultStatsCode_Unknown() {
-        final GeneralStats gStats =
-                new GeneralStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE)
-                        .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS)
-                        .build();
-
-        assertThat(gStats.getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
-        assertThat(gStats.getDatabase()).isEqualTo(TEST_DATA_BASE);
-        assertThat(gStats.getStatusCode()).isEqualTo(AppSearchResult.RESULT_UNKNOWN_ERROR);
-        assertThat(gStats.getTotalLatencyMillis()).isEqualTo(TEST_TOTAL_LATENCY_MILLIS);
-    }
-
-    @Test
     public void testAppSearchStats_CallStats() {
         final int estimatedBinderLatencyMillis = 1;
         final int numOperationsSucceeded = 2;
         final int numOperationsFailed = 3;
         final @CallStats.CallType int callType = CallStats.CALL_TYPE_PUT_DOCUMENTS;
 
-        final CallStats.Builder cStatsBuilder =
-                new CallStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE)
+        final CallStats cStats =
+                new CallStats.Builder()
+                        .setPackageName(TEST_PACKAGE_NAME)
+                        .setDatabase(TEST_DATA_BASE)
+                        .setStatusCode(TEST_STATUS_CODE)
+                        .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS)
                         .setCallType(callType)
                         .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
                         .setNumOperationsSucceeded(numOperationsSucceeded)
-                        .setNumOperationsFailed(numOperationsFailed);
-        cStatsBuilder
-                .getGeneralStatsBuilder()
-                .setStatusCode(TEST_STATUS_CODE)
-                .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS);
-        final CallStats cStats = cStatsBuilder.build();
+                        .setNumOperationsFailed(numOperationsFailed)
+                        .build();
 
-        assertThat(cStats.getGeneralStats().getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
-        assertThat(cStats.getGeneralStats().getDatabase()).isEqualTo(TEST_DATA_BASE);
-        assertThat(cStats.getGeneralStats().getStatusCode()).isEqualTo(TEST_STATUS_CODE);
-        assertThat(cStats.getGeneralStats().getTotalLatencyMillis())
-                .isEqualTo(TEST_TOTAL_LATENCY_MILLIS);
+        assertThat(cStats.getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
+        assertThat(cStats.getDatabase()).isEqualTo(TEST_DATA_BASE);
+        assertThat(cStats.getStatusCode()).isEqualTo(TEST_STATUS_CODE);
+        assertThat(cStats.getTotalLatencyMillis()).isEqualTo(TEST_TOTAL_LATENCY_MILLIS);
         assertThat(cStats.getEstimatedBinderLatencyMillis())
                 .isEqualTo(estimatedBinderLatencyMillis);
         assertThat(cStats.getCallType()).isEqualTo(callType);
@@ -88,6 +59,19 @@
     }
 
     @Test
+    public void testAppSearchCallStats_nullValues() {
+        final @CallStats.CallType int callType = CallStats.CALL_TYPE_PUT_DOCUMENTS;
+
+        final CallStats.Builder cStatsBuilder = new CallStats.Builder().setCallType(callType);
+
+        final CallStats cStats = cStatsBuilder.build();
+
+        assertThat(cStats.getPackageName()).isNull();
+        assertThat(cStats.getDatabase()).isNull();
+        assertThat(cStats.getCallType()).isEqualTo(callType);
+    }
+
+    @Test
     public void testAppSearchStats_PutDocumentStats() {
         final int generateDocumentProtoLatencyMillis = 1;
         final int rewriteDocumentTypesLatencyMillis = 2;
@@ -100,6 +84,8 @@
         final boolean nativeExceededMaxNumTokens = true;
         final PutDocumentStats.Builder pStatsBuilder =
                 new PutDocumentStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE)
+                        .setStatusCode(TEST_STATUS_CODE)
+                        .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS)
                         .setGenerateDocumentProtoLatencyMillis(generateDocumentProtoLatencyMillis)
                         .setRewriteDocumentTypesLatencyMillis(rewriteDocumentTypesLatencyMillis)
                         .setNativeLatencyMillis(nativeLatencyMillis)
@@ -109,17 +95,13 @@
                         .setNativeDocumentSizeBytes(nativeDocumentSize)
                         .setNativeNumTokensIndexed(nativeNumTokensIndexed)
                         .setNativeExceededMaxNumTokens(nativeExceededMaxNumTokens);
-        pStatsBuilder
-                .getGeneralStatsBuilder()
-                .setStatusCode(TEST_STATUS_CODE)
-                .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS);
+
         final PutDocumentStats pStats = pStatsBuilder.build();
 
-        assertThat(pStats.getGeneralStats().getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
-        assertThat(pStats.getGeneralStats().getDatabase()).isEqualTo(TEST_DATA_BASE);
-        assertThat(pStats.getGeneralStats().getStatusCode()).isEqualTo(TEST_STATUS_CODE);
-        assertThat(pStats.getGeneralStats().getTotalLatencyMillis())
-                .isEqualTo(TEST_TOTAL_LATENCY_MILLIS);
+        assertThat(pStats.getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
+        assertThat(pStats.getDatabase()).isEqualTo(TEST_DATA_BASE);
+        assertThat(pStats.getStatusCode()).isEqualTo(TEST_STATUS_CODE);
+        assertThat(pStats.getTotalLatencyMillis()).isEqualTo(TEST_TOTAL_LATENCY_MILLIS);
         assertThat(pStats.getGenerateDocumentProtoLatencyMillis())
                 .isEqualTo(generateDocumentProtoLatencyMillis);
         assertThat(pStats.getRewriteDocumentTypesLatencyMillis())
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 7241fa0..90a1277 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -253,6 +253,7 @@
                 .setPerson(makePerson("person", "personKey", "personUri"))
                 .setLongLived(true)
                 .setExtras(pb)
+                .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen)
                 .build();
         si.addFlags(ShortcutInfo.FLAG_PINNED);
         si.setBitmapPath("abc");
@@ -288,6 +289,8 @@
         assertEquals(null, si.getTextResName());
         assertEquals(0, si.getDisabledMessageResourceId());
         assertEquals(null, si.getDisabledMessageResName());
+        assertEquals("android:style/Theme.Black.NoTitleBar.Fullscreen",
+                si.getStartingThemeResName());
     }
 
     public void testShortcutInfoParcel_resId() {
@@ -308,6 +311,7 @@
                 .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
                 .setRank(123)
                 .setExtras(pb)
+                .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen)
                 .build();
         si.addFlags(ShortcutInfo.FLAG_PINNED);
         si.setBitmapPath("abc");
@@ -339,6 +343,8 @@
         assertEquals(456, si.getIconResourceId());
         assertEquals("string/r456", si.getIconResName());
         assertEquals("test_uri", si.getIconUri());
+        assertEquals("android:style/Theme.Black.NoTitleBar.Fullscreen",
+                si.getStartingThemeResName());
     }
 
     public void testShortcutInfoClone() {
@@ -2210,6 +2216,10 @@
                 android.R.drawable.alert_dark_frame, true, getTestContext().getPackageName()));
         assertEquals("" + android.R.string.cancel, ShortcutInfo.lookUpResourceName(res,
                 android.R.string.cancel, false, getTestContext().getPackageName()));
+        assertEquals("" + android.R.style.Theme_Black_NoTitleBar_Fullscreen,
+                ShortcutInfo.lookUpResourceName(
+                        res, android.R.style.Theme_Black_NoTitleBar_Fullscreen, true,
+                        getTestContext().getPackageName()));
     }
 
     public void testLookUpResourceName_appResources() {
@@ -2236,6 +2246,10 @@
         assertEquals(android.R.drawable.alert_dark_frame, ShortcutInfo.lookUpResourceId(res,
                 "" + android.R.drawable.alert_dark_frame, null,
                 getTestContext().getPackageName()));
+        assertEquals(android.R.style.Theme_Black_NoTitleBar_Fullscreen,
+                ShortcutInfo.lookUpResourceId(
+                        res, "" + android.R.style.Theme_Black_NoTitleBar_Fullscreen,
+                        null, getTestContext().getPackageName()));
     }
 
     // Test for a ShortcutInfo method.
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
index 00b05d4..14cab02 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
@@ -17,6 +17,7 @@
 package com.android.server.vibrator;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import android.hardware.vibrator.IVibrator;
 import android.os.VibrationEffect;
@@ -25,8 +26,11 @@
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.InstrumentationRegistry;
+
 import org.junit.Before;
 import org.junit.Test;
 
@@ -58,7 +62,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mAdapter = new DeviceVibrationEffectAdapter();
+        mAdapter = new DeviceVibrationEffectAdapter(InstrumentationRegistry.getContext());
     }
 
     @Test
@@ -84,28 +88,19 @@
                 new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f,
                         /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 10),
                 new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
-                        /* startFrequency= */ 0, /* endFrequency= */ 0, /* duration= */ 11),
+                        /* startFrequency= */ 0, /* endFrequency= */ 0, /* duration= */ 100),
                 new RampSegment(/* startAmplitude= */ 0.65f, /* endAmplitude= */ 0.65f,
-                        /* startFrequency= */ 0, /* endFrequency= */ 1, /* duration= */ 200)),
+                        /* startFrequency= */ 0, /* endFrequency= */ 1, /* duration= */ 1000)),
                 /* repeatIndex= */ 3);
 
-        VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, Float.NaN, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.5f, Float.NaN, /* duration= */ 100),
-                // 10ms ramp becomes 2 steps
-                new StepSegment(/* amplitude= */ 1, Float.NaN, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.2f, Float.NaN, /* duration= */ 5),
-                // 11ms ramp becomes 3 steps
-                new StepSegment(/* amplitude= */ 0.8f, Float.NaN, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.6f, Float.NaN, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.2f, Float.NaN, /* duration= */ 1),
-                // 200ms ramp with same amplitude becomes a single step
-                new StepSegment(/* amplitude= */ 0.65f, Float.NaN, /* duration= */ 200)),
-                // Repeat index fixed after intermediate steps added
-                /* repeatIndex= */ 4);
+        VibrationEffect.Composed adaptedEffect = (VibrationEffect.Composed) mAdapter.apply(effect,
+                createVibratorInfo(EMPTY_FREQUENCY_MAPPING));
+        assertTrue(adaptedEffect.getSegments().size() > effect.getSegments().size());
+        assertTrue(adaptedEffect.getRepeatIndex() >= effect.getRepeatIndex());
 
-        VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_MAPPING);
-        assertEquals(expected, mAdapter.apply(effect, info));
+        for (VibrationEffectSegment adaptedSegment : adaptedEffect.getSegments()) {
+            assertTrue(adaptedSegment instanceof StepSegment);
+        }
     }
 
     @Test
@@ -136,33 +131,6 @@
     }
 
     @Test
-    public void testStepAndRampSegments_withPwleCapabilityAndNoFrequency_keepsOriginalSteps() {
-        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
-                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
-                        /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 50),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
-                        /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20)),
-                /* repeatIndex= */ 2);
-
-        VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequency= */ 150, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 150, /* duration= */ 100),
-                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
-                new RampSegment(/* startAmplitude= */ 0.1f, /* endAmplitude= */ 0.8f,
-                        /* startFrequency= */ 50, /* endFrequency= */ 200, /* duration= */ 50),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.1f,
-                        /* startFrequency= */ 200, /* endFrequency= */ 50, /* duration= */ 20)),
-                /* repeatIndex= */ 2);
-
-        VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_MAPPING,
-                IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
-        assertEquals(expected, mAdapter.apply(effect, info));
-    }
-
-    @Test
     public void testStepAndRampSegments_withEmptyFreqMapping_returnsSameAmplitudesAndZeroFreq() {
         VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
                 new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java
new file mode 100644
index 0000000..95c3bd9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.vibrator;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.IntStream;
+
+/**
+ * Tests for {@link RampToStepAdapter}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:RampToStepAdapterTest
+ */
+@Presubmit
+public class RampToStepAdapterTest {
+    private static final int TEST_STEP_DURATION = 5;
+
+    private RampToStepAdapter mAdapter;
+
+    @Before
+    public void setUp() throws Exception {
+        mAdapter = new RampToStepAdapter(TEST_STEP_DURATION);
+    }
+
+    @Test
+    public void testStepAndPrebakedAndPrimitiveSegments_keepsListUnchanged() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
+                new PrebakedSegment(
+                        VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(-1, mAdapter.apply(segments, -1, createVibratorInfo()));
+        assertEquals(1, mAdapter.apply(segments, 1, createVibratorInfo()));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    public void testRampSegments_withPwleCapability_keepsListUnchanged() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
+        assertEquals(0, mAdapter.apply(segments, 0, vibratorInfo));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    public void testRampSegments_withoutPwleCapability_convertsRampsToSteps() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f,
+                        /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequency= */ -3, /* endFrequency= */ 0, /* duration= */ 11),
+                new RampSegment(/* startAmplitude= */ 0.65f, /* endAmplitude= */ 0.65f,
+                        /* startFrequency= */ 0, /* endFrequency= */ 1, /* duration= */ 200)));
+
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
+                // 10ms ramp becomes 2 steps
+                new StepSegment(/* amplitude= */ 1, /* frequency= */ -4, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.2f, /* frequency= */ 2, /* duration= */ 5),
+                // 11ms ramp becomes 3 steps
+                new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ -3, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.6f, /* frequency= */ -2, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.2f, /* frequency= */ 0, /* duration= */ 1),
+                // 200ms ramp with same amplitude becomes a single step
+                new StepSegment(/* amplitude= */ 0.65f, /* frequency= */ 0, /* duration= */ 200));
+
+        // Repeat index fixed after intermediate steps added
+        assertEquals(4, mAdapter.apply(segments, 3, createVibratorInfo()));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    private static VibratorInfo createVibratorInfo(int... capabilities) {
+        return new VibratorInfo.Builder(0)
+                .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
+                .build();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java
new file mode 100644
index 0000000..f4eb2de
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java
@@ -0,0 +1,312 @@
+/*
+ * 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.vibrator;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.IntStream;
+
+/**
+ * Tests for {@link StepToRampAdapter}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:StepToRampAdapterTest
+ */
+@Presubmit
+public class StepToRampAdapterTest {
+    private StepToRampAdapter mAdapter;
+
+    @Before
+    public void setUp() throws Exception {
+        mAdapter = new StepToRampAdapter(/* rampDownDuration= */ 0);
+    }
+
+    @Test
+    public void testRampAndPrebakedAndPrimitiveSegments_returnsOriginalSegments() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f,
+                        /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 10),
+                new PrebakedSegment(
+                        VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(-1, mAdapter.apply(segments, -1, createVibratorInfo()));
+        assertEquals(1, mAdapter.apply(segments, 1, createVibratorInfo()));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    public void testStepAndRampSegments_withoutPwleCapability_keepsListUnchanged() {
+        mAdapter = new StepToRampAdapter(50);
+
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(-1, mAdapter.apply(segments, -1, createVibratorInfo()));
+        assertEquals(0, mAdapter.apply(segments, 0, createVibratorInfo()));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    public void testStepAndRampSegments_withPwleCapabilityAndNoFrequency_keepsOriginalSteps() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 50),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
+        assertEquals(3, mAdapter.apply(segments, 3, vibratorInfo));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    public void testStepAndRampSegments_withPwleCapabilityAndStepNextToRamp_convertsStepsToRamps() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 50),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20),
+                new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ -1, /* duration= */ 60)));
+
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
+                        /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
+                        /* startFrequency= */ 0, /* endFrequency= */ 0, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 50),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.8f,
+                        /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 60));
+
+        VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
+        assertEquals(2, mAdapter.apply(segments, 2, vibratorInfo));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withPwleCapabilityAndFrequency_convertsStepsToRamps() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequency= */ -1, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 1, /* duration= */ 100)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
+                        /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
+                        /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 100));
+
+        VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
+        assertEquals(0, mAdapter.apply(segments, 0, vibratorInfo));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withRampDownEndingAtNonZero_noRampDownAdded() {
+        int rampDownDuration = 50;
+        mAdapter = new StepToRampAdapter(rampDownDuration);
+
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 1, /* duration= */ 100)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+                        /* startFrequency= */ 0, /* endFrequency= */ 0, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.8f,
+                        /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 100));
+
+        VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withRampDownAndShortZeroSegment_replaceWithRampDown() {
+        mAdapter = new StepToRampAdapter(50);
+
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ -1, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, /* duration= */ 20),
+                new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+                        /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0,
+                        /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 20),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30));
+
+        VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        assertEquals(2, mAdapter.apply(segments, 2, vibratorInfo));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withRampDownAndLongZeroSegment_splitAndAddRampDown() {
+        mAdapter = new StepToRampAdapter(50);
+
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ -1, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 150),
+                new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+                        /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0,
+                        /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 50),
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
+                        /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30));
+
+        VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        // Repeat index fixed after intermediate steps added
+        assertEquals(3, mAdapter.apply(segments, 2, vibratorInfo));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withRampDownAndNoZeroSegment_noRampDownAdded() {
+        mAdapter = new StepToRampAdapter(50);
+
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ -1, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+                        /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10));
+
+        VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withRampDownAndRepeatToNonZeroSegment_noRampDownAdded() {
+        mAdapter = new StepToRampAdapter(50);
+
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ -1, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+                        /* startFrequency= */ -1, /* endFrequency= */ -1, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30));
+
+        VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        assertEquals(0, mAdapter.apply(segments, 0, vibratorInfo));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withRampDownAndRepeatToShortZeroSegment_skipAndAppendRampDown() {
+        mAdapter = new StepToRampAdapter(50);
+
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 20),
+                new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
+                        /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 20),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
+                        /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 20));
+
+        VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        // Shift repeat index to the right to use append instead of zero segment.
+        assertEquals(1, mAdapter.apply(segments, 0, vibratorInfo));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withRampDownAndRepeatToLongZeroSegment_splitAndAppendRampDown() {
+        mAdapter = new StepToRampAdapter(50);
+
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 120),
+                new StepSegment(/* amplitude= */ 1, /* frequency= */ 1, /* duration= */ 30)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                // Split long zero segment to skip part of it.
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
+                        /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 50),
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
+                        /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 70),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 30),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
+                        /* startFrequency= */ 1, /* endFrequency= */ 1, /* duration= */ 50));
+
+        VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        // Shift repeat index to the right to use append with part of the zero segment.
+        assertEquals(1, mAdapter.apply(segments, 0, vibratorInfo));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    private static VibratorInfo createVibratorInfo(int... capabilities) {
+        return new VibratorInfo.Builder(0)
+                .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
+                .build();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index ac3e05d..f02e2f0 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
 import android.hardware.vibrator.Braking;
 import android.hardware.vibrator.IVibrator;
 import android.hardware.vibrator.IVibratorManager;
@@ -98,13 +99,17 @@
     private IBatteryStats mIBatteryStatsMock;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
+    private DeviceVibrationEffectAdapter mEffectAdapter;
     private PowerManager.WakeLock mWakeLock;
     private TestLooper mTestLooper;
 
     @Before
     public void setUp() throws Exception {
         mTestLooper = new TestLooper();
-        mWakeLock = InstrumentationRegistry.getContext().getSystemService(
+
+        Context context = InstrumentationRegistry.getContext();
+        mEffectAdapter = new DeviceVibrationEffectAdapter(context);
+        mWakeLock = context.getSystemService(
                 PowerManager.class).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
 
         mockVibrators(VIBRATOR_ID);
@@ -985,8 +990,8 @@
     }
 
     private VibrationThread startThreadAndDispatcher(Vibration vib) {
-        VibrationThread thread = new VibrationThread(vib, createVibratorControllers(), mWakeLock,
-                mIBatteryStatsMock, mThreadCallbacks);
+        VibrationThread thread = new VibrationThread(vib, mEffectAdapter,
+                createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mThreadCallbacks);
         doAnswer(answer -> {
             thread.vibratorComplete(answer.getArgument(0));
             return null;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 5614aa2..577e36c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -394,7 +394,7 @@
                 "disabledMessage", 0, "disabledMessageResName",
                 null, null, 0, null, 0, 0,
                 0, "iconResName", "bitmapPath", null, 0,
-                null, null, 0);
+                null, null, null);
         return si;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 3f1248a..a1b3159 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -796,6 +796,9 @@
         @Override
         public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
         }
+        @Override
+        public void onAppSplashScreenViewRemoved(int taskId) {
+        }
     };
 
     private ActivityRecord makePipableActivity() {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index a14912e..bc812c2 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -562,11 +562,10 @@
         }
 
         void switchImplementationIfNeededLocked(boolean force) {
-            if (!mCurUserSupported || mTemporarilyDisabled) {
+            if (!mCurUserSupported) {
                 if (DEBUG_USER) {
                     Slog.d(TAG, "switchImplementationIfNeeded(): skipping: force= " + force
-                            + "mCurUserSupported=" + mCurUserSupported
-                            + "mTemporarilyDisabled=" + mTemporarilyDisabled);
+                            + "mCurUserSupported=" + mCurUserSupported);
                 }
                 if (mImpl != null) {
                     mImpl.shutdownLocked();
@@ -1048,13 +1047,16 @@
                     if (DEBUG) Slog.d(TAG, "setDisabled(): already " + disabled);
                     return;
                 }
-                Slog.i(TAG, "setDisabled(): changing to " + disabled);
-                final long caller = Binder.clearCallingIdentity();
-                try {
-                    mTemporarilyDisabled = disabled;
-                    switchImplementationIfNeeded(/* force= */ false);
-                } finally {
-                    Binder.restoreCallingIdentity(caller);
+                mTemporarilyDisabled = disabled;
+                if (mTemporarilyDisabled) {
+                    Slog.i(TAG, "setDisabled(): temporarily disabling and hiding current session");
+                    try {
+                        hideCurrentSession();
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to call hideCurrentSession", e);
+                    }
+                } else {
+                    Slog.i(TAG, "setDisabled(): re-enabling");
                 }
             }
         }
@@ -1508,12 +1510,20 @@
         public boolean showSessionForActiveService(Bundle args, int sourceFlags,
                 IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken) {
             enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE);
+            if (DEBUG_USER) Slog.d(TAG, "showSessionForActiveService()");
+
             synchronized (this) {
                 if (mImpl == null) {
                     Slog.w(TAG, "showSessionForActiveService without running voice interaction"
                             + "service");
                     return false;
                 }
+                if (mTemporarilyDisabled) {
+                    Slog.i(TAG, "showSessionForActiveService(): ignored while temporarily "
+                            + "disabled");
+                    return false;
+                }
+
                 final long caller = Binder.clearCallingIdentity();
                 try {
                     return mImpl.showSessionLocked(args,
@@ -1530,22 +1540,21 @@
         @Override
         public void hideCurrentSession() throws RemoteException {
             enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE);
-            synchronized (this) {
-                if (mImpl == null) {
-                    return;
-                }
-                final long caller = Binder.clearCallingIdentity();
-                try {
-                    if (mImpl.mActiveSession != null && mImpl.mActiveSession.mSession != null) {
-                        try {
-                            mImpl.mActiveSession.mSession.closeSystemDialogs();
-                        } catch (RemoteException e) {
-                            Log.w(TAG, "Failed to call closeSystemDialogs", e);
-                        }
+
+            if (mImpl == null) {
+                return;
+            }
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                if (mImpl.mActiveSession != null && mImpl.mActiveSession.mSession != null) {
+                    try {
+                        mImpl.mActiveSession.mSession.closeSystemDialogs();
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to call closeSystemDialogs", e);
                     }
-                } finally {
-                    Binder.restoreCallingIdentity(caller);
                 }
+            } finally {
+                Binder.restoreCallingIdentity(caller);
             }
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java
index 6c355a3..2e3ca01 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java
@@ -71,6 +71,7 @@
             pw.println("");
             pw.println("  hide");
             pw.println("    Hides the current session");
+            pw.println("");
             pw.println("  disable [true|false]");
             pw.println("    Temporarily disable (when true) service");
             pw.println("");