Merge "Fixed NPE on VoiceInteractor.destroy()" into qt-dev
diff --git a/api/current.txt b/api/current.txt
index 8b24826..54fb459 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -143,6 +143,7 @@
     field public static final String SET_WALLPAPER_HINTS = "android.permission.SET_WALLPAPER_HINTS";
     field public static final String SIGNAL_PERSISTENT_PROCESSES = "android.permission.SIGNAL_PERSISTENT_PROCESSES";
     field public static final String SMS_FINANCIAL_TRANSACTIONS = "android.permission.SMS_FINANCIAL_TRANSACTIONS";
+    field public static final String START_VIEW_PERMISSION_USAGE = "android.permission.START_VIEW_PERMISSION_USAGE";
     field public static final String STATUS_BAR = "android.permission.STATUS_BAR";
     field public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
     field public static final String TRANSMIT_IR = "android.permission.TRANSMIT_IR";
@@ -10326,6 +10327,7 @@
     field public static final String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
     field public static final String ACTION_VIEW = "android.intent.action.VIEW";
     field public static final String ACTION_VIEW_LOCUS = "android.intent.action.VIEW_LOCUS";
+    field @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE) public static final String ACTION_VIEW_PERMISSION_USAGE = "android.intent.action.VIEW_PERMISSION_USAGE";
     field public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
     field @Deprecated public static final String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
     field public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index b9a4b52..f53ac1b 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -298,6 +298,7 @@
         VehicleMapServicePacketFailureReported vms_packet_failure_reported = 202;
         CarPowerStateChanged car_power_state_changed = 203;
         GarageModeInfo garage_mode_info = 204;
+        TestAtomReported test_atom_reported = 205 [(log_from_module) = "cts"];
     }
 
     // Pulled events will start at field 10000.
@@ -3342,6 +3343,23 @@
     optional int32 user_id = 8;
 }
 
+/* Test atom, is not logged anywhere */
+message TestAtomReported {
+    repeated AttributionNode attribution_node = 1;
+    optional int32 int_field = 2;
+    optional int64 long_field = 3;
+    optional float float_field = 4;
+    optional string string_field = 5;
+    optional bool boolean_field = 6;
+    enum State {
+        UNKNOWN = 0;
+        OFF = 1;
+        ON = 2;
+    }
+    optional State state = 7;
+    optional TrainExperimentIds bytes_field = 8 [(android.os.statsd.log_mode) = MODE_BYTES];
+}
+
 /** Represents USB port overheat event. */
 message UsbPortOverheatEvent {
     /* Temperature of USB port at USB plug event, in 1/10ths of degree C. */
diff --git a/core/java/android/app/JobSchedulerImpl.java b/core/java/android/app/JobSchedulerImpl.java
index 5494e2a..e877018 100644
--- a/core/java/android/app/JobSchedulerImpl.java
+++ b/core/java/android/app/JobSchedulerImpl.java
@@ -83,7 +83,7 @@
     @Override
     public List<JobInfo> getAllPendingJobs() {
         try {
-            return mBinder.getAllPendingJobs();
+            return mBinder.getAllPendingJobs().getList();
         } catch (RemoteException e) {
             return null;
         }
@@ -110,7 +110,7 @@
     @Override
     public List<JobSnapshot> getAllJobSnapshots() {
         try {
-            return mBinder.getAllJobSnapshots();
+            return mBinder.getAllJobSnapshots().getList();
         } catch (RemoteException e) {
             return null;
         }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index b4c6d94..789351e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -8832,8 +8832,8 @@
              * <p>Setting this flag is optional; it defaults to false.</p>
              */
             @NonNull
-            public BubbleMetadata.Builder setSuppressNotification(boolean shouldSupressNotif) {
-                setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSupressNotif);
+            public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) {
+                setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif);
                 return this;
             }
 
diff --git a/core/java/android/app/job/IJobScheduler.aidl b/core/java/android/app/job/IJobScheduler.aidl
index 53b33c2..3006f50 100644
--- a/core/java/android/app/job/IJobScheduler.aidl
+++ b/core/java/android/app/job/IJobScheduler.aidl
@@ -19,6 +19,7 @@
 import android.app.job.JobInfo;
 import android.app.job.JobSnapshot;
 import android.app.job.JobWorkItem;
+import android.content.pm.ParceledListSlice;
 
  /**
   * IPC interface that supports the app-facing {@link #JobScheduler} api.
@@ -30,8 +31,8 @@
     int scheduleAsPackage(in JobInfo job, String packageName, int userId, String tag);
     void cancel(int jobId);
     void cancelAll();
-    List<JobInfo> getAllPendingJobs();
+    ParceledListSlice getAllPendingJobs();
     JobInfo getPendingJob(int jobId);
     List<JobInfo> getStartedJobs();
-    List<JobSnapshot> getAllJobSnapshots();
+    ParceledListSlice getAllJobSnapshots();
 }
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 8046776..2c5860a 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -825,7 +825,9 @@
      * @param sortOrder How to order the rows, formatted as an SQL ORDER BY
      *         clause (excluding the ORDER BY itself). Passing null will use the
      *         default sort order, which may be unordered.
-     * @return A Cursor object, which is positioned before the first entry, or null
+     * @return A Cursor object, which is positioned before the first entry. May return
+     *         <code>null</code> if the underlying content provider returns <code>null</code>,
+     *         or if it crashes.
      * @see Cursor
      */
     public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri,
@@ -865,7 +867,9 @@
      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
      * If the operation is canceled, then {@link OperationCanceledException} will be thrown
      * when the query is executed.
-     * @return A Cursor object, which is positioned before the first entry, or null
+     * @return A Cursor object, which is positioned before the first entry. May return
+     *         <code>null</code> if the underlying content provider returns <code>null</code>,
+     *         or if it crashes.
      * @see Cursor
      */
     public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri,
@@ -902,7 +906,9 @@
      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
      * If the operation is canceled, then {@link OperationCanceledException} will be thrown
      * when the query is executed.
-     * @return A Cursor object, which is positioned before the first entry, or null
+     * @return A Cursor object, which is positioned before the first entry. May return
+     *         <code>null</code> if the underlying content provider returns <code>null</code>,
+     *         or if it crashes.
      * @see Cursor
      */
     @Override
@@ -1799,7 +1805,8 @@
      * @param url The URL of the table to insert into.
      * @param values The initial values for the newly inserted row. The key is the column name for
      *               the field. Passing an empty ContentValues will create an empty row.
-     * @return the URL of the newly created row.
+     * @return the URL of the newly created row. May return <code>null</code> if the underlying
+     *         content provider returns <code>null</code>, or if it crashes.
      */
     @Override
     public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 50d1785..9e5fcfb 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1881,6 +1881,31 @@
             "android.intent.action.REVIEW_PERMISSIONS";
 
     /**
+     * Activity action: Launch UI to show information about the usage
+     * of a given permission. This action would be handled by apps that
+     * want to show details about how and why given permission is being
+     * used.
+     * <p>
+     * <strong>Important:</strong>You must protect the activity that handles
+     * this action with the {@link android.Manifest.permission#START_VIEW_PERMISSION_USAGE
+     *  START_VIEW_PERMISSION_USAGE} permission to ensure that only the
+     * system can launch this activity. The system will not launch
+     * activities that are not properly protected.
+     *
+     * <p>
+     * Input: {@code android.intent.extra.PERMISSION_NAME} specifies the permission
+     * for which the launched UI would be targeted.
+     * </p>
+     * <p>
+     * Output: Nothing.
+     * </p>
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE)
+    public static final String ACTION_VIEW_PERMISSION_USAGE =
+            "android.intent.action.VIEW_PERMISSION_USAGE";
+
+    /**
      * Activity action: Launch UI to manage a default app.
      * <p>
      * Input: {@link #EXTRA_ROLE_NAME} specifies the role of the default app which will be managed
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index ae36e4e..c42dc81 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -37,10 +37,6 @@
     public static final String SEAMLESS_TRANSFER = "settings_seamless_transfer";
     public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid";
     public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
-    public static final String FORCE_GLOBAL_ACTIONS_GRID_ENABLED =
-            "settings_global_actions_force_grid_enabled";
-    public static final String GLOBAL_ACTIONS_PANEL_ENABLED =
-            "settings_global_actions_panel_enabled";
     public static final String PIXEL_WALLPAPER_CATEGORY_SWITCH =
             "settings_pixel_wallpaper_category_switch";
     public static final String DYNAMIC_SYSTEM = "settings_dynamic_system";
@@ -57,8 +53,6 @@
         DEFAULT_FLAGS.put(SEAMLESS_TRANSFER, "false");
         DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
         DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false");
-        DEFAULT_FLAGS.put(FORCE_GLOBAL_ACTIONS_GRID_ENABLED, "false");
-        DEFAULT_FLAGS.put(GLOBAL_ACTIONS_PANEL_ENABLED, "true");
         DEFAULT_FLAGS.put(PIXEL_WALLPAPER_CATEGORY_SWITCH, "false");
         DEFAULT_FLAGS.put("settings_wifi_details_datausage_header", "false");
     }
diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java
index 74fea3f..80b1607 100644
--- a/core/java/android/util/MemoryIntArray.java
+++ b/core/java/android/util/MemoryIntArray.java
@@ -20,9 +20,8 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 
-import dalvik.system.CloseGuard;
-
 import libcore.io.IoUtils;
+import dalvik.system.CloseGuard;
 
 import java.io.Closeable;
 import java.io.IOException;
@@ -57,7 +56,7 @@
 
     private final boolean mIsOwner;
     private final long mMemoryAddr;
-    private ParcelFileDescriptor mFd;
+    private int mFd = -1;
 
     /**
      * Creates a new instance.
@@ -72,8 +71,8 @@
         }
         mIsOwner = true;
         final String name = UUID.randomUUID().toString();
-        mFd = ParcelFileDescriptor.adoptFd(nativeCreate(name, size));
-        mMemoryAddr = nativeOpen(mFd.getFd(), mIsOwner);
+        mFd = nativeCreate(name, size);
+        mMemoryAddr = nativeOpen(mFd, mIsOwner);
         mCloseGuard.open("close");
     }
 
@@ -83,8 +82,8 @@
         if (pfd == null) {
             throw new IOException("No backing file descriptor");
         }
-        mFd = ParcelFileDescriptor.adoptFd(pfd.detachFd());
-        mMemoryAddr = nativeOpen(mFd.getFd(), mIsOwner);
+        mFd = pfd.detachFd();
+        mMemoryAddr = nativeOpen(mFd, mIsOwner);
         mCloseGuard.open("close");
     }
 
@@ -106,7 +105,7 @@
     public int get(int index) throws IOException {
         enforceNotClosed();
         enforceValidIndex(index);
-        return nativeGet(mFd.getFd(), mMemoryAddr, index);
+        return nativeGet(mFd, mMemoryAddr, index);
     }
 
     /**
@@ -122,7 +121,7 @@
         enforceNotClosed();
         enforceWritable();
         enforceValidIndex(index);
-        nativeSet(mFd.getFd(), mMemoryAddr, index, value);
+        nativeSet(mFd, mMemoryAddr, index, value);
     }
 
     /**
@@ -132,7 +131,7 @@
      */
     public int size() throws IOException {
         enforceNotClosed();
-        return nativeSize(mFd.getFd());
+        return nativeSize(mFd);
     }
 
     /**
@@ -143,9 +142,8 @@
     @Override
     public void close() throws IOException {
         if (!isClosed()) {
-            nativeClose(mFd.getFd(), mMemoryAddr, mIsOwner);
-            mFd.close();
-            mFd = null;
+            nativeClose(mFd, mMemoryAddr, mIsOwner);
+            mFd = -1;
             mCloseGuard.close();
         }
     }
@@ -154,7 +152,7 @@
      * @return Whether this array is closed and shouldn't be used.
      */
     public boolean isClosed() {
-        return mFd == null;
+        return mFd == -1;
     }
 
     @Override
@@ -177,8 +175,13 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        // Don't let writing to a parcel to close our fd - plz
-        parcel.writeParcelable(mFd, flags & ~Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.adoptFd(mFd);
+        try {
+            // Don't let writing to a parcel to close our fd - plz
+            parcel.writeParcelable(pfd, flags & ~Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+        } finally {
+            pfd.detachFd();
+        }
     }
 
     @Override
@@ -192,13 +195,13 @@
         if (getClass() != obj.getClass()) {
             return false;
         }
-
-        return false;
+        MemoryIntArray other = (MemoryIntArray) obj;
+        return mFd == other.mFd;
     }
 
     @Override
     public int hashCode() {
-        return mFd.hashCode();
+        return mFd;
     }
 
     private void enforceNotClosed() {
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index cc2caca..cdb79ab 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -247,7 +247,6 @@
             }
             childId = getDocIdForFile(file);
             onDocIdChanged(childId);
-            addFolderToMediaStore(getFileForDocId(childId, true));
         } else {
             try {
                 if (!file.createNewFile()) {
@@ -259,19 +258,11 @@
                 throw new IllegalStateException("Failed to touch " + file + ": " + e);
             }
         }
+        MediaStore.scanFile(getContext(), file);
 
         return childId;
     }
 
-    private void addFolderToMediaStore(@Nullable File visibleFolder) {
-        // visibleFolder is null if we're adding a folder to external thumb drive or SD card.
-        if (visibleFolder != null) {
-            assert (visibleFolder.isDirectory());
-
-            MediaStore.scanFile(getContext(), visibleFolder);
-        }
-    }
-
     @Override
     public String renameDocument(String docId, String displayName) throws FileNotFoundException {
         // Since this provider treats renames as generating a completely new
@@ -293,7 +284,6 @@
         moveInMediaStore(beforeVisibleFile, afterVisibleFile);
 
         if (!TextUtils.equals(docId, afterDocId)) {
-            scanFile(afterVisibleFile);
             return afterDocId;
         } else {
             return null;
diff --git a/core/java/com/android/internal/infra/AbstractRemoteService.java b/core/java/com/android/internal/infra/AbstractRemoteService.java
index 64f8857..3900f16 100644
--- a/core/java/com/android/internal/infra/AbstractRemoteService.java
+++ b/core/java/com/android/internal/infra/AbstractRemoteService.java
@@ -231,6 +231,7 @@
         @SuppressWarnings("unchecked") // TODO(b/117779333): fix this warning
         final S castService = (S) this;
         mVultureCallback.onServiceDied(castService);
+        handleBindFailure();
     }
 
     // Note: we are dumping without a lock held so this is a bit racy but
@@ -406,7 +407,8 @@
             @NonNull BasePendingRequest<S, I> pendingRequest);
 
     /**
-     * Called if {@link Context#bindServiceAsUser} returns {@code false}.
+     * Called if {@link Context#bindServiceAsUser} returns {@code false}, or
+     * if {@link DeathRecipient#binderDied()} is called.
      */
     abstract void handleBindFailure();
 
@@ -431,8 +433,6 @@
             mBinding = false;
 
             if (!mServiceDied) {
-                // TODO(b/126266412): merge these 2 calls?
-                handleBindFailure();
                 handleBinderDied();
             }
         }
diff --git a/core/jni/android_util_MemoryIntArray.cpp b/core/jni/android_util_MemoryIntArray.cpp
index b68f9ec..2dfbe3e 100644
--- a/core/jni/android_util_MemoryIntArray.cpp
+++ b/core/jni/android_util_MemoryIntArray.cpp
@@ -142,6 +142,8 @@
         jniThrowException(env, "java/io/IOException", "ashmem unpinning failed");
         return;
     }
+
+    close(fd);
 }
 
 static jint android_util_MemoryIntArray_get(JNIEnv* env, jobject clazz,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 57b7704..b634bb2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4213,6 +4213,15 @@
         android:description="@string/permdesc_bindCarrierServices"
         android:protectionLevel="signature|privileged" />
 
+    <!--
+        Allows the holder to start the permission usage screen for an app.
+        <p>Protection level: signature|installer
+    -->
+    <permission android:name="android.permission.START_VIEW_PERMISSION_USAGE"
+        android:label="@string/permlab_startViewPermissionUsage"
+        android:description="@string/permdesc_startViewPermissionUsage"
+        android:protectionLevel="signature|installer" />
+
     <!-- Allows an application to query whether DO_NOT_ASK_CREDENTIALS_ON_BOOT
          flag is set.
          @hide -->
diff --git a/core/res/res/layout/media_route_chooser_dialog.xml b/core/res/res/layout/media_route_chooser_dialog.xml
index d1c6267..cd1c74f 100644
--- a/core/res/res/layout/media_route_chooser_dialog.xml
+++ b/core/res/res/layout/media_route_chooser_dialog.xml
@@ -40,7 +40,7 @@
         <TextView android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:layout_gravity="center"
-                  android:paddingLeft="16dp"
+                  android:paddingStart="16dp"
                   android:text="@string/media_route_chooser_searching" />
     </LinearLayout>
 
diff --git a/core/res/res/layout/media_route_list_item.xml b/core/res/res/layout/media_route_list_item.xml
index bdca433..e8460db 100644
--- a/core/res/res/layout/media_route_list_item.xml
+++ b/core/res/res/layout/media_route_list_item.xml
@@ -34,6 +34,7 @@
                   android:layout_height="wrap_content"
                   android:singleLine="true"
                   android:ellipsize="marquee"
+                  android:textAlignment="viewStart"
                   android:textAppearance="?android:attr/textAppearanceMedium"
                   android:duplicateParentState="true" />
 
@@ -42,6 +43,7 @@
                   android:layout_height="wrap_content"
                   android:singleLine="true"
                   android:ellipsize="marquee"
+                  android:textAlignment="viewStart"
                   android:textAppearance="?android:attr/textAppearanceSmall"
                   android:duplicateParentState="true" />
     </LinearLayout>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 5652c85..0cf9500 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1726,6 +1726,11 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_access_notification_policy">Allows the app to read and write Do Not Disturb configuration.</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_startViewPermissionUsage">start view permission usage</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_startViewPermissionUsage">Allows the holder to start the permission usage for an app. Should never be needed for normal apps.</string>
+
     <!-- Policy administration -->
 
     <!-- Title of policy access to limiting the user's password choices -->
diff --git a/core/tests/utiltests/Android.mk b/core/tests/utiltests/Android.mk
index 343c07a..9ef73e9 100644
--- a/core/tests/utiltests/Android.mk
+++ b/core/tests/utiltests/Android.mk
@@ -18,6 +18,7 @@
     androidx.test.rules \
     frameworks-base-testutils \
     mockito-target-minus-junit4 \
+    androidx.test.ext.junit
 
 LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock
 
diff --git a/core/tests/utiltests/jni/android_util_MemoryIntArrayTest.cpp b/core/tests/utiltests/jni/android_util_MemoryIntArrayTest.cpp
index 4b14284..57ee2d5 100644
--- a/core/tests/utiltests/jni/android_util_MemoryIntArrayTest.cpp
+++ b/core/tests/utiltests/jni/android_util_MemoryIntArrayTest.cpp
@@ -21,6 +21,36 @@
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 
+jint android_util_MemoryIntArrayTest_createAshmem(__attribute__((unused)) JNIEnv* env,
+        __attribute__((unused)) jobject clazz,
+        jstring name, jint size)
+{
+
+    if (name == NULL) {
+        return -1;
+    }
+
+    if (size < 0) {
+        return -1;
+    }
+
+    const char* nameStr = env->GetStringUTFChars(name, NULL);
+    const int ashmemSize = sizeof(std::atomic_int) * size;
+    int fd = ashmem_create_region(nameStr, ashmemSize);
+    env->ReleaseStringUTFChars(name, nameStr);
+
+    if (fd < 0) {
+        return -1;
+    }
+
+    int setProtResult = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
+    if (setProtResult < 0) {
+        return -1;
+    }
+
+    return fd;
+}
+
 void android_util_MemoryIntArrayTest_setAshmemSize(__attribute__((unused)) JNIEnv* env,
         __attribute__((unused)) jobject clazz, jint fd, jint size)
 {
diff --git a/core/tests/utiltests/jni/registration.cpp b/core/tests/utiltests/jni/registration.cpp
index d4fc2fb..0c84d98 100644
--- a/core/tests/utiltests/jni/registration.cpp
+++ b/core/tests/utiltests/jni/registration.cpp
@@ -16,14 +16,25 @@
 
 #include <jni.h>
 
+extern jint android_util_MemoryIntArrayTest_createAshmem(JNIEnv* env,
+        jobject clazz, jstring name, jint size);
 extern void android_util_MemoryIntArrayTest_setAshmemSize(JNIEnv* env,
        jobject clazz, jint fd, jint size);
 
 extern "C" {
+    JNIEXPORT jint JNICALL Java_android_util_MemoryIntArrayTest_nativeCreateAshmem(
+            JNIEnv * env, jobject obj, jstring name, jint size);
     JNIEXPORT void JNICALL Java_android_util_MemoryIntArrayTest_nativeSetAshmemSize(
             JNIEnv * env, jobject obj, jint fd, jint size);
 };
 
+JNIEXPORT jint JNICALL Java_android_util_MemoryIntArrayTest_nativeCreateAshmem(
+        __attribute__((unused)) JNIEnv * env,__attribute__((unused)) jobject obj,
+        jstring name, jint size)
+{
+    return android_util_MemoryIntArrayTest_createAshmem(env, obj, name, size);
+}
+
 JNIEXPORT void JNICALL Java_android_util_MemoryIntArrayTest_nativeSetAshmemSize(
         __attribute__((unused)) JNIEnv * env,__attribute__((unused)) jobject obj,
         jint fd, jint size)
diff --git a/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
index 2daefe7..1966e12 100644
--- a/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
+++ b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
@@ -23,9 +23,8 @@
 import static org.junit.Assert.fail;
 
 import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
 
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import libcore.io.IoUtils;
 
@@ -36,6 +35,8 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 @RunWith(AndroidJUnit4.class)
 public class MemoryIntArrayTest {
     static {
@@ -255,11 +256,13 @@
                 // Create a MemoryIntArray to muck with
                 MemoryIntArray array = new MemoryIntArray(1);
 
-                // Grab the internal ashmem fd.
-                Field fdField = MemoryIntArray.class.getDeclaredField("mFd");
-                fdField.setAccessible(true);
-                int fd = ((ParcelFileDescriptor)fdField.get(array)).getFd();
-                assertTrue("fd must be valid", fd != -1);
+                // Create the fd to stuff in the MemoryIntArray
+                final int fd = nativeCreateAshmem("foo", 1);
+
+                // Replace the fd with our ahsmem region
+                Field fdFiled = MemoryIntArray.class.getDeclaredField("mFd");
+                fdFiled.setAccessible(true);
+                fdFiled.set(array, fd);
 
                 CountDownLatch countDownLatch = new CountDownLatch(2);
 
@@ -294,9 +297,10 @@
         }
 
         if (!success) {
-            fail("MemoryIntArray should catch ashmem size changing under it");
+            fail("MemoryIntArray should catch ahshmem size changing under it");
         }
     }
 
+    private native int nativeCreateAshmem(String name, int size);
     private native void nativeSetAshmemSize(int fd, int size);
 }
diff --git a/data/etc/com.android.dialer.xml b/data/etc/com.android.dialer.xml
index ccdb21f..405279f 100644
--- a/data/etc/com.android.dialer.xml
+++ b/data/etc/com.android.dialer.xml
@@ -24,5 +24,7 @@
         <permission name="android.permission.STOP_APP_SWITCHES"/>
         <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/>
         <permission name="com.android.voicemail.permission.WRITE_VOICEMAIL"/>
+        <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
+        <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
     </privapp-permissions>
 </permissions>
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index 5de56c7..b3c2bb7 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -52,6 +52,7 @@
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.Objects;
 import java.util.function.ToIntFunction;
 
 /**
@@ -369,10 +370,12 @@
             // If we're okay with something larger than native format, just
             // return a frame without up-scaling it
             if (size.getWidth() > width && size.getHeight() > height) {
-                return mmr.getFrameAtTime(duration / 2, OPTION_CLOSEST_SYNC);
+                return Objects.requireNonNull(
+                        mmr.getFrameAtTime(duration / 2, OPTION_CLOSEST_SYNC));
             } else {
-                return mmr.getScaledFrameAtTime(duration / 2, OPTION_CLOSEST_SYNC,
-                        size.getWidth(), size.getHeight());
+                return Objects.requireNonNull(
+                        mmr.getScaledFrameAtTime(duration / 2, OPTION_CLOSEST_SYNC,
+                        size.getWidth(), size.getHeight()));
             }
         } catch (RuntimeException e) {
             throw new IOException("Failed to create thumbnail", e);
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 835cedf..f5dab01 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -131,6 +131,8 @@
     // The container for the notifications.
     private CarNotificationView mNotificationView;
     private RecyclerView mNotificationList;
+    // The controller for the notification view.
+    private NotificationViewController mNotificationViewController;
     // The state of if the notification list is currently showing the bottom.
     private boolean mNotificationListAtBottom;
     // Was the notification list at the bottom when the user first touched the screen
@@ -544,7 +546,7 @@
             }
         });
 
-        NotificationViewController mNotificationViewController = new NotificationViewController(
+        mNotificationViewController = new NotificationViewController(
                 mNotificationView,
                 PreprocessingManager.getInstance(mContext),
                 carNotificationListener,
@@ -651,9 +653,11 @@
                     mStatusBarWindowController.setPanelVisible(false);
                     mNotificationView.setVisibility(View.INVISIBLE);
                     mNotificationList.setClipBounds(null);
+                    mNotificationViewController.setIsInForeground(false);
                     // let the status bar know that the panel is closed
                     setPanelExpanded(false);
                 } else {
+                    mNotificationViewController.setIsInForeground(true);
                     // let the status bar know that the panel is open
                     mNotificationView.setVisibleNotificationsAsSeen();
                     setPanelExpanded(true);
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index 62de2ba..64718da 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -108,6 +108,8 @@
     defaults: ["NetworkStackAppCommon"],
     certificate: "platform",
     manifest: "AndroidManifest_InProcess.xml",
+    // InProcessNetworkStack is a replacement for NetworkStack
+    overrides: ["NetworkStack"],
 }
 
 // Updatable network stack packaged as an application
@@ -116,6 +118,7 @@
     defaults: ["NetworkStackAppCommon"],
     certificate: "networkstack",
     manifest: "AndroidManifest.xml",
+    use_embedded_native_libs: true,
 }
 
 genrule {
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
index 252b90f..bfcd6c1 100644
--- a/packages/NetworkStack/AndroidManifest.xml
+++ b/packages/NetworkStack/AndroidManifest.xml
@@ -41,7 +41,7 @@
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <!-- Signature permission defined in NetworkStackStub -->
     <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK" />
-    <application>
+    <application android:extractNativeLibs="false">
         <service android:name="com.android.server.NetworkStackService">
             <intent-filter>
                 <action android:name="android.net.INetworkStackConnector"/>
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
index cb0b7c2..98eb573 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
@@ -286,8 +286,7 @@
 
     /** Returns the state representing empty mobile signal with the given number of levels. */
     public static int getEmptyState(int numLevels) {
-        // TODO empty state == 0 state. does there need to be a new drawable for this?
-        return getState(0, numLevels, false);
+        return getState(0, numLevels, true);
     }
 
     /** Returns the state representing carrier change with the given number of levels. */
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
index b8e1251..6fd8749 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
@@ -18,11 +18,11 @@
 
 import android.app.AppOpsManager;
 import android.content.Context;
+import android.content.PermissionChecker;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.graphics.drawable.Drawable;
-import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.text.format.DateUtils;
@@ -48,10 +48,15 @@
     private static final long RECENT_TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS;
 
     @VisibleForTesting
-    static final int[] LOCATION_OPS = new int[] {
+    static final int[] LOCATION_REQUEST_OPS = new int[]{
             AppOpsManager.OP_MONITOR_LOCATION,
             AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
     };
+    @VisibleForTesting
+    static final int[] LOCATION_PERMISSION_OPS = new int[]{
+            AppOpsManager.OP_FINE_LOCATION,
+            AppOpsManager.OP_COARSE_LOCATION,
+    };
 
     private final PackageManager mPackageManager;
     private final Context mContext;
@@ -67,11 +72,13 @@
      * Fills a list of applications which queried location recently within specified time.
      * Apps are sorted by recency. Apps with more recent location requests are in the front.
      */
-    public List<Request> getAppList() {
+    public List<Request> getAppList(boolean showSystemApps) {
+        // Retrieve a location usage list from AppOps
+        PackageManager pm = mContext.getPackageManager();
         // Retrieve a location usage list from AppOps
         AppOpsManager aoManager =
                 (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-        List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_OPS);
+        List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_REQUEST_OPS);
 
         final int appOpsCount = appOps != null ? appOps.size() : 0;
 
@@ -83,26 +90,58 @@
 
         for (int i = 0; i < appOpsCount; ++i) {
             AppOpsManager.PackageOps ops = appOps.get(i);
-            // Don't show the Android System in the list - it's not actionable for the user.
-            // Also don't show apps belonging to background users except managed users.
             String packageName = ops.getPackageName();
             int uid = ops.getUid();
-            int userId = UserHandle.getUserId(uid);
-            boolean isAndroidOs =
-                    (uid == Process.SYSTEM_UID) && ANDROID_SYSTEM_PACKAGE_NAME.equals(packageName);
-            if (isAndroidOs || !profiles.contains(new UserHandle(userId))) {
+            final UserHandle user = UserHandle.getUserHandleForUid(uid);
+
+            // Don't show apps belonging to background users except managed users.
+            if (!profiles.contains(user)) {
                 continue;
             }
-            Request request = getRequestFromOps(now, ops);
-            if (request != null) {
-                requests.add(request);
+
+            // Don't show apps that do not have user sensitive location permissions
+            boolean showApp = true;
+            if (!showSystemApps) {
+                for (int op : LOCATION_PERMISSION_OPS) {
+                    final String permission = AppOpsManager.opToPermission(op);
+                    final int permissionFlags = pm.getPermissionFlags(permission, packageName,
+                            user);
+                    if (PermissionChecker.checkPermission(mContext, permission, -1, uid,
+                            packageName)
+                            == PermissionChecker.PERMISSION_GRANTED) {
+                        if ((permissionFlags
+                                & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)
+                                == 0) {
+                            showApp = false;
+                            break;
+                        }
+                    } else {
+                        if ((permissionFlags
+                                & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0) {
+                            showApp = false;
+                            break;
+                        }
+                    }
+                }
+            }
+            if (showApp) {
+                Request request = getRequestFromOps(now, ops);
+                if (request != null) {
+                    requests.add(request);
+                }
             }
         }
         return requests;
     }
 
-    public List<Request> getAppListSorted() {
-        List<Request> requests = getAppList();
+    /**
+     * Gets a list of apps that requested for location recently, sorting by recency.
+     *
+     * @param showSystemApps whether includes system apps in the list.
+     * @return the list of apps that recently requested for location.
+     */
+    public List<Request> getAppListSorted(boolean showSystemApps) {
+        List<Request> requests = getAppList(showSystemApps);
         // Sort the list of Requests by recency. Most recent request first.
         Collections.sort(requests, Collections.reverseOrder(new Comparator<Request>() {
             @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 05af4e1..e28c612 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -180,7 +180,8 @@
     public static final int SECURITY_SAE = 5;
     public static final int SECURITY_EAP_SUITE_B = 6;
     public static final int SECURITY_PSK_SAE_TRANSITION = 7;
-    public static final int SECURITY_MAX_VAL = 8; // Has to be the last
+    public static final int SECURITY_OWE_TRANSITION = 8;
+    public static final int SECURITY_MAX_VAL = 9; // Has to be the last
 
     private static final int PSK_UNKNOWN = 0;
     private static final int PSK_WPA = 1;
@@ -869,6 +870,12 @@
                     return concise ? context.getString(R.string.wifi_security_short_sae) :
                             context.getString(R.string.wifi_security_sae);
                 }
+            case SECURITY_OWE_TRANSITION:
+                if (mConfig != null && getSecurity(mConfig) == SECURITY_OWE) {
+                    return concise ? context.getString(R.string.wifi_security_short_owe) :
+                            context.getString(R.string.wifi_security_owe);
+                }
+                return concise ? "" : context.getString(R.string.wifi_security_none);
             case SECURITY_OWE:
                 return concise ? context.getString(R.string.wifi_security_short_owe) :
                     context.getString(R.string.wifi_security_owe);
@@ -1179,7 +1186,8 @@
      * Can only be called for unsecured networks.
      */
     public void generateOpenNetworkConfig() {
-        if ((security != SECURITY_NONE) && (security != SECURITY_OWE)) {
+        if ((security != SECURITY_NONE) && (security != SECURITY_OWE)
+                && (security != SECURITY_OWE_TRANSITION)) {
             throw new IllegalStateException();
         }
         if (mConfig != null)
@@ -1187,7 +1195,7 @@
         mConfig = new WifiConfiguration();
         mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
 
-        if (security == SECURITY_NONE) {
+        if (security == SECURITY_NONE || !getWifiManager().isEasyConnectSupported()) {
             mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
         } else {
             mConfig.allowedKeyManagement.set(KeyMgmt.OWE);
@@ -1229,6 +1237,9 @@
     private static final String sPskSuffix = "," + String.valueOf(SECURITY_PSK);
     private static final String sSaeSuffix = "," + String.valueOf(SECURITY_SAE);
     private static final String sPskSaeSuffix = "," + String.valueOf(SECURITY_PSK_SAE_TRANSITION);
+    private static final String sOweSuffix = "," + String.valueOf(SECURITY_OWE);
+    private static final String sOpenSuffix = "," + String.valueOf(SECURITY_NONE);
+    private static final String sOweTransSuffix = "," + String.valueOf(SECURITY_OWE_TRANSITION);
 
     private boolean isKeyEqual(String compareTo) {
         if (mKey == null) {
@@ -1243,6 +1254,14 @@
                         compareTo.substring(0, compareTo.lastIndexOf(',')));
             }
         }
+        if (compareTo.endsWith(sOpenSuffix) || compareTo.endsWith(sOweSuffix)) {
+            if (mKey.endsWith(sOweTransSuffix)) {
+                // Special handling for OWE/Open networks. If AP advertises OWE in transition mode
+                // and we have an Open network saved, allow this connection to be established.
+                return TextUtils.equals(mKey.substring(0, mKey.lastIndexOf(',')),
+                        compareTo.substring(0, compareTo.lastIndexOf(',')));
+            }
+        }
         return mKey.equals(compareTo);
     }
 
@@ -1579,10 +1598,11 @@
             return SECURITY_EAP_SUITE_B;
         } else if (result.capabilities.contains("EAP")) {
             return SECURITY_EAP;
+        } else if (result.capabilities.contains("OWE_TRANSITION")) {
+            return SECURITY_OWE_TRANSITION;
         } else if (result.capabilities.contains("OWE")) {
             return SECURITY_OWE;
         }
-
         return SECURITY_NONE;
     }
 
@@ -1628,6 +1648,8 @@
             return "OWE";
         } else if (security == SECURITY_PSK_SAE_TRANSITION) {
             return "PSK+SAE";
+        } else if (security == SECURITY_OWE_TRANSITION) {
+            return "OWE_TRANSITION";
         }
         return "NONE";
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 6269a71..dae5464 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -201,7 +201,8 @@
             return;
         }
         if ((mAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE)
-                && (mAccessPoint.getSecurity() != AccessPoint.SECURITY_OWE)) {
+                && (mAccessPoint.getSecurity() != AccessPoint.SECURITY_OWE)
+                && (mAccessPoint.getSecurity() != AccessPoint.SECURITY_OWE_TRANSITION)) {
             mFrictionSld.setState(STATE_SECURED);
         } else if (mAccessPoint.isMetered()) {
             mFrictionSld.setState(STATE_METERED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java
index 8bd5fd2..7a553fc 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java
@@ -16,8 +16,8 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
-
 import android.util.LongSparseLongArray;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -75,7 +75,8 @@
 
         long[] testRequestTime = {ONE_MIN_AGO, TWENTY_THREE_HOURS_AGO, TWO_DAYS_AGO};
         List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
-        when(mAppOpsManager.getPackagesForOps(RecentLocationApps.LOCATION_OPS)).thenReturn(appOps);
+        when(mAppOpsManager.getPackagesForOps(RecentLocationApps.LOCATION_REQUEST_OPS)).thenReturn(
+                appOps);
         mockTestApplicationInfos(mTestUserId, TEST_PACKAGE_NAMES);
 
         mRecentLocationApps = new RecentLocationApps(mContext);
@@ -83,7 +84,7 @@
 
     @Test
     public void testGetAppList_shouldFilterRecentApps() {
-        List<RecentLocationApps.Request> requests = mRecentLocationApps.getAppList();
+        List<RecentLocationApps.Request> requests = mRecentLocationApps.getAppList(true);
         // Only two of the apps have requested location within 15 min.
         assertThat(requests).hasSize(2);
         // Make sure apps are ordered by recency
@@ -107,11 +108,12 @@
                 {ONE_MIN_AGO, TWENTY_THREE_HOURS_AGO, TWO_DAYS_AGO, ONE_MIN_AGO};
         List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
         appOps.add(androidSystemPackageOps);
-        when(mAppOpsManager.getPackagesForOps(RecentLocationApps.LOCATION_OPS)).thenReturn(appOps);
+        when(mAppOpsManager.getPackagesForOps(RecentLocationApps.LOCATION_REQUEST_OPS)).thenReturn(
+                appOps);
         mockTestApplicationInfos(
                 Process.SYSTEM_UID, RecentLocationApps.ANDROID_SYSTEM_PACKAGE_NAME);
 
-        List<RecentLocationApps.Request> requests = mRecentLocationApps.getAppList();
+        List<RecentLocationApps.Request> requests = mRecentLocationApps.getAppList(true);
         // Android OS shouldn't show up in the list of apps.
         assertThat(requests).hasSize(2);
         // Make sure apps are ordered by recency
@@ -133,7 +135,7 @@
 
     private List<PackageOps> createTestPackageOpsList(String[] packageNameList, long[] time) {
         List<PackageOps> packageOpsList = new ArrayList<>();
-        for (int i = 0; i < packageNameList.length ; i++) {
+        for (int i = 0; i < packageNameList.length; i++) {
             PackageOps packageOps = createPackageOps(
                     packageNameList[i],
                     TEST_UID,
@@ -156,11 +158,11 @@
     private OpEntry createOpEntryWithTime(int op, long time, int duration) {
         final LongSparseLongArray accessTimes = new LongSparseLongArray();
         accessTimes.put(AppOpsManager.makeKey(AppOpsManager.UID_STATE_TOP,
-            AppOpsManager.OP_FLAG_SELF), time);
+                AppOpsManager.OP_FLAG_SELF), time);
         final LongSparseLongArray durations = new LongSparseLongArray();
         durations.put(AppOpsManager.makeKey(AppOpsManager.UID_STATE_TOP,
-            AppOpsManager.OP_FLAG_SELF), duration);
+                AppOpsManager.OP_FLAG_SELF), duration);
         return new OpEntry(op, false, AppOpsManager.MODE_ALLOWED, accessTimes,
-            null /*rejectTimes*/, durations, null /* proxyUids */, null /* proxyPackages */);
+                null /*rejectTimes*/, durations, null /* proxyUids */, null /* proxyPackages */);
     }
 }
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index c2495b5..9425941 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -58,6 +58,7 @@
         "androidx.arch.core_core-runtime",
         "androidx.lifecycle_lifecycle-extensions",
         "androidx.dynamicanimation_dynamicanimation",
+        "iconloader_base",
         "SystemUI-tags",
         "SystemUI-proto",
         "dagger2-2.19",
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
index 29376ce0..796123d 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
@@ -38,6 +38,7 @@
         android:clipChildren="false"
         android:clipToPadding="false"
         android:padding="0dp"
+        android:fitsSystemWindows="true"
         android:layout_gravity="center">
         <com.android.keyguard.KeyguardSecurityViewFlipper
             android:id="@+id/view_flipper"
diff --git a/packages/SystemUI/res/drawable/bubble_flyout.xml b/packages/SystemUI/res/drawable/bubble_flyout.xml
deleted file mode 100644
index afe5372..0000000
--- a/packages/SystemUI/res/drawable/bubble_flyout.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
-    <item>
-        <shape android:shape="rectangle">
-            <solid android:color="?android:attr/colorBackgroundFloating" />
-            <corners
-                android:bottomLeftRadius="?android:attr/dialogCornerRadius"
-                android:topLeftRadius="?android:attr/dialogCornerRadius"
-                android:bottomRightRadius="?android:attr/dialogCornerRadius"
-                android:topRightRadius="?android:attr/dialogCornerRadius" />
-            <padding
-                android:left="@dimen/bubble_flyout_pointer_size"
-                android:right="@dimen/bubble_flyout_pointer_size" />
-        </shape>
-    </item>
-</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_flyout.xml b/packages/SystemUI/res/layout/bubble_flyout.xml
index 0e4d298..5f773f4 100644
--- a/packages/SystemUI/res/layout/bubble_flyout.xml
+++ b/packages/SystemUI/res/layout/bubble_flyout.xml
@@ -13,18 +13,13 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:paddingLeft="@dimen/bubble_flyout_pointer_size"
-    android:paddingRight="@dimen/bubble_flyout_pointer_size">
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
 
     <FrameLayout
-        android:id="@+id/bubble_flyout"
+        android:id="@+id/bubble_flyout_text_container"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
-        android:background="@drawable/bubble_flyout"
+        android:clipToPadding="false"
         android:paddingLeft="@dimen/bubble_flyout_padding_x"
         android:paddingRight="@dimen/bubble_flyout_padding_x"
         android:paddingTop="@dimen/bubble_flyout_padding_y"
@@ -41,4 +36,4 @@
 
     </FrameLayout>
 
-</FrameLayout>
\ No newline at end of file
+</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 4cf5f85..a914930 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -69,7 +69,7 @@
         android:layout_height="match_parent"
         android:importantForAccessibility="no"
         sysui:ignoreRightInset="true"
-        />
+    />
 
     <LinearLayout
         android:id="@+id/lock_icon_container"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index fbb439a..6297423 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -252,6 +252,9 @@
     <!-- size at which Notification icons will be drawn on Ambient Display -->
     <dimen name="status_bar_icon_drawing_size_dark">@*android:dimen/notification_header_icon_size_ambient</dimen>
 
+    <!-- size of notification icons on AOD -->
+    <dimen name="dark_shelf_icon_size">16dp</dimen>
+
     <!-- opacity at which Notification icons will be drawn in the status bar -->
     <item type="dimen" name="status_bar_icon_drawing_alpha">90%</item>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 6709804..577e3bb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -134,9 +134,4 @@
      * Sent when some system ui state changes.
      */
     void onSystemUiStateChanged(int stateFlags) = 16;
-
-    /**
-     * Sent when the scrim colors (based on wallpaper) change.
-     */
-    void onScrimColorsChanged(int color, int type) = 17;
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index d051def..0914fb8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -20,6 +20,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.graphics.Rect;
 import android.metrics.LogMaker;
 import android.os.UserHandle;
 import android.util.AttributeSet;
@@ -139,7 +140,6 @@
             getSecurityView(mCurrentSecuritySelection).onResume(reason);
         }
         updateBiometricRetry();
-        updatePaddings();
     }
 
     @Override
@@ -180,7 +180,7 @@
                 }
                 int index = event.findPointerIndex(mActivePointerId);
                 int touchSlop = mViewConfiguration.getScaledTouchSlop();
-                if (mCurrentSecurityView != null
+                if (mCurrentSecurityView != null && index != -1
                         && mStartTouchY - event.getY(index) > touchSlop) {
                     mIsDragging = true;
                     return true;
@@ -319,17 +319,11 @@
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        updatePaddings();
-    }
-
-    private void updatePaddings() {
-        int bottomPadding = getRootWindowInsets().getSystemWindowInsets().bottom;
-        if (getPaddingBottom() == bottomPadding) {
-            return;
-        }
-        setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), bottomPadding);
+    protected boolean fitSystemWindows(Rect insets) {
+        // Consume bottom insets because we're setting the padding locally (for IME and navbar.)
+        setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), insets.bottom);
+        insets.bottom = 0;
+        return false;
     }
 
     private void showDialog(String title, String message) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index dd6ccb2..6402884 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1608,23 +1608,31 @@
     }
 
     private boolean shouldListenForFingerprint() {
+        final boolean switchingUsers;
+        synchronized (this) {
+            switchingUsers = mSwitchingUser;
+        }
         // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
         final boolean shouldListen = (mKeyguardIsVisible || !mDeviceInteractive ||
                 (mBouncer && !mKeyguardGoingAway) || mGoingToSleep ||
                 shouldListenForFingerprintAssistant() || (mKeyguardOccluded && mIsDreaming))
-                && !mSwitchingUser && !isFingerprintDisabled(getCurrentUser())
+                && !switchingUsers && !isFingerprintDisabled(getCurrentUser())
                 && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser;
         return shouldListen;
     }
 
     private boolean shouldListenForFace() {
+        final boolean switchingUsers;
+        synchronized (this) {
+            switchingUsers = mSwitchingUser;
+        }
         final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep;
         final int user = getCurrentUser();
         // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
         return (mBouncer || mAuthInterruptActive || awakeKeyguard || shouldListenForFaceAssistant())
-                && !mSwitchingUser && !getUserCanSkipBouncer(user) && !isFaceDisabled(user)
+                && !switchingUsers && !getUserCanSkipBouncer(user) && !isFaceDisabled(user)
                 && !mKeyguardGoingAway && mFaceSettingEnabledForUser
                 && mUserManager.isUserUnlocked(user) && mIsPrimaryUser;
     }
@@ -2180,13 +2188,11 @@
         sendUpdates(callback);
     }
 
-    public boolean isSwitchingUser() {
-        return mSwitchingUser;
-    }
-
     @AnyThread
     public void setSwitchingUser(boolean switching) {
-        mSwitchingUser = switching;
+        synchronized (this) {
+            mSwitchingUser = switching;
+        }
         // Since this comes in on a binder thread, we need to post if first
         mHandler.post(mUpdateBiometricListeningState);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
index 845b084..74ad0fa 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
@@ -18,12 +18,15 @@
 import static android.graphics.Paint.ANTI_ALIAS_FLAG;
 import static android.graphics.Paint.FILTER_BITMAP_FLAG;
 
+import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.Log;
 
+import com.android.systemui.R;
+
 // XXX: Mostly opied from launcher code / can we share?
 /**
  * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
@@ -32,20 +35,31 @@
 
     private static final String TAG = "BadgeRenderer";
 
-    // The badge sizes are defined as percentages of the app icon size.
+    /** The badge sizes are defined as percentages of the app icon size. */
     private static final float SIZE_PERCENTAGE = 0.38f;
 
-    // Extra scale down of the dot
+    /** Extra scale down of the dot. */
     private static final float DOT_SCALE = 0.6f;
 
     private final float mDotCenterOffset;
     private final float mCircleRadius;
     private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
 
-    public BadgeRenderer(int iconSizePx) {
-        mDotCenterOffset = SIZE_PERCENTAGE * iconSizePx;
-        int size = (int) (DOT_SCALE * mDotCenterOffset);
-        mCircleRadius = size / 2f;
+    public BadgeRenderer(Context context) {
+        mDotCenterOffset = getDotCenterOffset(context);
+        mCircleRadius = getDotRadius(mDotCenterOffset);
+    }
+
+    /** Space between the center of the dot and the top or left of the bubble stack. */
+    static float getDotCenterOffset(Context context) {
+        final int iconSizePx =
+                context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
+        return SIZE_PERCENTAGE * iconSizePx;
+    }
+
+    static float getDotRadius(float dotCenterOffset) {
+        int size = (int) (DOT_SCALE * dotCenterOffset);
+        return size / 2f;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
index f15e8e4..783780f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -57,7 +57,7 @@
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mIconSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
-        mDotRenderer = new BadgeRenderer(mIconSize);
+        mDotRenderer = new BadgeRenderer(getContext());
 
         TypedArray ta = context.obtainStyledAttributes(
                 new int[] {android.R.attr.colorBackgroundFloating});
@@ -83,6 +83,10 @@
         invalidate();
     }
 
+    public boolean getDotPosition() {
+        return mOnLeft;
+    }
+
     /**
      * Set whether the dot should show or not.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index ac4a93b..8aad0f8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -83,7 +83,7 @@
 
     public void updateDotVisibility() {
         if (iconView != null) {
-            iconView.updateDotVisibility();
+            iconView.updateDotVisibility(true /* animate */);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
new file mode 100644
index 0000000..71f68c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import static android.graphics.Paint.ANTI_ALIAS_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+
+import android.animation.ArgbEvaluator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.graphics.drawable.ShapeDrawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
+
+import com.android.systemui.R;
+import com.android.systemui.recents.TriangleShape;
+
+/**
+ * Flyout view that appears as a 'chat bubble' alongside the bubble stack. The flyout can visually
+ * transform into the 'new' dot, which is used during flyout dismiss animations/gestures.
+ */
+public class BubbleFlyoutView extends FrameLayout {
+    /** Max width of the flyout, in terms of percent of the screen width. */
+    private static final float FLYOUT_MAX_WIDTH_PERCENT = .6f;
+
+    private final int mFlyoutPadding;
+    private final int mFlyoutSpaceFromBubble;
+    private final int mPointerSize;
+    private final int mBubbleSize;
+    private final int mFlyoutElevation;
+    private final int mBubbleElevation;
+    private final int mFloatingBackgroundColor;
+    private final float mCornerRadius;
+
+    private final ViewGroup mFlyoutTextContainer;
+    private final TextView mFlyoutText;
+    /** Spring animation for the flyout. */
+    private final SpringAnimation mFlyoutSpring =
+            new SpringAnimation(this, DynamicAnimation.TRANSLATION_X);
+
+    /** Values related to the 'new' dot which we use to figure out where to collapse the flyout. */
+    private final float mNewDotRadius;
+    private final float mNewDotSize;
+    private final float mNewDotOffsetFromBubbleBounds;
+
+    /**
+     * The paint used to draw the background, whose color changes as the flyout transitions to the
+     * tinted 'new' dot.
+     */
+    private final Paint mBgPaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
+    private final ArgbEvaluator mArgbEvaluator = new ArgbEvaluator();
+
+    /**
+     * Triangular ShapeDrawables used for the triangle that points from the flyout to the bubble
+     * stack (a chat-bubble effect).
+     */
+    private final ShapeDrawable mLeftTriangleShape;
+    private final ShapeDrawable mRightTriangleShape;
+
+    /** Whether the flyout arrow is on the left (pointing left) or right (pointing right). */
+    private boolean mArrowPointingLeft = true;
+
+    /** Color of the 'new' dot that the flyout will transform into. */
+    private int mDotColor;
+
+    /** The outline of the triangle, used for elevation shadows. */
+    private final Outline mTriangleOutline = new Outline();
+
+    /** The bounds of the flyout background, kept up to date as it transitions to the 'new' dot. */
+    private final RectF mBgRect = new RectF();
+
+    /**
+     * Percent progress in the transition from flyout to 'new' dot. These two values are the inverse
+     * of each other (if we're 40% transitioned to the dot, we're 60% flyout), but it makes the code
+     * much more readable.
+     */
+    private float mPercentTransitionedToDot = 1f;
+    private float mPercentStillFlyout = 0f;
+
+    /**
+     * The difference in values between the flyout and the dot. These differences are gradually
+     * added over the course of the animation to transform the flyout into the 'new' dot.
+     */
+    private float mFlyoutToDotWidthDelta = 0f;
+    private float mFlyoutToDotHeightDelta = 0f;
+    private float mFlyoutToDotCornerRadiusDelta;
+
+    /** The translation values when the flyout is completely transitioned into the dot. */
+    private float mTranslationXWhenDot = 0f;
+    private float mTranslationYWhenDot = 0f;
+
+    /**
+     * The current translation values applied to the flyout background as it transitions into the
+     * 'new' dot.
+     */
+    private float mBgTranslationX;
+    private float mBgTranslationY;
+
+    /** The flyout's X translation when at rest (not animating or dragging). */
+    private float mRestingTranslationX = 0f;
+
+    /** Callback to run when the flyout is hidden. */
+    private Runnable mOnHide;
+
+    public BubbleFlyoutView(Context context) {
+        super(context);
+        LayoutInflater.from(context).inflate(R.layout.bubble_flyout, this, true);
+
+        mFlyoutTextContainer = findViewById(R.id.bubble_flyout_text_container);
+        mFlyoutText = mFlyoutTextContainer.findViewById(R.id.bubble_flyout_text);
+
+        final Resources res = getResources();
+        mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
+        mFlyoutSpaceFromBubble = res.getDimensionPixelSize(R.dimen.bubble_flyout_space_from_bubble);
+        mPointerSize = res.getDimensionPixelSize(R.dimen.bubble_flyout_pointer_size);
+        mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+        mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
+        mFlyoutElevation = res.getDimensionPixelSize(R.dimen.bubble_flyout_elevation);
+        mNewDotOffsetFromBubbleBounds = BadgeRenderer.getDotCenterOffset(context);
+        mNewDotRadius = BadgeRenderer.getDotRadius(mNewDotOffsetFromBubbleBounds);
+        mNewDotSize = mNewDotRadius * 2f;
+
+        final TypedArray ta = mContext.obtainStyledAttributes(
+                new int[] {
+                        android.R.attr.colorBackgroundFloating,
+                        android.R.attr.dialogCornerRadius});
+        mFloatingBackgroundColor = ta.getColor(0, Color.WHITE);
+        mCornerRadius = ta.getDimensionPixelSize(1, 0);
+        mFlyoutToDotCornerRadiusDelta = mNewDotRadius - mCornerRadius;
+        ta.recycle();
+
+        // Add padding for the pointer on either side, onDraw will draw it in this space.
+        setPadding(mPointerSize, 0, mPointerSize, 0);
+        setWillNotDraw(false);
+        setClipChildren(false);
+        setTranslationZ(mFlyoutElevation);
+        setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                BubbleFlyoutView.this.getOutline(outline);
+            }
+        });
+
+        mBgPaint.setColor(mFloatingBackgroundColor);
+
+        mLeftTriangleShape =
+                new ShapeDrawable(TriangleShape.createHorizontal(
+                        mPointerSize, mPointerSize, true /* isPointingLeft */));
+        mLeftTriangleShape.setBounds(0, 0, mPointerSize, mPointerSize);
+        mLeftTriangleShape.getPaint().setColor(mFloatingBackgroundColor);
+
+        mRightTriangleShape =
+                new ShapeDrawable(TriangleShape.createHorizontal(
+                        mPointerSize, mPointerSize, false /* isPointingLeft */));
+        mRightTriangleShape.setBounds(0, 0, mPointerSize, mPointerSize);
+        mRightTriangleShape.getPaint().setColor(mFloatingBackgroundColor);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        renderBackground(canvas);
+        invalidateOutline();
+        super.onDraw(canvas);
+    }
+
+    /** Configures the flyout and animates it in. */
+    void showFlyout(
+            CharSequence updateMessage, PointF stackPos, float parentWidth,
+            boolean arrowPointingLeft, int dotColor, Runnable onHide) {
+        mArrowPointingLeft = arrowPointingLeft;
+        mDotColor = dotColor;
+        mOnHide = onHide;
+
+        setCollapsePercent(0f);
+        setAlpha(0f);
+        setVisibility(VISIBLE);
+
+        // Set the flyout TextView's max width in terms of percent, and then subtract out the
+        // padding so that the entire flyout view will be the desired width (rather than the
+        // TextView being the desired width + extra padding).
+        mFlyoutText.setMaxWidth(
+                (int) (parentWidth * FLYOUT_MAX_WIDTH_PERCENT) - mFlyoutPadding * 2);
+        mFlyoutText.setText(updateMessage);
+
+        // Wait for the TextView to lay out so we know its line count.
+        post(() -> {
+            // Multi line flyouts get top-aligned to the bubble.
+            if (mFlyoutText.getLineCount() > 1) {
+                setTranslationY(stackPos.y);
+            } else {
+                // Single line flyouts are vertically centered with respect to the bubble.
+                setTranslationY(
+                        stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f);
+            }
+
+            // Calculate the translation required to position the flyout next to the bubble stack,
+            // with the desired padding.
+            mRestingTranslationX = mArrowPointingLeft
+                    ? stackPos.x + mBubbleSize + mFlyoutSpaceFromBubble
+                    : stackPos.x - getWidth() - mFlyoutSpaceFromBubble;
+
+            // Translate towards the stack slightly.
+            setTranslationX(
+                    mRestingTranslationX + (arrowPointingLeft ? -mBubbleSize : mBubbleSize));
+
+            // Fade in the entire flyout and spring it to its normal position.
+            animate().alpha(1f);
+            mFlyoutSpring.animateToFinalPosition(mRestingTranslationX);
+
+            // Calculate the difference in size between the flyout and the 'dot' so that we can
+            // transform into the dot later.
+            mFlyoutToDotWidthDelta = getWidth() - mNewDotSize;
+            mFlyoutToDotHeightDelta = getHeight() - mNewDotSize;
+
+            // Calculate the translation values needed to be in the correct 'new dot' position.
+            final float distanceFromFlyoutLeftToDotCenterX =
+                    mFlyoutSpaceFromBubble + mNewDotOffsetFromBubbleBounds / 2;
+            if (mArrowPointingLeft) {
+                mTranslationXWhenDot = -distanceFromFlyoutLeftToDotCenterX - mNewDotRadius;
+            } else {
+                mTranslationXWhenDot =
+                        getWidth() + distanceFromFlyoutLeftToDotCenterX - mNewDotRadius;
+            }
+
+            mTranslationYWhenDot =
+                    getHeight() / 2f
+                            - mNewDotRadius
+                            - mBubbleSize / 2f
+                            + mNewDotOffsetFromBubbleBounds / 2;
+        });
+    }
+
+    /**
+     * Hides the flyout and runs the optional callback passed into showFlyout. The flyout has been
+     * animated into the 'new' dot by the time we call this, so no animations are needed.
+     */
+    void hideFlyout() {
+        if (mOnHide != null) {
+            mOnHide.run();
+            mOnHide = null;
+        }
+
+        setVisibility(GONE);
+    }
+
+    /** Sets the percentage that the flyout should be collapsed into dot form. */
+    void setCollapsePercent(float percentCollapsed) {
+        mPercentTransitionedToDot = Math.max(0f, Math.min(percentCollapsed, 1f));
+        mPercentStillFlyout = (1f - mPercentTransitionedToDot);
+
+        // Move and fade out the text.
+        mFlyoutText.setTranslationX(
+                (mArrowPointingLeft ? -getWidth() : getWidth()) * mPercentTransitionedToDot);
+        mFlyoutText.setAlpha(clampPercentage(
+                (mPercentStillFlyout - (1f - BubbleStackView.FLYOUT_DRAG_PERCENT_DISMISS))
+                        / BubbleStackView.FLYOUT_DRAG_PERCENT_DISMISS));
+
+        // Reduce the elevation towards that of the topmost bubble.
+        setTranslationZ(
+                mFlyoutElevation
+                        - (mFlyoutElevation - mBubbleElevation) * mPercentTransitionedToDot);
+        invalidate();
+    }
+
+    /** Return the flyout's resting X translation (translation when not dragging or animating). */
+    float getRestingTranslationX() {
+        return mRestingTranslationX;
+    }
+
+    /** Clamps a float to between 0 and 1. */
+    private float clampPercentage(float percent) {
+        return Math.min(1f, Math.max(0f, percent));
+    }
+
+    /**
+     * Renders the background, which is either the rounded 'chat bubble' flyout, or some state
+     * between that and the 'new' dot over the bubbles.
+     */
+    private void renderBackground(Canvas canvas) {
+        // Calculate the width, height, and corner radius of the flyout given the current collapsed
+        // percentage.
+        final float width = getWidth() - (mFlyoutToDotWidthDelta * mPercentTransitionedToDot);
+        final float height = getHeight() - (mFlyoutToDotHeightDelta * mPercentTransitionedToDot);
+        final float cornerRadius = mCornerRadius
+                - (mFlyoutToDotCornerRadiusDelta * mPercentTransitionedToDot);
+
+        // Translate the flyout background towards the collapsed 'dot' state.
+        mBgTranslationX = mTranslationXWhenDot * mPercentTransitionedToDot;
+        mBgTranslationY = mTranslationYWhenDot * mPercentTransitionedToDot;
+
+        // Set the bounds of the rounded rectangle that serves as either the flyout background or
+        // the collapsed 'dot'. These bounds will also be used to provide the outline for elevation
+        // shadows. In the expanded flyout state, the left and right bounds leave space for the
+        // pointer triangle - as the flyout collapses, this space is reduced since the triangle
+        // retracts into the flyout.
+        mBgRect.set(
+                mPointerSize * mPercentStillFlyout /* left */,
+                0 /* top */,
+                width - mPointerSize * mPercentStillFlyout /* right */,
+                height /* bottom */);
+
+        mBgPaint.setColor(
+                (int) mArgbEvaluator.evaluate(
+                        mPercentTransitionedToDot, mFloatingBackgroundColor, mDotColor));
+
+        canvas.save();
+        canvas.translate(mBgTranslationX, mBgTranslationY);
+        renderPointerTriangle(canvas, width, height);
+        canvas.drawRoundRect(mBgRect, cornerRadius, cornerRadius, mBgPaint);
+        canvas.restore();
+    }
+
+    /** Renders the 'pointer' triangle that points from the flyout to the bubble stack. */
+    private void renderPointerTriangle(
+            Canvas canvas, float currentFlyoutWidth, float currentFlyoutHeight) {
+        canvas.save();
+
+        // Translation to apply for the 'retraction' effect as the flyout collapses.
+        final float retractionTranslationX =
+                (mArrowPointingLeft ? 1 : -1) * (mPercentTransitionedToDot * mPointerSize * 2f);
+
+        // Place the arrow either at the left side, or the far right, depending on whether the
+        // flyout is on the left or right side.
+        final float arrowTranslationX =
+                mArrowPointingLeft
+                        ? retractionTranslationX
+                        : currentFlyoutWidth - mPointerSize + retractionTranslationX;
+
+        // Vertically center the arrow at all times.
+        final float arrowTranslationY = currentFlyoutHeight / 2f - mPointerSize / 2f;
+
+        // Draw the appropriate direction of arrow.
+        final ShapeDrawable relevantTriangle =
+                mArrowPointingLeft ? mLeftTriangleShape : mRightTriangleShape;
+        canvas.translate(arrowTranslationX, arrowTranslationY);
+        relevantTriangle.setAlpha((int) (255f * mPercentStillFlyout));
+        relevantTriangle.draw(canvas);
+
+        // Save the triangle's outline for use in the outline provider, offsetting it to reflect its
+        // current position.
+        relevantTriangle.getOutline(mTriangleOutline);
+        mTriangleOutline.offset((int) arrowTranslationX, (int) arrowTranslationY);
+
+        canvas.restore();
+    }
+
+    /** Builds an outline that includes the transformed flyout background and triangle. */
+    private void getOutline(Outline outline) {
+        if (!mTriangleOutline.isEmpty()) {
+            // Draw the rect into the outline as a path so we can merge the triangle path into it.
+            final Path rectPath = new Path();
+            rectPath.addRoundRect(mBgRect, mCornerRadius, mCornerRadius, Path.Direction.CW);
+            outline.setConvexPath(rectPath);
+
+            // Get rid of the triangle path once it has disappeared behind the flyout.
+            if (mPercentStillFlyout > 0.5f) {
+                outline.mPath.addPath(mTriangleOutline.mPath);
+            }
+
+            // Translate the outline to match the background's position.
+            final Matrix outlineMatrix = new Matrix();
+            outlineMatrix.postTranslate(getLeft() + mBgTranslationX, getTop() + mBgTranslationY);
+
+            // At the very end, retract the outline into the bubble so the shadow will be pulled
+            // into the flyout-dot as it (visually) becomes part of the bubble. We can't do this by
+            // animating translationZ to zero since then it'll go under the bubbles, which have
+            // elevation.
+            if (mPercentTransitionedToDot > 0.98f) {
+                final float percentBetween99and100 = (mPercentTransitionedToDot - 0.98f) / .02f;
+                final float percentShadowVisible = 1f - percentBetween99and100;
+
+                // Keep it centered.
+                outlineMatrix.postTranslate(
+                        mNewDotRadius * percentBetween99and100,
+                        mNewDotRadius * percentBetween99and100);
+                outlineMatrix.preScale(percentShadowVisible, percentShadowVisible);
+            }
+
+            outline.mPath.transform(outlineMatrix);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 2b17425..4fef157 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -25,8 +25,6 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Color;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Outline;
@@ -35,8 +33,6 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.ShapeDrawable;
 import android.os.Bundle;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
@@ -56,11 +52,11 @@
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.FrameLayout;
-import android.widget.TextView;
 
 import androidx.annotation.MainThread;
 import androidx.annotation.Nullable;
 import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
@@ -70,7 +66,6 @@
 import com.android.systemui.bubbles.animation.ExpandedAnimationController;
 import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
 import com.android.systemui.bubbles.animation.StackAnimationController;
-import com.android.systemui.recents.TriangleShape;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import java.math.BigDecimal;
@@ -86,12 +81,21 @@
     private static final String TAG = "BubbleStackView";
     private static final boolean DEBUG = false;
 
+    /** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
+    static final float FLYOUT_DRAG_PERCENT_DISMISS = 0.25f;
+
+    /** Velocity required to dismiss the flyout via drag. */
+    private static final float FLYOUT_DISMISS_VELOCITY = 2000f;
+
+    /**
+     * Factor for attenuating translation when the flyout is overscrolled (8f = flyout moves 1 pixel
+     * for every 8 pixels overscrolled).
+     */
+    private static final float FLYOUT_OVERSCROLL_ATTENUATION_FACTOR = 8f;
+
     /** Duration of the flyout alpha animations. */
     private static final int FLYOUT_ALPHA_ANIMATION_DURATION = 100;
 
-    /** Max width of the flyout, in terms of percent of the screen width. */
-    private static final float FLYOUT_MAX_WIDTH_PERCENT = .6f;
-
     /** Percent to darken the bubbles when they're in the dismiss target. */
     private static final float DARKEN_PERCENT = 0.3f;
 
@@ -152,17 +156,9 @@
 
     private FrameLayout mExpandedViewContainer;
 
-    private FrameLayout mFlyoutContainer;
-    private FrameLayout mFlyout;
-    private TextView mFlyoutText;
-    private ShapeDrawable mLeftFlyoutTriangle;
-    private ShapeDrawable mRightFlyoutTriangle;
-    /** Spring animation for the flyout. */
-    private SpringAnimation mFlyoutSpring;
+    private BubbleFlyoutView mFlyout;
     /** Runnable that fades out the flyout and then sets it to GONE. */
-    private Runnable mHideFlyout =
-            () -> mFlyoutContainer.animate().alpha(0f).withEndAction(
-                    () -> mFlyoutContainer.setVisibility(GONE));
+    private Runnable mHideFlyout = () -> animateFlyoutCollapsed(true, 0 /* velX */);
 
     /** Layout change listener that moves the stack to the nearest valid position on rotation. */
     private OnLayoutChangeListener mMoveStackToValidPositionOnLayoutListener;
@@ -176,9 +172,6 @@
 
     private int mBubbleSize;
     private int mBubblePadding;
-    private int mFlyoutPadding;
-    private int mFlyoutSpaceFromBubble;
-    private int mPointerSize;
     private int mExpandedAnimateXDistance;
     private int mExpandedAnimateYDistance;
     private int mStatusBarHeight;
@@ -189,8 +182,11 @@
     private boolean mIsExpanded;
     private boolean mImeVisible;
 
-    /** Whether the stack is currently being dragged. */
-    private boolean mIsDragging = false;
+    /** Whether the stack is currently on the left side of the screen, or animating there. */
+    private boolean mStackOnLeftOrWillBe = false;
+
+    /** Whether a touch gesture, such as a stack/bubble drag or flyout drag, is in progress. */
+    private boolean mIsGestureInProgress = false;
 
     private BubbleTouchHandler mTouchHandler;
     private BubbleController.BubbleExpandListener mExpandListener;
@@ -249,6 +245,40 @@
         }
     };
 
+    /** Float property that 'drags' the flyout. */
+    private final FloatPropertyCompat mFlyoutCollapseProperty =
+            new FloatPropertyCompat("FlyoutCollapseSpring") {
+                @Override
+                public float getValue(Object o) {
+                    return mFlyoutDragDeltaX;
+                }
+
+                @Override
+                public void setValue(Object o, float v) {
+                    onFlyoutDragged(v);
+                }
+            };
+
+    /** SpringAnimation that springs the flyout collapsed via onFlyoutDragged. */
+    private final SpringAnimation mFlyoutTransitionSpring =
+            new SpringAnimation(this, mFlyoutCollapseProperty);
+
+    /** Distance the flyout has been dragged in the X axis. */
+    private float mFlyoutDragDeltaX = 0f;
+
+    /**
+     * End listener for the flyout spring that either posts a runnable to hide the flyout, or hides
+     * it immediately.
+     */
+    private final DynamicAnimation.OnAnimationEndListener mAfterFlyoutTransitionSpring =
+            (dynamicAnimation, b, v, v1) -> {
+                if (mFlyoutDragDeltaX == 0) {
+                    mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
+                } else {
+                    mFlyout.hideFlyout();
+                }
+            };
+
     @NonNull private final SurfaceSynchronizer mSurfaceSynchronizer;
 
     private BubbleDismissView mDismissContainer;
@@ -267,9 +297,6 @@
         Resources res = getResources();
         mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
         mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
-        mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
-        mFlyoutSpaceFromBubble = res.getDimensionPixelSize(R.dimen.bubble_flyout_space_from_bubble);
-        mPointerSize = res.getDimensionPixelSize(R.dimen.bubble_flyout_pointer_size);
         mExpandedAnimateXDistance =
                 res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance);
         mExpandedAnimateYDistance =
@@ -307,17 +334,24 @@
         mExpandedViewContainer.setClipChildren(false);
         addView(mExpandedViewContainer);
 
-        mFlyoutContainer = (FrameLayout) mInflater.inflate(R.layout.bubble_flyout, this, false);
-        mFlyoutContainer.setVisibility(GONE);
-        mFlyoutContainer.setClipToPadding(false);
-        mFlyoutContainer.setClipChildren(false);
-        mFlyoutContainer.animate()
+        mFlyout = new BubbleFlyoutView(context);
+        mFlyout.setVisibility(GONE);
+        mFlyout.animate()
                 .setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
                 .setInterpolator(new AccelerateDecelerateInterpolator());
+        addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
 
-        mFlyout = mFlyoutContainer.findViewById(R.id.bubble_flyout);
-        addView(mFlyoutContainer);
-        setupFlyout();
+        mFlyoutTransitionSpring.setSpring(new SpringForce()
+                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
+        mFlyoutTransitionSpring.addEndListener(mAfterFlyoutTransitionSpring);
+
+        mDismissContainer = new BubbleDismissView(mContext);
+        mDismissContainer.setLayoutParams(new FrameLayout.LayoutParams(
+                MATCH_PARENT,
+                getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height),
+                Gravity.BOTTOM));
+        addView(mDismissContainer);
 
         mDismissContainer = new BubbleDismissView(mContext);
         mDismissContainer.setLayoutParams(new FrameLayout.LayoutParams(
@@ -742,7 +776,7 @@
             }
             // Outside parts of view we care about.
             return null;
-        } else if (mFlyoutContainer.getVisibility() == VISIBLE && isIntersecting(mFlyout, x, y)) {
+        } else if (mFlyout.getVisibility() == VISIBLE && isIntersecting(mFlyout, x, y)) {
             return mFlyout;
         }
 
@@ -931,7 +965,6 @@
         mBubbleContainer.setController(mStackAnimationController);
         hideFlyoutImmediate();
 
-        mIsDragging = true;
         mDraggingInDismissTarget = false;
     }
 
@@ -948,20 +981,87 @@
         if (DEBUG) {
             Log.d(TAG, "onDragFinish");
         }
-        // TODO: Add fling to bottom to dismiss.
-        mIsDragging = false;
 
         if (mIsExpanded || mIsExpansionAnimating) {
             return;
         }
 
-        mStackAnimationController.flingStackThenSpringToEdge(x, velX, velY);
+        final float newStackX = mStackAnimationController.flingStackThenSpringToEdge(x, velX, velY);
         logBubbleEvent(null /* no bubble associated with bubble stack move */,
                 StatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
 
+        mStackOnLeftOrWillBe = newStackX <= 0;
+        updateBubbleShadowsAndDotPosition(true /* animate */);
         springOutDismissTargetAndHideCircle();
     }
 
+    void onFlyoutDragStart() {
+        mFlyout.removeCallbacks(mHideFlyout);
+    }
+
+    void onFlyoutDragged(float deltaX) {
+        final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
+        mFlyoutDragDeltaX = deltaX;
+
+        final float collapsePercent =
+                onLeft ? -deltaX / mFlyout.getWidth() : deltaX / mFlyout.getWidth();
+        mFlyout.setCollapsePercent(Math.min(1f, Math.max(0f, collapsePercent)));
+
+        // Calculate how to translate the flyout if it has been dragged too far in etiher direction.
+        float overscrollTranslation = 0f;
+        if (collapsePercent < 0f || collapsePercent > 1f) {
+            // Whether we are more than 100% transitioned to the dot.
+            final boolean overscrollingPastDot = collapsePercent > 1f;
+
+            // Whether we are overscrolling physically to the left - this can either be pulling the
+            // flyout away from the stack (if the stack is on the right) or pushing it to the left
+            // after it has already become the dot.
+            final boolean overscrollingLeft =
+                    (onLeft && collapsePercent > 1f) || (!onLeft && collapsePercent < 0f);
+
+            overscrollTranslation =
+                    (overscrollingPastDot ? collapsePercent - 1f : collapsePercent * -1)
+                            * (overscrollingLeft ? -1 : 1)
+                            * (mFlyout.getWidth() / (FLYOUT_OVERSCROLL_ATTENUATION_FACTOR
+                                // Attenuate the smaller dot less than the larger flyout.
+                                / (overscrollingPastDot ? 2 : 1)));
+        }
+
+        mFlyout.setTranslationX(mFlyout.getRestingTranslationX() + overscrollTranslation);
+    }
+
+    /**
+     * Called when the flyout drag has finished, and returns true if the gesture successfully
+     * dismissed the flyout.
+     */
+    void onFlyoutDragFinished(float deltaX, float velX) {
+        final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
+        final boolean metRequiredVelocity =
+                onLeft ? velX < -FLYOUT_DISMISS_VELOCITY : velX > FLYOUT_DISMISS_VELOCITY;
+        final boolean metRequiredDeltaX =
+                onLeft
+                        ? deltaX < -mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS
+                        : deltaX > mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS;
+        final boolean isCancelFling = onLeft ? velX > 0 : velX < 0;
+        final boolean shouldDismiss = metRequiredVelocity || (metRequiredDeltaX && !isCancelFling);
+
+        mFlyout.removeCallbacks(mHideFlyout);
+        animateFlyoutCollapsed(shouldDismiss, velX);
+    }
+
+    /**
+     * Called when the first touch event of a gesture (stack drag, bubble drag, flyout drag, etc.)
+     * is received.
+     */
+    void onGestureStart() {
+        mIsGestureInProgress = true;
+    }
+
+    /** Called when a gesture is completed or cancelled. */
+    void onGestureFinished() {
+        mIsGestureInProgress = false;
+    }
+
     /** Prepares and starts the desaturate/darken animation on the bubble stack. */
     private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
         mDesaturateAndDarkenTargetView = targetView;
@@ -1119,12 +1219,22 @@
         mShowingDismiss = false;
     }
 
-
     /** Whether the location of the given MotionEvent is within the dismiss target area. */
-    public boolean isInDismissTarget(MotionEvent ev) {
+    boolean isInDismissTarget(MotionEvent ev) {
         return isIntersecting(mDismissContainer.getDismissTarget(), ev.getRawX(), ev.getRawY());
     }
 
+    /** Animates the flyout collapsed (to dot), or the reverse, starting with the given velocity. */
+    private void animateFlyoutCollapsed(boolean collapsed, float velX) {
+        final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
+        mFlyoutTransitionSpring
+                .setStartValue(mFlyoutDragDeltaX)
+                .setStartVelocity(velX)
+                .animateToFinalPosition(collapsed
+                        ? (onLeft ? -mFlyout.getWidth() : mFlyout.getWidth())
+                        : 0f);
+    }
+
     /**
      * Calculates how large the expanded view of the bubble can be. This takes into account the
      * y position when the bubbles are expanded as well as the bounds of the dismiss target.
@@ -1161,55 +1271,27 @@
         final CharSequence updateMessage = bubble.entry.getUpdateMessage(getContext());
 
         // Show the message if one exists, and we're not expanded or animating expansion.
-        if (updateMessage != null && !isExpanded() && !mIsExpansionAnimating && !mIsDragging) {
-            final PointF stackPos = mStackAnimationController.getStackPosition();
+        if (updateMessage != null
+                && !isExpanded()
+                && !mIsExpansionAnimating
+                && !mIsGestureInProgress) {
+            if (bubble.iconView != null) {
+                bubble.iconView.setSuppressDot(true /* suppressDot */, false /* animate */);
+                mFlyoutDragDeltaX = 0f;
+                mFlyout.setAlpha(0f);
 
-            // Set the flyout TextView's max width in terms of percent, and then subtract out the
-            // padding so that the entire flyout view will be the desired width (rather than the
-            // TextView being the desired width + extra padding).
-            mFlyoutText.setMaxWidth(
-                    (int) (getWidth() * FLYOUT_MAX_WIDTH_PERCENT) - mFlyoutPadding * 2);
-
-            mFlyoutContainer.setAlpha(0f);
-            mFlyoutContainer.setVisibility(VISIBLE);
-
-            mFlyoutText.setText(updateMessage);
-
-            final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
-
-            if (onLeft) {
-                mLeftFlyoutTriangle.setAlpha(255);
-                mRightFlyoutTriangle.setAlpha(0);
-            } else {
-                mLeftFlyoutTriangle.setAlpha(0);
-                mRightFlyoutTriangle.setAlpha(255);
+                // Post in case layout isn't complete and getWidth returns 0.
+                post(() -> mFlyout.showFlyout(
+                        updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
+                        mStackAnimationController.isStackOnLeftSide(),
+                        bubble.iconView.getBadgeColor(),
+                        () -> {
+                            bubble.iconView.setSuppressDot(
+                                    false /* suppressDot */, false /* animate */);
+                        }));
             }
-
-            mFlyoutContainer.post(() -> {
-                // Multi line flyouts get top-aligned to the bubble.
-                if (mFlyoutText.getLineCount() > 1) {
-                    mFlyoutContainer.setTranslationY(stackPos.y);
-                } else {
-                    // Single line flyouts are vertically centered with respect to the bubble.
-                    mFlyoutContainer.setTranslationY(
-                            stackPos.y + (mBubbleSize - mFlyout.getHeight()) / 2f);
-                }
-
-                final float destinationX = onLeft
-                        ? stackPos.x + mBubbleSize + mFlyoutSpaceFromBubble
-                        : stackPos.x - mFlyoutContainer.getWidth() - mFlyoutSpaceFromBubble;
-
-                // Translate towards the stack slightly, then spring out from the stack.
-                mFlyoutContainer.setTranslationX(
-                        destinationX + (onLeft ? -mBubblePadding : mBubblePadding));
-
-                mFlyoutContainer.animate().alpha(1f);
-                mFlyoutSpring.animateToFinalPosition(destinationX);
-
-                mFlyout.removeCallbacks(mHideFlyout);
-                mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
-            });
-
+            mFlyout.removeCallbacks(mHideFlyout);
+            mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
             logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
         }
     }
@@ -1217,7 +1299,7 @@
     /** Hide the flyout immediately and cancel any pending hide runnables. */
     private void hideFlyoutImmediate() {
         mFlyout.removeCallbacks(mHideFlyout);
-        mHideFlyout.run();
+        mFlyout.hideFlyout();
     }
 
     @Override
@@ -1230,7 +1312,7 @@
             mBubbleContainer.getBoundsOnScreen(outRect);
         }
 
-        if (mFlyoutContainer.getVisibility() == View.VISIBLE) {
+        if (mFlyout.getVisibility() == View.VISIBLE) {
             final Rect flyoutBounds = new Rect();
             mFlyout.getBoundsOnScreen(flyoutBounds);
             outRect.union(flyoutBounds);
@@ -1287,78 +1369,11 @@
         }
     }
 
-    /** Sets up the flyout views and drawables. */
-    private void setupFlyout() {
-        // Retrieve the styled floating background color.
-        TypedArray ta = mContext.obtainStyledAttributes(
-                new int[]{android.R.attr.colorBackgroundFloating});
-        final int floatingBackgroundColor = ta.getColor(0, Color.WHITE);
-        ta.recycle();
-
-        // Retrieve the flyout background, which is currently a rounded white rectangle with a
-        // shadow but no triangular arrow pointing anywhere.
-        final LayerDrawable flyoutBackground = (LayerDrawable) mFlyout.getBackground();
-
-        // Create the triangle drawables and set their color.
-        mLeftFlyoutTriangle =
-                new ShapeDrawable(TriangleShape.createHorizontal(
-                        mPointerSize, mPointerSize, true /* isPointingLeft */));
-        mRightFlyoutTriangle =
-                new ShapeDrawable(TriangleShape.createHorizontal(
-                        mPointerSize, mPointerSize, false /* isPointingLeft */));
-        mLeftFlyoutTriangle.getPaint().setColor(floatingBackgroundColor);
-        mRightFlyoutTriangle.getPaint().setColor(floatingBackgroundColor);
-
-        // Add both triangles to the drawable. We'll show and hide the appropriate ones when we show
-        // the flyout.
-        final int leftTriangleIndex = flyoutBackground.addLayer(mLeftFlyoutTriangle);
-        flyoutBackground.setLayerSize(leftTriangleIndex, mPointerSize, mPointerSize);
-        flyoutBackground.setLayerGravity(leftTriangleIndex, Gravity.LEFT | Gravity.CENTER_VERTICAL);
-        flyoutBackground.setLayerInsetLeft(leftTriangleIndex, -mPointerSize);
-
-        final int rightTriangleIndex = flyoutBackground.addLayer(mRightFlyoutTriangle);
-        flyoutBackground.setLayerSize(rightTriangleIndex, mPointerSize, mPointerSize);
-        flyoutBackground.setLayerGravity(
-                rightTriangleIndex, Gravity.RIGHT | Gravity.CENTER_VERTICAL);
-        flyoutBackground.setLayerInsetRight(rightTriangleIndex, -mPointerSize);
-
-        // Append the appropriate triangle's outline to the view's outline so that the shadows look
-        // correct.
-        mFlyout.setOutlineProvider(new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                final boolean leftPointing = mStackAnimationController.isStackOnLeftSide();
-
-                // Get the outline from the appropriate triangle.
-                final Outline triangleOutline = new Outline();
-                if (leftPointing) {
-                    mLeftFlyoutTriangle.getOutline(triangleOutline);
-                } else {
-                    mRightFlyoutTriangle.getOutline(triangleOutline);
-                }
-
-                // Offset it to the correct position, since it has no intrinsic position since
-                // that is maintained by the parent LayerDrawable.
-                triangleOutline.offset(
-                        leftPointing ? -mPointerSize : mFlyout.getWidth(),
-                        mFlyout.getHeight() / 2 - mPointerSize / 2);
-
-                // Merge the outlines.
-                final Outline compoundOutline = new Outline();
-                flyoutBackground.getOutline(compoundOutline);
-                compoundOutline.mPath.addPath(triangleOutline.mPath);
-                outline.set(compoundOutline);
-            }
-        });
-
-        mFlyoutText = mFlyout.findViewById(R.id.bubble_flyout_text);
-        mFlyoutSpring = new SpringAnimation(mFlyoutContainer, DynamicAnimation.TRANSLATION_X);
-    }
-
     private void applyCurrentState() {
         if (DEBUG) {
             Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded);
         }
+
         mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
         if (mIsExpanded) {
             // First update the view so that it calculates a new height (ensuring the y position
@@ -1376,10 +1391,15 @@
             }
         }
 
+        mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
+        updateBubbleShadowsAndDotPosition(false);
+    }
+
+    /** Sets the appropriate Z-order and dot position for each bubble in the stack. */
+    private void updateBubbleShadowsAndDotPosition(boolean animate) {
         int bubbsCount = mBubbleContainer.getChildCount();
         for (int i = 0; i < bubbsCount; i++) {
             BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
-            bv.updateDotVisibility();
             bv.setZ((BubbleController.MAX_BUBBLES
                     * getResources().getDimensionPixelSize(R.dimen.bubble_elevation)) - i);
 
@@ -1393,6 +1413,11 @@
                 }
             });
             bv.setClipToOutline(false);
+
+            // If the dot is on the left, and so is the stack, we need to change the dot position.
+            if (bv.getDotPositionOnLeft() == mStackOnLeftOrWillBe) {
+                bv.setDotPosition(!mStackOnLeftOrWillBe, animate);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index f429c2c..8fe8bd3 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -111,12 +111,13 @@
                 trackMovement(event);
 
                 mTouchDown.set(rawX, rawY);
+                mStack.onGestureStart();
 
                 if (isStack) {
                     mViewPositionOnTouchDown.set(mStack.getStackPosition());
                     mStack.onDragStart();
                 } else if (isFlyout) {
-                    // TODO(b/129768381): Make the flyout dismissable with a gesture.
+                    mStack.onFlyoutDragStart();
                 } else {
                     mViewPositionOnTouchDown.set(
                             mTouchedView.getTranslationX(), mTouchedView.getTranslationY());
@@ -137,7 +138,7 @@
                     if (isStack) {
                         mStack.onDragged(viewX, viewY);
                     } else if (isFlyout) {
-                        // TODO(b/129768381): Make the flyout dismissable with a gesture.
+                        mStack.onFlyoutDragged(deltaX);
                     } else {
                         mStack.onBubbleDragged(mTouchedView, viewX, viewY);
                     }
@@ -152,8 +153,10 @@
                     final float velY = mVelocityTracker.getYVelocity();
 
                     // If the touch event is within the dismiss target, magnet the stack to it.
-                    mStack.animateMagnetToDismissTarget(
-                            mTouchedView, mInDismissTarget, viewX, viewY, velX, velY);
+                    if (!isFlyout) {
+                        mStack.animateMagnetToDismissTarget(
+                                mTouchedView, mInDismissTarget, viewX, viewY, velX, velY);
+                    }
                 }
                 break;
 
@@ -174,7 +177,9 @@
                                 : mInDismissTarget
                                         || velY > INDIVIDUAL_BUBBLE_DISMISS_MIN_VELOCITY;
 
-                if (shouldDismiss) {
+                if (isFlyout && mMovedEnough) {
+                    mStack.onFlyoutDragFinished(rawX - mTouchDown.x /* deltaX */, velX);
+                } else if (shouldDismiss) {
                     final String individualBubbleKey =
                             isStack ? null : ((BubbleView) mTouchedView).getKey();
                     mStack.magnetToStackIfNeededThenAnimateDismissal(mTouchedView, velX, velY,
@@ -200,7 +205,7 @@
                     }
                 } else if (mTouchedView == mStack.getExpandedBubbleView()) {
                     mBubbleData.setExpanded(false);
-                } else if (isStack) {
+                } else if (isStack || isFlyout) {
                     // Toggle expansion
                     mBubbleData.setExpanded(!mBubbleData.isExpanded());
                 } else {
@@ -251,9 +256,12 @@
             mVelocityTracker.recycle();
             mVelocityTracker = null;
         }
+
         mTouchedView = null;
         mMovedEnough = false;
         mInDismissTarget = false;
+
+        mStack.onGestureFinished();
     }
 
     private void trackMovement(MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 2681b6d..aa32b94 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -48,9 +48,12 @@
     private Context mContext;
 
     private BadgedImageView mBadgedImageView;
+    private int mBadgeColor;
     private int mPadding;
     private int mIconInset;
 
+    private boolean mSuppressDot = false;
+
     private NotificationEntry mEntry;
 
     public BubbleView(Context context) {
@@ -130,18 +133,54 @@
         return (mEntry != null) ? mEntry.getRow() : null;
     }
 
+    /** Changes the dot's visibility to match the bubble view's state. */
+    void updateDotVisibility(boolean animate) {
+        updateDotVisibility(animate, null /* after */);
+    }
+
     /**
-     * Marks this bubble as "read", i.e. no badge should show.
+     * Changes the dot's visibility to match the bubble view's state, running the provided callback
+     * after animation if requested.
      */
-    public void updateDotVisibility() {
-        boolean showDot = getEntry().showInShadeWhenBubble();
-        animateDot(showDot);
+    void updateDotVisibility(boolean animate, Runnable after) {
+        boolean showDot = getEntry().showInShadeWhenBubble() && !mSuppressDot;
+
+        if (animate) {
+            animateDot(showDot, after);
+        } else {
+            mBadgedImageView.setShowDot(showDot);
+        }
+    }
+
+    /**
+     * Sets whether or not to hide the dot even if we'd otherwise show it. This is used while the
+     * flyout is visible or animating, to hide the dot until the flyout visually transforms into it.
+     */
+    void setSuppressDot(boolean suppressDot, boolean animate) {
+        mSuppressDot = suppressDot;
+        updateDotVisibility(animate);
+    }
+
+    /** Sets the position of the 'new' dot, animating it out and back in if requested. */
+    void setDotPosition(boolean onLeft, boolean animate) {
+        if (animate && onLeft != mBadgedImageView.getDotPosition() && !mSuppressDot) {
+            animateDot(false /* showDot */, () -> {
+                mBadgedImageView.setDotPosition(onLeft);
+                animateDot(true /* showDot */, null);
+            });
+        } else {
+            mBadgedImageView.setDotPosition(onLeft);
+        }
+    }
+
+    boolean getDotPositionOnLeft() {
+        return mBadgedImageView.getDotPosition();
     }
 
     /**
      * Animates the badge to show or hide.
      */
-    private void animateDot(boolean showDot) {
+    private void animateDot(boolean showDot, Runnable after) {
         if (mBadgedImageView.isShowingDot() != showDot) {
             mBadgedImageView.setShowDot(showDot);
             mBadgedImageView.clearAnimation();
@@ -152,9 +191,13 @@
                         fraction = showDot ? fraction : 1 - fraction;
                         mBadgedImageView.setDotScale(fraction);
                     }).withEndAction(() -> {
-                if (!showDot) {
-                    mBadgedImageView.setShowDot(false);
-                }
+                        if (!showDot) {
+                            mBadgedImageView.setShowDot(false);
+                        }
+
+                        if (after != null) {
+                            after.run();
+                        }
             }).start();
         }
     }
@@ -181,8 +224,13 @@
             mBadgedImageView.setImageDrawable(iconDrawable);
         }
         int badgeColor = determineDominateColor(iconDrawable, n.color);
+        mBadgeColor = badgeColor;
         mBadgedImageView.setDotColor(badgeColor);
-        animateDot(mEntry.showInShadeWhenBubble() /* showDot */);
+        animateDot(mEntry.showInShadeWhenBubble() /* showDot */, null /* after */);
+    }
+
+    int getBadgeColor() {
+        return mBadgeColor;
     }
 
     private Drawable buildIconWithTint(Drawable iconDrawable, int backgroundColor) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index f937525..8529ed4 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -225,8 +225,10 @@
     /**
      * Flings the stack starting with the given velocities, springing it to the nearest edge
      * afterward.
+     *
+     * @return The X value that the stack will end up at after the fling/spring.
      */
-    public void flingStackThenSpringToEdge(float x, float velX, float velY) {
+    public float flingStackThenSpringToEdge(float x, float velX, float velY) {
         final boolean stackOnLeftSide = x - mIndividualBubbleSize / 2 < mLayout.getWidth() / 2;
 
         final boolean stackShouldFlingLeft = stackOnLeftSide
@@ -281,6 +283,7 @@
                 DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
 
         mIsMovingFromFlinging = true;
+        return destinationRelativeX;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
index de10690..05665b5 100644
--- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.colorextraction;
 
-import android.annotation.ColorInt;
-import android.annotation.IntDef;
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
 import android.content.Context;
@@ -36,13 +34,10 @@
 import com.android.internal.colorextraction.types.Tonal;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
-import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 
 import javax.inject.Inject;
@@ -55,41 +50,23 @@
 public class SysuiColorExtractor extends ColorExtractor implements Dumpable,
         ConfigurationController.ConfigurationListener {
     private static final String TAG = "SysuiColorExtractor";
-
-    public static final int SCRIM_TYPE_REGULAR = 1;
-    public static final int SCRIM_TYPE_LIGHT = 2;
-    public static final int SCRIM_TYPE_DARK = 3;
-
-    @IntDef(prefix = {"SCRIM_TYPE_"}, value = {
-            SCRIM_TYPE_REGULAR,
-            SCRIM_TYPE_LIGHT,
-            SCRIM_TYPE_DARK
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ScrimType {
-    }
-
     private final Tonal mTonal;
-    private final OverviewProxyService mOverviewProxyService;
     private boolean mWallpaperVisible;
     private boolean mHasBackdrop;
     // Colors to return when the wallpaper isn't visible
     private final GradientColors mWpHiddenColors;
 
     @Inject
-    public SysuiColorExtractor(Context context, ConfigurationController configurationController,
-            OverviewProxyService overviewProxyService) {
-        this(context, new Tonal(context), configurationController, true, overviewProxyService);
+    public SysuiColorExtractor(Context context, ConfigurationController configurationController) {
+        this(context, new Tonal(context), configurationController, true);
     }
 
     @VisibleForTesting
     public SysuiColorExtractor(Context context, ExtractionType type,
-            ConfigurationController configurationController, boolean registerVisibility,
-            OverviewProxyService overviewProxyService) {
+            ConfigurationController configurationController, boolean registerVisibility) {
         super(context, type, false /* immediately */);
         mTonal = type instanceof Tonal ? (Tonal) type : new Tonal(context);
         mWpHiddenColors = new GradientColors();
-        mOverviewProxyService = overviewProxyService;
         configurationController.addCallback(this);
 
         WallpaperColors systemColors = getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
@@ -133,35 +110,17 @@
             return;
         }
 
+        super.onColorsChanged(colors, which);
+
         if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
             updateDefaultGradients(colors);
         }
-        super.onColorsChanged(colors, which);
     }
 
     @Override
     public void onUiModeChanged() {
         WallpaperColors systemColors = getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
         updateDefaultGradients(systemColors);
-        triggerColorsChanged(WallpaperManager.FLAG_SYSTEM);
-    }
-
-    @Override
-    protected void triggerColorsChanged(int which) {
-        super.triggerColorsChanged(which);
-
-        if (mWpHiddenColors != null && (which & WallpaperManager.FLAG_SYSTEM) != 0) {
-            @ColorInt int colorInt = mWpHiddenColors.getMainColor();
-            @ScrimType int scrimType;
-            if (colorInt == Tonal.MAIN_COLOR_LIGHT) {
-                scrimType = SCRIM_TYPE_LIGHT;
-            } else if (colorInt == Tonal.MAIN_COLOR_DARK) {
-                scrimType = SCRIM_TYPE_DARK;
-            } else {
-                scrimType = SCRIM_TYPE_REGULAR;
-            }
-            mOverviewProxyService.onScrimColorsChanged(colorInt, scrimType);
-        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index e87ff52..dcabb78 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -25,6 +25,7 @@
 import android.app.Dialog;
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
+import android.app.StatusBarManager;
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
@@ -38,7 +39,9 @@
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.net.ConnectivityManager;
+import android.os.Binder;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -75,6 +78,7 @@
 import com.android.internal.colorextraction.drawable.ScrimDrawable;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.util.EmergencyAffordanceManager;
@@ -1501,6 +1505,8 @@
 
         private final Context mContext;
         private final MyAdapter mAdapter;
+        private final IStatusBarService mStatusBarService;
+        private final IBinder mToken = new Binder();
         private MultiListLayout mGlobalActionsLayout;
         private Drawable mBackgroundDrawable;
         private final SysuiColorExtractor mColorExtractor;
@@ -1516,6 +1522,7 @@
             mContext = context;
             mAdapter = adapter;
             mColorExtractor = Dependency.get(SysuiColorExtractor.class);
+            mStatusBarService = Dependency.get(IStatusBarService.class);
 
             // Window initialization
             Window window = getWindow();
@@ -1542,9 +1549,7 @@
         }
 
         private boolean shouldUsePanel() {
-            return isPanelEnabled(mContext)
-                    && mPanelController != null
-                    && mPanelController.getPanelContent() != null;
+            return mPanelController != null && mPanelController.getPanelContent() != null;
         }
 
         private void initializePanel() {
@@ -1574,6 +1579,9 @@
                             mContext, true, RotationUtils.ROTATION_NONE);
                 }
 
+                // Disable rotation suggestions, if enabled
+                setRotationSuggestionsEnabled(false);
+
                 FrameLayout panelContainer = new FrameLayout(mContext);
                 FrameLayout.LayoutParams panelParams =
                         new FrameLayout.LayoutParams(
@@ -1732,11 +1740,24 @@
             }
         }
 
+        private void setRotationSuggestionsEnabled(boolean enabled) {
+            try {
+                final int userId = Binder.getCallingUserHandle().getIdentifier();
+                final int what = enabled
+                        ? StatusBarManager.DISABLE2_NONE
+                        : StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
+                mStatusBarService.disable2ForUser(what, mToken, mContext.getPackageName(), userId);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+
         private void resetOrientation() {
             if (mResetOrientationData != null) {
                 RotationPolicy.setRotationLockAtAngle(mContext, mResetOrientationData.locked,
                         mResetOrientationData.rotation);
             }
+            setRotationSuggestionsEnabled(true);
         }
 
         @Override
@@ -1792,15 +1813,6 @@
     }
 
     /**
-     * Determines whether or not the Global Actions Panel should appear when the power button
-     * is held.
-     */
-    private static boolean isPanelEnabled(Context context) {
-        return FeatureFlagUtils.isEnabled(
-                context, FeatureFlagUtils.GLOBAL_ACTIONS_PANEL_ENABLED);
-    }
-
-    /**
      * Determines whether the Global Actions menu should use a separated view for emergency actions.
      */
     private static boolean shouldUseSeparatedView() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index c5591cf..78c7cd4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -31,7 +31,6 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 
-import android.annotation.ColorInt;
 import android.annotation.FloatRange;
 import android.app.ActivityTaskManager;
 import android.content.BroadcastReceiver;
@@ -60,7 +59,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.SysUiServiceProvider;
-import com.android.systemui.colorextraction.SysuiColorExtractor.ScrimType;
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -537,16 +535,6 @@
         dispatchNavButtonBounds();
     }
 
-    public void onScrimColorsChanged(@ColorInt int color, @ScrimType int type) {
-        if (mOverviewProxy != null) {
-            try {
-                mOverviewProxy.onScrimColorsChanged(color, type);
-            } catch (RemoteException e) {
-                Log.e(TAG_OPS, "Failed to call onScrimColorsChanged()", e);
-            }
-        }
-    }
-
     private void dispatchNavButtonBounds() {
         if (mOverviewProxy != null && mActiveNavBarRegion != null) {
             try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 2cca701..d202190 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -71,6 +71,7 @@
     private int mIconAppearTopPadding;
     private int mShelfAppearTranslation;
     private float mDarkShelfPadding;
+    private float mDarkShelfIconSize;
     private int mStatusBarHeight;
     private int mStatusBarPaddingStart;
     private AmbientState mAmbientState;
@@ -151,6 +152,7 @@
         mScrollFastThreshold = res.getDimensionPixelOffset(R.dimen.scroll_fast_threshold);
         mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
         mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
+        mDarkShelfIconSize = res.getDimensionPixelOffset(R.dimen.dark_shelf_icon_size);
         mGapHeight = res.getDimensionPixelSize(R.dimen.qs_notification_padding);
 
         if (!mShowNotificationShelf) {
@@ -705,12 +707,13 @@
         }
         notificationIconPosition += iconTopPadding;
         float shelfIconPosition = getTranslationY() + icon.getTop();
-        shelfIconPosition += (icon.getHeight() - icon.getIconScale() * mIconSize) / 2.0f;
+        float iconSize = mDark ? mDarkShelfIconSize : mIconSize;
+        shelfIconPosition += (icon.getHeight() - icon.getIconScale() * iconSize) / 2.0f;
         float iconYTranslation = NotificationUtils.interpolate(
                 notificationIconPosition - shelfIconPosition,
                 0,
                 transitionAmount);
-        float shelfIconSize = mIconSize * icon.getIconScale();
+        float shelfIconSize = iconSize * icon.getIconScale();
         float alpha = 1.0f;
         boolean noIcon = !row.isShowingIcon();
         if (noIcon) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 1074f3a..f93c5f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -29,6 +29,7 @@
 import android.graphics.drawable.Drawable;
 import android.hardware.biometrics.BiometricSourceType;
 import android.os.Handler;
+import android.os.Trace;
 import android.util.AttributeSet;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -89,6 +90,7 @@
     private float mDozeAmount;
     private int mIconRes;
     private boolean mWasPulsingOnThisFrame;
+    private boolean mWakeAndUnlockRunning;
 
     private final Runnable mDrawOffTimeout = () -> update(true /* forceUpdate */);
     private final DockManager.DockEventListener mDockEventListener =
@@ -255,9 +257,12 @@
                             if (getDrawable() == animation && state == getState()
                                     && doesAnimationLoop(iconAnimRes)) {
                                 animation.start();
+                            } else {
+                                Trace.endAsyncSection("LockIcon#Animation", state);
                             }
                         }
                     });
+                    Trace.beginAsyncSection("LockIcon#Animation", state);
                     animation.start();
                 }
             }
@@ -277,7 +282,8 @@
             mLastBouncerVisible = mBouncerVisible;
         }
 
-        boolean invisible = mDozing && (!mPulsing || mDocked);
+        boolean onAodNotPulsingOrDocked = mDozing && (!mPulsing || mDocked);
+        boolean invisible = onAodNotPulsingOrDocked || mWakeAndUnlockRunning;
         setVisibility(invisible ? INVISIBLE : VISIBLE);
         updateClickability();
     }
@@ -450,4 +456,23 @@
     public void onUnlockMethodStateChanged() {
         update();
     }
+
+    /**
+     * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
+     * icon on top of the black front scrim.
+     */
+    public void onBiometricAuthModeChanged(boolean wakeAndUnlock) {
+        if (wakeAndUnlock) {
+            mWakeAndUnlockRunning = true;
+        }
+        update();
+    }
+
+    /**
+     * Triggered after the unlock animation is over and the user is looking at launcher.
+     */
+    public void onKeyguardFadedAway() {
+        mWakeAndUnlockRunning = false;
+        update();
+    }
 }
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 b4b4235..17f0d5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -538,6 +538,7 @@
             }
             if (mKeyguardMonitor.isKeyguardFadingAway()) {
                 mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+                mStatusBarWindow.onKeyguardFadedAway();
             }
         }
 
@@ -3798,6 +3799,7 @@
     public void notifyBiometricAuthModeChanged() {
         updateDozing();
         updateScrimController();
+        mStatusBarWindow.onBiometricAuthModeChanged(mBiometricUnlockController.isWakeAndUnlock());
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 6fe8964..b6f95b83c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -41,7 +41,6 @@
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.widget.MessagingGroup;
 import com.android.internal.widget.MessagingMessage;
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
 import com.android.systemui.ForegroundServiceNotificationListener;
 import com.android.systemui.InitController;
@@ -122,8 +121,6 @@
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final int mMaxAllowedKeyguardNotifications;
     private final IStatusBarService mBarService;
-    private boolean mReinflateNotificationsOnUserSwitched;
-    private boolean mDispatchUiModeChangeOnUserSwitched;
     private final UnlockMethodCache mUnlockMethodCache;
     private TextView mNotificationPanelDebugText;
 
@@ -242,20 +239,12 @@
     public void onDensityOrFontScaleChanged() {
         MessagingMessage.dropCache();
         MessagingGroup.dropCache();
-        if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
-            updateNotificationsOnDensityOrFontScaleChanged();
-        } else {
-            mReinflateNotificationsOnUserSwitched = true;
-        }
+        updateNotificationsOnDensityOrFontScaleChanged();
     }
 
     @Override
     public void onUiModeChanged() {
-        if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
-            updateNotificationOnUiModeChanged();
-        } else {
-            mDispatchUiModeChangeOnUserSwitched = true;
-        }
+        updateNotificationOnUiModeChanged();
     }
 
     @Override
@@ -385,14 +374,6 @@
         // End old BaseStatusBar.userSwitched
         if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
         mCommandQueue.animateCollapsePanels();
-        if (mReinflateNotificationsOnUserSwitched) {
-            updateNotificationsOnDensityOrFontScaleChanged();
-            mReinflateNotificationsOnUserSwitched = false;
-        }
-        if (mDispatchUiModeChangeOnUserSwitched) {
-            updateNotificationOnUiModeChanged();
-            mDispatchUiModeChangeOnUserSwitched = false;
-        }
         updateNotificationViews();
         mMediaManager.clearCurrentMediaNotification();
         mShadeController.setLockscreenUser(newUserId);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 9f538bb..0b83b69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -265,6 +265,21 @@
         mLockIcon.setPulsing(pulsing);
     }
 
+    /**
+     * Called when the biometric authentication mode changes.
+     * @param wakeAndUnlock If the type is {@link BiometricUnlockController#isWakeAndUnlock()}
+     */
+    public void onBiometricAuthModeChanged(boolean wakeAndUnlock) {
+        mLockIcon.onBiometricAuthModeChanged(wakeAndUnlock);
+    }
+
+    /**
+     * Called after finished unlocking and the status bar window is already collapsed.
+     */
+    public void onKeyguardFadedAway() {
+        mLockIcon.onKeyguardFadedAway();
+    }
+
     public void setStatusBarView(PhoneStatusBarView statusBarView) {
         mStatusBarView = statusBarView;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
new file mode 100644
index 0000000..173237f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotSame;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.verify;
+
+import android.graphics.Color;
+import android.graphics.PointF;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class BubbleFlyoutViewTest extends SysuiTestCase {
+    private BubbleFlyoutView mFlyout;
+    private TextView mFlyoutText;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mFlyout = new BubbleFlyoutView(getContext());
+
+        mFlyoutText = mFlyout.findViewById(R.id.bubble_flyout_text);
+    }
+
+    @Test
+    public void testShowFlyout_isVisible() {
+        mFlyout.showFlyout("Hello", new PointF(100, 100), 500, true, Color.WHITE, null);
+        assertEquals("Hello", mFlyoutText.getText());
+        assertEquals(View.VISIBLE, mFlyout.getVisibility());
+        assertEquals(1f, mFlyoutText.getAlpha(), .01f);
+    }
+
+    @Test
+    public void testFlyoutHide_runsCallback() {
+        Runnable after = Mockito.mock(Runnable.class);
+        mFlyout.showFlyout("Hello", new PointF(100, 100), 500, true, Color.WHITE, after);
+        mFlyout.hideFlyout();
+
+        verify(after).run();
+    }
+
+    @Test
+    public void testSetCollapsePercent() {
+        mFlyout.showFlyout("Hello", new PointF(100, 100), 500, true, Color.WHITE, null);
+
+        float initialTranslationZ = mFlyout.getTranslationZ();
+
+        mFlyout.setCollapsePercent(1f);
+        assertEquals(0f, mFlyoutText.getAlpha(), 0.01f);
+        assertNotSame(0f, mFlyoutText.getTranslationX()); // Should have moved to collapse.
+        assertTrue(mFlyout.getTranslationZ() < initialTranslationZ); // Should be descending.
+
+        mFlyout.setCollapsePercent(0f);
+        assertEquals(1f, mFlyoutText.getAlpha(), 0.01f);
+        assertEquals(0f, mFlyoutText.getTranslationX());
+        assertEquals(initialTranslationZ, mFlyout.getTranslationZ());
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleStackViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleStackViewTest.java
deleted file mode 100644
index bafae6c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleStackViewTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.bubbles;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.when;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.widget.TextView;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class BubbleStackViewTest extends SysuiTestCase {
-    private BubbleStackView mStackView;
-    @Mock private Bubble mBubble;
-    @Mock private NotificationEntry mNotifEntry;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mStackView = new BubbleStackView(mContext, new BubbleData(getContext()), null);
-        mBubble.entry = mNotifEntry;
-    }
-
-    @Test
-    public void testAnimateInFlyoutForBubble() {
-        when(mNotifEntry.getUpdateMessage(any())).thenReturn("Test Flyout Message.");
-        mStackView.animateInFlyoutForBubble(mBubble);
-
-        assertEquals("Test Flyout Message.",
-                ((TextView) mStackView.findViewById(R.id.bubble_flyout_text)).getText());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
index 3d3c295..67df60a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
@@ -34,14 +34,10 @@
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.types.Tonal;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
 /**
  * Tests color extraction generation.
@@ -57,13 +53,6 @@
             ColorExtractor.TYPE_NORMAL,
             ColorExtractor.TYPE_DARK,
             ColorExtractor.TYPE_EXTRA_DARK};
-    @Mock
-    private OverviewProxyService mOverviewProxyService;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-    }
 
     @Test
     public void getColors_usesGreyIfWallpaperNotVisible() {
@@ -129,8 +118,7 @@
         Tonal tonal = mock(Tonal.class);
         ConfigurationController configurationController = mock(ConfigurationController.class);
         SysuiColorExtractor sysuiColorExtractor = new SysuiColorExtractor(getContext(),
-                tonal, configurationController, false /* registerVisibility */,
-                mOverviewProxyService);
+                tonal, configurationController, false /* registerVisibility */);
         verify(configurationController).addCallback(eq(sysuiColorExtractor));
 
         reset(tonal);
@@ -145,7 +133,7 @@
                     outGradientColorsNormal.set(colors);
                     outGradientColorsDark.set(colors);
                     outGradientColorsExtraDark.set(colors);
-                }, mock(ConfigurationController.class), false, mOverviewProxyService);
+                }, mock(ConfigurationController.class), false);
     }
 
     private void simulateEvent(SysuiColorExtractor extractor) {
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index f9aaf11..a7fb99f 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -140,6 +140,7 @@
         private boolean mCompleted;
         private Monitor mCurrentMonitor;
         private long mStartTime;
+        private int mPauseCount;
 
         HandlerChecker(Handler handler, String name, long waitMaxMillis) {
             mHandler = handler;
@@ -160,17 +161,18 @@
                 mMonitors.addAll(mMonitorQueue);
                 mMonitorQueue.clear();
             }
-            if (mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling()) {
+            if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling())
+                    || (mPauseCount > 0)) {
+                // Don't schedule until after resume OR
                 // If the target looper has recently been polling, then
                 // there is no reason to enqueue our checker on it since that
                 // is as good as it not being deadlocked.  This avoid having
-                // to do a context switch to check the thread.  Note that we
-                // only do this if mCheckReboot is false and we have no
-                // monitors, since those would need to be executed at this point.
+                // to do a context switch to check the thread. Note that we
+                // only do this if we have no monitors since those would need to
+                // be executed at this point.
                 mCompleted = true;
                 return;
             }
-
             if (!mCompleted) {
                 // we already have a check in flight, so no need
                 return;
@@ -236,6 +238,28 @@
                 mCurrentMonitor = null;
             }
         }
+
+        /** Pause the HandlerChecker. */
+        public void pauseLocked(String reason) {
+            mPauseCount++;
+            // Mark as completed, because there's a chance we called this after the watchog
+            // thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure
+            // the next call to #getCompletionStateLocked for this checker returns 'COMPLETED'
+            mCompleted = true;
+            Slog.i(TAG, "Pausing HandlerChecker: " + mName + " for reason: "
+                    + reason + ". Pause count: " + mPauseCount);
+        }
+
+        /** Resume the HandlerChecker from the last {@link #pauseLocked}. */
+        public void resumeLocked(String reason) {
+            if (mPauseCount > 0) {
+                mPauseCount--;
+                Slog.i(TAG, "Resuming HandlerChecker: " + mName + " for reason: "
+                        + reason + ". Pause count: " + mPauseCount);
+            } else {
+                Slog.wtf(TAG, "Already resumed HandlerChecker: " + mName);
+            }
+        }
     }
 
     final class RebootRequestReceiver extends BroadcastReceiver {
@@ -364,6 +388,51 @@
     }
 
     /**
+     * Pauses Watchdog action for the currently running thread. Useful before executing long running
+     * operations that could falsely trigger the watchdog. Each call to this will require a matching
+     * call to {@link #resumeWatchingCurrentThread}.
+     *
+     * <p>If the current thread has not been added to the Watchdog, this call is a no-op.
+     *
+     * <p>If the Watchdog is already paused for the current thread, this call adds
+     * adds another pause and will require an additional {@link #resumeCurrentThread} to resume.
+     *
+     * <p>Note: Use with care, as any deadlocks on the current thread will be undetected until all
+     * pauses have been resumed.
+     */
+    public void pauseWatchingCurrentThread(String reason) {
+        synchronized (this) {
+            for (HandlerChecker hc : mHandlerCheckers) {
+                if (Thread.currentThread().equals(hc.getThread())) {
+                    hc.pauseLocked(reason);
+                }
+            }
+        }
+    }
+
+    /**
+     * Resumes the last pause from {@link #pauseWatchingCurrentThread} for the currently running
+     * thread.
+     *
+     * <p>If the current thread has not been added to the Watchdog, this call is a no-op.
+     *
+     * <p>If the Watchdog action for the current thread is already resumed, this call logs a wtf.
+     *
+     * <p>If all pauses have been resumed, the Watchdog action is finally resumed, otherwise,
+     * the Watchdog action for the current thread remains paused until resume is called at least
+     * as many times as the calls to pause.
+     */
+    public void resumeWatchingCurrentThread(String reason) {
+        synchronized (this) {
+            for (HandlerChecker hc : mHandlerCheckers) {
+                if (Thread.currentThread().equals(hc.getThread())) {
+                    hc.resumeLocked(reason);
+                }
+            }
+        }
+    }
+
+    /**
      * Perform a full reboot of the system.
      */
     void rebootSystem(String reason) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 696697e..b394eea 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1658,23 +1658,15 @@
         app.killed = false;
         final long startSeq = app.startSeq = ++mProcStartSeqCounter;
         app.setStartParams(uid, hostingRecord, seInfo, startTime);
+        app.setUsingWrapper(invokeWith != null
+                || SystemProperties.get("wrap." + app.processName) != null);
+        mPendingStarts.put(startSeq, app);
+
         if (mService.mConstants.FLAG_PROCESS_START_ASYNC) {
             if (DEBUG_PROCESSES) Slog.i(TAG_PROCESSES,
                     "Posting procStart msg for " + app.toShortString());
             mService.mProcStartHandler.post(() -> {
                 try {
-                    synchronized (mService) {
-                        final String reason = isProcStartValidLocked(app, startSeq);
-                        if (reason != null) {
-                            Slog.w(TAG_PROCESSES, app + " not valid anymore,"
-                                    + " don't start process, " + reason);
-                            app.pendingStart = false;
-                            return;
-                        }
-                        app.setUsingWrapper(invokeWith != null
-                                || SystemProperties.get("wrap." + app.processName) != null);
-                        mPendingStarts.put(startSeq, app);
-                    }
                     final Process.ProcessStartResult startResult = startProcess(app.hostingRecord,
                             entryPoint, app, app.startUid, gids, runtimeFlags, mountExternal,
                             app.seInfo, requiredAbi, instructionSet, invokeWith, app.startTime);
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index 7605ccb..e148468 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -270,7 +270,7 @@
                 return;
             }
             if (!userState.mCurrentAttentionCheck.mCallbackInternal.equals(callbackInternal)) {
-                Slog.e(LOG_TAG, "Cannot cancel a non-current request");
+                Slog.w(LOG_TAG, "Cannot cancel a non-current request");
                 return;
             }
             cancel(userState);
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 7e79a12..d8b7c2e 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -3256,14 +3256,17 @@
                 }
             }
 
-            // On account add, check if there are any settings to be restored.
-            for (AccountAndUser aau : mRunningAccounts) {
-                if (!containsAccountAndUser(oldAccounts, aau.account, aau.userId)) {
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "Account " + aau.account + " added, checking sync restore data");
+            if (syncTargets != null) {
+                // On account add, check if there are any settings to be restored.
+                for (AccountAndUser aau : mRunningAccounts) {
+                    if (!containsAccountAndUser(oldAccounts, aau.account, aau.userId)) {
+                        if (Log.isLoggable(TAG, Log.DEBUG)) {
+                            Log.d(TAG, "Account " + aau.account
+                                    + " added, checking sync restore data");
+                        }
+                        AccountSyncSettingsBackupHelper.accountAdded(mContext, syncTargets.userId);
+                        break;
                     }
-                    AccountSyncSettingsBackupHelper.accountAdded(mContext, syncTargets.userId);
-                    break;
                 }
             }
 
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index b676618..35a82ae 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -49,6 +49,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
@@ -1625,6 +1626,15 @@
     }
 
     /**
+     * Maximum time buffer in which JobScheduler will try to optimize periodic job scheduling. This
+     * does not cause a job's period to be larger than requested (eg: if the requested period is
+     * shorter than this buffer). This is used to put a limit on when JobScheduler will intervene
+     * and try to optimize scheduling if the current job finished less than this amount of time to
+     * the start of the next period
+     */
+    private static final long PERIODIC_JOB_WINDOW_BUFFER = 30 * MINUTE_IN_MILLIS;
+
+    /**
      * Called after a periodic has executed so we can reschedule it. We take the last execution
      * time of the job to be the time of completion (i.e. the time at which this function is
      * called).
@@ -1644,16 +1654,18 @@
         final long period = periodicToReschedule.getJob().getIntervalMillis();
         final long latestRunTimeElapsed = periodicToReschedule.getOriginalLatestRunTimeElapsed();
         final long flex = periodicToReschedule.getJob().getFlexMillis();
+        long rescheduleBuffer = 0;
 
+        final long diffMs = Math.abs(elapsedNow - latestRunTimeElapsed);
         if (elapsedNow > latestRunTimeElapsed) {
             // The job ran past its expected run window. Have it count towards the current window
             // and schedule a new job for the next window.
             if (DEBUG) {
                 Slog.i(TAG, "Periodic job ran after its intended window.");
             }
-            final long diffMs = (elapsedNow - latestRunTimeElapsed);
             int numSkippedWindows = (int) (diffMs / period) + 1; // +1 to include original window
-            if (period != flex && diffMs > Math.min(30 * MINUTE_IN_MILLIS, (period - flex) / 2)) {
+            if (period != flex && diffMs > Math.min(PERIODIC_JOB_WINDOW_BUFFER,
+                    (period - flex) / 2)) {
                 if (DEBUG) {
                     Slog.d(TAG, "Custom flex job ran too close to next window.");
                 }
@@ -1664,9 +1676,15 @@
             newLatestRuntimeElapsed = latestRunTimeElapsed + (period * numSkippedWindows);
         } else {
             newLatestRuntimeElapsed = latestRunTimeElapsed + period;
+            if (diffMs < PERIODIC_JOB_WINDOW_BUFFER && diffMs < period / 6) {
+                // Add a little buffer to the start of the next window so the job doesn't run
+                // too soon after this completed one.
+                rescheduleBuffer = Math.min(PERIODIC_JOB_WINDOW_BUFFER, period / 6 - diffMs);
+            }
         }
 
-        final long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
+        final long newEarliestRunTimeElapsed = newLatestRuntimeElapsed
+                - Math.min(flex, period - rescheduleBuffer);
 
         if (DEBUG) {
             Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
@@ -2764,12 +2782,12 @@
         }
 
         @Override
-        public List<JobInfo> getAllPendingJobs() throws RemoteException {
+        public ParceledListSlice<JobInfo> getAllPendingJobs() throws RemoteException {
             final int uid = Binder.getCallingUid();
 
             long ident = Binder.clearCallingIdentity();
             try {
-                return JobSchedulerService.this.getPendingJobs(uid);
+                return new ParceledListSlice<>(JobSchedulerService.this.getPendingJobs(uid));
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2905,7 +2923,7 @@
          * <p class="note">This is a slow operation, so it should be called sparingly.
          */
         @Override
-        public List<JobSnapshot> getAllJobSnapshots() {
+        public ParceledListSlice<JobSnapshot> getAllJobSnapshots() {
             final int uid = Binder.getCallingUid();
             if (uid != Process.SYSTEM_UID) {
                 throw new SecurityException(
@@ -2916,7 +2934,7 @@
                 mJobs.forEachJob((job) -> snapshots.add(
                         new JobSnapshot(job.getJob(), job.getSatisfiedConstraintFlags(),
                                 isReadyToBeExecutedLocked(job))));
-                return snapshots;
+                return new ParceledListSlice<>(snapshots);
             }
         }
     };
diff --git a/services/core/java/com/android/server/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java
index 3262eb6..14f1196 100644
--- a/services/core/java/com/android/server/power/AttentionDetector.java
+++ b/services/core/java/com/android/server/power/AttentionDetector.java
@@ -75,6 +75,8 @@
      */
     private final AtomicBoolean mRequested;
 
+    private long mLastActedOnNextScreenDimming;
+
     /**
      * Monotonously increasing ID for the requests sent.
      */
@@ -150,6 +152,9 @@
     }
 
     public long updateUserActivity(long nextScreenDimming) {
+        if (nextScreenDimming == mLastActedOnNextScreenDimming) {
+            return nextScreenDimming;
+        }
         if (!mIsSettingEnabled) {
             return nextScreenDimming;
         }
@@ -190,13 +195,14 @@
         // afterwards if AttentionManager couldn't deliver it.
         mRequested.set(true);
         mRequestId++;
+        mLastActedOnNextScreenDimming = nextScreenDimming;
         mCallback = new AttentionCallbackInternalImpl(mRequestId);
+        Slog.v(TAG, "Checking user attention, ID: " + mRequestId);
         final boolean sent = mAttentionManager.checkAttention(getAttentionTimeout(), mCallback);
         if (!sent) {
             mRequested.set(false);
         }
 
-        Slog.v(TAG, "Checking user attention, ID: " + mRequestId);
         return whenToCheck;
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index ba41586..3d59e66 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -281,8 +281,9 @@
         if (display != null && inSplitScreenPrimaryWindowingMode()) {
             // If we created a docked stack we want to resize it so it resizes all other stacks
             // in the system.
-            getStackDockedModeBounds(null, null, mTmpRect2, mTmpRect3);
-            mStackSupervisor.resizeDockedStackLocked(getRequestedOverrideBounds(), mTmpRect2,
+            getStackDockedModeBounds(null /* dockedBounds */, null /* currentTempTaskBounds */,
+                    mTmpRect /* outStackBounds */, mTmpRect2 /* outTempTaskBounds */);
+            mStackSupervisor.resizeDockedStackLocked(getRequestedOverrideBounds(), mTmpRect,
                     mTmpRect2, null, null, PRESERVE_WINDOWS);
         }
         mRootActivityContainer.updateUIDsPresentOnDisplay();
@@ -396,7 +397,6 @@
 
     private final Rect mTmpRect = new Rect();
     private final Rect mTmpRect2 = new Rect();
-    private final Rect mTmpRect3 = new Rect();
     private final ActivityOptions mTmpOptions = ActivityOptions.makeBasic();
 
     /** List for processing through a set of activities */
@@ -512,7 +512,6 @@
         mWindowManager = mService.mWindowManager;
         mStackId = stackId;
         mCurrentUser = mService.mAmInternal.getCurrentUserId();
-        mTmpRect2.setEmpty();
         // Set display id before setting activity and window type to make sure it won't affect
         // stacks on a wrong display.
         mDisplayId = display.mDisplayId;
@@ -572,90 +571,87 @@
     public void onConfigurationChanged(Configuration newParentConfig) {
         final int prevWindowingMode = getWindowingMode();
         final boolean prevIsAlwaysOnTop = isAlwaysOnTop();
-        final ActivityDisplay display = getDisplay();
         final int prevRotation = getWindowConfiguration().getRotation();
         final int prevDensity = getConfiguration().densityDpi;
         final int prevScreenW = getConfiguration().screenWidthDp;
         final int prevScreenH = getConfiguration().screenHeightDp;
-
-        getBounds(mTmpRect); // previous bounds
+        final Rect newBounds = mTmpRect;
+        // Initialize the new bounds by previous bounds as the input and output for calculating
+        // override bounds in pinned (pip) or split-screen mode.
+        getBounds(newBounds);
 
         super.onConfigurationChanged(newParentConfig);
-        if (display == null) {
-            return;
-        }
-        if (getTaskStack() == null) {
+        final ActivityDisplay display = getDisplay();
+        if (display == null || getTaskStack() == null) {
             return;
         }
 
+        final boolean windowingModeChanged = prevWindowingMode != getWindowingMode();
+        final int overrideWindowingMode = getRequestedOverrideWindowingMode();
         // Update bounds if applicable
         boolean hasNewOverrideBounds = false;
         // Use override windowing mode to prevent extra bounds changes if inheriting the mode.
-        if (getRequestedOverrideWindowingMode() == WINDOWING_MODE_PINNED) {
+        if (overrideWindowingMode == WINDOWING_MODE_PINNED) {
             // Pinned calculation already includes rotation
-            mTmpRect2.set(mTmpRect);
-            hasNewOverrideBounds = getTaskStack().calculatePinnedBoundsForConfigChange(mTmpRect2);
-        } else {
+            hasNewOverrideBounds = getTaskStack().calculatePinnedBoundsForConfigChange(newBounds);
+        } else if (!matchParentBounds()) {
+            // If the parent (display) has rotated, rotate our bounds to best-fit where their
+            // bounds were on the pre-rotated display.
             final int newRotation = getWindowConfiguration().getRotation();
-            if (!matchParentBounds()) {
-                // If the parent (display) has rotated, rotate our bounds to best-fit where their
-                // bounds were on the pre-rotated display.
-                if (prevRotation != newRotation) {
-                    mTmpRect2.set(mTmpRect);
-                    getDisplay().mDisplayContent
-                            .rotateBounds(newParentConfig.windowConfiguration.getBounds(),
-                                    prevRotation, newRotation, mTmpRect2);
-                    hasNewOverrideBounds = true;
-                }
+            final boolean rotationChanged = prevRotation != newRotation;
+            if (rotationChanged) {
+                display.mDisplayContent.rotateBounds(
+                        newParentConfig.windowConfiguration.getBounds(), prevRotation, newRotation,
+                        newBounds);
+                hasNewOverrideBounds = true;
+            }
 
+            // Use override windowing mode to prevent extra bounds changes if inheriting the mode.
+            if (overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                    || overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
                 // If entering split screen or if something about the available split area changes,
                 // recalculate the split windows to match the new configuration.
-                if (prevRotation != newRotation
+                if (rotationChanged || windowingModeChanged
                         || prevDensity != getConfiguration().densityDpi
-                        || prevWindowingMode != getWindowingMode()
                         || prevScreenW != getConfiguration().screenWidthDp
                         || prevScreenH != getConfiguration().screenHeightDp) {
-                    // Use override windowing mode to prevent extra bounds changes if inheriting
-                    // the mode.
-                    if (getRequestedOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                            || getRequestedOverrideWindowingMode()
-                            == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
-                        mTmpRect2.set(mTmpRect);
-                        getTaskStack()
-                                .calculateDockedBoundsForConfigChange(newParentConfig, mTmpRect2);
-                        hasNewOverrideBounds = true;
-                    }
+                    getTaskStack().calculateDockedBoundsForConfigChange(newParentConfig, newBounds);
+                    hasNewOverrideBounds = true;
                 }
             }
         }
-        if (getWindowingMode() != prevWindowingMode) {
+
+        if (windowingModeChanged) {
             // Use override windowing mode to prevent extra bounds changes if inheriting the mode.
-            if (getRequestedOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-                getStackDockedModeBounds(null, null, mTmpRect2, mTmpRect3);
+            if (overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                getStackDockedModeBounds(null /* dockedBounds */, null /* currentTempTaskBounds */,
+                        newBounds /* outStackBounds */, mTmpRect2 /* outTempTaskBounds */);
                 // immediately resize so docked bounds are available in onSplitScreenModeActivated
                 setTaskDisplayedBounds(null);
-                setTaskBounds(mTmpRect2);
-                setBounds(mTmpRect2);
-            } else if (
-                    getRequestedOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+                setTaskBounds(newBounds);
+                setBounds(newBounds);
+                newBounds.set(newBounds);
+            } else if (overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
                 Rect dockedBounds = display.getSplitScreenPrimaryStack().getBounds();
                 final boolean isMinimizedDock =
-                        getDisplay().mDisplayContent.getDockedDividerController().isMinimizedDock();
+                        display.mDisplayContent.getDockedDividerController().isMinimizedDock();
                 if (isMinimizedDock) {
                     TaskRecord topTask = display.getSplitScreenPrimaryStack().topTask();
                     if (topTask != null) {
                         dockedBounds = topTask.getBounds();
                     }
                 }
-                getStackDockedModeBounds(dockedBounds, null, mTmpRect2, mTmpRect3);
+                getStackDockedModeBounds(dockedBounds, null /* currentTempTaskBounds */,
+                        newBounds /* outStackBounds */, mTmpRect2 /* outTempTaskBounds */);
                 hasNewOverrideBounds = true;
             }
-        }
-        if (prevWindowingMode != getWindowingMode()) {
             display.onStackWindowingModeChanged(this);
         }
         if (hasNewOverrideBounds) {
-            mRootActivityContainer.resizeStack(this, mTmpRect2, null, null, PRESERVE_WINDOWS,
+            // Note the resizeStack may enter onConfigurationChanged recursively, so we make a copy
+            // of the temporary bounds (newBounds is mTmpRect) to avoid it being modified.
+            mRootActivityContainer.resizeStack(this, new Rect(newBounds), null /* tempTaskBounds */,
+                    null /* tempTaskInsetBounds */, PRESERVE_WINDOWS,
                     true /* allowResizeInDockedMode */, true /* deferResume */);
         }
         if (prevIsAlwaysOnTop != isAlwaysOnTop()) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index b8442a8..1cdb49d 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -46,7 +46,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -304,7 +303,7 @@
             return null;
         }
         // TODO(b/28935539): should allow certain activities to bypass work challenge
-        final IntentSender target = createIntentSenderForOriginalIntent(Binder.getCallingUid(),
+        final IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
                 FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT | FLAG_IMMUTABLE);
         final KeyguardManager km = (KeyguardManager) mServiceContext
                 .getSystemService(KEYGUARD_SERVICE);
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 204a1ea..fb3076b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -59,6 +59,7 @@
 #include <android_view_PointerIcon.h>
 #include <android/graphics/GraphicsJNI.h>
 
+#include <nativehelper/ScopedLocalFrame.h>
 #include <nativehelper/ScopedLocalRef.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedUtfChars.h>
@@ -723,6 +724,7 @@
     ATRACE_CALL();
 
     JNIEnv* env = jniEnv();
+    ScopedLocalFrame localFrame(env);
 
     jobject tokenObj = javaObjectForIBinder(env, token);
     jstring reasonObj = env->NewStringUTF(reason.c_str());
@@ -735,8 +737,6 @@
     } else {
         assert(newTimeout >= 0);
     }
-
-    env->DeleteLocalRef(reasonObj);
     return newTimeout;
 }
 
@@ -747,6 +747,7 @@
     ATRACE_CALL();
 
     JNIEnv* env = jniEnv();
+    ScopedLocalFrame localFrame(env);
 
     jobject tokenObj = javaObjectForIBinder(env, token);
     if (tokenObj) {
@@ -764,6 +765,7 @@
     ATRACE_CALL();
 
     JNIEnv* env = jniEnv();
+    ScopedLocalFrame localFrame(env);
 
     jobject oldTokenObj = javaObjectForIBinder(env, oldToken);
     jobject newTokenObj = javaObjectForIBinder(env, newToken);
@@ -1139,6 +1141,7 @@
     nsecs_t result = 0;
     if (policyFlags & POLICY_FLAG_TRUSTED) {
         JNIEnv* env = jniEnv();
+        ScopedLocalFrame localFrame(env);
 
         // Token may be null
         jobject tokenObj = javaObjectForIBinder(env, token);
@@ -1173,6 +1176,7 @@
     bool result = false;
     if (policyFlags & POLICY_FLAG_TRUSTED) {
         JNIEnv* env = jniEnv();
+        ScopedLocalFrame localFrame(env);
 
         // Note: tokenObj may be null.
         jobject tokenObj = javaObjectForIBinder(env, token);
@@ -1224,6 +1228,7 @@
 void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) {
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
+    ScopedLocalFrame localFrame(env);
 
     jobject touchedTokenObj = javaObjectForIBinder(env, touchedToken);
     env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDownOutsideFocus, touchedTokenObj);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 8c65fa8..2b5cd01 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1167,9 +1167,12 @@
         if (!mOnlyCore) {
             traceBeginAndSlog("UpdatePackagesIfNeeded");
             try {
+                Watchdog.getInstance().pauseWatchingCurrentThread("dexopt");
                 mPackageManagerService.updatePackagesIfNeeded();
             } catch (Throwable e) {
                 reportWtf("update packages", e);
+            } finally {
+                Watchdog.getInstance().resumeWatchingCurrentThread("dexopt");
             }
             traceEnd();
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index f7edf65..18c524a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -183,15 +183,188 @@
         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
 
-        advanceElapsedClock(45 * MINUTE_IN_MILLIS); // now + 55 minutes
+        advanceElapsedClock(20 * MINUTE_IN_MILLIS); // now + 30 minutes
 
         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
 
+        advanceElapsedClock(25 * MINUTE_IN_MILLIS); // now + 55 minutes
+
+        rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        // Shifted because it's close to the end of the window.
+        assertEquals(nextWindowStartTime + 5 * MINUTE_IN_MILLIS,
+                rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
         advanceElapsedClock(4 * MINUTE_IN_MILLIS); // now + 59 minutes
 
         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        // Shifted because it's close to the end of the window.
+        assertEquals(nextWindowStartTime + 9 * MINUTE_IN_MILLIS,
+                rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+    }
+
+    /**
+     * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
+     * with an extra delay and correct deadline constraint if the periodic job is completed near the
+     * end of its expected running window.
+     */
+    @Test
+    public void testGetRescheduleJobForPeriodic_closeToEndOfWindow() {
+        JobStatus frequentJob = createJobStatus(
+                "testGetRescheduleJobForPeriodic_closeToEndOfWindow",
+                createJobInfo().setPeriodic(15 * MINUTE_IN_MILLIS));
+        long now = sElapsedRealtimeClock.millis();
+        long nextWindowStartTime = now + 15 * MINUTE_IN_MILLIS;
+        long nextWindowEndTime = now + 30 * MINUTE_IN_MILLIS;
+
+        // At the beginning of the window. Next window should be unaffected.
+        JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(frequentJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // Halfway through window. Next window should be unaffected.
+        advanceElapsedClock((long) (7.5 * MINUTE_IN_MILLIS));
+        rescheduledJob = mService.getRescheduleJobForPeriodic(frequentJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // In last 1/6 of window. Next window start time should be shifted slightly.
+        advanceElapsedClock(6 * MINUTE_IN_MILLIS);
+        rescheduledJob = mService.getRescheduleJobForPeriodic(frequentJob);
+        assertEquals(nextWindowStartTime + MINUTE_IN_MILLIS,
+                rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        JobStatus mediumJob = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
+                createJobInfo().setPeriodic(HOUR_IN_MILLIS));
+        now = sElapsedRealtimeClock.millis();
+        nextWindowStartTime = now + HOUR_IN_MILLIS;
+        nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
+
+        // At the beginning of the window. Next window should be unaffected.
+        rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // Halfway through window. Next window should be unaffected.
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+        rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // At the edge 1/6 of window. Next window should be unaffected.
+        advanceElapsedClock(20 * MINUTE_IN_MILLIS);
+        rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // In last 1/6 of window. Next window start time should be shifted slightly.
+        advanceElapsedClock(6 * MINUTE_IN_MILLIS);
+        rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
+        assertEquals(nextWindowStartTime + (6 * MINUTE_IN_MILLIS),
+                rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        JobStatus longJob = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
+                createJobInfo().setPeriodic(6 * HOUR_IN_MILLIS));
+        now = sElapsedRealtimeClock.millis();
+        nextWindowStartTime = now + 6 * HOUR_IN_MILLIS;
+        nextWindowEndTime = now + 12 * HOUR_IN_MILLIS;
+
+        // At the beginning of the window. Next window should be unaffected.
+        rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // Halfway through window. Next window should be unaffected.
+        advanceElapsedClock(3 * HOUR_IN_MILLIS);
+        rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // At the edge 1/6 of window. Next window should be unaffected.
+        advanceElapsedClock(2 * HOUR_IN_MILLIS);
+        rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // In last 1/6 of window. Next window should be unaffected since we're over the shift cap.
+        advanceElapsedClock(15 * MINUTE_IN_MILLIS);
+        rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // In last 1/6 of window. Next window start time should be shifted slightly.
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+        rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
+        assertEquals(nextWindowStartTime + (30 * MINUTE_IN_MILLIS),
+                rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // Flex duration close to period duration.
+        JobStatus gameyFlex = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
+                createJobInfo().setPeriodic(HOUR_IN_MILLIS, 59 * MINUTE_IN_MILLIS));
+        now = sElapsedRealtimeClock.millis();
+        nextWindowStartTime = now + HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+        nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
+        advanceElapsedClock(MINUTE_IN_MILLIS);
+
+        // At the beginning of the window. Next window should be unaffected.
+        rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // Halfway through window. Next window should be unaffected.
+        advanceElapsedClock(29 * MINUTE_IN_MILLIS);
+        rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // At the edge 1/6 of window. Next window should be unaffected.
+        advanceElapsedClock(20 * MINUTE_IN_MILLIS);
+        rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // In last 1/6 of window. Next window start time should be shifted slightly.
+        advanceElapsedClock(6 * MINUTE_IN_MILLIS);
+        rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
+        assertEquals(nextWindowStartTime + (5 * MINUTE_IN_MILLIS),
+                rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // Very short flex duration compared to period duration.
+        JobStatus superFlex = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
+                createJobInfo().setPeriodic(HOUR_IN_MILLIS, 10 * MINUTE_IN_MILLIS));
+        now = sElapsedRealtimeClock.millis();
+        nextWindowStartTime = now + HOUR_IN_MILLIS + 50 * MINUTE_IN_MILLIS;
+        nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
+        advanceElapsedClock(MINUTE_IN_MILLIS);
+
+        // At the beginning of the window. Next window should be unaffected.
+        rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // Halfway through window. Next window should be unaffected.
+        advanceElapsedClock(29 * MINUTE_IN_MILLIS);
+        rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // At the edge 1/6 of window. Next window should be unaffected.
+        advanceElapsedClock(20 * MINUTE_IN_MILLIS);
+        rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // In last 1/6 of window. Next window should be unaffected since the flex duration pushes
+        // the next window start time far enough away.
+        advanceElapsedClock(6 * MINUTE_IN_MILLIS);
+        rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
     }
@@ -265,7 +438,9 @@
         advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 55 minutes
 
         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
-        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        // Shifted because it's close to the end of the window.
+        assertEquals(nextWindowStartTime + 5 * MINUTE_IN_MILLIS,
+                rescheduledJob.getEarliestRunTime());
         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
 
         advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 57 minutes
@@ -273,7 +448,9 @@
         advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 59 minutes
 
         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
-        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        // Shifted because it's close to the end of the window.
+        assertEquals(nextWindowStartTime + 9 * MINUTE_IN_MILLIS,
+                rescheduledJob.getEarliestRunTime());
         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 8af4edd..18453aa 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -27,6 +27,7 @@
 import android.content.pm.UserInfo;
 import android.database.sqlite.SQLiteDatabase;
 import android.os.FileUtils;
+import android.os.SystemClock;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
 import android.platform.test.annotations.Presubmit;
@@ -125,7 +126,7 @@
         List<Thread> threads = new ArrayList<>();
         for (int i = 0; i < 100; i++) {
             final int threadId = i;
-            threads.add(new Thread() {
+            threads.add(new Thread("testKeyValue_Concurrency_" + i) {
                 @Override
                 public void run() {
                     synchronized (monitor) {
@@ -134,17 +135,17 @@
                         } catch (InterruptedException e) {
                             return;
                         }
-                        mStorage.writeKeyValue("key", "1 from thread " + threadId, 0);
-                        mStorage.readKeyValue("key", "default", 0);
-                        mStorage.writeKeyValue("key", "2 from thread " + threadId, 0);
-                        mStorage.readKeyValue("key", "default", 0);
-                        mStorage.writeKeyValue("key", "3 from thread " + threadId, 0);
-                        mStorage.readKeyValue("key", "default", 0);
-                        mStorage.writeKeyValue("key", "4 from thread " + threadId, 0);
-                        mStorage.readKeyValue("key", "default", 0);
-                        mStorage.writeKeyValue("key", "5 from thread " + threadId, 0);
-                        mStorage.readKeyValue("key", "default", 0);
                     }
+                    mStorage.writeKeyValue("key", "1 from thread " + threadId, 0);
+                    mStorage.readKeyValue("key", "default", 0);
+                    mStorage.writeKeyValue("key", "2 from thread " + threadId, 0);
+                    mStorage.readKeyValue("key", "default", 0);
+                    mStorage.writeKeyValue("key", "3 from thread " + threadId, 0);
+                    mStorage.readKeyValue("key", "default", 0);
+                    mStorage.writeKeyValue("key", "4 from thread " + threadId, 0);
+                    mStorage.readKeyValue("key", "default", 0);
+                    mStorage.writeKeyValue("key", "5 from thread " + threadId, 0);
+                    mStorage.readKeyValue("key", "default", 0);
                 }
             });
             threads.get(i).start();
@@ -153,12 +154,7 @@
         synchronized (monitor) {
             monitor.notifyAll();
         }
-        for (int i = 0; i < threads.size(); i++) {
-            try {
-                threads.get(i).join();
-            } catch (InterruptedException e) {
-            }
-        }
+        joinAll(threads, 10000);
         assertEquals('5', mStorage.readKeyValue("key", "default", 0).charAt(0));
         mStorage.clearCache();
         assertEquals('5', mStorage.readKeyValue("key", "default", 0).charAt(0));
@@ -515,4 +511,29 @@
         }
         return captured[0];
     }
+
+    private static void joinAll(List<Thread> threads, long timeoutMillis) {
+        long deadline = SystemClock.uptimeMillis() + timeoutMillis;
+        for (Thread t : threads) {
+            try {
+                t.join(deadline - SystemClock.uptimeMillis());
+                if (t.isAlive()) {
+                    t.interrupt();
+                    throw new RuntimeException(
+                            "Joining " + t + " timed out. Stack: \n" + getStack(t));
+                }
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Interrupted while joining " + t, e);
+            }
+        }
+    }
+
+    private static String getStack(Thread t) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(t.toString()).append('\n');
+        for (StackTraceElement ste : t.getStackTrace()) {
+            sb.append("\tat ").append(ste.toString()).append('\n');
+        }
+        return sb.toString();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
index c30a7dd..a63f49b 100644
--- a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -182,10 +183,22 @@
     }
 
     @Test
+    public void testOnUserActivity_ignoresIfAlreadyDoneForThatNextScreenDimming() {
+        long when = registerAttention();
+        verify(mAttentionManagerInternal).checkAttention(anyLong(), any());
+        assertThat(when).isLessThan(mNextDimming);
+        clearInvocations(mAttentionManagerInternal);
+
+        long redundantWhen = mAttentionDetector.updateUserActivity(mNextDimming);
+        assertThat(redundantWhen).isEqualTo(mNextDimming);
+        verify(mAttentionManagerInternal, never()).checkAttention(anyLong(), any());
+    }
+
+    @Test
     public void testOnUserActivity_skipsIfAlreadyScheduled() {
         registerAttention();
         reset(mAttentionManagerInternal);
-        long when = mAttentionDetector.updateUserActivity(mNextDimming);
+        long when = mAttentionDetector.updateUserActivity(mNextDimming + 1);
         verify(mAttentionManagerInternal, never()).checkAttention(anyLong(), any());
         assertThat(when).isLessThan(mNextDimming);
     }
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index 7786627..75e8fb5 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -1356,7 +1356,7 @@
     private void fetchCarrierPrivilegedAppsLocked() {
         TelephonyManager telephonyManager =
                 mContext.getSystemService(TelephonyManager.class);
-        mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges();
+        mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivilegesForAllPhones();
         mHaveCarrierPrivilegedApps = true;
         if (DEBUG) {
             Slog.d(TAG, "apps with carrier privilege " + mCarrierPrivilegedApps);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 328a0a7..dab1e6f 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -7475,7 +7475,7 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null)
-                return telephony.checkCarrierPrivilegesForPackage(pkgName);
+                return telephony.checkCarrierPrivilegesForPackage(getSubId(), pkgName);
         } catch (RemoteException ex) {
             Rlog.e(TAG, "checkCarrierPrivilegesForPackage RemoteException", ex);
         } catch (NullPointerException ex) {
@@ -7526,7 +7526,7 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.getPackagesWithCarrierPrivileges();
+                return telephony.getPackagesWithCarrierPrivileges(getPhoneId());
             }
         } catch (RemoteException ex) {
             Rlog.e(TAG, "getPackagesWithCarrierPrivileges RemoteException", ex);
@@ -7537,6 +7537,22 @@
     }
 
     /** @hide */
+    public List<String> getPackagesWithCarrierPrivilegesForAllPhones() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getPackagesWithCarrierPrivilegesForAllPhones();
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "getPackagesWithCarrierPrivilegesForAllPhones RemoteException", ex);
+        } catch (NullPointerException ex) {
+            Rlog.e(TAG, "getPackagesWithCarrierPrivilegesForAllPhones NPE", ex);
+        }
+        return Collections.EMPTY_LIST;
+    }
+
+
+    /** @hide */
     @SystemApi
     @SuppressLint("Doclava125")
     public void dial(String number) {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index e8ce2b4..68fd9ac 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1000,7 +1000,7 @@
     /**
      * Similar to above, but check for the package whose name is pkgName.
      */
-    int checkCarrierPrivilegesForPackage(String pkgName);
+    int checkCarrierPrivilegesForPackage(int subId, String pkgName);
 
     /**
      * Similar to above, but check across all phones.
@@ -1357,9 +1357,14 @@
             in PhoneAccountHandle phoneAccountHandle, boolean enabled);
 
     /**
-     * Returns a list of packages that have carrier privileges.
+     * Returns a list of packages that have carrier privileges for the specific phone.
      */
-    List<String> getPackagesWithCarrierPrivileges();
+    List<String> getPackagesWithCarrierPrivileges(int phoneId);
+
+     /**
+      * Returns a list of packages that have carrier privileges.
+      */
+    List<String> getPackagesWithCarrierPrivilegesForAllPhones();
 
     /**
      * Return the application ID for the app type.
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 0b1108d..c0c0361 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -160,6 +160,11 @@
     public static final int KEY_MGMT_FT_SAE = 11;
     /**
      * @hide
+     * Security key management scheme: OWE in transition mode.
+     */
+    public static final int KEY_MGMT_OWE_TRANSITION = 12;
+    /**
+     * @hide
      * No cipher suite.
      */
     public static final int CIPHER_NONE = 0;