Merge "Always submit after texture uploads" into sc-dev
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 b52a503..666d497 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -795,7 +795,7 @@
                     AppSearchUserInstance instance =
                             mAppSearchUserInstanceManager.getUserInstance(callingUser);
                     SearchResultPage searchResultPage =
-                            instance.getAppSearchImpl().getNextPage(nextPageToken);
+                            instance.getAppSearchImpl().getNextPage(packageName, nextPageToken);
                     invokeCallbackOnResult(
                             callback,
                             AppSearchResult.newSuccessfulResult(searchResultPage.getBundle()));
@@ -821,7 +821,7 @@
                     verifyNotInstantApp(userContext, packageName);
                     AppSearchUserInstance instance =
                             mAppSearchUserInstanceManager.getUserInstance(callingUser);
-                    instance.getAppSearchImpl().invalidateNextPageToken(nextPageToken);
+                    instance.getAppSearchImpl().invalidateNextPageToken(packageName, nextPageToken);
                 } catch (Throwable t) {
                     Log.e(TAG, "Unable to invalidate the query page token", t);
                 }
@@ -871,7 +871,7 @@
                                                 .getGenericDocument().getBundle());
                             }
                             searchResultPage = instance.getAppSearchImpl().getNextPage(
-                                    searchResultPage.getNextPageToken());
+                                    packageName, searchResultPage.getNextPageToken());
                         }
                     }
                     invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
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 a1b93ce..830e76c 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
@@ -173,6 +173,21 @@
     @GuardedBy("mReadWriteLock")
     private final Map<String, Integer> mDocumentCountMapLocked = new ArrayMap<>();
 
+    // Maps packages to the set of valid nextPageTokens that the package can manipulate. A token
+    // is unique and constant per query (i.e. the same token '123' is used to iterate through
+    // pages of search results). The tokens themselves are generated and tracked by
+    // IcingSearchEngine. IcingSearchEngine considers a token valid and won't be reused
+    // until we call invalidateNextPageToken on the token.
+    //
+    // Note that we synchronize on itself because the nextPageToken cache is checked at
+    // query-time, and queries are done in parallel with a read lock. Ideally, this would be
+    // guarded by the normal mReadWriteLock.writeLock, but ReentrantReadWriteLocks can't upgrade
+    // read to write locks. This lock should be acquired at the smallest scope possible.
+    // mReadWriteLock is a higher-level lock, so calls shouldn't be made out
+    // to any functions that grab the lock.
+    @GuardedBy("mNextPageTokensLocked")
+    private final Map<String, Set<Long>> mNextPageTokensLocked = new ArrayMap<>();
+
     /**
      * The counter to check when to call {@link #checkForOptimize}. The interval is {@link
      * #CHECK_OPTIMIZE_INTERVAL}.
@@ -837,12 +852,15 @@
             String prefix = createPrefix(packageName, databaseName);
             Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemasLocked(prefix, searchSpec);
 
-            return doQueryLocked(
-                    Collections.singleton(createPrefix(packageName, databaseName)),
-                    allowedPrefixedSchemas,
-                    queryExpression,
-                    searchSpec,
-                    sStatsBuilder);
+            SearchResultPage searchResultPage =
+                    doQueryLocked(
+                            Collections.singleton(createPrefix(packageName, databaseName)),
+                            allowedPrefixedSchemas,
+                            queryExpression,
+                            searchSpec,
+                            sStatsBuilder);
+            addNextPageToken(packageName, searchResultPage.getNextPageToken());
+            return searchResultPage;
         } finally {
             mReadWriteLock.readLock().unlock();
             if (logger != null) {
@@ -956,12 +974,15 @@
                 }
             }
 
-            return doQueryLocked(
-                    prefixFilters,
-                    prefixedSchemaFilters,
-                    queryExpression,
-                    searchSpec,
-                    sStatsBuilder);
+            SearchResultPage searchResultPage =
+                    doQueryLocked(
+                            prefixFilters,
+                            prefixedSchemaFilters,
+                            queryExpression,
+                            searchSpec,
+                            sStatsBuilder);
+            addNextPageToken(callerPackageName, searchResultPage.getNextPageToken());
+            return searchResultPage;
         } finally {
             mReadWriteLock.readLock().unlock();
 
@@ -1093,17 +1114,20 @@
      *
      * <p>This method belongs to query group.
      *
+     * @param packageName Package name of the caller.
      * @param nextPageToken The token of pre-loaded results of previously executed query.
      * @return The next page of results of previously executed query.
-     * @throws AppSearchException on IcingSearchEngine error.
+     * @throws AppSearchException on IcingSearchEngine error or if can't advance on nextPageToken.
      */
     @NonNull
-    public SearchResultPage getNextPage(long nextPageToken) throws AppSearchException {
+    public SearchResultPage getNextPage(@NonNull String packageName, long nextPageToken)
+            throws AppSearchException {
         mReadWriteLock.readLock().lock();
         try {
             throwIfClosedLocked();
 
             mLogUtil.piiTrace("getNextPage, request", nextPageToken);
+            checkNextPageToken(packageName, nextPageToken);
             SearchResultProto searchResultProto =
                     mIcingSearchEngineLocked.getNextPage(nextPageToken);
             mLogUtil.piiTrace(
@@ -1122,16 +1146,26 @@
      *
      * <p>This method belongs to query group.
      *
+     * @param packageName Package name of the caller.
      * @param nextPageToken The token of pre-loaded results of previously executed query to be
      *     Invalidated.
+     * @throws AppSearchException if nextPageToken is unusable.
      */
-    public void invalidateNextPageToken(long nextPageToken) {
+    public void invalidateNextPageToken(@NonNull String packageName, long nextPageToken)
+            throws AppSearchException {
         mReadWriteLock.readLock().lock();
         try {
             throwIfClosedLocked();
 
             mLogUtil.piiTrace("invalidateNextPageToken, request", nextPageToken);
+            checkNextPageToken(packageName, nextPageToken);
             mIcingSearchEngineLocked.invalidateNextPageToken(nextPageToken);
+
+            synchronized (mNextPageTokensLocked) {
+                // At this point, we're guaranteed that this nextPageToken exists for this package,
+                // otherwise checkNextPageToken would've thrown an exception.
+                mNextPageTokensLocked.get(packageName).remove(nextPageToken);
+            }
         } finally {
             mReadWriteLock.readLock().unlock();
         }
@@ -1568,6 +1602,9 @@
                 Set<String> databaseNames = entry.getValue();
                 if (!installedPackages.contains(packageName) && databaseNames != null) {
                     mDocumentCountMapLocked.remove(packageName);
+                    synchronized (mNextPageTokensLocked) {
+                        mNextPageTokensLocked.remove(packageName);
+                    }
                     for (String databaseName : databaseNames) {
                         String removedPrefix = createPrefix(packageName, databaseName);
                         mSchemaMapLocked.remove(removedPrefix);
@@ -1601,6 +1638,9 @@
         mSchemaMapLocked.clear();
         mNamespaceMapLocked.clear();
         mDocumentCountMapLocked.clear();
+        synchronized (mNextPageTokensLocked) {
+            mNextPageTokensLocked.clear();
+        }
         if (initStatsBuilder != null) {
             initStatsBuilder
                     .setHasReset(true)
@@ -2015,6 +2055,32 @@
         return schemaProto.getSchema();
     }
 
+    private void addNextPageToken(String packageName, long nextPageToken) {
+        synchronized (mNextPageTokensLocked) {
+            Set<Long> tokens = mNextPageTokensLocked.get(packageName);
+            if (tokens == null) {
+                tokens = new ArraySet<>();
+                mNextPageTokensLocked.put(packageName, tokens);
+            }
+            tokens.add(nextPageToken);
+        }
+    }
+
+    private void checkNextPageToken(String packageName, long nextPageToken)
+            throws AppSearchException {
+        synchronized (mNextPageTokensLocked) {
+            Set<Long> nextPageTokens = mNextPageTokensLocked.get(packageName);
+            if (nextPageTokens == null || !nextPageTokens.contains(nextPageToken)) {
+                throw new AppSearchException(
+                        AppSearchResult.RESULT_SECURITY_ERROR,
+                        "Package \""
+                                + packageName
+                                + "\" cannot use nextPageToken: "
+                                + nextPageToken);
+            }
+        }
+    }
+
     private static void addToMap(
             Map<String, Set<String>> map, String prefix, String prefixedValue) {
         Set<String> values = map.get(prefix);
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index ed80ddb..9f52954 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -256,6 +256,7 @@
     AlarmHandler mHandler;
     AppWakeupHistory mAppWakeupHistory;
     AppWakeupHistory mAllowWhileIdleHistory;
+    AppWakeupHistory mAllowWhileIdleCompatHistory;
     private final SparseLongArray mLastPriorityAlarmDispatch = new SparseLongArray();
     private final SparseArray<RingBuffer<RemovedAlarm>> mRemovalHistory = new SparseArray<>();
     ClockReceiver mClockReceiver;
@@ -1633,6 +1634,7 @@
 
             mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
             mAllowWhileIdleHistory = new AppWakeupHistory(INTERVAL_HOUR);
+            mAllowWhileIdleCompatHistory = new AppWakeupHistory(INTERVAL_HOUR);
 
             mNextWakeup = mNextNonWakeup = 0;
 
@@ -2142,20 +2144,23 @@
             final int userId = UserHandle.getUserId(alarm.creatorUid);
             final int quota;
             final long window;
+            final AppWakeupHistory history;
             if ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0) {
                 quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
                 window = mConstants.ALLOW_WHILE_IDLE_WINDOW;
+                history = mAllowWhileIdleHistory;
             } else {
                 quota = mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
                 window = mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW;
+                history = mAllowWhileIdleCompatHistory;
             }
-            final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
+            final int dispatchesInHistory = history.getTotalWakeupsInWindow(
                     alarm.sourcePackage, userId);
-            if (dispatchesInWindow < quota) {
+            if (dispatchesInHistory < quota) {
                 // fine to go out immediately.
                 batterySaverPolicyElapsed = nowElapsed;
             } else {
-                batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
+                batterySaverPolicyElapsed = history.getNthLastWakeupForPackage(
                         alarm.sourcePackage, userId, quota) + window;
             }
         } else if ((alarm.flags & FLAG_PRIORITIZE) != 0) {
@@ -2201,20 +2206,23 @@
             final int userId = UserHandle.getUserId(alarm.creatorUid);
             final int quota;
             final long window;
+            final AppWakeupHistory history;
             if ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0) {
                 quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
                 window = mConstants.ALLOW_WHILE_IDLE_WINDOW;
+                history = mAllowWhileIdleHistory;
             } else {
                 quota = mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
                 window = mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW;
+                history = mAllowWhileIdleCompatHistory;
             }
-            final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
+            final int dispatchesInHistory = history.getTotalWakeupsInWindow(
                     alarm.sourcePackage, userId);
-            if (dispatchesInWindow < quota) {
+            if (dispatchesInHistory < quota) {
                 // fine to go out immediately.
                 deviceIdlePolicyTime = nowElapsed;
             } else {
-                final long whenInQuota = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
+                final long whenInQuota = history.getNthLastWakeupForPackage(
                         alarm.sourcePackage, userId, quota) + window;
                 deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed());
             }
@@ -2502,6 +2510,7 @@
                         Binder.getCallingPid(), callingUid, "AlarmManager.setPrioritized");
                 // The API doesn't allow using both together.
                 flags &= ~FLAG_ALLOW_WHILE_IDLE;
+                // Prioritized alarms don't need any extra permission to be exact.
             } else if (exact || allowWhileIdle) {
                 final boolean needsPermission;
                 boolean lowerQuota;
@@ -2992,6 +3001,10 @@
             mAllowWhileIdleHistory.dump(pw, nowELAPSED);
             pw.println();
 
+            pw.println("Allow while idle compat history:");
+            mAllowWhileIdleCompatHistory.dump(pw, nowELAPSED);
+            pw.println();
+
             if (mLastPriorityAlarmDispatch.size() > 0) {
                 pw.println("Last priority alarm dispatches:");
                 pw.increaseIndent();
@@ -4553,6 +4566,7 @@
                             removeUserLocked(userHandle);
                             mAppWakeupHistory.removeForUser(userHandle);
                             mAllowWhileIdleHistory.removeForUser(userHandle);
+                            mAllowWhileIdleCompatHistory.removeForUser(userHandle);
                         }
                         return;
                     case Intent.ACTION_UID_REMOVED:
@@ -4588,6 +4602,8 @@
                             // package-removed and package-restarted case
                             mAppWakeupHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
                             mAllowWhileIdleHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
+                            mAllowWhileIdleCompatHistory.removeForPackage(pkg,
+                                    UserHandle.getUserId(uid));
                             removeLocked(uid, REMOVE_REASON_UNDEFINED);
                         } else {
                             // external-applications-unavailable case
@@ -4965,7 +4981,10 @@
                 if (isAllowedWhileIdleRestricted(alarm)) {
                     // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm while the
                     // device was in doze or battery saver.
-                    mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage,
+                    final AppWakeupHistory history = ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0)
+                            ? mAllowWhileIdleHistory
+                            : mAllowWhileIdleCompatHistory;
+                    history.recordAlarmForPackage(alarm.sourcePackage,
                             UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
                     mAlarmStore.updateAlarmDeliveries(a -> {
                         if (a.creatorUid != alarm.creatorUid || !isAllowedWhileIdleRestricted(a)) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 91e2d88..4376d22 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -406,6 +406,12 @@
     public static final int BROADCAST_FAILED_USER_STOPPED = -2;
 
     /**
+     * Type for IActivityManaqer.getIntentSender: this PendingIntent type is unknown.
+     * @hide
+     */
+    public static final int INTENT_SENDER_UNKNOWN = 0;
+
+    /**
      * Type for IActivityManaqer.getIntentSender: this PendingIntent is
      * for a sendBroadcast operation.
      * @hide
@@ -4860,12 +4866,12 @@
      */
     public static final class PendingIntentInfo implements Parcelable {
 
-        private final String mCreatorPackage;
+        @Nullable private final String mCreatorPackage;
         private final int mCreatorUid;
         private final boolean mImmutable;
         private final int mIntentSenderType;
 
-        public PendingIntentInfo(String creatorPackage, int creatorUid, boolean immutable,
+        public PendingIntentInfo(@Nullable String creatorPackage, int creatorUid, boolean immutable,
                 int intentSenderType) {
             mCreatorPackage = creatorPackage;
             mCreatorUid = creatorUid;
@@ -4873,6 +4879,7 @@
             mIntentSenderType = intentSenderType;
         }
 
+        @Nullable
         public String getCreatorPackage() {
             return mCreatorPackage;
         }
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 444cc4e..85758a9 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -28,11 +28,13 @@
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.DisplayCutout;
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
 
@@ -174,6 +176,15 @@
     public PictureInPictureParams pictureInPictureParams;
 
     /**
+     * The {@link Rect} copied from {@link DisplayCutout#getSafeInsets()} if the cutout is not of
+     * (LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS),
+     * {@code null} otherwise.
+     * @hide
+     */
+    @Nullable
+    public Rect displayCutoutInsets;
+
+    /**
      * The activity type of the top activity in this task.
      * @hide
      */
@@ -332,6 +343,7 @@
                 && supportsMultiWindow == that.supportsMultiWindow
                 && Objects.equals(positionInParent, that.positionInParent)
                 && Objects.equals(pictureInPictureParams, that.pictureInPictureParams)
+                && Objects.equals(displayCutoutInsets, that.displayCutoutInsets)
                 && getWindowingMode() == that.getWindowingMode()
                 && Objects.equals(taskDescription, that.taskDescription)
                 && isFocused == that.isFocused
@@ -382,6 +394,7 @@
         token = WindowContainerToken.CREATOR.createFromParcel(source);
         topActivityType = source.readInt();
         pictureInPictureParams = source.readTypedObject(PictureInPictureParams.CREATOR);
+        displayCutoutInsets = source.readTypedObject(Rect.CREATOR);
         topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR);
         isResizeable = source.readBoolean();
         source.readBinderList(launchCookies);
@@ -419,6 +432,7 @@
         token.writeToParcel(dest, flags);
         dest.writeInt(topActivityType);
         dest.writeTypedObject(pictureInPictureParams, flags);
+        dest.writeTypedObject(displayCutoutInsets, flags);
         dest.writeTypedObject(topActivityInfo, flags);
         dest.writeBoolean(isResizeable);
         dest.writeBinderList(launchCookies);
@@ -447,6 +461,7 @@
                 + " token=" + token
                 + " topActivityType=" + topActivityType
                 + " pictureInPictureParams=" + pictureInPictureParams
+                + " displayCutoutSafeInsets=" + displayCutoutInsets
                 + " topActivityInfo=" + topActivityInfo
                 + " launchCookies=" + launchCookies
                 + " positionInParent=" + positionInParent
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 0e22705..bdb7900 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -252,7 +252,8 @@
      */
     public boolean checkCallingUid() {
         final int callingUid = Binder.getCallingUid();
-        if (callingUid != Process.SYSTEM_UID
+        if (callingUid != Process.ROOT_UID
+                && callingUid != Process.SYSTEM_UID
                 && callingUid != mAttributionSourceState.uid) {
             return false;
         }
diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java
index 35d00f0..9309c5b 100644
--- a/core/java/android/hardware/location/ContextHubClientCallback.java
+++ b/core/java/android/hardware/location/ContextHubClientCallback.java
@@ -68,8 +68,11 @@
 
     /**
      * Callback invoked when a nanoapp is dynamically loaded at the attached Context Hub through
-     * the {@link android.hardware.location.ContextHubManager}. This callback is not invoked for a
-     * nanoapp that is loaded internally by CHRE (e.g. nanoapps that are preloaded by the system).
+     * the {@link android.hardware.location.ContextHubManager}.
+     *
+     * NOTE: This callback is <b>not</b> invoked for a nanoapp that is loaded internally by CHRE
+     * (e.g. nanoapps that are preloaded by the system). To check the availability of these
+     * nanoapps, use the {@link ContextHubManager#queryNanoApps(ContextHubInfo)} API.
      *
      * @param client the client that is associated with this callback
      * @param nanoAppId the ID of the nanoapp that had been loaded
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index f69a7d7..9af0e09 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -128,7 +128,8 @@
     public static final int AUTHORIZATION_GRANTED = 2;
 
     /**
-     * Constants describing the type of events from a Context Hub.
+     * Constants describing the type of events from a Context Hub, as defined in
+     * {@link ContextHubClientCallback}.
      * {@hide}
      */
     @Retention(RetentionPolicy.SOURCE)
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2806850..3550a31 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1955,22 +1955,23 @@
        return mBoundsLayer;
     }
 
-    Surface getOrCreateBLASTSurface(int width, int height,
-            @Nullable WindowManager.LayoutParams params) {
+    Surface getOrCreateBLASTSurface() {
         if (!mSurfaceControl.isValid()) {
             return null;
         }
 
-        int format = params == null ? PixelFormat.TRANSLUCENT : params.format;
         Surface ret = null;
         if (mBlastBufferQueue == null) {
-            mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, width, height,
-                    format);
+            mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
+                mSurfaceSize.x, mSurfaceSize.y,
+                mWindowAttributes.format);
             // We only return the Surface the first time, as otherwise
             // it hasn't changed and there is no need to update.
             ret = mBlastBufferQueue.createSurface();
         } else {
-            mBlastBufferQueue.update(mSurfaceControl, width, height, format);
+            mBlastBufferQueue.update(mSurfaceControl,
+                mSurfaceSize.x, mSurfaceSize.y,
+                mWindowAttributes.format);
         }
 
         return ret;
@@ -7784,8 +7785,7 @@
             if (!useBLAST()) {
                 mSurface.copyFrom(mSurfaceControl);
             } else {
-                final Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x, mSurfaceSize.y,
-                        params);
+                final Surface blastSurface = getOrCreateBLASTSurface();
                 // If blastSurface == null that means it hasn't changed since the last time we
                 // called. In this situation, avoid calling transferFrom as we would then
                 // inc the generation ID and cause EGL resources to be recreated.
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 472e3e7..c110ab9 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -365,6 +365,10 @@
         mDuration = PULL_TIME;
 
         mPullDistance += deltaDistance;
+        if (edgeEffectBehavior == TYPE_STRETCH) {
+            // Don't allow stretch beyond 1
+            mPullDistance = Math.min(1f, mPullDistance);
+        }
         mDistance = Math.max(0f, mPullDistance);
         mVelocity = 0;
 
@@ -783,6 +787,10 @@
                 + mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT));
         mDistance = (float) distance / mHeight;
         mVelocity = (float) velocity;
+        if (mDistance > 1f) {
+            mDistance = 1f;
+            mVelocity = 0f;
+        }
         if (isAtEquilibrium()) {
             mDistance = 0;
             mVelocity = 0;
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 667dd9c..93e25a6 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -2287,10 +2287,10 @@
     <string name="window_magnification_prompt_content" msgid="8159173903032344891">"Nú geturðu stækkað hluta skjásins"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Kveikja á í stillingum"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Hunsa"</string>
-    <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="2420858361276370367">"Opna á hljóðnema tækisins"</string>
+    <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="2420858361276370367">"Opna fyrir hljóðnema tækisins"</string>
     <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="7287720213963466672">"Opna fyrir myndavél tækisins"</string>
     <string name="sensor_privacy_start_use_notification_content_text" msgid="7595608891015777346">"Fyrir &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; og öll forrit og þjónustur"</string>
-    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7089318886628390827">"Opna á"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7089318886628390827">"Opna fyrir"</string>
     <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Persónuvernd skynjara"</string>
     <string name="splash_screen_view_icon_description" msgid="180638751260598187">"Forritstákn"</string>
     <string name="splash_screen_view_branding_description" msgid="7911129347402728216">"Mynd af merki forrits"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 5139522..68a5ce7 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -142,7 +142,7 @@
     <string name="wfcSpnFormat_wifi" msgid="1376356951297043426">"WLAN"</string>
     <string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"WLAN 通话"</string>
     <string name="wfcSpnFormat_vowifi" msgid="8371335230890725606">"VoWifi"</string>
-    <string name="wifi_calling_off_summary" msgid="5626710010766902560">"关闭"</string>
+    <string name="wifi_calling_off_summary" msgid="5626710010766902560">"已关闭"</string>
     <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"通过 WLAN 进行通话"</string>
     <string name="wfc_mode_cellular_preferred_summary" msgid="4958965609212575619">"通过移动网络进行通话"</string>
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"仅限 WLAN"</string>
@@ -1696,8 +1696,8 @@
     <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"同时按住两个音量键几秒钟,即可开启<xliff:g id="SERVICE">%1$s</xliff:g>无障碍功能。这样做可能会改变您设备的工作方式。\n\n您可以在“设置”&gt;“无障碍”中将此快捷方式更改为开启另一项功能。"</string>
     <string name="accessibility_shortcut_on" msgid="5463618449556111344">"开启"</string>
     <string name="accessibility_shortcut_off" msgid="3651336255403648739">"不开启"</string>
-    <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"开启"</string>
-    <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"关闭"</string>
+    <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"已开启"</string>
+    <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"已关闭"</string>
     <string name="accessibility_enable_service_title" msgid="3931558336268541484">"要允许<xliff:g id="SERVICE">%1$s</xliff:g>完全控制您的设备吗?"</string>
     <string name="accessibility_enable_service_encryption_warning" msgid="8603532708618236909">"如果您开启<xliff:g id="SERVICE">%1$s</xliff:g>,您的设备将无法使用屏幕锁定功能来增强数据加密效果。"</string>
     <string name="accessibility_service_warning_description" msgid="291674995220940133">"对于能满足您的无障碍功能需求的应用,可授予其完全控制权限;但对大部分应用来说,都不适合授予此权限。"</string>
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index d8735ce..a743d30 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -183,8 +183,10 @@
     SkPaint paint;
     paint.setAlpha(255);
     paint.setBlendMode(SkBlendMode::kSrc);
-    canvas->drawImageRect(image, imageSrcRect, imageDstRect, sampling, &paint,
-                          SkCanvas::kFast_SrcRectConstraint);
+    const bool hasBufferCrop = cropRect.left < cropRect.right && cropRect.top < cropRect.bottom;
+    auto constraint =
+            hasBufferCrop ? SkCanvas::kStrict_SrcRectConstraint : SkCanvas::kFast_SrcRectConstraint;
+    canvas->drawImageRect(image, imageSrcRect, imageDstRect, sampling, &paint, constraint);
     canvas->restore();
 
     if (!tmpSurface->readPixels(*bitmap, 0, 0)) {
diff --git a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
index 2cf872c..1dcd337 100644
--- a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
@@ -20,7 +20,7 @@
     <string name="chooser_title" msgid="2262294130493605839">"Choisissez un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré par &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
-    <string name="confirmation_title" msgid="8455544820286920304">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pour gérer votre &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="8455544820286920304">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à gérer votre &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_summary" msgid="2059360676631420073">"Cette application est nécessaire pour gérer votre <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string>
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 761b1f4..1b15d20 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -36,7 +36,6 @@
 
     static_libs: [
         "PluginCoreLib",
-        "WindowManager-Shell",
     ],
 
     manifest: "AndroidManifest.xml",
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 8505a62..a50efd7 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -7,6 +7,7 @@
 import android.app.ActivityTaskManager
 import android.app.AppGlobals
 import android.app.PendingIntent
+import android.app.TaskInfo
 import android.content.Context
 import android.graphics.Matrix
 import android.graphics.PorterDuff
@@ -30,8 +31,6 @@
 import android.view.animation.PathInterpolator
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.policy.ScreenDecorationsUtils
-import com.android.wm.shell.startingsurface.SplashscreenContentDrawer
-import com.android.wm.shell.startingsurface.StartingSurface
 import kotlin.math.roundToInt
 
 private const val TAG = "ActivityLaunchAnimator"
@@ -41,8 +40,7 @@
  * nicely into the starting window.
  */
 class ActivityLaunchAnimator(
-    private val keyguardHandler: KeyguardHandler,
-    private val startingSurface: StartingSurface?,
+    private val callback: Callback,
     context: Context
 ) {
     companion object {
@@ -120,7 +118,7 @@
 
         Log.d(TAG, "Starting intent with a launch animation")
         val runner = Runner(controller)
-        val isOnKeyguard = keyguardHandler.isOnKeyguard()
+        val isOnKeyguard = callback.isOnKeyguard()
 
         // Pass the RemoteAnimationAdapter to the intent starter only if we are not on the keyguard.
         val animationAdapter = if (!isOnKeyguard) {
@@ -163,7 +161,7 @@
 
             // Hide the keyguard using the launch animation instead of the default unlock animation.
             if (isOnKeyguard) {
-                keyguardHandler.hideKeyguardWithAnimation(runner)
+                callback.hideKeyguardWithAnimation(runner)
             }
         }
     }
@@ -212,7 +210,7 @@
         fun startPendingIntent(animationAdapter: RemoteAnimationAdapter?): Int
     }
 
-    interface KeyguardHandler {
+    interface Callback {
         /** Whether we are currently on the keyguard or not. */
         fun isOnKeyguard(): Boolean
 
@@ -221,6 +219,9 @@
 
         /** Enable/disable window blur so they don't overlap with the window launch animation **/
         fun setBlursDisabledForAppLaunch(disabled: Boolean)
+
+        /* Get the background color of [task]. */
+        fun getBackgroundColor(task: TaskInfo): Int
     }
 
     /**
@@ -484,12 +485,7 @@
             // which is usually the same color of the app background. We first fade in this layer
             // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
             // launch container and reveal the opening window.
-            val windowBackgroundColor = if (startingSurface != null) {
-                startingSurface.getBackgroundColor(window.taskInfo)
-            } else {
-                Log.w(TAG, "No starting surface, defaulting to SystemBGColor")
-                SplashscreenContentDrawer.getSystemBGColor()
-            }
+            val windowBackgroundColor = callback.getBackgroundColor(window.taskInfo)
             val windowBackgroundLayer = GradientDrawable().apply {
                 setColor(windowBackgroundColor)
                 alpha = 0
@@ -505,7 +501,7 @@
             animator.addListener(object : AnimatorListenerAdapter() {
                 override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
                     Log.d(TAG, "Animation started")
-                    keyguardHandler.setBlursDisabledForAppLaunch(true)
+                    callback.setBlursDisabledForAppLaunch(true)
                     controller.onLaunchAnimationStart(isExpandingFullyAbove)
 
                     // Add the drawable to the launch container overlay. Overlays always draw
@@ -516,7 +512,7 @@
 
                 override fun onAnimationEnd(animation: Animator?) {
                     Log.d(TAG, "Animation ended")
-                    keyguardHandler.setBlursDisabledForAppLaunch(false)
+                    callback.setBlursDisabledForAppLaunch(false)
                     iCallback?.invoke()
                     controller.onLaunchAnimationEnd(isExpandingFullyAbove)
                     launchContainerOverlay.remove(windowBackgroundLayer)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
index 95c2d2e..6d1408d 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
@@ -72,7 +72,8 @@
     }
 
     /**
-     * @return if detail panel should animate when shown or closed
+     * Indicates whether the detail view wants to animate when shown. This has no affect over the
+     * closing animation. Detail panels will always animate when closed.
      */
     default boolean shouldAnimate() {
         return true;
diff --git a/packages/SystemUI/res/drawable/fingerprint_bg.xml b/packages/SystemUI/res/drawable/fingerprint_bg.xml
index 2b0ab6f..558ec08 100644
--- a/packages/SystemUI/res/drawable/fingerprint_bg.xml
+++ b/packages/SystemUI/res/drawable/fingerprint_bg.xml
@@ -14,10 +14,11 @@
 -->
 <shape
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="oval">
 
     <solid
-      android:color="?android:attr/colorBackground"/>
+      android:color="?androidprv:attr/colorSurface"/>
 
     <size
         android:width="64dp"
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index 99e8116..3c9e44e 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -52,6 +52,7 @@
             android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
             android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
             android:layout_gravity="center"
+            android:contentDescription="@null"
             android:scaleType="fitXY" />
 
     </FrameLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 1a91202..b0f1f48 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -55,9 +55,23 @@
         android:id="@+id/lock_icon_view"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:padding="48px"
-        android:layout_gravity="center"
-        android:scaleType="centerCrop"/>
+        android:layout_gravity="center">
+        <!-- Background protection -->
+        <ImageView
+            android:id="@+id/lock_icon_bg"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@drawable/fingerprint_bg"
+            android:visibility="invisible"/>
+
+        <ImageView
+            android:id="@+id/lock_icon"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:padding="48px"
+            android:layout_gravity="center"
+            android:scaleType="centerCrop"/>
+    </com.android.keyguard.LockIconView>
 
     <com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 2e6f01b..cd3966d 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -275,7 +275,7 @@
     <string name="accessibility_quick_settings_location_off" msgid="6122523378294740598">"स्थान अहवाल बंद."</string>
     <string name="accessibility_quick_settings_location_on" msgid="6869947200325467243">"स्थान अहवाल सुरू."</string>
     <string name="accessibility_quick_settings_location_changed_off" msgid="5132776369388699133">"स्थान अहवाल बंद केला."</string>
-    <string name="accessibility_quick_settings_location_changed_on" msgid="7159115433070112154">"स्थान अहवाल सुरू केला."</string>
+    <string name="accessibility_quick_settings_location_changed_on" msgid="7159115433070112154">"स्थान अहवाल देणे सुरू केले."</string>
     <string name="accessibility_quick_settings_alarm" msgid="558094529584082090">"<xliff:g id="TIME">%s</xliff:g> साठी अलार्म सेट केला."</string>
     <string name="accessibility_quick_settings_close" msgid="2974895537860082341">"पॅनल बंद करा."</string>
     <string name="accessibility_quick_settings_more_time" msgid="7646479831704665284">"अधिक वेळ."</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 409eced..2bc2923 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -1149,8 +1149,7 @@
     <string name="new_notification_text_content_description" msgid="2915029960094389291">"<xliff:g id="NAME">%1$s</xliff:g> మెసేజ్‌ను పంపారు: <xliff:g id="NOTIFICATION">%2$s</xliff:g>"</string>
     <string name="new_notification_image_content_description" msgid="6017506886810813123">"<xliff:g id="NAME">%1$s</xliff:g> ఇమేజ్‌ను పంపారు"</string>
     <string name="new_status_content_description" msgid="6046637888641308327">"<xliff:g id="NAME">%1$s</xliff:g>, స్టేటస్‌ను గురించిన అప్‌డేట్‌ను కలిగి ఉన్నారు: <xliff:g id="STATUS">%2$s</xliff:g>"</string>
-    <!-- no translation found for person_available (2318599327472755472) -->
-    <skip />
+    <string name="person_available" msgid="2318599327472755472">"అందుబాటులో ఉంది"</string>
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"మీ బ్యాటరీ మీటర్‌ను చదవడంలో సమస్య"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"మరింత సమాచారం కోసం ట్యాప్ చేయండి"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"అలారం సెట్ చేయలేదు"</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java
deleted file mode 100644
index 7b9ebc0..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shared.system;
-
-import android.app.ActivityManager;
-import android.app.PictureInPictureParams;
-import android.app.TaskInfo;
-import android.content.ComponentName;
-import android.content.pm.ActivityInfo;
-import android.graphics.Rect;
-
-public class TaskInfoCompat {
-
-    public static int getUserId(TaskInfo info) {
-        return info.userId;
-    }
-
-    public static int getActivityType(TaskInfo info) {
-        return info.configuration.windowConfiguration.getActivityType();
-    }
-
-    public static int getWindowingMode(TaskInfo info) {
-        return info.configuration.windowConfiguration.getWindowingMode();
-    }
-
-    public static Rect getWindowConfigurationBounds(TaskInfo info) {
-        return info.configuration.windowConfiguration.getBounds();
-    }
-
-    public static boolean supportsSplitScreenMultiWindow(TaskInfo info) {
-        return info.supportsSplitScreenMultiWindow;
-    }
-
-    public static ComponentName getTopActivity(TaskInfo info) {
-        return info.topActivity;
-    }
-
-    public static ActivityManager.TaskDescription getTaskDescription(TaskInfo info) {
-        return info.taskDescription;
-    }
-
-    public static ActivityInfo getTopActivityInfo(TaskInfo info) {
-        return info.topActivityInfo;
-    }
-
-    public static boolean isAutoEnterPipEnabled(PictureInPictureParams params) {
-        return params.isAutoEnterEnabled();
-    }
-
-    public static Rect getPipSourceRectHint(PictureInPictureParams params) {
-        return params.getSourceRectHint();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bd000b2..e6e2ac9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -285,7 +285,7 @@
     @VisibleForTesting
     protected boolean mTelephonyCapable;
 
-    private final boolean mAcquiredHapticEnabled;
+    private final boolean mAcquiredHapticEnabled = false;
     @Nullable private final Vibrator mVibrator;
 
     // Device provisioning state
@@ -1413,11 +1413,7 @@
     @VisibleForTesting
     public void playAcquiredHaptic() {
         if (mAcquiredHapticEnabled && mVibrator != null) {
-            String effect = Settings.Global.getString(
-                    mContext.getContentResolver(),
-                    "udfps_acquired_type");
-            mVibrator.vibrate(UdfpsController.getVibration(effect,
-                    UdfpsController.EFFECT_TICK),
+            mVibrator.vibrate(UdfpsController.EFFECT_CLICK,
                     UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES);
         }
     }
@@ -1730,8 +1726,6 @@
         mLockPatternUtils = lockPatternUtils;
         mAuthController = authController;
         dumpManager.registerDumpable(getClass().getName(), this);
-        mAcquiredHapticEnabled = Settings.Global.getInt(mContext.getContentResolver(),
-            "udfps_acquired", 0) == 1;
         mVibrator = vibrator;
 
         mHandler = new Handler(mainLooper) {
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index c1d448d..867e3ae 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -16,16 +16,29 @@
 
 package com.android.keyguard;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ArgbEvaluator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.graphics.PointF;
 import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import androidx.annotation.NonNull;
 
+import com.android.settingslib.Utils;
 import com.android.systemui.Dumpable;
+import com.android.systemui.R;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -33,16 +46,96 @@
 /**
  * A view positioned under the notification shade.
  */
-public class LockIconView extends ImageView implements Dumpable {
+public class LockIconView extends FrameLayout implements Dumpable {
     @NonNull private final RectF mSensorRect;
     @NonNull private PointF mLockIconCenter = new PointF(0f, 0f);
     private int mRadius;
 
+    private ImageView mLockIcon;
+    private ImageView mUnlockBgView;
+
+    private AnimatorSet mBgAnimator;
+    private int mLockIconColor;
+    private int mUnlockStartColor;
+    private int mUnlockEndColor;
+
     public LockIconView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mSensorRect = new RectF();
     }
 
+    @Override
+    public void onFinishInflate() {
+        super.onFinishInflate();
+        mLockIcon = findViewById(R.id.lock_icon);
+        mUnlockBgView = findViewById(R.id.lock_icon_bg);
+    }
+
+    void updateColor() {
+        mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
+            R.attr.wallpaperTextColorAccent);
+        mUnlockStartColor = mLockIconColor;
+        mUnlockEndColor = Utils.getColorAttrDefaultColor(getContext(),
+            android.R.attr.textColorPrimary);
+        mUnlockBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg));
+    }
+
+    void setImageDrawable(Drawable drawable) {
+        mLockIcon.setImageDrawable(drawable);
+    }
+
+    void hideBg() {
+        mUnlockBgView.setVisibility(View.INVISIBLE);
+        mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor));
+    }
+
+    void animateBg() {
+        ValueAnimator bgAlphaAnimator = ObjectAnimator.ofFloat(mUnlockBgView, View.ALPHA, 0f, 1f);
+        bgAlphaAnimator.setDuration(133);
+
+        Interpolator interpolator = new PathInterpolator(0f, 0f, 0f, 1f);
+        Animator scaleXAnimator = ObjectAnimator.ofFloat(mUnlockBgView, View.SCALE_X, .9f, 1f);
+        scaleXAnimator.setInterpolator(interpolator);
+        scaleXAnimator.setDuration(300);
+        Animator scaleYAnimator = ObjectAnimator.ofFloat(mUnlockBgView, View.SCALE_Y, .9f, 1f);
+        scaleYAnimator.setDuration(300);
+        scaleYAnimator.setInterpolator(interpolator);
+
+        ValueAnimator lockIconColorAnimator =
+                ValueAnimator.ofObject(new ArgbEvaluator(), mUnlockStartColor, mUnlockEndColor);
+        lockIconColorAnimator.addUpdateListener(
+                animation -> mLockIcon.setImageTintList(
+                    ColorStateList.valueOf((int) animation.getAnimatedValue())));
+        lockIconColorAnimator.setDuration(150);
+
+        if (mBgAnimator != null) {
+            if (mBgAnimator.isRunning()) {
+                return;
+            }
+            mBgAnimator.cancel();
+        }
+        mBgAnimator = new AnimatorSet();
+        mBgAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                mBgAnimator = null;
+            }
+        });
+        mBgAnimator.playTogether(
+                bgAlphaAnimator,
+                scaleYAnimator,
+                scaleXAnimator,
+                lockIconColorAnimator);
+        mBgAnimator.setStartDelay(167);
+        mUnlockBgView.setAlpha(0f);
+        mUnlockBgView.setScaleX(0);
+        mUnlockBgView.setScaleY(0);
+        mUnlockBgView.setVisibility(View.VISIBLE);
+
+        mBgAnimator.start();
+    }
+
     void setCenterLocation(@NonNull PointF center, int radius) {
         mLockIconCenter = center;
         mRadius = radius;
@@ -70,7 +163,6 @@
         return mLockIconCenter.y - mRadius;
     }
 
-
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("Center in px (x, y)= (" + mLockIconCenter.x + ", " + mLockIconCenter.y + ")");
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index afea272..a7bd4c8 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -40,7 +40,6 @@
 import androidx.annotation.Nullable;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 
-import com.android.settingslib.Utils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.biometrics.AuthController;
@@ -145,6 +144,7 @@
         mUnlockedLabel = context.getResources().getString(R.string.accessibility_unlock_button);
         mLockedLabel = context.getResources().getString(R.string.accessibility_lock_icon);
         dumpManager.registerDumpable("LockIconViewController", this);
+
     }
 
     @Override
@@ -224,6 +224,7 @@
             mView.setImageDrawable(mLockIcon);
             mView.setVisibility(View.VISIBLE);
             mView.setContentDescription(mLockedLabel);
+            mView.hideBg();
         } else if (mShowUnlockIcon) {
             if (wasShowingFpIcon) {
                 mView.setImageDrawable(mFpToUnlockIcon);
@@ -234,9 +235,11 @@
                 mLockToUnlockIcon.forceAnimationOnUI();
                 mLockToUnlockIcon.start();
             }
+            mView.animateBg();
             mView.setVisibility(View.VISIBLE);
             mView.setContentDescription(mUnlockedLabel);
         } else {
+            mView.hideBg();
             mView.setVisibility(View.INVISIBLE);
             mView.setContentDescription(null);
         }
@@ -281,11 +284,7 @@
     }
 
     private void updateColors() {
-        final int color = Utils.getColorAttrDefaultColor(mView.getContext(),
-                R.attr.wallpaperTextColorAccent);
-        mFpToUnlockIcon.setTint(color);
-        mLockToUnlockIcon.setTint(color);
-        mLockIcon.setTint(color);
+        mView.updateColor();
     }
 
     private void updateConfiguration() {
@@ -445,6 +444,7 @@
         @Override
         public void onConfigChanged(Configuration newConfig) {
             updateConfiguration();
+            updateColors();
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 099e6f4..57407f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -88,13 +88,10 @@
      * Reload colors from resources.
      **/
     public void reloadColors() {
-        if (mAnimator != null) {
-            mAnimator.reloadColors(getContext());
-	} else {
-            // Needed for old style pin
-            int textColor = Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary)
-                    .getDefaultColor();
-            ((VectorDrawable) getDrawable()).setTintList(ColorStateList.valueOf(textColor));
-        }
+        if (mAnimator != null) mAnimator.reloadColors(getContext());
+
+        int textColor = Utils.getColorAttrDefaultColor(getContext(),
+                android.R.attr.colorBackground);
+        ((VectorDrawable) getDrawable()).setTintList(ColorStateList.valueOf(textColor));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
index 45ee4ad..ee602bc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
@@ -23,6 +23,8 @@
 import android.util.AttributeSet;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.R;
 
 public class AuthBiometricFingerprintView extends AuthBiometricView {
@@ -94,12 +96,37 @@
 
         mIconView.setImageDrawable(icon);
 
+        final CharSequence iconContentDescription = getIconContentDescription(newState);
+        if (iconContentDescription != null) {
+            mIconView.setContentDescription(iconContentDescription);
+        }
+
         if (animation != null && shouldAnimateForTransition(lastState, newState)) {
             animation.forceAnimationOnUI();
             animation.start();
         }
     }
 
+    @Nullable
+    private CharSequence getIconContentDescription(int newState) {
+        switch (newState) {
+            case STATE_IDLE:
+            case STATE_AUTHENTICATING_ANIMATING_IN:
+            case STATE_AUTHENTICATING:
+            case STATE_PENDING_CONFIRMATION:
+            case STATE_AUTHENTICATED:
+                return mContext.getString(
+                        R.string.accessibility_fingerprint_dialog_fingerprint_icon);
+
+            case STATE_ERROR:
+            case STATE_HELP:
+                return mContext.getString(R.string.biometric_dialog_try_again);
+
+            default:
+                return null;
+        }
+    }
+
     private boolean shouldAnimateForTransition(int oldState, int newState) {
         switch (newState) {
             case STATE_HELP:
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index bebf813..60b0637 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -636,7 +636,6 @@
         mIndicatorView.setText(message);
         mIndicatorView.setTextColor(mTextColorError);
         mIndicatorView.setVisibility(View.VISIBLE);
-        mIndicatorView.setSelected(true);
         mHandler.postDelayed(resetMessageRunnable, mInjector.getDelayAfterError());
 
         Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index e51baed..ab3e042e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -17,7 +17,6 @@
 package com.android.systemui.biometrics;
 
 import static android.hardware.fingerprint.IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD;
-import static android.os.VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
 
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.internal.util.Preconditions.checkNotNull;
@@ -27,7 +26,6 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -47,8 +45,6 @@
 import android.os.Trace;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
-import android.provider.Settings;
-import android.text.TextUtils;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -160,16 +156,8 @@
                     .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
                     .build();
 
-    public static final VibrationEffect EFFECT_TICK =
-            VibrationEffect.get(VibrationEffect.EFFECT_TICK);
-    private static final VibrationEffect EFFECT_TEXTURE_TICK =
-            VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK);
-    @VisibleForTesting
-    static final VibrationEffect EFFECT_CLICK = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
-    private static final VibrationEffect EFFECT_HEAVY =
-            VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
-    private static final VibrationEffect EFFECT_DOUBLE_CLICK =
-            VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+    public static final VibrationEffect EFFECT_CLICK =
+            VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
 
     private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
@@ -437,7 +425,6 @@
                             mTouchLogTime = SystemClock.elapsedRealtime();
                             mPowerManager.userActivity(SystemClock.uptimeMillis(),
                                     PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
-                            playStartHaptic();
                             handled = true;
                         } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) {
                             Log.v(TAG, "onTouch | finger move: " + touchInfo);
@@ -552,18 +539,7 @@
     @VisibleForTesting
     public void playStartHaptic() {
         if (mVibrator != null) {
-            final ContentResolver contentResolver =
-                    mContext.getContentResolver();
-            // TODO: these settings checks should eventually be removed after ux testing
-            //  (b/185124905)
-            int startEnabled = Settings.Global.getInt(contentResolver,
-                    "udfps_start", 1);
-            if (startEnabled > 0) {
-                String startEffectSetting = Settings.Global.getString(
-                        contentResolver, "udfps_start_type");
-                mVibrator.vibrate(getVibration(startEffectSetting,
-                        EFFECT_CLICK), VIBRATION_SONIFICATION_ATTRIBUTES);
-            }
+            mVibrator.vibrate(EFFECT_CLICK, VIBRATION_SONIFICATION_ATTRIBUTES);
         }
     }
 
@@ -698,7 +674,8 @@
                 // This view overlaps the sensor area, so prevent it from being selectable
                 // during a11y.
                 if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR
-                        || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) {
+                        || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING
+                        || reason == IUdfpsOverlayController.REASON_AUTH_BP) {
                     mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
                 }
 
@@ -853,6 +830,9 @@
             Log.w(TAG, "Null view in onFingerDown");
             return;
         }
+        if (!mOnFingerDown) {
+            playStartHaptic();
+        }
         mOnFingerDown = true;
         mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major);
         Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);
@@ -880,38 +860,6 @@
         }
     }
 
-    /**
-     * get vibration to play given string
-     * used for testing purposes (b/185124905)
-     */
-    public static VibrationEffect getVibration(String effect, VibrationEffect defaultEffect) {
-        if (TextUtils.isEmpty(effect)) {
-            return defaultEffect;
-        }
-
-        switch (effect.toLowerCase()) {
-            case "click":
-                return EFFECT_CLICK;
-            case "heavy":
-                return EFFECT_HEAVY;
-            case "texture_tick":
-                return EFFECT_TEXTURE_TICK;
-            case "tick":
-                return EFFECT_TICK;
-            case "double_tap":
-                return EFFECT_DOUBLE_CLICK;
-            default:
-                try {
-                    int primitive = Integer.parseInt(effect);
-                    if (primitive <= PRIMITIVE_LOW_TICK && primitive > -1) {
-                        return VibrationEffect.startComposition().addPrimitive(primitive).compose();
-                    }
-                } catch (NumberFormatException e) {
-                }
-                return defaultEffect;
-        }
-    }
-
     private void updateTouchListener() {
         if (mView == null) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index 1316184..eb02aa0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -162,7 +162,12 @@
     }
 
     void updateColor() {
+        mWallpaperTextColor = Utils.getColorAttrDefaultColor(mContext,
+            R.attr.wallpaperTextColorAccent);
+        mTextColorPrimary = Utils.getColorAttrDefaultColor(mContext,
+            android.R.attr.textColorPrimary);
         mLockScreenFp.invalidate();
+        mBgProtection.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg));
     }
 
     private boolean showingUdfpsBouncer() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c33f4fa..c7c2590 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2311,7 +2311,6 @@
         // only play "unlock" noises if not on a call (since the incall UI
         // disables the keyguard)
         if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) {
-            Log.i("TEST", "playSounds: false");
             playSounds(false);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 1d6c7c9..929927e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -212,6 +212,11 @@
                 Dependency.get(CommandQueue.class).animateCollapsePanels();
                 mTriggeredExpand = false;
             }
+            // Always animate on close, even if the last opened detail adapter had shouldAnimate()
+            // return false. This is necessary to avoid a race condition which could leave the
+            // keyguard in a bad state where QS remains visible underneath the notifications, clock,
+            // and status area.
+            mShouldAnimate = true;
         }
 
         boolean visibleDiff = wasShowingDetail != showingDetail;
@@ -245,10 +250,15 @@
             mClosingDetail = true;
             mDetailAdapter = null;
             listener = mTeardownDetailWhenDone;
-            mHeader.setVisibility(View.VISIBLE);
-            mFooter.setVisibility(View.VISIBLE);
-            mQsPanelController.setGridContentVisibility(true);
-            mQsPanelCallback.onScanStateChanged(false);
+            // Only update visibility if already expanded. Otherwise, a race condition can cause the
+            // keyguard to enter a bad state where the QS tiles are displayed underneath the
+            // notifications, clock, and status area.
+            if (mQsPanelController.isExpanded()) {
+                mHeader.setVisibility(View.VISIBLE);
+                mFooter.setVisibility(View.VISIBLE);
+                mQsPanelController.setGridContentVisibility(true);
+                mQsPanelCallback.onScanStateChanged(false);
+            }
         }
         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
         animateDetailVisibleDiff(x, y, visibleDiff, listener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index e65038b..f460a13 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
@@ -148,8 +148,22 @@
             AmbientState ambientState) {
 
         NotificationShelf shelf = ambientState.getShelf();
-        if (shelf != null) {
-            shelf.updateState(algorithmState, ambientState);
+        if (shelf == null) {
+            return;
+        }
+
+        shelf.updateState(algorithmState, ambientState);
+
+        // After the shelf has updated its yTranslation,
+        // explicitly hide views below the shelf to skip rendering them in the hardware layer.
+        final float shelfTop = shelf.getViewState().yTranslation;
+
+        for (ExpandableView view : algorithmState.visibleChildren) {
+            final float viewTop = view.getViewState().yTranslation;
+
+            if (viewTop >= shelfTop) {
+                view.getViewState().hidden = true;
+            }
         }
     }
 
@@ -411,8 +425,7 @@
         } else {
             if (view != ambientState.getTrackedHeadsUpRow()) {
                 if (ambientState.isExpansionChanging()) {
-                    // Show all views. Views below the shelf will later be clipped (essentially
-                    // hidden) in NotificationShelf.
+                    // We later update shelf state, then hide views below the shelf.
                     viewState.hidden = false;
                     viewState.inShelf = algorithmState.firstViewInShelf != null
                             && i >= algorithmState.visibleChildren.indexOf(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 16f8319..91d503b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -74,6 +74,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.Utils;
 import com.android.systemui.ActivityIntentHelper;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -459,6 +460,10 @@
             if (tileIcon != null) {
                 mWalletButton.setImageDrawable(tileIcon);
             }
+            mWalletButton.getDrawable().setTint(
+                    Utils.getColorAttr(
+                            mContext,
+                            com.android.internal.R.attr.textColorPrimary).getDefaultColor());
             mWalletButton.setVisibility(VISIBLE);
             mWalletButton.setOnClickListener(this::onWalletClick);
             mIndicationArea.setPadding(mIndicationPadding, 0, mIndicationPadding, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index d6ea4a8..8c0dfc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -87,7 +87,7 @@
     private final Runnable mResetRunnable = ()-> {
         if (mKeyguardViewController != null) {
             mKeyguardViewController.resetSecurityContainer();
-            for (KeyguardResetCallback callback : mResetCallbacks) {
+            for (KeyguardResetCallback callback : new ArrayList<>(mResetCallbacks)) {
                 callback.onKeyguardReset();
             }
         }
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 13aa15e..3c1892d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -1575,7 +1575,10 @@
     public void expandWithQsDetail(DetailAdapter qsDetailAdapter) {
         traceQsJank(true /* startTracing */, false /* wasCancelled */);
         flingSettings(0 /* velocity */, FLING_EXPAND);
-        mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, 0, 0);
+        // When expanding with a panel, there's no meaningful touch point to correspond to. Set the
+        // origin to somewhere above the screen. This is used for animations.
+        int x = mQsFrame.getWidth() / 2;
+        mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, x, -getHeight());
         if (mAccessibilityManager.isEnabled()) {
             mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index 022faf78..5e105bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -55,16 +55,18 @@
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
-import com.google.android.collect.Lists;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
@@ -100,7 +102,7 @@
     private ForcePluginOpenListener mForcePluginOpenListener;
     private Consumer<Integer> mScrimsVisibilityListener;
     private final ArrayList<WeakReference<StatusBarWindowCallback>>
-            mCallbacks = Lists.newArrayList();
+            mCallbacks = new ArrayList<>();
 
     private final SysuiColorExtractor mColorExtractor;
     private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@@ -464,13 +466,15 @@
 
     @Override
     public void notifyStateChangedCallbacks() {
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            StatusBarWindowCallback cb = mCallbacks.get(i).get();
-            if (cb != null) {
-                cb.onStateChanged(mCurrentState.mKeyguardShowing,
-                        mCurrentState.mKeyguardOccluded,
-                        mCurrentState.mBouncerShowing);
-            }
+        // Copy callbacks to separate ArrayList to avoid concurrent modification
+        List<StatusBarWindowCallback> activeCallbacks = mCallbacks.stream()
+                .map(Reference::get)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        for (StatusBarWindowCallback cb : activeCallbacks) {
+            cb.onStateChanged(mCurrentState.mKeyguardShowing,
+                    mCurrentState.mKeyguardOccluded,
+                    mCurrentState.mBouncerShowing);
         }
     }
 
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 6b8686b..db7ead7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -55,6 +55,7 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
+import android.app.TaskInfo;
 import android.app.UiModeManager;
 import android.app.WallpaperInfo;
 import android.app.WallpaperManager;
@@ -247,6 +248,7 @@
 import com.android.systemui.wmshell.BubblesManager;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.startingsurface.SplashscreenContentDrawer;
 import com.android.wm.shell.startingsurface.StartingSurface;
 
 import java.io.FileDescriptor;
@@ -269,7 +271,7 @@
         ColorExtractor.OnColorsChangedListener, ConfigurationListener,
         StatusBarStateController.StateListener,
         LifecycleOwner, BatteryController.BatteryStateChangeCallback,
-        ActivityLaunchAnimator.KeyguardHandler {
+        ActivityLaunchAnimator.Callback {
     public static final boolean MULTIUSER_DEBUG = false;
 
     protected static final int MSG_HIDE_RECENT_APPS = 1020;
@@ -1421,9 +1423,7 @@
 
     private void setUpPresenter() {
         // Set up the initial notification state.
-        mActivityLaunchAnimator = new ActivityLaunchAnimator(this,
-                mStartingSurfaceOptional.orElse(null),
-                mContext);
+        mActivityLaunchAnimator = new ActivityLaunchAnimator(this, mContext);
         mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
                 mNotificationShadeWindowViewController,
                 mStackScrollerController.getNotificationListContainer(),
@@ -2123,6 +2123,16 @@
         mKeyguardViewMediator.setBlursDisabledForAppLaunch(disabled);
     }
 
+    @Override
+    public int getBackgroundColor(TaskInfo task) {
+        if (!mStartingSurfaceOptional.isPresent()) {
+            Log.w(TAG, "No starting surface, defaulting to SystemBGColor");
+            return SplashscreenContentDrawer.getSystemBGColor();
+        }
+
+        return mStartingSurfaceOptional.get().getBackgroundColor(task);
+    }
+
     public boolean isDeviceInVrMode() {
         return mPresenter.isDeviceInVrMode();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 081fe5a..a8097c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -41,6 +41,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.HashSet;
 
 /**
@@ -157,7 +158,7 @@
                         NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(),
                         entry.getSbn().getPackageName(), entry.getSbn().getInstanceId());
             }
-            for (OnHeadsUpChangedListener listener : mListeners) {
+            for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
                 if (isPinned) {
                     listener.onHeadsUpPinned(entry);
                 } else {
@@ -177,7 +178,7 @@
         entry.setHeadsUp(true);
         setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(entry));
         EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */);
-        for (OnHeadsUpChangedListener listener : mListeners) {
+        for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
             listener.onHeadsUpStateChanged(entry, true);
         }
     }
@@ -188,7 +189,7 @@
         entry.setHeadsUp(false);
         setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
         EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
-        for (OnHeadsUpChangedListener listener : mListeners) {
+        for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
             listener.onHeadsUpStateChanged(entry, false);
         }
     }
@@ -206,7 +207,7 @@
         if (mHasPinnedNotification) {
             MetricsLogger.count(mContext, "note_peek", 1);
         }
-        for (OnHeadsUpChangedListener listener : mListeners) {
+        for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
             listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index b18dfd2..bbaf65a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -807,7 +807,7 @@
     }
 
     private void onEditTextFocusChanged(RemoteEditText remoteEditText, boolean focused) {
-        for (View.OnFocusChangeListener listener : mEditTextFocusChangeListeners) {
+        for (View.OnFocusChangeListener listener : new ArrayList<>(mEditTextFocusChangeListeners)) {
             listener.onFocusChange(remoteEditText, focused);
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index 33cc782..14f112b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -10,6 +10,7 @@
 import android.os.Looper
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import android.util.Log
 import android.view.IRemoteAnimationFinishedCallback
 import android.view.RemoteAnimationAdapter
 import android.view.RemoteAnimationTarget
@@ -21,12 +22,12 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
-import com.android.wm.shell.startingsurface.StartingSurface
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertNotNull
 import junit.framework.Assert.assertNull
 import junit.framework.Assert.assertTrue
 import junit.framework.AssertionFailedError
+import kotlin.concurrent.thread
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -39,24 +40,23 @@
 import org.mockito.Mockito.verify
 import org.mockito.Spy
 import org.mockito.junit.MockitoJUnit
-import kotlin.concurrent.thread
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
 class ActivityLaunchAnimatorTest : SysuiTestCase() {
     private val launchContainer = LinearLayout(mContext)
-    @Mock lateinit var keyguardHandler: ActivityLaunchAnimator.KeyguardHandler
+    @Mock lateinit var callback: ActivityLaunchAnimator.Callback
     @Spy private val controller = TestLaunchAnimatorController(launchContainer)
     @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
-    @Mock lateinit var startingSurface: StartingSurface
+    @Mock lateinit var failHandler: Log.TerribleFailureHandler
 
     private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
     @get:Rule val rule = MockitoJUnit.rule()
 
     @Before
     fun setup() {
-        activityLaunchAnimator = ActivityLaunchAnimator(keyguardHandler, startingSurface, mContext)
+        activityLaunchAnimator = ActivityLaunchAnimator(callback, mContext)
     }
 
     private fun startIntentWithAnimation(
@@ -119,8 +119,8 @@
 
     @Test
     fun animatesIfActivityIsAlreadyOpenAndIsOnKeyguard() {
-        `when`(keyguardHandler.isOnKeyguard()).thenReturn(true)
-        val animator = ActivityLaunchAnimator(keyguardHandler, startingSurface, context)
+        `when`(callback.isOnKeyguard()).thenReturn(true)
+        val animator = ActivityLaunchAnimator(callback, context)
 
         val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
         var animationAdapter: RemoteAnimationAdapter? = null
@@ -132,7 +132,7 @@
 
         waitForIdleSync()
         verify(controller).onIntentStarted(willAnimateCaptor.capture())
-        verify(keyguardHandler).hideKeyguardWithAnimation(any())
+        verify(callback).hideKeyguardWithAnimation(any())
 
         assertTrue(willAnimateCaptor.value)
         assertNull(animationAdapter)
@@ -174,13 +174,15 @@
         val runner = activityLaunchAnimator.createRunner(controller)
         runner.onAnimationStart(0, arrayOf(fakeWindow()), emptyArray(), emptyArray(), iCallback)
         waitForIdleSync()
-        verify(keyguardHandler).setBlursDisabledForAppLaunch(eq(true))
+        verify(callback).setBlursDisabledForAppLaunch(eq(true))
         verify(controller).onLaunchAnimationStart(anyBoolean())
     }
 
     @Test
-    fun controllerFromOrphanViewReturnsNull() {
+    fun controllerFromOrphanViewReturnsNullAndIsATerribleFailure() {
+        Log.setWtfHandler(failHandler)
         assertNull(ActivityLaunchAnimator.Controller.fromView(View(mContext)))
+        verify(failHandler).onTerribleFailure(any(), any(), anyBoolean())
     }
 
     private fun fakeWindow(): RemoteAnimationTarget {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
index c2bd024f..84776c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
@@ -148,8 +148,10 @@
                 eq(true) /* in */, any());
         clearInvocations(mQsDetail.mClipper);
 
+        // Detail adapters should always animate on close. shouldAnimate() should only affect the
+        // open transition
         mQsDetail.handleShowingDetail(null, 0, 0, false);
-        verify(mQsDetail.mClipper).updateCircularClip(eq(false) /* animate */, anyInt(), anyInt(),
+        verify(mQsDetail.mClipper).updateCircularClip(eq(true) /* animate */, anyInt(), anyInt(),
                 eq(false) /* in */, any());
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index e1af2c4..d7bc040 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -258,7 +258,12 @@
             super.onMotionEvent(event, rawEvent, policyFlags);
             return;
         }
-
+        try {
+            checkForMalformedEvent(event);
+        } catch (IllegalArgumentException e) {
+            Slog.e(LOG_TAG, "Ignoring malformed event: " + event.toString(), e);
+            return;
+        }
         if (DEBUG) {
             Slog.d(LOG_TAG, "Received event: " + event + ", policyFlags=0x"
                     + Integer.toHexString(policyFlags));
@@ -1223,6 +1228,32 @@
     }
 
     /**
+     * Checks to see whether an event is consistent with itself.
+     *
+     * @throws IllegalArgumentException in the case of a malformed event.
+     */
+    private static void checkForMalformedEvent(MotionEvent event) {
+        if (event.getPointerCount() < 0) {
+            throw new IllegalArgumentException("Invalid pointer count: " + event.getPointerCount());
+        }
+        for (int i = 0; i < event.getPointerCount(); ++i) {
+            try {
+                int pointerId = event.getPointerId(i);
+                float x = event.getX(i);
+                float y = event.getY(i);
+                if (Float.isNaN(x) || Float.isNaN(y) || x < 0.0f || y < 0.0f) {
+                    throw new IllegalArgumentException(
+                            "Invalid coordinates: (" + x + ", " + y + ")");
+                }
+            } catch (Exception e) {
+                throw new IllegalArgumentException(
+                        "Encountered exception getting details of pointer " + i + " / "
+                                + event.getPointerCount(), e);
+            }
+        }
+    }
+
+    /**
      * Class for delayed sending of hover enter and move events.
      */
     class SendHoverEnterAndMoveDelayed implements Runnable {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4ec5559..2057a7e4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5003,7 +5003,7 @@
                     (res.key.flags & PendingIntent.FLAG_IMMUTABLE) != 0,
                     res.key.type);
         } else {
-            throw new IllegalArgumentException();
+            return new PendingIntentInfo(null, -1, false, ActivityManager.INTENT_SENDER_UNKNOWN);
         }
     }
 
@@ -12425,6 +12425,15 @@
             return sticky;
         }
 
+        // SafetyNet logging for b/177931370. If any process other than system_server tries to
+        // listen to this broadcast action, then log it.
+        if (callingPid != Process.myPid()) {
+            if (filter.hasAction("com.android.server.net.action.SNOOZE_WARNING")
+                    || filter.hasAction("com.android.server.net.action.SNOOZE_RAPID")) {
+                EventLog.writeEvent(0x534e4554, "177931370", callingUid, "");
+            }
+        }
+
         synchronized (this) {
             IApplicationThread thread;
             if (callerApp != null && ((thread = callerApp.getThread()) == null
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index ca357b4c..f11fe8a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -188,18 +188,7 @@
         mPowerManager.userActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
     }
 
-    protected boolean successHapticsEnabled() {
-        return true;
-    }
-
-    protected boolean errorHapticsEnabled() {
-        return true;
-    }
-
     protected final void vibrateSuccess() {
-        if (!successHapticsEnabled()) {
-            return;
-        }
         Vibrator vibrator = getContext().getSystemService(Vibrator.class);
         if (vibrator != null) {
             vibrator.vibrate(SUCCESS_VIBRATION_EFFECT, VIBRATION_SONIFICATION_ATTRIBUTES);
@@ -207,9 +196,6 @@
     }
 
     protected final void vibrateError() {
-        if (!errorHapticsEnabled()) {
-            return;
-        }
         Vibrator vibrator = getContext().getSystemService(Vibrator.class);
         if (vibrator != null) {
             vibrator.vibrate(ERROR_VIBRATION_EFFECT, VIBRATION_SONIFICATION_ATTRIBUTES);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index db927b2..3757404 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.NotificationManager;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricAuthenticator;
@@ -33,7 +32,6 @@
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.provider.Settings;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -59,9 +57,6 @@
     @Nullable private final NotificationManager mNotificationManager;
     @Nullable private ICancellationSignal mCancellationSignal;
 
-    @NonNull private final ContentResolver mContentResolver;
-    private final boolean mCustomHaptics;
-
     private final int[] mBiometricPromptIgnoreList;
     private final int[] mBiometricPromptIgnoreListVendor;
     private final int[] mKeyguardIgnoreList;
@@ -92,10 +87,6 @@
                 R.array.config_face_acquire_keyguard_ignorelist);
         mKeyguardIgnoreListVendor = resources.getIntArray(
                 R.array.config_face_acquire_vendor_keyguard_ignorelist);
-
-        mContentResolver = context.getContentResolver();
-        mCustomHaptics = Settings.Global.getInt(mContentResolver,
-                "face_custom_success_error", 0) == 1;
     }
 
     @NonNull
@@ -261,18 +252,4 @@
             Slog.e(TAG, "Remote exception", e);
         }
     }
-
-    @Override
-    protected boolean successHapticsEnabled() {
-        return mCustomHaptics
-            ? Settings.Global.getInt(mContentResolver, "face_success_enabled", 1) == 0
-            : super.successHapticsEnabled();
-    }
-
-    @Override
-    protected boolean errorHapticsEnabled() {
-        return mCustomHaptics
-            ? Settings.Global.getInt(mContentResolver, "face_error_enabled", 1) == 0
-            : super.errorHapticsEnabled();
-    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 6c0adaf..c3de7aa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -17,7 +17,6 @@
 package com.android.server.biometrics.sensors.face.hidl;
 
 import android.annotation.NonNull;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricAuthenticator;
@@ -28,7 +27,6 @@
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.provider.Settings;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -49,8 +47,6 @@
 
     private static final String TAG = "FaceAuthenticationClient";
 
-    @NonNull private final ContentResolver mContentResolver;
-    private final boolean mCustomHaptics;
     private final UsageStats mUsageStats;
 
     private final int[] mBiometricPromptIgnoreList;
@@ -81,10 +77,6 @@
                 R.array.config_face_acquire_keyguard_ignorelist);
         mKeyguardIgnoreListVendor = resources.getIntArray(
                 R.array.config_face_acquire_vendor_keyguard_ignorelist);
-
-        mContentResolver = context.getContentResolver();
-        mCustomHaptics = Settings.Global.getInt(mContentResolver,
-                "face_custom_success_error", 0) == 1;
     }
 
     @NonNull
@@ -200,18 +192,4 @@
         final boolean shouldSend = shouldSend(acquireInfo, vendorCode);
         onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
     }
-
-    @Override
-    protected boolean successHapticsEnabled() {
-        return mCustomHaptics
-            ? Settings.Global.getInt(mContentResolver, "face_success_enabled", 1) == 0
-            : super.successHapticsEnabled();
-    }
-
-    @Override
-    protected boolean errorHapticsEnabled() {
-        return mCustomHaptics
-            ? Settings.Global.getInt(mContentResolver, "face_error_enabled", 1) == 0
-            : super.errorHapticsEnabled();
-    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 6a05ed4..19134e4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskStackListener;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
@@ -30,7 +29,6 @@
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.provider.Settings;
 import android.util.Slog;
 
 import com.android.server.biometrics.Utils;
@@ -57,9 +55,6 @@
     @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ICancellationSignal mCancellationSignal;
 
-    @NonNull private final ContentResolver mContentResolver;
-    private final boolean mCustomHaptics;
-
     FingerprintAuthenticationClient(@NonNull Context context,
             @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
@@ -74,10 +69,6 @@
                 lockoutCache, allowBackgroundAuthentication);
         mLockoutCache = lockoutCache;
         mUdfpsOverlayController = udfpsOverlayController;
-
-        mContentResolver = context.getContentResolver();
-        mCustomHaptics = Settings.Global.getInt(mContentResolver,
-            "fp_custom_success_error", 0) == 1;
     }
 
     @NonNull
@@ -213,18 +204,4 @@
         UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
         mCallback.onClientFinished(this, false /* success */);
     }
-
-    @Override
-    protected boolean successHapticsEnabled() {
-        return mCustomHaptics
-            ? Settings.Global.getInt(mContentResolver, "fp_success_enabled", 1) == 0
-            : super.successHapticsEnabled();
-    }
-
-    @Override
-    protected boolean errorHapticsEnabled() {
-        return mCustomHaptics
-            ? Settings.Global.getInt(mContentResolver, "fp_error_enabled", 1) == 0
-            : super.errorHapticsEnabled();
-    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 04771b9..ee44c10 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7955,12 +7955,9 @@
                     } catch (PackageManagerException e) {
                         Slog.w(TAG, "updateAllSharedLibrariesLPw failed: ", e);
                     }
-                    final int[] userIds = mUserManager.getUserIds();
-                    for (final int userId : userIds) {
-                        mPermissionManager.onPackageInstalled(pkg,
-                                PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT,
-                                userId);
-                    }
+                    mPermissionManager.onPackageInstalled(pkg,
+                            PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT,
+                            UserHandle.USER_ALL);
                     writeSettingsLPrTEMP();
                 }
             } catch (PackageManagerException e) {
@@ -19213,12 +19210,7 @@
                 }
                 final int autoRevokePermissionsMode = installArgs.autoRevokePermissionsMode;
                 permissionParamsBuilder.setAutoRevokePermissionsMode(autoRevokePermissionsMode);
-                for (int currentUserId : allUsersList) {
-                    if (ps.getInstalled(currentUserId)) {
-                        mPermissionManager.onPackageInstalled(pkg, permissionParamsBuilder.build(),
-                                currentUserId);
-                    }
-                }
+                mPermissionManager.onPackageInstalled(pkg, permissionParamsBuilder.build(), userId);
             }
             res.name = pkgName;
             res.uid = pkg.getUid();
@@ -21862,10 +21854,8 @@
                         if (sharedUserPkgs == null) {
                             sharedUserPkgs = Collections.emptyList();
                         }
-                        for (final int userId : allUserHandles) {
-                            mPermissionManager.onPackageUninstalled(packageName, deletedPs.appId,
-                                    deletedPs.pkg, sharedUserPkgs, userId);
-                        }
+                        mPermissionManager.onPackageUninstalled(packageName, deletedPs.appId,
+                                deletedPs.pkg, sharedUserPkgs, UserHandle.USER_ALL);
                     }
                     clearPackagePreferredActivitiesLPw(
                             deletedPs.name, changedUsers, UserHandle.USER_ALL);
@@ -22082,11 +22072,12 @@
                 }
             }
 
+            // The method below will take care of removing obsolete permissions and granting
+            // install permissions.
+            mPermissionManager.onPackageInstalled(pkg,
+                    PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT,
+                    UserHandle.USER_ALL);
             for (final int userId : allUserHandles) {
-                // The method below will take care of removing obsolete permissions and granting
-                // install permissions.
-                mPermissionManager.onPackageInstalled(pkg,
-                        PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT, userId);
                 if (applyUserRestrictions) {
                     mSettings.writePermissionStateForUserLPr(userId, false);
                 }
@@ -22409,10 +22400,9 @@
             }
             removeKeystoreDataIfNeeded(mInjector.getUserManagerInternal(), nextUserId, ps.appId);
             clearPackagePreferredActivities(ps.name, nextUserId);
-            mPermissionManager.onPackageUninstalled(ps.name, ps.appId, pkg, sharedUserPkgs,
-                    nextUserId);
             mDomainVerificationManager.clearPackageForUser(ps.name, nextUserId);
         }
+        mPermissionManager.onPackageUninstalled(ps.name, ps.appId, pkg, sharedUserPkgs, userId);
 
         if (outInfo != null) {
             if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 97ccfe6..dfc14bd 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -4040,17 +4040,14 @@
      *
      * @param packageName The package that is updated
      * @param pkg The package that is updated, or {@code null} if package is deleted
-     * @param filterUserId If not {@link UserHandle.USER_ALL}, only restore the permission state for
-     *                     this particular user
      */
-    private void updatePermissions(@NonNull String packageName, @Nullable AndroidPackage pkg,
-                                   @UserIdInt int filterUserId) {
+    private void updatePermissions(@NonNull String packageName, @Nullable AndroidPackage pkg) {
         // If the package is being deleted, update the permissions of all the apps
         final int flags =
                 (pkg == null ? UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG
                         : UPDATE_PERMISSIONS_REPLACE_PKG);
-        updatePermissions(packageName, pkg, getVolumeUuidForPackage(pkg), flags,
-                mDefaultPermissionCallback, filterUserId);
+        updatePermissions(
+                packageName, pkg, getVolumeUuidForPackage(pkg), flags, mDefaultPermissionCallback);
     }
 
     /**
@@ -4072,8 +4069,7 @@
                     (fingerprintChanged
                             ? UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL
                             : 0);
-            updatePermissions(null, null, volumeUuid, flags, mDefaultPermissionCallback,
-                    UserHandle.USER_ALL);
+            updatePermissions(null, null, volumeUuid, flags, mDefaultPermissionCallback);
         } finally {
             PackageManager.uncorkPackageInfoCache();
         }
@@ -4122,14 +4118,12 @@
      *                          all volumes
      * @param flags Control permission for which apps should be updated
      * @param callback Callback to call after permission changes
-     * @param filterUserId If not {@link UserHandle.USER_ALL}, only restore the permission state for
-     *                     this particular user
      */
     private void updatePermissions(final @Nullable String changingPkgName,
             final @Nullable AndroidPackage changingPkg,
             final @Nullable String replaceVolumeUuid,
             @UpdatePermissionFlags int flags,
-            final @Nullable PermissionCallback callback, @UserIdInt int filterUserId) {
+            final @Nullable PermissionCallback callback) {
         // TODO: Most of the methods exposing BasePermission internals [source package name,
         // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't
         // have package settings, we should make note of it elsewhere [map between
@@ -4165,7 +4159,8 @@
                 // Only replace for packages on requested volume
                 final String volumeUuid = getVolumeUuidForPackage(pkg);
                 final boolean replace = replaceAll && Objects.equals(replaceVolumeUuid, volumeUuid);
-                restorePermissionState(pkg, replace, changingPkgName, callback, filterUserId);
+                restorePermissionState(pkg, replace, changingPkgName, callback,
+                        UserHandle.USER_ALL);
             });
         }
 
@@ -4174,7 +4169,8 @@
             final String volumeUuid = getVolumeUuidForPackage(changingPkg);
             final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0)
                     && Objects.equals(replaceVolumeUuid, volumeUuid);
-            restorePermissionState(changingPkg, replace, changingPkgName, callback, filterUserId);
+            restorePermissionState(changingPkg, replace, changingPkgName, callback,
+                    UserHandle.USER_ALL);
         }
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
     }
@@ -4841,18 +4837,20 @@
 
     private void onPackageInstalledInternal(@NonNull AndroidPackage pkg,
             @NonNull PermissionManagerServiceInternal.PackageInstalledParams params,
-            @UserIdInt int userId) {
-        updatePermissions(pkg.getPackageName(), pkg, userId);
-        addAllowlistedRestrictedPermissionsInternal(pkg,
-                params.getAllowlistedRestrictedPermissions(),
-                FLAG_PERMISSION_WHITELIST_INSTALLER, userId);
-        final int autoRevokePermissionsMode = params.getAutoRevokePermissionsMode();
-        if (autoRevokePermissionsMode == AppOpsManager.MODE_ALLOWED
-                || autoRevokePermissionsMode == AppOpsManager.MODE_IGNORED) {
-            setAutoRevokeExemptedInternal(pkg,
-                    autoRevokePermissionsMode == AppOpsManager.MODE_IGNORED, userId);
+            @UserIdInt int[] userIds) {
+        updatePermissions(pkg.getPackageName(), pkg);
+        for (final int userId : userIds) {
+            addAllowlistedRestrictedPermissionsInternal(pkg,
+                    params.getAllowlistedRestrictedPermissions(),
+                    FLAG_PERMISSION_WHITELIST_INSTALLER, userId);
+            final int autoRevokePermissionsMode = params.getAutoRevokePermissionsMode();
+            if (autoRevokePermissionsMode == AppOpsManager.MODE_ALLOWED
+                    || autoRevokePermissionsMode == AppOpsManager.MODE_IGNORED) {
+                setAutoRevokeExemptedInternal(pkg,
+                        autoRevokePermissionsMode == AppOpsManager.MODE_IGNORED, userId);
+            }
+            grantRequestedRuntimePermissionsInternal(pkg, params.getGrantedPermissions(), userId);
         }
-        grantRequestedRuntimePermissionsInternal(pkg, params.getGrantedPermissions(), userId);
     }
 
     private void addAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg,
@@ -4875,7 +4873,7 @@
 
     private void onPackageUninstalledInternal(@NonNull String packageName, int appId,
             @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs,
-            @UserIdInt int userId) {
+            @UserIdInt int[] userIds) {
         // TODO: Move these checks to check PackageState to be more reliable.
         // System packages should always have an available APK.
         if (pkg != null && pkg.isSystem()
@@ -4886,27 +4884,31 @@
             // If we are only marking a system package as uninstalled, we need to keep its
             // pregranted permission state so that it still works once it gets reinstalled, thus
             // only reset the user modifications to its permission state.
-            resetRuntimePermissionsInternal(pkg, userId);
+            for (final int userId : userIds) {
+                resetRuntimePermissionsInternal(pkg, userId);
+            }
             return;
         }
-        updatePermissions(packageName, null, userId);
-        if (sharedUserPkgs.isEmpty()) {
-            removeUidStateAndResetPackageInstallPermissionsFixed(appId, packageName, userId);
-        } else {
-            // Remove permissions associated with package. Since runtime
-            // permissions are per user we have to kill the removed package
-            // or packages running under the shared user of the removed
-            // package if revoking the permissions requested only by the removed
-            // package is successful and this causes a change in gids.
-            final int userIdToKill = revokeSharedUserPermissionsForDeletedPackageInternal(pkg,
-                    sharedUserPkgs, userId);
-            final boolean shouldKill = userIdToKill != UserHandle.USER_NULL;
-            // If gids changed, kill all affected packages.
-            if (shouldKill) {
-                mHandler.post(() -> {
-                    // This has to happen with no lock held.
-                    killUid(appId, UserHandle.USER_ALL, KILL_APP_REASON_GIDS_CHANGED);
-                });
+        updatePermissions(packageName, null);
+        for (final int userId : userIds) {
+            if (sharedUserPkgs.isEmpty()) {
+                removeUidStateAndResetPackageInstallPermissionsFixed(appId, packageName, userId);
+            } else {
+                // Remove permissions associated with package. Since runtime
+                // permissions are per user we have to kill the removed package
+                // or packages running under the shared user of the removed
+                // package if revoking the permissions requested only by the removed
+                // package is successful and this causes a change in gids.
+                final int userIdToKill = revokeSharedUserPermissionsForDeletedPackageInternal(pkg,
+                        sharedUserPkgs, userId);
+                final boolean shouldKill = userIdToKill != UserHandle.USER_NULL;
+                // If gids changed, kill all affected packages.
+                if (shouldKill) {
+                    mHandler.post(() -> {
+                        // This has to happen with no lock held.
+                        killUid(appId, UserHandle.USER_ALL, KILL_APP_REASON_GIDS_CHANGED);
+                    });
+                }
             }
         }
     }
@@ -5181,8 +5183,11 @@
                 @NonNull PackageInstalledParams params, @UserIdInt int userId) {
             Objects.requireNonNull(pkg, "pkg");
             Objects.requireNonNull(params, "params");
-            Preconditions.checkArgumentNonNegative(userId, "userId");
-            onPackageInstalledInternal(pkg, params, userId);
+            Preconditions.checkArgument(userId >= UserHandle.USER_SYSTEM
+                    || userId == UserHandle.USER_ALL, "userId");
+            final int[] userIds = userId == UserHandle.USER_ALL ? getAllUserIds()
+                    : new int[] { userId };
+            onPackageInstalledInternal(pkg, params, userIds);
         }
 
         @Override
@@ -5197,8 +5202,11 @@
                 @UserIdInt int userId) {
             Objects.requireNonNull(packageName, "packageName");
             Objects.requireNonNull(sharedUserPkgs, "sharedUserPkgs");
-            Preconditions.checkArgumentNonNegative(userId, "userId");
-            onPackageUninstalledInternal(packageName, appId, pkg, sharedUserPkgs, userId);
+            Preconditions.checkArgument(userId >= UserHandle.USER_SYSTEM
+                    || userId == UserHandle.USER_ALL, "userId");
+            final int[] userIds = userId == UserHandle.USER_ALL ? getAllUserIds()
+                    : new int[] { userId };
+            onPackageUninstalledInternal(packageName, appId, pkg, sharedUserPkgs, userIds);
         }
 
         @NonNull
diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
index 9026262..ab71355 100644
--- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
+++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
@@ -189,12 +189,16 @@
                             return false;
                         }
 
-                        // 3. The app has WRITE_MEDIA_STORAGE, OR
-                        //      the app already has legacy external storage or requested it,
-                        //      and is < R.
-                        return hasWriteMediaStorageGrantedForUid
-                                || ((hasLegacyExternalStorage || hasRequestedLegacyExternalStorage)
-                                    && targetSDK < Build.VERSION_CODES.R);
+                        // 3. The app targetSDK should be less than R
+                        if (targetSDK >= Build.VERSION_CODES.R) {
+                            return false;
+                        }
+
+                        // 4. The app has WRITE_MEDIA_STORAGE,
+                        //    OR the app already has legacy external storage
+                        //    OR the app requested legacy external storage
+                        return hasWriteMediaStorageGrantedForUid || hasLegacyExternalStorage
+                                || hasRequestedLegacyExternalStorage;
                     }
                     @Override
                     public boolean mayDenyExtraAppOpIfGranted() {
@@ -216,10 +220,8 @@
                             return true;
                         }
 
-                        // The package doesn't have WRITE_MEDIA_STORAGE,
-                        // AND didn't request legacy storage to be preserved
-                        if (!hasWriteMediaStorageGrantedForUid
-                                && !hasRequestedPreserveLegacyExternalStorage) {
+                        // The package doesn't request legacy storage to be preserved
+                        if (!hasRequestedPreserveLegacyExternalStorage) {
                             return true;
                         }
 
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 0386372..dd4e260 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -1294,21 +1294,32 @@
         @Override
         public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
             if (controller.getVibratorInfo().getId() == vibratorId) {
+                mVibratorCallbackReceived = true;
                 mNextOffTime = SystemClock.uptimeMillis();
             }
-            // Timings are tightly controlled here, so never anticipate when vibrator is complete.
-            return false;
+            // Timings are tightly controlled here, so only anticipate if the vibrator was supposed
+            // to be ON but has completed prematurely, to turn it back on as soon as possible.
+            return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0;
         }
 
         @Override
         public List<Step> play() {
             Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "AmplitudeStep");
             try {
+                long now = SystemClock.uptimeMillis();
+                long latency = now - startTime;
                 if (DEBUG) {
-                    long latency = SystemClock.uptimeMillis() - startTime;
                     Slog.d(TAG, "Running amplitude step with " + latency + "ms latency.");
                 }
 
+                if (mVibratorCallbackReceived && latency < 0) {
+                    // This step was anticipated because the vibrator turned off prematurely.
+                    // Turn it back on and return this same step to run at the exact right time.
+                    mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency);
+                    return Arrays.asList(new AmplitudeStep(startTime, controller, effect,
+                            segmentIndex, mNextOffTime));
+                }
+
                 VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
                 if (!(segment instanceof StepSegment)) {
                     Slog.w(TAG, "Ignoring wrong segment for a AmplitudeStep: " + segment);
@@ -1321,17 +1332,16 @@
                     return skipToNextSteps(/* segmentsSkipped= */ 1);
                 }
 
-                long now = SystemClock.uptimeMillis();
                 float amplitude = stepSegment.getAmplitude();
                 if (amplitude == 0) {
-                    if (mNextOffTime > now) {
+                    if (vibratorOffTimeout > now) {
                         // Amplitude cannot be set to zero, so stop the vibrator.
                         stopVibrating();
                         mNextOffTime = now;
                     }
                 } else {
                     if (startTime >= mNextOffTime) {
-                        // Vibrator has stopped. Turn vibrator back on for the duration of another
+                        // Vibrator is OFF. Turn vibrator back on for the duration of another
                         // cycle before setting the amplitude.
                         long onDuration = getVibratorOnDuration(effect, segmentIndex);
                         if (onDuration > 0) {
@@ -1350,6 +1360,22 @@
             }
         }
 
+        private long turnVibratorBackOn(long remainingDuration) {
+            long onDuration = getVibratorOnDuration(effect, segmentIndex);
+            if (onDuration <= 0) {
+                // Vibrator is supposed to go back off when this step starts, so just leave it off.
+                return vibratorOffTimeout;
+            }
+            onDuration += remainingDuration;
+            float expectedAmplitude = controller.getCurrentAmplitude();
+            mVibratorOnResult = startVibrating(onDuration);
+            if (mVibratorOnResult > 0) {
+                // Set the amplitude back to the value it was supposed to be playing at.
+                changeAmplitude(expectedAmplitude);
+            }
+            return SystemClock.uptimeMillis() + onDuration + CALLBACKS_EXTRA_TIMEOUT;
+        }
+
         private long startVibrating(long duration) {
             if (DEBUG) {
                 Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
@@ -1383,7 +1409,10 @@
                     repeatIndex = -1;
                 }
                 if (i == startIndex) {
-                    return 1000;
+                    // The repeating waveform keeps the vibrator ON all the time. Use a minimum
+                    // of 1s duration to prevent short patterns from turning the vibrator ON too
+                    // frequently.
+                    return Math.max(timing, 1000);
                 }
             }
             if (i == segmentCount && effect.getRepeatIndex() < 0) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f3dbed5..4468252 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -801,6 +801,10 @@
     // Tracking cookie for the launch of this activity and it's task.
     IBinder mLaunchCookie;
 
+    // Entering PiP is usually done in two phases, we put the task into pinned mode first and
+    // SystemUi sets the pinned mode on activity after transition is done.
+    boolean mWaitForEnteringPinnedMode;
+
     private final Runnable mPauseTimeoutRunnable = new Runnable() {
         @Override
         public void run() {
@@ -7705,6 +7709,7 @@
         // mode (see RootWindowContainer#moveActivityToPinnedRootTask). So once the windowing mode
         // of activity is changed, it is the signal of the last step to update the PiP states.
         if (!wasInPictureInPicture && inPinnedWindowingMode() && task != null) {
+            mWaitForEnteringPinnedMode = false;
             mTaskSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, task.getBounds());
         }
 
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 97c19ab..73d31bf 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1568,6 +1568,10 @@
             layoutStatusBar(displayFrames, mBarContentFrames.get(TYPE_STATUS_BAR));
             return;
         }
+        if (win.mActivityRecord != null && win.mActivityRecord.mWaitForEnteringPinnedMode) {
+            // Skip layout of the window when in transition to pip mode.
+            return;
+        }
         final WindowManager.LayoutParams attrs = win.getAttrs();
 
         final int type = attrs.type;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 9a6a518..3214721 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2191,6 +2191,7 @@
             // from doing work and changing the activity visuals while animating
             // TODO(task-org): Figure-out more structured way to do this long term.
             r.setWindowingMode(intermediateWindowingMode);
+            r.mWaitForEnteringPinnedMode = true;
             rootTask.setWindowingMode(WINDOWING_MODE_PINNED);
             rootTask.setDeferTaskAppear(false);
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 777306a..936b2ef 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -61,6 +61,8 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.SurfaceControl.METADATA_TASK_ID;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -4105,6 +4107,7 @@
         info.positionInParent = getRelativePosition();
 
         info.pictureInPictureParams = getPictureInPictureParams(top);
+        info.displayCutoutInsets = getDisplayCutoutInsets(top);
         info.topActivityInfo = mReuseActivitiesReport.top != null
                 ? mReuseActivitiesReport.top.info
                 : null;
@@ -4139,6 +4142,18 @@
                 ? null : new PictureInPictureParams(topVisibleActivity.pictureInPictureArgs);
     }
 
+    private Rect getDisplayCutoutInsets(Task top) {
+        if (top == null || top.mDisplayContent == null
+                || top.getDisplayInfo().displayCutout == null) return null;
+        final WindowState w = top.getTopVisibleAppMainWindow();
+        final int displayCutoutMode = w == null
+                ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
+                : w.getAttrs().layoutInDisplayCutoutMode;
+        return (displayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+                || displayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)
+                ? null : top.getDisplayInfo().displayCutout.getSafeInsets();
+    }
+
     /**
      * Returns a {@link TaskInfo} with information from this task.
      */
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e9babce..193d92a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -15019,10 +15019,11 @@
             }
             if (active) {
                 if (shouldSendNotification) {
-                    sendNetworkLoggingNotification();
+                    mHandler.post(() -> sendNetworkLoggingNotification());
                 }
             } else {
-                mInjector.getNotificationManager().cancel(SystemMessage.NOTE_NETWORK_LOGGING);
+                mHandler.post(() -> mInjector.getNotificationManager().cancel(
+                        SystemMessage.NOTE_NETWORK_LOGGING));
             }
         });
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 583797e..a254f68 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -514,8 +514,16 @@
     }
 
     private void setAllowWhileIdleAlarm(int type, long triggerTime, PendingIntent pi,
-            boolean unrestricted) {
-        final int flags = unrestricted ? FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED : FLAG_ALLOW_WHILE_IDLE;
+            boolean unrestricted, boolean compat) {
+        assertFalse("Alarm cannot be compat and unrestricted", unrestricted && compat);
+        final int flags;
+        if (unrestricted) {
+            flags = FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
+        } else if (compat) {
+            flags = FLAG_ALLOW_WHILE_IDLE_COMPAT;
+        } else {
+            flags = FLAG_ALLOW_WHILE_IDLE;
+        }
         setTestAlarm(type, triggerTime, pi, 0, flags, TEST_CALLING_UID);
     }
 
@@ -1600,13 +1608,13 @@
         final long firstTrigger = mNowElapsedTest + 10;
         for (int i = 0; i < quota; i++) {
             setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    getNewMockPendingIntent(), false);
+                    getNewMockPendingIntent(), false, false);
             mNowElapsedTest = mTestTimer.getElapsed();
             mTestTimer.expire();
         }
         // This one should get deferred on set.
         setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
-                getNewMockPendingIntent(), false);
+                getNewMockPendingIntent(), false, false);
         final long expectedNextTrigger = firstTrigger + mAllowWhileIdleWindow;
         assertEquals("Incorrect trigger when no quota left", expectedNextTrigger,
                 mTestTimer.getElapsed());
@@ -1619,6 +1627,108 @@
     }
 
     @Test
+    public void allowWhileIdleCompatAlarmsWhileDeviceIdle() throws Exception {
+        setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 0);
+
+        final long window = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW;
+        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + window + 1000,
+                getNewMockPendingIntent());
+        assertNotNull(mService.mPendingIdleUntil);
+
+        final int quota = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < quota; i++) {
+            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
+                    getNewMockPendingIntent(), false, true);
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+        // This one should get deferred on set.
+        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
+                getNewMockPendingIntent(), false, true);
+        final long expectedNextTrigger = firstTrigger + window;
+        assertEquals("Incorrect trigger when no quota left", expectedNextTrigger,
+                mTestTimer.getElapsed());
+
+        // Bring the idle until alarm back.
+        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger - 50,
+                getNewMockPendingIntent());
+        assertEquals(expectedNextTrigger - 50, mService.mPendingIdleUntil.getWhenElapsed());
+        assertEquals(expectedNextTrigger - 50, mTestTimer.getElapsed());
+    }
+
+    @Test
+    public void allowWhileIdleCompatHistorySeparate() throws Exception {
+        when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
+                TEST_CALLING_PACKAGE)).thenReturn(true);
+        when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true);
+
+        final int fullQuota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA;
+        final int compatQuota = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
+
+        final long fullWindow = mAllowWhileIdleWindow;
+        final long compatWindow = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW;
+
+        final long firstFullTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < fullQuota; i++) {
+            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstFullTrigger + i,
+                    getNewMockPendingIntent(), false, false);
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+        // This one should get deferred on set, as full quota is not available.
+        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstFullTrigger + fullQuota,
+                getNewMockPendingIntent(), false, false);
+        final long expectedNextFullTrigger = firstFullTrigger + fullWindow;
+        assertEquals("Incorrect trigger when no quota left", expectedNextFullTrigger,
+                mTestTimer.getElapsed());
+        mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
+
+        // The following should be allowed, as compat quota should be free.
+        for (int i = 0; i < compatQuota; i++) {
+            final long trigger = mNowElapsedTest + 1;
+            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, getNewMockPendingIntent(),
+                    false, true);
+            assertEquals(trigger, mTestTimer.getElapsed());
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+
+        // Refresh the state
+        mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
+        mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
+        mService.mAllowWhileIdleCompatHistory.removeForPackage(TEST_CALLING_PACKAGE,
+                TEST_CALLING_USER);
+
+        // Now test with flipped order
+
+        final long firstCompatTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < compatQuota; i++) {
+            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstCompatTrigger + i,
+                    getNewMockPendingIntent(), false, true);
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+        // This one should get deferred on set, as full quota is not available.
+        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstCompatTrigger + compatQuota,
+                getNewMockPendingIntent(), false, true);
+        final long expectedNextCompatTrigger = firstCompatTrigger + compatWindow;
+        assertEquals("Incorrect trigger when no quota left", expectedNextCompatTrigger,
+                mTestTimer.getElapsed());
+        mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
+
+        // The following should be allowed, as full quota should be free.
+        for (int i = 0; i < fullQuota; i++) {
+            final long trigger = mNowElapsedTest + 1;
+            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, getNewMockPendingIntent(),
+                    false, false);
+            assertEquals(trigger, mTestTimer.getElapsed());
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+    }
+
+    @Test
     public void allowWhileIdleUnrestricted() throws Exception {
         setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 0);
 
@@ -1634,7 +1744,7 @@
         final long firstTrigger = mNowElapsedTest + 10;
         for (int i = 0; i < numAlarms; i++) {
             setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
-                    getNewMockPendingIntent(), true);
+                    getNewMockPendingIntent(), true, false);
         }
         // All of them should fire as expected.
         for (int i = 0; i < numAlarms; i++) {
@@ -1736,7 +1846,7 @@
         final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA;
 
         testQuotasDeferralOnSet(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
-                getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
+                getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow);
 
         // Refresh the state
         mService.removeLocked(TEST_CALLING_UID,
@@ -1744,7 +1854,7 @@
         mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
 
         testQuotasDeferralOnExpiration(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP,
-                trigger, getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
+                trigger, getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow);
 
         // Refresh the state
         mService.removeLocked(TEST_CALLING_UID,
@@ -1752,7 +1862,36 @@
         mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
 
         testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
-                getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
+                getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow);
+    }
+
+    @Test
+    public void allowWhileIdleCompatAlarmsInBatterySaver() throws Exception {
+        when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
+                TEST_CALLING_PACKAGE)).thenReturn(true);
+        when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true);
+
+        final int quota = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
+        final long window = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW;
+
+        testQuotasDeferralOnSet(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
+                getNewMockPendingIntent(), false, true), quota, window);
+
+        // Refresh the state
+        mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
+        mService.mAllowWhileIdleCompatHistory.removeForPackage(TEST_CALLING_PACKAGE,
+                TEST_CALLING_USER);
+
+        testQuotasDeferralOnExpiration(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP,
+                trigger, getNewMockPendingIntent(), false, true), quota, window);
+
+        // Refresh the state
+        mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
+        mService.mAllowWhileIdleCompatHistory.removeForPackage(TEST_CALLING_PACKAGE,
+                TEST_CALLING_USER);
+
+        testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
+                getNewMockPendingIntent(), false, true), quota, window);
     }
 
     @Test
@@ -2123,7 +2262,7 @@
         final PendingIntent alarmPi = getNewMockPendingIntent();
         final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
         mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
-                    alarmPi, null, null, null, alarmClock);
+                alarmPi, null, null, null, alarmClock);
 
         // Correct permission checks are invoked.
         verify(mService).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID);
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 91f4922..5b067bc 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
@@ -869,6 +869,446 @@
     }
 
     @Test
+    public void testGetNextPageToken_query() throws Exception {
+        // Insert package1 schema
+        List<AppSearchSchema> schema1 =
+                ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
+        mAppSearchImpl.setSchema(
+                "package1",
+                "database1",
+                schema1,
+                /*visibilityStore=*/ null,
+                /*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
+                /*schemasVisibleToPackages=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Insert two package1 documents
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
+        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
+        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+
+        // Query for only 1 result per page
+        SearchSpec searchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+                        .setResultCountPerPage(1)
+                        .build();
+        SearchResultPage searchResultPage =
+                mAppSearchImpl.query("package1", "database1", "", searchSpec, /*logger=*/ null);
+
+        // Document2 will come first because it was inserted last and default return order is
+        // most recent.
+        assertThat(searchResultPage.getResults()).hasSize(1);
+        assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
+
+        long nextPageToken = searchResultPage.getNextPageToken();
+        searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
+        assertThat(searchResultPage.getResults()).hasSize(1);
+        assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
+    }
+
+    @Test
+    public void testGetNextPageWithDifferentPackage_query() throws Exception {
+        // Insert package1 schema
+        List<AppSearchSchema> schema1 =
+                ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
+        mAppSearchImpl.setSchema(
+                "package1",
+                "database1",
+                schema1,
+                /*visibilityStore=*/ null,
+                /*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
+                /*schemasVisibleToPackages=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Insert two package1 documents
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
+        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
+        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+
+        // Query for only 1 result per page
+        SearchSpec searchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+                        .setResultCountPerPage(1)
+                        .build();
+        SearchResultPage searchResultPage =
+                mAppSearchImpl.query("package1", "database1", "", searchSpec, /*logger=*/ null);
+
+        // Document2 will come first because it was inserted last and default return order is
+        // most recent.
+        assertThat(searchResultPage.getResults()).hasSize(1);
+        assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
+
+        long nextPageToken = searchResultPage.getNextPageToken();
+
+        // Try getting next page with the wrong package, package2
+        AppSearchException e =
+                assertThrows(
+                        AppSearchException.class,
+                        () -> mAppSearchImpl.getNextPage("package2", nextPageToken));
+        assertThat(e)
+                .hasMessageThat()
+                .contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken);
+        assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
+
+        // Can continue getting next page for package1
+        searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
+        assertThat(searchResultPage.getResults()).hasSize(1);
+        assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
+    }
+
+    @Test
+    public void testGetNextPageToken_globalQuery() throws Exception {
+        // Insert package1 schema
+        List<AppSearchSchema> schema1 =
+                ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
+        mAppSearchImpl.setSchema(
+                "package1",
+                "database1",
+                schema1,
+                /*visibilityStore=*/ null,
+                /*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
+                /*schemasVisibleToPackages=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Insert two package1 documents
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
+        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
+        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+
+        // Query for only 1 result per page
+        SearchSpec searchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+                        .setResultCountPerPage(1)
+                        .build();
+        SearchResultPage searchResultPage =
+                mAppSearchImpl.globalQuery(
+                        /*queryExpression=*/ "",
+                        searchSpec,
+                        "package1",
+                        /*visibilityStore=*/ null,
+                        Process.myUid(),
+                        /*callerHasSystemAccess=*/ false,
+                        /*logger=*/ null);
+
+        // Document2 will come first because it was inserted last and default return order is
+        // most recent.
+        assertThat(searchResultPage.getResults()).hasSize(1);
+        assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
+
+        long nextPageToken = searchResultPage.getNextPageToken();
+        searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
+        assertThat(searchResultPage.getResults()).hasSize(1);
+        assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
+    }
+
+    @Test
+    public void testGetNextPageWithDifferentPackage_globalQuery() throws Exception {
+        // Insert package1 schema
+        List<AppSearchSchema> schema1 =
+                ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
+        mAppSearchImpl.setSchema(
+                "package1",
+                "database1",
+                schema1,
+                /*visibilityStore=*/ null,
+                /*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
+                /*schemasVisibleToPackages=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Insert two package1 documents
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
+        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
+        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+
+        // Query for only 1 result per page
+        SearchSpec searchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+                        .setResultCountPerPage(1)
+                        .build();
+        SearchResultPage searchResultPage =
+                mAppSearchImpl.globalQuery(
+                        /*queryExpression=*/ "",
+                        searchSpec,
+                        "package1",
+                        /*visibilityStore=*/ null,
+                        Process.myUid(),
+                        /*callerHasSystemAccess=*/ false,
+                        /*logger=*/ null);
+
+        // Document2 will come first because it was inserted last and default return order is
+        // most recent.
+        assertThat(searchResultPage.getResults()).hasSize(1);
+        assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
+
+        long nextPageToken = searchResultPage.getNextPageToken();
+
+        // Try getting next page with the wrong package, package2
+        AppSearchException e =
+                assertThrows(
+                        AppSearchException.class,
+                        () -> mAppSearchImpl.getNextPage("package2", nextPageToken));
+        assertThat(e)
+                .hasMessageThat()
+                .contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken);
+        assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
+
+        // Can continue getting next page for package1
+        searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
+        assertThat(searchResultPage.getResults()).hasSize(1);
+        assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
+    }
+
+    @Test
+    public void testInvalidateNextPageToken_query() throws Exception {
+        // Insert package1 schema
+        List<AppSearchSchema> schema1 =
+                ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
+        mAppSearchImpl.setSchema(
+                "package1",
+                "database1",
+                schema1,
+                /*visibilityStore=*/ null,
+                /*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
+                /*schemasVisibleToPackages=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Insert two package1 documents
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
+        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
+        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+
+        // Query for only 1 result per page
+        SearchSpec searchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+                        .setResultCountPerPage(1)
+                        .build();
+        SearchResultPage searchResultPage =
+                mAppSearchImpl.query("package1", "database1", "", searchSpec, /*logger=*/ null);
+
+        // Document2 will come first because it was inserted last and default return order is
+        // most recent.
+        assertThat(searchResultPage.getResults()).hasSize(1);
+        assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
+
+        long nextPageToken = searchResultPage.getNextPageToken();
+
+        // Invalidate the token
+        mAppSearchImpl.invalidateNextPageToken("package1", nextPageToken);
+
+        // Can't get next page because we invalidated the token.
+        AppSearchException e =
+                assertThrows(
+                        AppSearchException.class,
+                        () -> mAppSearchImpl.getNextPage("package1", nextPageToken));
+        assertThat(e)
+                .hasMessageThat()
+                .contains("Package \"package1\" cannot use nextPageToken: " + nextPageToken);
+        assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
+    }
+
+    @Test
+    public void testInvalidateNextPageTokenWithDifferentPackage_query() throws Exception {
+        // Insert package1 schema
+        List<AppSearchSchema> schema1 =
+                ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
+        mAppSearchImpl.setSchema(
+                "package1",
+                "database1",
+                schema1,
+                /*visibilityStore=*/ null,
+                /*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
+                /*schemasVisibleToPackages=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Insert two package1 documents
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
+        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
+        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+
+        // Query for only 1 result per page
+        SearchSpec searchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+                        .setResultCountPerPage(1)
+                        .build();
+        SearchResultPage searchResultPage =
+                mAppSearchImpl.query("package1", "database1", "", searchSpec, /*logger=*/ null);
+
+        // Document2 will come first because it was inserted last and default return order is
+        // most recent.
+        assertThat(searchResultPage.getResults()).hasSize(1);
+        assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
+
+        long nextPageToken = searchResultPage.getNextPageToken();
+
+        // Try getting next page with the wrong package, package2
+        AppSearchException e =
+                assertThrows(
+                        AppSearchException.class,
+                        () -> mAppSearchImpl.invalidateNextPageToken("package2", nextPageToken));
+        assertThat(e)
+                .hasMessageThat()
+                .contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken);
+        assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
+
+        // Can continue getting next page for package1
+        searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
+        assertThat(searchResultPage.getResults()).hasSize(1);
+        assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
+    }
+
+    @Test
+    public void testInvalidateNextPageToken_globalQuery() throws Exception {
+        // Insert package1 schema
+        List<AppSearchSchema> schema1 =
+                ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
+        mAppSearchImpl.setSchema(
+                "package1",
+                "database1",
+                schema1,
+                /*visibilityStore=*/ null,
+                /*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
+                /*schemasVisibleToPackages=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Insert two package1 documents
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
+        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
+        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+
+        // Query for only 1 result per page
+        SearchSpec searchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+                        .setResultCountPerPage(1)
+                        .build();
+        SearchResultPage searchResultPage =
+                mAppSearchImpl.globalQuery(
+                        /*queryExpression=*/ "",
+                        searchSpec,
+                        "package1",
+                        /*visibilityStore=*/ null,
+                        Process.myUid(),
+                        /*callerHasSystemAccess=*/ false,
+                        /*logger=*/ null);
+
+        // Document2 will come first because it was inserted last and default return order is
+        // most recent.
+        assertThat(searchResultPage.getResults()).hasSize(1);
+        assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
+
+        long nextPageToken = searchResultPage.getNextPageToken();
+
+        // Invalidate the token
+        mAppSearchImpl.invalidateNextPageToken("package1", nextPageToken);
+
+        // Can't get next page because we invalidated the token.
+        AppSearchException e =
+                assertThrows(
+                        AppSearchException.class,
+                        () -> mAppSearchImpl.getNextPage("package1", nextPageToken));
+        assertThat(e)
+                .hasMessageThat()
+                .contains("Package \"package1\" cannot use nextPageToken: " + nextPageToken);
+        assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
+    }
+
+    @Test
+    public void testInvalidateNextPageTokenWithDifferentPackage_globalQuery() throws Exception {
+        // Insert package1 schema
+        List<AppSearchSchema> schema1 =
+                ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
+        mAppSearchImpl.setSchema(
+                "package1",
+                "database1",
+                schema1,
+                /*visibilityStore=*/ null,
+                /*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
+                /*schemasVisibleToPackages=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0);
+
+        // Insert two package1 documents
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
+        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
+        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+
+        // Query for only 1 result per page
+        SearchSpec searchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+                        .setResultCountPerPage(1)
+                        .build();
+        SearchResultPage searchResultPage =
+                mAppSearchImpl.globalQuery(
+                        /*queryExpression=*/ "",
+                        searchSpec,
+                        "package1",
+                        /*visibilityStore=*/ null,
+                        Process.myUid(),
+                        /*callerHasSystemAccess=*/ false,
+                        /*logger=*/ null);
+
+        // Document2 will come first because it was inserted last and default return order is
+        // most recent.
+        assertThat(searchResultPage.getResults()).hasSize(1);
+        assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
+
+        long nextPageToken = searchResultPage.getNextPageToken();
+
+        // Try getting next page with the wrong package, package2
+        AppSearchException e =
+                assertThrows(
+                        AppSearchException.class,
+                        () -> mAppSearchImpl.invalidateNextPageToken("package2", nextPageToken));
+        assertThat(e)
+                .hasMessageThat()
+                .contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken);
+        assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
+
+        // Can continue getting next page for package1
+        searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
+        assertThat(searchResultPage.getResults()).hasSize(1);
+        assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
+    }
+
+    @Test
     public void testRemoveEmptyDatabase_noExceptionThrown() throws Exception {
         SearchSpec searchSpec =
                 new SearchSpec.Builder()
@@ -1777,11 +2217,11 @@
 
         assertThrows(
                 IllegalStateException.class,
-                () -> appSearchImpl.getNextPage(/*nextPageToken=*/ 1L));
+                () -> appSearchImpl.getNextPage("package", /*nextPageToken=*/ 1L));
 
         assertThrows(
                 IllegalStateException.class,
-                () -> appSearchImpl.invalidateNextPageToken(/*nextPageToken=*/ 1L));
+                () -> appSearchImpl.invalidateNextPageToken("package", /*nextPageToken=*/ 1L));
 
         assertThrows(
                 IllegalStateException.class,
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 1596483..2e5c24c 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -246,6 +246,81 @@
     }
 
     @Test
+    public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForASecond()
+            throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        long vibrationId = 1;
+        int[] amplitudes = new int[]{1, 2, 3};
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                new long[]{1, 10, 100}, amplitudes, 0);
+        VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
+
+        assertTrue(waitUntil(t -> !fakeVibrator.getAmplitudes().isEmpty(), thread,
+                TEST_TIMEOUT_MILLIS));
+        thread.cancel();
+        waitForCompletion(thread);
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
+        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertEquals(Arrays.asList(expectedOneShot(1000)), fakeVibrator.getEffectSegments());
+    }
+
+    @Test
+    public void vibrate_singleVibratorRepeatingLongAlwaysOnWaveform_turnsVibratorOnForACycle()
+            throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        long vibrationId = 1;
+        int[] amplitudes = new int[]{1, 2, 3};
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                new long[]{5000, 500, 50}, amplitudes, 0);
+        VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
+
+        assertTrue(waitUntil(t -> !fakeVibrator.getAmplitudes().isEmpty(), thread,
+                TEST_TIMEOUT_MILLIS));
+        thread.cancel();
+        waitForCompletion(thread);
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
+        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertEquals(Arrays.asList(expectedOneShot(5550)), fakeVibrator.getEffectSegments());
+    }
+
+
+    @Test
+    public void vibrate_singleVibratorRepeatingAlwaysOnWaveform_turnsVibratorBackOn()
+            throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        long vibrationId = 1;
+        int[] amplitudes = new int[]{1, 2};
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                new long[]{900, 50}, amplitudes, 0);
+        VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
+
+        assertTrue(waitUntil(t -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length,
+                thread, 1000 + TEST_TIMEOUT_MILLIS));
+        thread.cancel();
+        waitForCompletion(thread);
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
+        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertEquals(2, fakeVibrator.getEffectSegments().size());
+        // First time turn vibrator ON for minimum of 1s.
+        assertEquals(1000L, fakeVibrator.getEffectSegments().get(0).getDuration());
+        // Vibrator turns off in the middle of the second execution of first step, turn it back ON
+        // for another 1s + remaining of 850ms.
+        assertEquals(1850, fakeVibrator.getEffectSegments().get(1).getDuration(), /* delta= */ 20);
+        // Set amplitudes for a cycle {1, 2}, start second loop then turn it back on to same value.
+        assertEquals(expectedAmplitudes(1, 2, 1, 1),
+                mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().subList(0, 4));
+    }
+
+    @Test
     public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately()
             throws Exception {
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);