Merge "Offload role manager work to background thread" 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/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index 7828573..b37120f 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -79,10 +79,10 @@
     /** @hide */
     public static final String KEY_KILL_SIGNAL = "key_kill_signal";
 
-    IVoiceInteractor mInteractor;
+    @Nullable IVoiceInteractor mInteractor;
 
-    Context mContext;
-    Activity mActivity;
+    @Nullable Context mContext;
+    @Nullable Activity mActivity;
     boolean mRetaining;
 
     final HandlerCaller mHandlerCaller;
@@ -999,7 +999,9 @@
 
         // destroyed now
         mInteractor = null;
-        mActivity.setVoiceInteractor(null);
+        if (mActivity != null) {
+            mActivity.setVoiceInteractor(null);
+        }
     }
 
     public boolean submitRequest(Request request) {
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/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 59ae334..8e40449 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -278,6 +278,12 @@
             return null;
         }
 
+        return querySummary(template, startTime, endTime);
+    }
+
+    /** @hide */
+    public NetworkStats querySummary(NetworkTemplate template, long startTime,
+            long endTime) throws SecurityException, RemoteException {
         NetworkStats result;
         result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
         result.startSummaryEnumeration();
@@ -296,6 +302,13 @@
             NetworkStats.Bucket.TAG_NONE, NetworkStats.Bucket.STATE_ALL);
     }
 
+    /** @hide */
+    public NetworkStats queryDetailsForUid(NetworkTemplate template,
+            long startTime, long endTime, int uid) throws SecurityException {
+        return queryDetailsForUidTagState(template, startTime, endTime, uid,
+                NetworkStats.Bucket.TAG_NONE, NetworkStats.Bucket.STATE_ALL);
+    }
+
     /**
      * Query network usage statistics details for a given uid and tag.
      *
@@ -340,6 +353,13 @@
         NetworkTemplate template;
         template = createTemplate(networkType, subscriberId);
 
+        return queryDetailsForUidTagState(template, startTime, endTime, uid, tag, state);
+    }
+
+    /** @hide */
+    public NetworkStats queryDetailsForUidTagState(NetworkTemplate template,
+            long startTime, long endTime, int uid, int tag, int state) throws SecurityException {
+
         NetworkStats result;
         try {
             result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
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..7de6ca5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1533,7 +1533,7 @@
     <!-- Message shown during face acquisition when only the left part of the user's face was detected [CHAR LIMIT=50] -->
     <string name="face_acquired_too_left">Move phone to the right.</string>
     <!-- Message shown during face acquisition when the user is not front facing the sensor [CHAR LIMIT=50] -->
-    <string name="face_acquired_poor_gaze">Look at the screen with your eyes open.</string>
+    <string name="face_acquired_poor_gaze">Please look more directly at your device.</string>
     <!-- Message shown during face acquisition when the user is not detected [CHAR LIMIT=50] -->
     <string name="face_acquired_not_detected">Can\u2019t see your face. Look at the phone.</string>
     <!-- Message shown during face acquisition when the device is not steady [CHAR LIMIT=50] -->
@@ -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/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 11d635e..297153d 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -637,8 +637,7 @@
      * @see #setOrientation(Orientation)
      */
     public Orientation getOrientation() {
-        updateGradientStateOrientation();
-        return mGradientState.mOrientation;
+        return mGradientState.getOrientation();
     }
 
     /**
@@ -654,10 +653,7 @@
      * @see #getOrientation()
      */
     public void setOrientation(Orientation orientation) {
-        // Update the angle here so that subsequent attempts to obtain the orientation
-        // from the angle overwrite previously configured values during inflation
-        mGradientState.mAngle = getAngleFromOrientation(orientation);
-        mGradientState.mOrientation = orientation;
+        mGradientState.setOrientation(orientation);
         mGradientIsDirty = true;
         invalidateSelf();
     }
@@ -1246,76 +1242,6 @@
     }
 
     /**
-     * Update the orientation of the gradient based on the given angle only if the type is
-     * {@link #LINEAR_GRADIENT}
-     */
-    private void updateGradientStateOrientation() {
-        if (mGradientState.mGradient == LINEAR_GRADIENT) {
-            int angle = mGradientState.mAngle;
-            if (angle % 45 != 0) {
-                throw new IllegalArgumentException("Linear gradient requires 'angle' attribute to "
-                     + "be a multiple of 45");
-            }
-
-            Orientation orientation;
-            switch (angle) {
-                case 0:
-                    orientation = Orientation.LEFT_RIGHT;
-                    break;
-                case 45:
-                    orientation = Orientation.BL_TR;
-                    break;
-                case 90:
-                    orientation = Orientation.BOTTOM_TOP;
-                    break;
-                case 135:
-                    orientation = Orientation.BR_TL;
-                    break;
-                case 180:
-                    orientation = Orientation.RIGHT_LEFT;
-                    break;
-                case 225:
-                    orientation = Orientation.TR_BL;
-                    break;
-                case 270:
-                    orientation = Orientation.TOP_BOTTOM;
-                    break;
-                case 315:
-                    orientation = Orientation.TL_BR;
-                    break;
-                default:
-                    // Should not get here as exception is thrown above if angle is not multiple
-                    // of 45 degrees
-                    orientation = Orientation.LEFT_RIGHT;
-                    break;
-            }
-            mGradientState.mOrientation = orientation;
-        }
-    }
-
-    private int getAngleFromOrientation(Orientation orientation) {
-        switch (orientation) {
-            default:
-            case LEFT_RIGHT:
-                return 0;
-            case BL_TR:
-                return 45;
-            case BOTTOM_TOP:
-                return 90;
-            case BR_TL:
-                return 135;
-            case RIGHT_LEFT:
-                return 180;
-            case TR_BL:
-                return 225;
-            case TOP_BOTTOM:
-                return 270;
-            case TL_BR:
-                return 315;
-        }
-    }
-
-    /**
      * This checks mGradientIsDirty, and if it is true, recomputes both our drawing
      * rectangle (mRect) and the gradient itself, since it depends on our
      * rectangle too.
@@ -1344,8 +1270,7 @@
 
                 if (st.mGradient == LINEAR_GRADIENT) {
                     final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f;
-                    updateGradientStateOrientation();
-                    switch (st.mOrientation) {
+                    switch (st.getOrientation()) {
                     case TOP_BOTTOM:
                         x0 = r.left;            y0 = r.top;
                         x1 = x0;                y1 = level * r.bottom;
@@ -2056,7 +1981,7 @@
         int[] mAttrPadding;
 
         public GradientState(Orientation orientation, int[] gradientColors) {
-            mOrientation = orientation;
+            setOrientation(orientation);
             setGradientColors(gradientColors);
         }
 
@@ -2259,6 +2184,93 @@
             mCenterY = y;
         }
 
+        public void setOrientation(Orientation orientation) {
+            // Update the angle here so that subsequent attempts to obtain the orientation
+            // from the angle overwrite previously configured values during inflation
+            mAngle = getAngleFromOrientation(orientation);
+            mOrientation = orientation;
+        }
+
+        @NonNull
+        public Orientation getOrientation() {
+            updateGradientStateOrientation();
+            return mOrientation;
+        }
+
+        /**
+         * Update the orientation of the gradient based on the given angle only if the type is
+         * {@link #LINEAR_GRADIENT}
+         */
+        private void updateGradientStateOrientation() {
+            if (mGradient == LINEAR_GRADIENT) {
+                int angle = mAngle;
+                if (angle % 45 != 0) {
+                    throw new IllegalArgumentException("Linear gradient requires 'angle' attribute "
+                            + "to be a multiple of 45");
+                }
+
+                Orientation orientation;
+                switch (angle) {
+                    case 0:
+                        orientation = Orientation.LEFT_RIGHT;
+                        break;
+                    case 45:
+                        orientation = Orientation.BL_TR;
+                        break;
+                    case 90:
+                        orientation = Orientation.BOTTOM_TOP;
+                        break;
+                    case 135:
+                        orientation = Orientation.BR_TL;
+                        break;
+                    case 180:
+                        orientation = Orientation.RIGHT_LEFT;
+                        break;
+                    case 225:
+                        orientation = Orientation.TR_BL;
+                        break;
+                    case 270:
+                        orientation = Orientation.TOP_BOTTOM;
+                        break;
+                    case 315:
+                        orientation = Orientation.TL_BR;
+                        break;
+                    default:
+                        // Should not get here as exception is thrown above if angle is not multiple
+                        // of 45 degrees
+                        orientation = Orientation.LEFT_RIGHT;
+                        break;
+                }
+                mOrientation = orientation;
+            }
+        }
+
+        private int getAngleFromOrientation(@Nullable Orientation orientation) {
+            if (orientation != null) {
+                switch (orientation) {
+                    default:
+                    case LEFT_RIGHT:
+                        return 0;
+                    case BL_TR:
+                        return 45;
+                    case BOTTOM_TOP:
+                        return 90;
+                    case BR_TL:
+                        return 135;
+                    case RIGHT_LEFT:
+                        return 180;
+                    case TR_BL:
+                        return 225;
+                    case TOP_BOTTOM:
+                        return 270;
+                    case TL_BR:
+                        return 315;
+                }
+            } else {
+                return 0;
+            }
+        }
+
         public void setGradientColors(@Nullable int[] colors) {
             mGradientColors = colors;
             mSolidColors = null;
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 62fd489..5173f63 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -167,8 +167,6 @@
     LOG_ALWAYS_FATAL_IF(physDeviceProperties.apiVersion < VK_MAKE_VERSION(1, 1, 0));
     mDriverVersion = physDeviceProperties.driverVersion;
 
-    mIsQualcomm = physDeviceProperties.vendorID == 20803;
-
     // query to get the initial queue props size
     uint32_t queueCount;
     mGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueCount, nullptr);
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 31de803..dd3c6d0 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -179,13 +179,6 @@
     SwapBehavior mSwapBehavior = SwapBehavior::Discard;
     GrVkExtensions mExtensions;
     uint32_t mDriverVersion = 0;
-
-    // TODO: Remove once fix has landed. Temporaryly needed for workaround for setting up AHB
-    // surfaces on Qualcomm. Currently if you don't use VkSwapchain Qualcomm is not setting
-    // reporting that we need to use one of their private vendor usage bits which greatly effects
-    // performance if it is not used.
-    bool mIsQualcomm = false;
-    bool isQualcomm() const { return mIsQualcomm; }
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index df6b9ed..b2cc23e 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -297,11 +297,6 @@
     native_window_get_consumer_usage(window, &consumerUsage);
     windowInfo.windowUsageFlags = consumerUsage | hwbUsage.androidHardwareBufferUsage;
 
-    if (vkManager.isQualcomm()) {
-        windowInfo.windowUsageFlags =
-                windowInfo.windowUsageFlags | AHARDWAREBUFFER_USAGE_VENDOR_0;
-    }
-
     /*
      * Now we attempt to modify the window!
      */
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/net/DataUsageController.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
index 2bfcc91..f30de13 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
@@ -175,9 +175,7 @@
 
     private long getUsageLevel(NetworkTemplate template, long start, long end) {
         try {
-            final Bucket bucket = mNetworkStatsManager.querySummaryForDevice(
-                    getNetworkType(template), getActiveSubscriberId(),
-                    start, end);
+            final Bucket bucket = mNetworkStatsManager.querySummaryForDevice(template, start, end);
             if (bucket != null) {
                 return bucket.getRxBytes() + bucket.getTxBytes();
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartDataLoader.java
index ec5a0b5..787dc55 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartDataLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartDataLoader.java
@@ -37,14 +37,14 @@
 
     private NetworkCycleChartDataLoader(Builder builder) {
         super(builder);
-        mData = new ArrayList<NetworkCycleChartData>();
+        mData = new ArrayList<>();
     }
 
     @Override
     void recordUsage(long start, long end) {
         try {
             final NetworkStats.Bucket bucket = mNetworkStatsManager.querySummaryForDevice(
-                mNetworkType, mSubId, start, end);
+                    mNetworkTemplate, start, end);
             final long total = bucket == null ? 0L : bucket.getRxBytes() + bucket.getTxBytes();
             if (total > 0L) {
                 final NetworkCycleChartData.Builder builder = new NetworkCycleChartData.Builder();
@@ -81,7 +81,7 @@
             long usage = 0L;
             try {
                 final NetworkStats.Bucket bucket = mNetworkStatsManager.querySummaryForDevice(
-                    mNetworkType, mSubId, bucketStart, bucketEnd);
+                        mNetworkTemplate, bucketStart, bucketEnd);
                 if (bucket != null) {
                     usage = bucket.getRxBytes() + bucket.getTxBytes();
                 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUidLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUidLoader.java
index bd9a636..43c05b8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUidLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUidLoader.java
@@ -23,11 +23,11 @@
 import android.content.Context;
 import android.util.Log;
 
+import androidx.annotation.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.annotation.VisibleForTesting;
-
 /**
  * Loader for network data usage history. It returns a list of usage data per billing cycle for the
  * specific Uid(s).
@@ -44,7 +44,7 @@
         super(builder);
         mUids = builder.mUids;
         mRetrieveDetail = builder.mRetrieveDetail;
-        mData = new ArrayList<NetworkCycleDataForUid>();
+        mData = new ArrayList<>();
     }
 
     @Override
@@ -54,7 +54,7 @@
             long totalForeground = 0L;
             for (int uid : mUids) {
                 final NetworkStats stats = mNetworkStatsManager.queryDetailsForUid(
-                    mNetworkType, mSubId, start, end, uid);
+                        mNetworkTemplate, start, end, uid);
                 final long usage = getTotalUsage(stats);
                 if (usage > 0L) {
                     totalUsage += usage;
@@ -100,7 +100,7 @@
 
     private long getForegroundUsage(long start, long end, int uid) {
         final NetworkStats stats = mNetworkStatsManager.queryDetailsForUidTagState(
-            mNetworkType, mSubId, start, end, uid, TAG_NONE, STATE_FOREGROUND);
+                mNetworkTemplate, start, end, uid, TAG_NONE, STATE_FOREGROUND);
         return getTotalUsage(stats);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
index dd6d563..3e95b01 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
@@ -49,18 +49,14 @@
 public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> {
     private static final String TAG = "NetworkCycleDataLoader";
     protected final NetworkStatsManager mNetworkStatsManager;
-    protected final String mSubId;
-    protected final int mNetworkType;
+    protected final NetworkTemplate mNetworkTemplate;
     private final NetworkPolicy mPolicy;
-    private final NetworkTemplate mNetworkTemplate;
     private final ArrayList<Long> mCycles;
     @VisibleForTesting
     final INetworkStatsService mNetworkStatsService;
 
     protected NetworkCycleDataLoader(Builder<?> builder) {
         super(builder.mContext);
-        mSubId = builder.mSubId;
-        mNetworkType = builder.mNetworkType;
         mNetworkTemplate = builder.mNetworkTemplate;
         mCycles = builder.mCycles;
         mNetworkStatsManager = (NetworkStatsManager)
@@ -180,8 +176,6 @@
 
     public static abstract class Builder<T extends NetworkCycleDataLoader> {
         private final Context mContext;
-        private String mSubId;
-        private int mNetworkType;
         private NetworkTemplate mNetworkTemplate;
         private ArrayList<Long> mCycles;
 
@@ -189,14 +183,8 @@
             mContext = context;
         }
 
-        public Builder<T> setSubscriberId(String subId) {
-            mSubId = subId;
-            return this;
-        }
-
         public Builder<T> setNetworkTemplate(NetworkTemplate template) {
             mNetworkTemplate = template;
-            mNetworkType = DataUsageController.getNetworkType(template);
             return this;
         }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java
index 34e6097..ed093629 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java
@@ -16,9 +16,10 @@
 
 package com.android.settingslib.net;
 
-import android.app.usage.NetworkStatsManager;
 import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
 import android.content.Context;
+import android.net.NetworkTemplate;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -33,15 +34,13 @@
     private final NetworkStatsManager mNetworkStatsManager;
     private final long mStart;
     private final long mEnd;
-    private final String mSubId;
-    private final int mNetworkType;
+    private final NetworkTemplate mNetworkTemplate;
 
     private NetworkStatsSummaryLoader(Builder builder) {
         super(builder.mContext);
         mStart = builder.mStart;
         mEnd = builder.mEnd;
-        mSubId = builder.mSubId;
-        mNetworkType = builder.mNetworkType;
+        mNetworkTemplate = builder.mNetworkTemplate;
         mNetworkStatsManager = (NetworkStatsManager)
                 builder.mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
     }
@@ -55,7 +54,7 @@
     @Override
     public NetworkStats loadInBackground() {
         try {
-            return mNetworkStatsManager.querySummary(mNetworkType, mSubId, mStart, mEnd);
+            return mNetworkStatsManager.querySummary(mNetworkTemplate, mStart, mEnd);
         } catch (RemoteException e) {
             Log.e(TAG, "Exception querying network detail.", e);
             return null;
@@ -78,8 +77,7 @@
         private final Context mContext;
         private long mStart;
         private long mEnd;
-        private String mSubId;
-        private int mNetworkType;
+        private NetworkTemplate mNetworkTemplate;
 
         public Builder(Context context) {
             mContext = context;
@@ -95,13 +93,11 @@
             return this;
         }
 
-        public Builder setSubscriberId(String subId) {
-            mSubId = subId;
-            return this;
-        }
-
-        public Builder setNetworkType(int networkType) {
-            mNetworkType = networkType;
+        /**
+         * Set {@link NetworkTemplate} for builder
+         */
+        public Builder setNetworkTemplate(NetworkTemplate template) {
+            mNetworkTemplate = template;
             return this;
         }
 
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/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java
index a28bb6c..3da5e76 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java
@@ -31,7 +31,6 @@
 import android.app.usage.NetworkStats;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
-import android.net.ConnectivityManager;
 import android.net.INetworkStatsSession;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
@@ -52,6 +51,7 @@
 public class DataUsageControllerTest {
 
     private static final String SUB_ID = "Test Subscriber";
+    private static final String SUB_ID_2 = "Test Subscriber 2";
 
     @Mock
     private INetworkStatsSession mSession;
@@ -63,6 +63,9 @@
     private NetworkStatsManager mNetworkStatsManager;
     @Mock
     private Context mContext;
+    private NetworkTemplate mNetworkTemplate;
+    private NetworkTemplate mNetworkTemplate2;
+    private NetworkTemplate mWifiNetworkTemplate;
 
     private DataUsageController mController;
     private NetworkStatsHistory mNetworkStatsHistory;
@@ -83,24 +86,27 @@
                 .when(mSession).getHistoryForNetwork(any(NetworkTemplate.class), anyInt());
         ShadowSubscriptionManager.setDefaultDataSubscriptionId(mDefaultSubscriptionId);
         doReturn(SUB_ID).when(mTelephonyManager).getSubscriberId();
+
+        mNetworkTemplate = NetworkTemplate.buildTemplateMobileAll(SUB_ID);
+        mNetworkTemplate2 = NetworkTemplate.buildTemplateMobileAll(SUB_ID_2);
+        mWifiNetworkTemplate = NetworkTemplate.buildTemplateWifiWildcard();
     }
 
     @Test
     public void getHistoricalUsageLevel_shouldQuerySummaryForDevice() throws Exception {
+        mController.getHistoricalUsageLevel(mWifiNetworkTemplate);
 
-        mController.getHistoricalUsageLevel(NetworkTemplate.buildTemplateWifiWildcard());
-
-        verify(mNetworkStatsManager).querySummaryForDevice(eq(ConnectivityManager.TYPE_WIFI),
-                eq(SUB_ID), eq(0L) /* startTime */, anyLong() /* endTime */);
+        verify(mNetworkStatsManager).querySummaryForDevice(eq(mWifiNetworkTemplate),
+                eq(0L) /* startTime */, anyLong() /* endTime */);
     }
 
     @Test
     public void getHistoricalUsageLevel_noUsageData_shouldReturn0() throws Exception {
-        when(mNetworkStatsManager.querySummaryForDevice(eq(ConnectivityManager.TYPE_WIFI),
-                eq(SUB_ID), eq(0L) /* startTime */, anyLong() /* endTime */))
+        when(mNetworkStatsManager.querySummaryForDevice(eq(mWifiNetworkTemplate),
+                eq(0L) /* startTime */, anyLong() /* endTime */))
                 .thenReturn(mock(NetworkStats.Bucket.class));
-        assertThat(mController.getHistoricalUsageLevel(NetworkTemplate.buildTemplateWifiWildcard()))
-            .isEqualTo(0L);
+        assertThat(mController.getHistoricalUsageLevel(mWifiNetworkTemplate))
+                .isEqualTo(0L);
     }
 
     @Test
@@ -110,10 +116,10 @@
         final NetworkStats.Bucket bucket = mock(NetworkStats.Bucket.class);
         when(bucket.getRxBytes()).thenReturn(receivedBytes);
         when(bucket.getTxBytes()).thenReturn(transmittedBytes);
-        when(mNetworkStatsManager.querySummaryForDevice(eq(ConnectivityManager.TYPE_WIFI),
-                eq(SUB_ID), eq(0L) /* startTime */, anyLong() /* endTime */)).thenReturn(bucket);
+        when(mNetworkStatsManager.querySummaryForDevice(eq(mWifiNetworkTemplate),
+                eq(0L) /* startTime */, anyLong() /* endTime */)).thenReturn(bucket);
 
-        assertThat(mController.getHistoricalUsageLevel(NetworkTemplate.buildTemplateWifiWildcard()))
+        assertThat(mController.getHistoricalUsageLevel(mWifiNetworkTemplate))
                 .isEqualTo(receivedBytes + transmittedBytes);
     }
 
@@ -126,9 +132,8 @@
         final NetworkStats.Bucket defaultSubscriberBucket = mock(NetworkStats.Bucket.class);
         when(defaultSubscriberBucket.getRxBytes()).thenReturn(defaultSubRx);
         when(defaultSubscriberBucket.getTxBytes()).thenReturn(defaultSubTx);
-        when(mNetworkStatsManager.querySummaryForDevice(eq(ConnectivityManager.TYPE_MOBILE),
-                eq(SUB_ID), eq(0L)/* startTime */, anyLong() /* endTime */)).thenReturn(
-                defaultSubscriberBucket);
+        when(mNetworkStatsManager.querySummaryForDevice(eq(mNetworkTemplate), eq(0L)/* startTime */,
+                anyLong() /* endTime */)).thenReturn(defaultSubscriberBucket);
 
         // Now setup a stats bucket for a different, non-default subscription / subscriber ID.
         final long nonDefaultSubRx = 7654321L;
@@ -137,25 +142,21 @@
         when(nonDefaultSubscriberBucket.getRxBytes()).thenReturn(nonDefaultSubRx);
         when(nonDefaultSubscriberBucket.getTxBytes()).thenReturn(nonDefaultSubTx);
         final int explicitSubscriptionId = 55;
-        final String subscriberId2 = "Test Subscriber 2";
-        when(mNetworkStatsManager.querySummaryForDevice(eq(ConnectivityManager.TYPE_MOBILE),
-                eq(subscriberId2), eq(0L)/* startTime */, anyLong() /* endTime */)).thenReturn(
+        when(mNetworkStatsManager.querySummaryForDevice(eq(mNetworkTemplate2),
+                eq(0L)/* startTime */, anyLong() /* endTime */)).thenReturn(
                 nonDefaultSubscriberBucket);
-        doReturn(subscriberId2).when(mTelephonyManager).getSubscriberId();
+        doReturn(SUB_ID_2).when(mTelephonyManager).getSubscriberId();
 
         // Now verify that when we're asking for stats on the non-default subscription, we get
         // the data back for that subscription and *not* the default one.
         mController.setSubscriptionId(explicitSubscriptionId);
 
-        assertThat(mController.getHistoricalUsageLevel(
-                NetworkTemplate.buildTemplateMobileAll(subscriberId2))).isEqualTo(
+        assertThat(mController.getHistoricalUsageLevel(mNetworkTemplate2)).isEqualTo(
                 nonDefaultSubRx + nonDefaultSubTx);
-
-        verify(mTelephonyManager).createForSubscriptionId(explicitSubscriptionId);
     }
 
     @Test
-    public void getTelephonyManager_shouldCreateWithExplicitSubId() throws Exception {
+    public void getTelephonyManager_shouldCreateWithExplicitSubId() {
         int explicitSubId = 1;
         TelephonyManager tmForSub1 = new TelephonyManager(mContext, explicitSubId);
         when(mTelephonyManager.createForSubscriptionId(eq(explicitSubId))).thenReturn(tmForSub1);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleChartDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleChartDataLoaderTest.java
index 011f234..c3e1613 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleChartDataLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleChartDataLoaderTest.java
@@ -21,9 +21,9 @@
 
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
-import android.net.ConnectivityManager;
 import android.net.NetworkPolicy;
 import android.net.NetworkPolicyManager;
+import android.net.NetworkTemplate;
 import android.os.RemoteException;
 import android.text.format.DateUtils;
 
@@ -43,6 +43,8 @@
     private NetworkPolicyManager mNetworkPolicyManager;
     @Mock
     private Context mContext;
+    @Mock
+    private NetworkTemplate mNetworkTemplate;
 
     private NetworkCycleChartDataLoader mLoader;
 
@@ -60,13 +62,12 @@
     public void recordUsage_shouldQueryNetworkSummaryForDevice() throws RemoteException {
         final long end = System.currentTimeMillis();
         final long start = end - (DateUtils.WEEK_IN_MILLIS * 4);
-        final int networkType = ConnectivityManager.TYPE_MOBILE;
-        final String subId = "TestSubscriber";
         mLoader = NetworkCycleChartDataLoader.builder(mContext)
-            .setSubscriberId(subId).build();
+                .setNetworkTemplate(mNetworkTemplate)
+                .build();
 
         mLoader.recordUsage(start, end);
 
-        verify(mNetworkStatsManager).querySummaryForDevice(networkType, subId, start, end);
+        verify(mNetworkStatsManager).querySummaryForDevice(mNetworkTemplate, start, end);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java
index aafb46a..877eb61 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java
@@ -28,9 +28,9 @@
 
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
-import android.net.ConnectivityManager;
 import android.net.NetworkPolicy;
 import android.net.NetworkPolicyManager;
+import android.net.NetworkTemplate;
 import android.text.format.DateUtils;
 
 import org.junit.Before;
@@ -42,6 +42,7 @@
 
 @RunWith(RobolectricTestRunner.class)
 public class NetworkCycleDataForUidLoaderTest {
+    private static final String SUB_ID = "Test Subscriber";
 
     @Mock
     private NetworkStatsManager mNetworkStatsManager;
@@ -49,6 +50,7 @@
     private NetworkPolicyManager mNetworkPolicyManager;
     @Mock
     private Context mContext;
+    private NetworkTemplate mNetworkTemplate;
 
     private NetworkCycleDataForUidLoader mLoader;
 
@@ -56,64 +58,62 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mContext.getSystemService(Context.NETWORK_STATS_SERVICE))
-            .thenReturn(mNetworkStatsManager);
+                .thenReturn(mNetworkStatsManager);
         when(mContext.getSystemService(Context.NETWORK_POLICY_SERVICE))
-            .thenReturn(mNetworkPolicyManager);
+                .thenReturn(mNetworkPolicyManager);
         when(mNetworkPolicyManager.getNetworkPolicies()).thenReturn(new NetworkPolicy[0]);
+        mNetworkTemplate = NetworkTemplate.buildTemplateMobileAll(SUB_ID);
     }
 
     @Test
     public void recordUsage_shouldQueryNetworkDetailsForUidAndForegroundState() {
         final long end = System.currentTimeMillis();
         final long start = end - (DateUtils.WEEK_IN_MILLIS * 4);
-        final int networkType = ConnectivityManager.TYPE_MOBILE;
-        final String subId = "TestSubscriber";
         final int uid = 1;
         mLoader = spy(NetworkCycleDataForUidLoader.builder(mContext)
-            .addUid(uid).setSubscriberId(subId).build());
+                .addUid(uid)
+                .setNetworkTemplate(mNetworkTemplate)
+                .build());
         doReturn(1024L).when(mLoader).getTotalUsage(any());
 
         mLoader.recordUsage(start, end);
 
-        verify(mNetworkStatsManager).queryDetailsForUid(networkType, subId, start, end, uid);
+        verify(mNetworkStatsManager).queryDetailsForUid(mNetworkTemplate, start, end, uid);
         verify(mNetworkStatsManager).queryDetailsForUidTagState(
-            networkType, subId, start, end, uid, TAG_NONE, STATE_FOREGROUND);
+                mNetworkTemplate, start, end, uid, TAG_NONE, STATE_FOREGROUND);
     }
 
     @Test
     public void recordUsage_retrieveDetailIsFalse_shouldNotQueryNetworkForegroundState() {
         final long end = System.currentTimeMillis();
         final long start = end - (DateUtils.WEEK_IN_MILLIS * 4);
-        final int networkType = ConnectivityManager.TYPE_MOBILE;
-        final String subId = "TestSubscriber";
         final int uid = 1;
         mLoader = spy(NetworkCycleDataForUidLoader.builder(mContext)
-            .setRetrieveDetail(false).addUid(uid).setSubscriberId(subId).build());
+                .setRetrieveDetail(false).addUid(uid).build());
         doReturn(1024L).when(mLoader).getTotalUsage(any());
 
         mLoader.recordUsage(start, end);
         verify(mNetworkStatsManager, never()).queryDetailsForUidTagState(
-            networkType, subId, start, end, uid, TAG_NONE, STATE_FOREGROUND);
+                mNetworkTemplate, start, end, uid, TAG_NONE, STATE_FOREGROUND);
     }
 
     @Test
     public void recordUsage_multipleUids_shouldQueryNetworkDetailsForEachUid() {
         final long end = System.currentTimeMillis();
         final long start = end - (DateUtils.WEEK_IN_MILLIS * 4);
-        final int networkType = ConnectivityManager.TYPE_MOBILE;
-        final String subId = "TestSubscriber";
         mLoader = spy(NetworkCycleDataForUidLoader.builder(mContext)
-            .addUid(1)
-            .addUid(2)
-            .addUid(3)
-            .setSubscriberId(subId).build());
+                .addUid(1)
+                .addUid(2)
+                .addUid(3)
+                .setNetworkTemplate(mNetworkTemplate)
+                .build());
         doReturn(1024L).when(mLoader).getTotalUsage(any());
 
         mLoader.recordUsage(start, end);
 
-        verify(mNetworkStatsManager).queryDetailsForUid(networkType, subId, start, end, 1);
-        verify(mNetworkStatsManager).queryDetailsForUid(networkType, subId, start, end, 2);
-        verify(mNetworkStatsManager).queryDetailsForUid(networkType, subId, start, end, 3);
+        verify(mNetworkStatsManager).queryDetailsForUid(mNetworkTemplate, start, end, 1);
+        verify(mNetworkStatsManager).queryDetailsForUid(mNetworkTemplate, start, end, 2);
+        verify(mNetworkStatsManager).queryDetailsForUid(mNetworkTemplate, start, end, 3);
     }
 
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
index c5f54bb..74b9151 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
@@ -126,8 +126,6 @@
         when(mIterator.next()).thenReturn(cycle);
         mLoader = spy(new NetworkCycleDataTestLoader(mContext));
         ReflectionHelpers.setField(mLoader, "mPolicy", mPolicy);
-        ReflectionHelpers.setField(mLoader, "mNetworkType", networkType);
-        ReflectionHelpers.setField(mLoader, "mSubId", subId);
 
         mLoader.loadPolicyData();
 
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/biometric_dialog.xml b/packages/SystemUI/res/layout/biometric_dialog.xml
index 1abb873..c560d7e 100644
--- a/packages/SystemUI/res/layout/biometric_dialog.xml
+++ b/packages/SystemUI/res/layout/biometric_dialog.xml
@@ -37,7 +37,8 @@
             android:id="@+id/space"
             android:layout_width="match_parent"
             android:layout_height="0dp"
-            android:layout_weight="1" />
+            android:layout_weight="1"
+            android:contentDescription="@string/biometric_dialog_empty_space_description"/>
 
         <ScrollView
             android:layout_width="match_parent"
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/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2643221..e01e6a8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -288,8 +288,18 @@
 
     <!-- Message shown when a biometric is authenticated, asking the user to confirm authentication [CHAR LIMIT=30] -->
     <string name="biometric_dialog_confirm">Confirm</string>
-    <!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR_LIMIT=30] -->
+    <!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR LIMIT=30] -->
     <string name="biometric_dialog_try_again">Try again</string>
+    <!-- Content description for empty spaces that are not taken by the biometric dialog. Clicking on these areas will cancel authentication and dismiss the biometric dialog [CHAR LIMIT=NONE] -->
+    <string name="biometric_dialog_empty_space_description">Empty region, tap to cancel authentication</string>
+    <!-- Content description for the face icon when the device is not authenticating anymore [CHAR LIMIT=NONE] -->
+    <string name="biometric_dialog_face_icon_description_idle">Please try again</string>
+    <!-- Content description for the face icon when the device is authenticating [CHAR LIMIT=NONE] -->
+    <string name="biometric_dialog_face_icon_description_authenticating">Looking for your face</string>
+    <!-- Content description for the face icon when the user has been authenticated [CHAR LIMIT=NONE] -->
+    <string name="biometric_dialog_face_icon_description_authenticated">Face authenticated</string>
+    <!-- Content description for the face icon when the user has been authenticated and the confirm button has been pressed [CHAR LIMIT=NONE] -->
+    <string name="biometric_dialog_face_icon_description_confirmed">Confirmed</string>
 
     <!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication -->
     <string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string>
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/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
index 9dfcf7d..45c19ad 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
@@ -325,7 +325,6 @@
 
     private void handleTryAgainPressed() {
         try {
-            mCurrentDialog.clearTemporaryMessage();
             mReceiver.onTryAgainPressed();
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException when handling try again", e);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
index f99587b..5717a54 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
@@ -33,7 +33,6 @@
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
@@ -224,13 +223,11 @@
         });
 
         mTryAgainButton.setOnClickListener((View v) -> {
+            handleResetMessage();
             updateState(STATE_AUTHENTICATING);
             showTryAgainButton(false /* show */);
             mCallback.onTryAgainPressed();
         });
-
-        mLayout.setFocusableInTouchMode(true);
-        mLayout.requestFocus();
     }
 
     public void onSaveState(Bundle bundle) {
@@ -269,6 +266,7 @@
         if (mRestoredState == null) {
             updateState(STATE_AUTHENTICATING);
             mErrorText.setText(getHintStringResourceId());
+            mErrorText.setContentDescription(mContext.getString(getHintStringResourceId()));
             mErrorText.setVisibility(View.VISIBLE);
         } else {
             updateState(mState);
@@ -278,7 +276,6 @@
 
         mTitleText.setVisibility(View.VISIBLE);
         mTitleText.setText(titleText);
-        mTitleText.setSelected(true);
 
         final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
         if (TextUtils.isEmpty(subtitleText)) {
@@ -323,11 +320,10 @@
 
     private void setDismissesDialog(View v) {
         v.setClickable(true);
-        v.setOnTouchListener((View view, MotionEvent event) -> {
+        v.setOnClickListener(v1 -> {
             if (mState != STATE_AUTHENTICATED && shouldGrayAreaDismissDialog()) {
                 mCallback.onUserCanceled();
             }
-            return true;
         });
     }
 
@@ -421,11 +417,6 @@
                 BiometricPrompt.HIDE_DIALOG_DELAY);
     }
 
-    public void clearTemporaryMessage() {
-        mHandler.removeMessages(MSG_RESET_MESSAGE);
-        mHandler.obtainMessage(MSG_RESET_MESSAGE).sendToTarget();
-    }
-
     /**
      * Transient help message (acquire) is received, dialog stays showing. Sensor stays in
      * "authenticating" state.
@@ -484,6 +475,7 @@
         mPositiveButton.setVisibility(bundle.getInt(KEY_CONFIRM_VISIBILITY));
         mState = bundle.getInt(KEY_STATE);
         mErrorText.setText(bundle.getCharSequence(KEY_ERROR_TEXT_STRING));
+        mErrorText.setContentDescription(bundle.getCharSequence(KEY_ERROR_TEXT_STRING));
         mErrorText.setVisibility(bundle.getInt(KEY_ERROR_TEXT_VISIBILITY));
         mErrorText.setTextColor(bundle.getInt(KEY_ERROR_TEXT_COLOR));
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
index dbbb71c..8f26f18 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
@@ -288,6 +288,7 @@
     @Override
     protected void handleResetMessage() {
         mErrorText.setText(getHintStringResourceId());
+        mErrorText.setContentDescription(mContext.getString(getHintStringResourceId()));
         mErrorText.setTextColor(mTextColor);
         if (getState() == STATE_AUTHENTICATING) {
             mErrorText.setVisibility(View.VISIBLE);
@@ -406,13 +407,21 @@
             } else {
                 mIconController.showIcon(R.drawable.face_dialog_pulse_dark_to_light);
             }
+            mBiometricIcon.setContentDescription(mContext.getString(
+                    R.string.biometric_dialog_face_icon_description_authenticating));
         } else if (oldState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
             mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark);
+            mBiometricIcon.setContentDescription(mContext.getString(
+                    R.string.biometric_dialog_face_icon_description_confirmed));
         } else if (oldState == STATE_ERROR && newState == STATE_IDLE) {
             mIconController.animateOnce(R.drawable.face_dialog_error_to_idle);
+            mBiometricIcon.setContentDescription(mContext.getString(
+                    R.string.biometric_dialog_face_icon_description_idle));
         } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATED) {
             mHandler.removeCallbacks(mErrorToIdleAnimationRunnable);
             mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark);
+            mBiometricIcon.setContentDescription(mContext.getString(
+                    R.string.biometric_dialog_face_icon_description_authenticated));
         } else if (newState == STATE_ERROR) {
             // It's easier to only check newState and gate showing the animation on the
             // mErrorToIdleAnimationRunnable as a proxy, than add a ton of extra state. For example,
@@ -426,11 +435,17 @@
             }
         } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
             mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark);
+            mBiometricIcon.setContentDescription(mContext.getString(
+                    R.string.biometric_dialog_face_icon_description_authenticated));
         } else if (newState == STATE_PENDING_CONFIRMATION) {
             mHandler.removeCallbacks(mErrorToIdleAnimationRunnable);
             mIconController.animateOnce(R.drawable.face_dialog_wink_from_dark);
+            mBiometricIcon.setContentDescription(mContext.getString(
+                    R.string.biometric_dialog_face_icon_description_authenticated));
         } else if (newState == STATE_IDLE) {
             mIconController.showStatic(R.drawable.face_dialog_idle_static);
+            mBiometricIcon.setContentDescription(mContext.getString(
+                    R.string.biometric_dialog_face_icon_description_idle));
         } else {
             Log.w(TAG, "Unknown animation from " + oldState + " -> " + newState);
         }
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/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 9f538bb..712e962 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -262,7 +262,28 @@
      * Propagate {@link StatusBar} pulsing state.
      */
     public void setPulsing(boolean pulsing) {
-        mLockIcon.setPulsing(pulsing);
+        if (mLockIcon != null) {
+            mLockIcon.setPulsing(pulsing);
+        }
+    }
+
+    /**
+     * Called when the biometric authentication mode changes.
+     * @param wakeAndUnlock If the type is {@link BiometricUnlockController#isWakeAndUnlock()}
+     */
+    public void onBiometricAuthModeChanged(boolean wakeAndUnlock) {
+        if (mLockIcon != null) {
+            mLockIcon.onBiometricAuthModeChanged(wakeAndUnlock);
+        }
+    }
+
+    /**
+     * Called after finished unlocking and the status bar window is already collapsed.
+     */
+    public void onKeyguardFadedAway() {
+        if (mLockIcon != null) {
+            mLockIcon.onKeyguardFadedAway();
+        }
     }
 
     public void setStatusBarView(PhoneStatusBarView 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/PreBootBroadcaster.java b/services/core/java/com/android/server/am/PreBootBroadcaster.java
index 376999d..beb0e47 100644
--- a/services/core/java/com/android/server/am/PreBootBroadcaster.java
+++ b/services/core/java/com/android/server/am/PreBootBroadcaster.java
@@ -107,9 +107,11 @@
         EventLogTags.writeAmPreBoot(mUserId, componentName.getPackageName());
 
         mIntent.setComponent(componentName);
-        mService.broadcastIntentLocked(null, null, mIntent, null, this, 0, null, null, null,
-                AppOpsManager.OP_NONE, null, true, false, ActivityManagerService.MY_PID,
-                Process.SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), mUserId);
+        synchronized (mService) {
+            mService.broadcastIntentLocked(null, null, mIntent, null, this, 0, null, null, null,
+                    AppOpsManager.OP_NONE, null, true, false, ActivityManagerService.MY_PID,
+                    Process.SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), mUserId);
+        }
     }
 
     @Override
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/location/GnssConfiguration.java b/services/core/java/com/android/server/location/GnssConfiguration.java
index bd6662d..aa51aec 100644
--- a/services/core/java/com/android/server/location/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/GnssConfiguration.java
@@ -317,8 +317,10 @@
         if (configManager == null) {
             return;
         }
-        PersistableBundle configs = configManager.getConfigForSubId(
-                SubscriptionManager.getDefaultDataSubscriptionId());
+
+        int ddSubId = SubscriptionManager.getDefaultDataSubscriptionId();
+        PersistableBundle configs = SubscriptionManager.isValidSubscriptionId(ddSubId)
+                ? configManager.getConfigForSubId(ddSubId) : null;
         if (configs == null) {
             if (DEBUG) Log.d(TAG, "SIM not ready, use default carrier config.");
             configs = CarrierConfigManager.getDefaultConfig();
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 5b7eca6..b2315c7 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -60,7 +60,6 @@
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
-import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
 import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
@@ -75,6 +74,7 @@
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
 import com.android.internal.location.gnssmetrics.GnssMetrics;
+import com.android.internal.telephony.TelephonyIntents;
 import com.android.server.location.GnssSatelliteBlacklistHelper.GnssSatelliteBlacklistCallback;
 import com.android.server.location.NtpTimeHelper.InjectNtpTimeCallback;
 
@@ -184,7 +184,6 @@
     private static final int DOWNLOAD_PSDS_DATA = 6;
     private static final int UPDATE_LOCATION = 7;  // Handle external location from network listener
     private static final int DOWNLOAD_PSDS_DATA_FINISHED = 11;
-    private static final int SUBSCRIPTION_OR_CARRIER_CONFIG_CHANGED = 12;
     private static final int INITIALIZE_HANDLER = 13;
     private static final int REQUEST_LOCATION = 16;
     private static final int REPORT_LOCATION = 17; // HAL reports location
@@ -484,22 +483,13 @@
                     updateLowPowerMode();
                     break;
                 case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
+                case TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
                     subscriptionOrCarrierConfigChanged(context);
                     break;
             }
         }
     };
 
-    // TODO: replace OnSubscriptionsChangedListener with ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED
-    //       broadcast receiver.
-    private final OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
-            new OnSubscriptionsChangedListener() {
-                @Override
-                public void onSubscriptionsChanged() {
-                    sendMessage(SUBSCRIPTION_OR_CARRIER_CONFIG_CHANGED, 0, null);
-                }
-            };
-
     /**
      * Implements {@link GnssSatelliteBlacklistCallback#onUpdateSatelliteBlacklist}.
      */
@@ -515,12 +505,15 @@
                 mContext.getSystemService(Context.TELEPHONY_SERVICE);
         CarrierConfigManager configManager = (CarrierConfigManager)
                 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        String mccMnc = phone.getSimOperator();
+        int ddSubId = SubscriptionManager.getDefaultDataSubscriptionId();
+        String mccMnc = SubscriptionManager.isValidSubscriptionId(ddSubId)
+                ? phone.getSimOperator(ddSubId) : phone.getSimOperator();
         boolean isKeepLppProfile = false;
         if (!TextUtils.isEmpty(mccMnc)) {
             if (DEBUG) Log.d(TAG, "SIM MCC/MNC is available: " + mccMnc);
             if (configManager != null) {
-                PersistableBundle b = configManager.getConfig();
+                PersistableBundle b = SubscriptionManager.isValidSubscriptionId(ddSubId)
+                        ? configManager.getConfigForSubId(ddSubId) : null;
                 if (b != null) {
                     isKeepLppProfile =
                             b.getBoolean(CarrierConfigManager.Gps.KEY_PERSIST_LPP_MODE_BOOL);
@@ -539,7 +532,6 @@
                 SystemProperties.set(GnssConfiguration.LPP_PROFILE, "");
             }
             reloadGpsProperties();
-            mNIHandler.setSuplEsEnabled(mSuplEsEnabled);
         } else {
             if (DEBUG) Log.d(TAG, "SIM MCC/MNC is still not available");
         }
@@ -577,9 +569,9 @@
         mC2KServerPort = mGnssConfiguration.getC2KPort(TCP_MIN_PORT);
         mNIHandler.setEmergencyExtensionSeconds(mGnssConfiguration.getEsExtensionSec());
         mSuplEsEnabled = mGnssConfiguration.getSuplEs(0) == 1;
+        mNIHandler.setSuplEsEnabled(mSuplEsEnabled);
         if (mGnssVisibilityControl != null) {
-            mGnssVisibilityControl.updateProxyApps(mGnssConfiguration.getProxyApps());
-            mGnssVisibilityControl.setEsNotify(mGnssConfiguration.getEsNotify(0));
+            mGnssVisibilityControl.onConfigurationUpdated(mGnssConfiguration);
         }
     }
 
@@ -1892,28 +1884,34 @@
         TelephonyManager phone = (TelephonyManager)
                 mContext.getSystemService(Context.TELEPHONY_SERVICE);
         int type = AGPS_SETID_TYPE_NONE;
-        String data = "";
+        String setId = null;
 
+        int ddSubId = SubscriptionManager.getDefaultDataSubscriptionId();
         if ((flags & AGPS_RIL_REQUEST_SETID_IMSI) == AGPS_RIL_REQUEST_SETID_IMSI) {
-            String data_temp = phone.getSubscriberId();
-            if (data_temp == null) {
-                // This means the framework does not have the SIM card ready.
-            } else {
+            if (SubscriptionManager.isValidSubscriptionId(ddSubId)) {
+                setId = phone.getSubscriberId(ddSubId);
+            }
+            if (setId == null) {
+                setId = phone.getSubscriberId();
+            }
+            if (setId != null) {
                 // This means the framework has the SIM card.
-                data = data_temp;
                 type = AGPS_SETID_TYPE_IMSI;
             }
         } else if ((flags & AGPS_RIL_REQUEST_SETID_MSISDN) == AGPS_RIL_REQUEST_SETID_MSISDN) {
-            String data_temp = phone.getLine1Number();
-            if (data_temp == null) {
-                // This means the framework does not have the SIM card ready.
-            } else {
+            if (SubscriptionManager.isValidSubscriptionId(ddSubId)) {
+                setId = phone.getLine1Number(ddSubId);
+            }
+            if (setId == null) {
+                setId = phone.getLine1Number();
+            }
+            if (setId != null) {
                 // This means the framework has the SIM card.
-                data = data_temp;
                 type = AGPS_SETID_TYPE_MSISDN;
             }
         }
-        native_agps_set_id(type, data);
+
+        native_agps_set_id(type, (setId == null) ? "" : setId);
     }
 
     @NativeEntryPoint
@@ -2025,9 +2023,6 @@
                 case UPDATE_LOCATION:
                     handleUpdateLocation((Location) msg.obj);
                     break;
-                case SUBSCRIPTION_OR_CARRIER_CONFIG_CHANGED:
-                    subscriptionOrCarrierConfigChanged(mContext);
-                    break;
                 case INITIALIZE_HANDLER:
                     handleInitialize();
                     break;
@@ -2066,17 +2061,6 @@
             // (this configuration might change in the future based on SIM changes)
             reloadGpsProperties();
 
-            // TODO: When this object "finishes" we should unregister by invoking
-            // SubscriptionManager.getInstance(mContext).unregister
-            // (mOnSubscriptionsChangedListener);
-            // This is not strictly necessary because it will be unregistered if the
-            // notification fails but it is good form.
-
-            // Register for SubscriptionInfo list changes which is guaranteed
-            // to invoke onSubscriptionsChanged the first time.
-            SubscriptionManager.from(mContext)
-                    .addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
-
             // listen for events
             IntentFilter intentFilter = new IntentFilter();
             intentFilter.addAction(ALARM_WAKEUP);
@@ -2086,6 +2070,7 @@
             intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
             intentFilter.addAction(Intent.ACTION_SCREEN_ON);
             intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+            intentFilter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
             mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, this);
 
             mNetworkConnectivityHandler.registerNetworkCallbacks();
@@ -2164,8 +2149,6 @@
                 return "DOWNLOAD_PSDS_DATA_FINISHED";
             case UPDATE_LOCATION:
                 return "UPDATE_LOCATION";
-            case SUBSCRIPTION_OR_CARRIER_CONFIG_CHANGED:
-                return "SUBSCRIPTION_OR_CARRIER_CONFIG_CHANGED";
             case INITIALIZE_HANDLER:
                 return "INITIALIZE_HANDLER";
             case REPORT_LOCATION:
diff --git a/services/core/java/com/android/server/location/GnssVisibilityControl.java b/services/core/java/com/android/server/location/GnssVisibilityControl.java
index a3670a4..c49d900 100644
--- a/services/core/java/com/android/server/location/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/GnssVisibilityControl.java
@@ -76,7 +76,7 @@
     private final GpsNetInitiatedHandler mNiHandler;
 
     private boolean mIsGpsEnabled;
-    private volatile boolean mEsNotify;
+    private boolean mEsNotify;
 
     // Number of non-framework location access proxy apps is expected to be small (< 5).
     private static final int ARRAY_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS = 7;
@@ -124,10 +124,6 @@
         }
     }
 
-    void updateProxyApps(List<String> nfwLocationAccessProxyApps) {
-        runOnHandler(() -> handleUpdateProxyApps(nfwLocationAccessProxyApps));
-    }
-
     void reportNfwNotification(String proxyAppPackageName, byte protocolStack,
             String otherProtocolStackName, byte requestor, String requestorId, byte responseType,
             boolean inEmergencyMode, boolean isCachedLocation) {
@@ -136,15 +132,25 @@
                         requestor, requestorId, responseType, inEmergencyMode, isCachedLocation)));
     }
 
-    void setEsNotify(int esNotifyConfig) {
-        if (esNotifyConfig != ES_NOTIFY_NONE && esNotifyConfig != ES_NOTIFY_ALL) {
+    void onConfigurationUpdated(GnssConfiguration configuration) {
+        // The configuration object must be accessed only in the caller thread and not in mHandler.
+        List<String> nfwLocationAccessProxyApps = configuration.getProxyApps();
+        int esNotify = configuration.getEsNotify(ES_NOTIFY_NONE);
+        runOnHandler(() -> {
+            setEsNotify(esNotify);
+            handleUpdateProxyApps(nfwLocationAccessProxyApps);
+        });
+    }
+
+    private void setEsNotify(int esNotify) {
+        if (esNotify != ES_NOTIFY_NONE && esNotify != ES_NOTIFY_ALL) {
             Log.e(TAG, "Config parameter " + GnssConfiguration.CONFIG_ES_NOTIFY_INT
-                    + " is set to invalid value: " + esNotifyConfig
+                    + " is set to invalid value: " + esNotify
                     + ". Using default value: " + ES_NOTIFY_NONE);
-            esNotifyConfig = ES_NOTIFY_NONE;
+            esNotify = ES_NOTIFY_NONE;
         }
 
-        mEsNotify = (esNotifyConfig == ES_NOTIFY_ALL);
+        mEsNotify = (esNotify == ES_NOTIFY_ALL);
     }
 
     private void handleInitialize() {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 37c1aaa..b2ba290 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -929,6 +929,7 @@
                 final BasePermission bp = mSettings.getPermissionLocked(permName);
                 final boolean appSupportsRuntimePermissions =
                         pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M;
+                String upgradedActivityRecognitionPermission = null;
 
                 if (DEBUG_INSTALL) {
                     Log.i(TAG, "Package " + pkg.packageName + " checking " + permName + ": " + bp);
@@ -947,11 +948,40 @@
                 // Cache newImplicitPermissions before modifing permissionsState as for the shared
                 // uids the original and new state are the same object
                 if (!origPermissions.hasRequestedPermission(permName)
-                        && pkg.implicitPermissions.contains(permName)) {
-                    newImplicitPermissions.add(permName);
+                        && (pkg.implicitPermissions.contains(permName)
+                                || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) {
+                    if (pkg.implicitPermissions.contains(permName)) {
+                        // If permName is an implicit permission, try to auto-grant
+                        newImplicitPermissions.add(permName);
 
-                    if (DEBUG_PERMISSIONS) {
-                        Slog.i(TAG, permName + " is newly added for " + pkg.packageName);
+                        if (DEBUG_PERMISSIONS) {
+                            Slog.i(TAG, permName + " is newly added for " + pkg.packageName);
+                        }
+                    } else {
+                        // Special case for Activity Recognition permission. Even if AR permission
+                        // is not an implicit permission we want to add it to the list (try to
+                        // auto-grant it) if the app was installed on a device before AR permission
+                        // was split, regardless of if the app now requests the new AR permission
+                        // or has updated its target SDK and AR is no longer implicit to it.
+                        // This is a compatibility workaround for apps when AR permission was
+                        // split in Q.
+                        int numSplitPerms = PermissionManager.SPLIT_PERMISSIONS.size();
+                        for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
+                            PermissionManager.SplitPermissionInfo sp =
+                                    PermissionManager.SPLIT_PERMISSIONS.get(splitPermNum);
+                            String splitPermName = sp.getSplitPermission();
+                            if (sp.getNewPermissions().contains(permName)
+                                    && origPermissions.hasInstallPermission(splitPermName)) {
+                                upgradedActivityRecognitionPermission = splitPermName;
+                                newImplicitPermissions.add(permName);
+
+                                if (DEBUG_PERMISSIONS) {
+                                    Slog.i(TAG, permName + " is newly added for "
+                                            + pkg.packageName);
+                                }
+                                break;
+                            }
+                        }
                     }
                 }
 
@@ -985,7 +1015,8 @@
                     // For all apps normal permissions are install time ones.
                     grant = GRANT_INSTALL;
                 } else if (bp.isRuntime()) {
-                    if (origPermissions.hasInstallPermission(bp.getName())) {
+                    if (origPermissions.hasInstallPermission(bp.getName())
+                            || upgradedActivityRecognitionPermission != null) {
                         // Before Q we represented some runtime permissions as install permissions,
                         // in Q we cannot do this anymore. Hence upgrade them all.
                         grant = GRANT_UPGRADE;
@@ -1161,10 +1192,15 @@
                                     .getInstallPermissionState(perm);
                             int flags = (permState != null) ? permState.getFlags() : 0;
 
+                            BasePermission bpToRevoke =
+                                    upgradedActivityRecognitionPermission == null
+                                    ? bp : mSettings.getPermissionLocked(
+                                            upgradedActivityRecognitionPermission);
                             // Remove install permission
-                            if (origPermissions.revokeInstallPermission(bp)
+                            if (origPermissions.revokeInstallPermission(bpToRevoke)
                                     != PERMISSION_OPERATION_FAILURE) {
-                                origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL,
+                                origPermissions.updatePermissionFlags(bpToRevoke,
+                                        UserHandle.USER_ALL,
                                         (MASK_PERMISSION_FLAGS_ALL
                                                 & ~FLAG_PERMISSION_APPLY_RESTRICTION), 0);
                                 changedInstallPermission = true;
@@ -1489,9 +1525,11 @@
                     for (int userNum = 0; userNum < numUsers; userNum++) {
                         int userId = users[userNum];
 
-                        ps.updatePermissionFlags(bp, userId,
-                                FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
-                                FLAG_PERMISSION_REVOKE_WHEN_REQUESTED);
+                        if (!newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)) {
+                            ps.updatePermissionFlags(bp, userId,
+                                    FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
+                                    FLAG_PERMISSION_REVOKE_WHEN_REQUESTED);
+                        }
                         updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
 
                         boolean inheritsFromInstallPerm = false;
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/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 93c1b21..eefaf47 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1451,6 +1451,9 @@
      * foreground call is ended.
      * <p>
      * Requires permission {@link android.Manifest.permission#ANSWER_PHONE_CALLS}.
+     * <p>
+     * Note: this method CANNOT be used to end ongoing emergency calls and will return {@code false}
+     * if an attempt is made to end an emergency call.
      *
      * @return {@code true} if there is a call which will be rejected or terminated, {@code false}
      * otherwise.
@@ -1458,8 +1461,6 @@
      * instead.  Apps performing call screening should use the {@link CallScreeningService} API
      * instead.
      */
-
-
     @RequiresPermission(Manifest.permission.ANSWER_PHONE_CALLS)
     @Deprecated
     public boolean endCall() {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d00341b..6ba359b 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -836,13 +836,6 @@
             "carrier_metered_roaming_apn_types_strings";
 
     /**
-     * Default APN types that are metered on IWLAN by the carrier
-     * @hide
-     */
-    public static final String KEY_CARRIER_METERED_IWLAN_APN_TYPES_STRINGS =
-            "carrier_metered_iwlan_apn_types_strings";
-
-    /**
      * CDMA carrier ERI (Enhanced Roaming Indicator) file name
      * @hide
      */
@@ -3116,15 +3109,6 @@
                 new String[]{"default", "mms", "dun", "supl"});
         sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
                 new String[]{"default", "mms", "dun", "supl"});
-        // By default all APNs should be unmetered if the device is on IWLAN. However, we add
-        // default APN as metered here as a workaround for P because in some cases, a data
-        // connection was brought up on cellular, but later on the device camped on IWLAN. That
-        // data connection was incorrectly treated as unmetered due to the current RAT IWLAN.
-        // Marking it as metered for now can workaround the issue.
-        // Todo: This will be fixed in Q when IWLAN full refactoring is completed.
-        sDefaults.putStringArray(KEY_CARRIER_METERED_IWLAN_APN_TYPES_STRINGS,
-                new String[]{"default"});
-
         sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY,
                 new int[]{
                     4, /* IS95A */
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index c57f9e6..f31ac2e 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -173,6 +173,11 @@
     private ParcelUuid mGroupUUID;
 
     /**
+     * A package name that specifies who created the group. Null if mGroupUUID is null.
+     */
+    private String mGroupOwner;
+
+    /**
      * Whether group of the subscription is disabled.
      * This is only useful if it's a grouped opportunistic subscription. In this case, if all
      * primary (non-opportunistic) subscriptions in the group are deactivated (unplugged pSIM
@@ -203,9 +208,10 @@
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
             @Nullable UiccAccessRule[] accessRules, String cardString) {
         this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
-                roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString,
-                false, null, TelephonyManager.UNKNOWN_CARRIER_ID,
-                SubscriptionManager.PROFILE_CLASS_DEFAULT);
+                roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1,
+                false, null, false, TelephonyManager.UNKNOWN_CARRIER_ID,
+                SubscriptionManager.PROFILE_CLASS_DEFAULT,
+                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null);
     }
 
     /**
@@ -219,7 +225,7 @@
         this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
                 roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1,
                 isOpportunistic, groupUUID, false, carrierId, profileClass,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
+                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null);
     }
 
     /**
@@ -229,8 +235,8 @@
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
             @Nullable UiccAccessRule[] accessRules, String cardString, int cardId,
-            boolean isOpportunistic, @Nullable String groupUUID,
-            boolean isGroupDisabled, int carrierId, int profileClass, int subType) {
+            boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled,
+            int carrierId, int profileClass, int subType, @Nullable String groupOwner) {
         this.mId = id;
         this.mIccId = iccId;
         this.mSimSlotIndex = simSlotIndex;
@@ -254,6 +260,7 @@
         this.mCarrierId = carrierId;
         this.mProfileClass = profileClass;
         this.mSubscriptionType = subType;
+        this.mGroupOwner = groupOwner;
     }
 
     /**
@@ -500,6 +507,15 @@
     }
 
     /**
+     * Return owner package of group the subscription belongs to.
+     *
+     * @hide
+     */
+    public @Nullable String getGroupOwner() {
+        return mGroupOwner;
+    }
+
+    /**
      * @return the profile class of this subscription.
      * @hide
      */
@@ -646,11 +662,12 @@
             int subType = source.readInt();
             String[] ehplmns = source.readStringArray();
             String[] hplmns = source.readStringArray();
+            String groupOwner = source.readString();
 
             SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName,
                     carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc,
                     countryIso, isEmbedded, accessRules, cardString, cardId, isOpportunistic,
-                    groupUUID, isGroupDisabled, carrierid, profileClass, subType);
+                    groupUUID, isGroupDisabled, carrierid, profileClass, subType, groupOwner);
             info.setAssociatedPlmns(ehplmns, hplmns);
             return info;
         }
@@ -688,6 +705,7 @@
         dest.writeInt(mSubscriptionType);
         dest.writeStringArray(mEhplmns);
         dest.writeStringArray(mHplmns);
+        dest.writeString(mGroupOwner);
     }
 
     @Override
@@ -727,7 +745,8 @@
                 + " profileClass=" + mProfileClass
                 + " ehplmns = " + Arrays.toString(mEhplmns)
                 + " hplmns = " + Arrays.toString(mHplmns)
-                + " subscriptionType=" + mSubscriptionType + "}";
+                + " subscriptionType=" + mSubscriptionType
+                + " mGroupOwner=" + mGroupOwner + "}";
     }
 
     @Override
@@ -735,7 +754,7 @@
         return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
                 mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc,
                 mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, mAccessRules,
-                mIsGroupDisabled, mCarrierId, mProfileClass);
+                mIsGroupDisabled, mCarrierId, mProfileClass, mGroupOwner);
     }
 
     @Override
@@ -767,6 +786,7 @@
                 && Objects.equals(mCountryIso, toCompare.mCountryIso)
                 && Objects.equals(mCardString, toCompare.mCardString)
                 && Objects.equals(mCardId, toCompare.mCardId)
+                && Objects.equals(mGroupOwner, toCompare.mGroupOwner)
                 && TextUtils.equals(mDisplayName, toCompare.mDisplayName)
                 && TextUtils.equals(mCarrierName, toCompare.mCarrierName)
                 && Arrays.equals(mAccessRules, toCompare.mAccessRules)
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 32105ad..b5da2ee 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -686,6 +686,15 @@
      * @hide
      */
     public static final String GROUP_UUID = "group_uuid";
+
+    /**
+     * TelephonyProvider column name for group owner. It's the package name who created
+     * the subscription group.
+     *
+     * @hide
+     */
+    public static final String GROUP_OWNER = "group_owner";
+
     /**
      * TelephonyProvider column name for whether a subscription is metered or not, that is, whether
      * the network it connects to charges for subscription or not. For example, paid CBRS or unpaid.
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/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index a86fda4..a78bae4 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -1273,6 +1273,23 @@
     }
 
     /**
+     * Get supported APN types
+     *
+     * @return list of APN types
+     * @hide
+     */
+    @ApnType
+    public List<Integer> getApnTypes() {
+        List<Integer> types = new ArrayList<>();
+        for (Integer type : APN_TYPE_INT_MAP.keySet()) {
+            if ((mApnTypeBitmask & type) == type) {
+                types.add(type);
+            }
+        }
+        return types;
+    }
+
+    /**
      * @param apnTypeBitmask bitmask of APN types.
      * @return comma delimited list of APN types.
      * @hide
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;