Merge RP1A.200621.001

Change-Id: Ibd868f53f230edec4c46523024c38708b5a01522
diff --git a/Android.bp b/Android.bp
index 7c2d6eb..ec77404 100755
--- a/Android.bp
+++ b/Android.bp
@@ -701,7 +701,6 @@
         "core/java/com/android/internal/util/TrafficStatsConstants.java",
         "core/java/com/android/internal/util/WakeupMessage.java",
         "core/java/com/android/internal/util/TokenBucket.java",
-        "core/java/android/net/shared/*.java",
     ],
 }
 
@@ -709,7 +708,6 @@
     name: "framework-services-net-module-wifi-shared-srcs",
     srcs: [
         "core/java/android/net/DhcpResults.java",
-        "core/java/android/net/shared/InetAddressUtils.java",
         "core/java/android/net/util/IpUtils.java",
         "core/java/android/util/LocalLog.java",
     ],
@@ -726,7 +724,6 @@
         "core/java/com/android/internal/util/State.java",
         "core/java/com/android/internal/util/StateMachine.java",
         "core/java/com/android/internal/util/TrafficStatsConstants.java",
-        "core/java/android/net/shared/Inet4AddressUtils.java",
     ],
 }
 
@@ -1163,7 +1160,6 @@
     srcs: [
         "core/java/android/content/pm/BaseParceledListSlice.java",
         "core/java/android/content/pm/ParceledListSlice.java",
-        "core/java/android/net/shared/Inet4AddressUtils.java",
         "core/java/android/os/HandlerExecutor.java",
         "core/java/com/android/internal/util/AsyncChannel.java",
         "core/java/com/android/internal/util/AsyncService.java",
diff --git a/apct-tests/perftests/multiuser/Android.bp b/apct-tests/perftests/multiuser/Android.bp
index 508bf60..04432f2 100644
--- a/apct-tests/perftests/multiuser/Android.bp
+++ b/apct-tests/perftests/multiuser/Android.bp
@@ -17,6 +17,7 @@
     srcs: ["src/**/*.java"],
     static_libs: [
         "androidx.test.rules",
+        "collector-device-lib-platform",
         "apct-perftests-utils",
     ],
     platform_apis: true,
diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml
index 893c8ca..e4196dd 100644
--- a/apct-tests/perftests/multiuser/AndroidManifest.xml
+++ b/apct-tests/perftests/multiuser/AndroidManifest.xml
@@ -17,12 +17,16 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.perftests.multiuser">
 
+    <uses-sdk android:targetSdkVersion="28" />
+
     <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
     <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.REAL_GET_TASKS" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
index 1193ae9..4d29045 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -358,7 +358,11 @@
             throw e.rethrowAsIOException();
         }
         try {
-            return createRevocableFd(fd, callingPackage);
+            if (BlobStoreConfig.shouldUseRevocableFdForReads()) {
+                return createRevocableFd(fd, callingPackage);
+            } else {
+                return new ParcelFileDescriptor(fd);
+            }
         } catch (IOException e) {
             IoUtils.closeQuietly(fd);
             throw e;
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
index 08ee244..d780d5d 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -122,6 +122,15 @@
         public static long COMMIT_COOL_OFF_DURATION_MS =
                 DEFAULT_COMMIT_COOL_OFF_DURATION_MS;
 
+        /**
+         * Denotes whether to use RevocableFileDescriptor when apps try to read session/blob data.
+         */
+        public static final String KEY_USE_REVOCABLE_FD_FOR_READS =
+                "use_revocable_fd_for_reads";
+        public static final boolean DEFAULT_USE_REVOCABLE_FD_FOR_READS = true;
+        public static boolean USE_REVOCABLE_FD_FOR_READS =
+                DEFAULT_USE_REVOCABLE_FD_FOR_READS;
+
         static void refresh(Properties properties) {
             if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) {
                 return;
@@ -151,6 +160,10 @@
                         COMMIT_COOL_OFF_DURATION_MS = properties.getLong(key,
                                 DEFAULT_COMMIT_COOL_OFF_DURATION_MS);
                         break;
+                    case KEY_USE_REVOCABLE_FD_FOR_READS:
+                        USE_REVOCABLE_FD_FOR_READS = properties.getBoolean(key,
+                                DEFAULT_USE_REVOCABLE_FD_FOR_READS);
+                        break;
                     default:
                         Slog.wtf(TAG, "Unknown key in device config properties: " + key);
                 }
@@ -178,6 +191,8 @@
             fout.println(String.format(dumpFormat, KEY_COMMIT_COOL_OFF_DURATION_MS,
                     TimeUtils.formatDuration(COMMIT_COOL_OFF_DURATION_MS),
                     TimeUtils.formatDuration(DEFAULT_COMMIT_COOL_OFF_DURATION_MS)));
+            fout.println(String.format(dumpFormat, KEY_USE_REVOCABLE_FD_FOR_READS,
+                    USE_REVOCABLE_FD_FOR_READS, DEFAULT_USE_REVOCABLE_FD_FOR_READS));
         }
     }
 
@@ -242,6 +257,13 @@
                 < System.currentTimeMillis();
     }
 
+    /**
+     * Return whether to use RevocableFileDescriptor when apps try to read session/blob data.
+     */
+    public static boolean shouldUseRevocableFdForReads() {
+        return DeviceConfigProperties.USE_REVOCABLE_FD_FOR_READS;
+    }
+
     @Nullable
     public static File prepareBlobFile(long sessionId) {
         final File blobsDir = prepareBlobsDir();
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
index 0098305..baafff5 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
@@ -268,6 +268,13 @@
                 throw new IllegalStateException("Not allowed to read in state: "
                         + stateToString(mState));
             }
+            if (!BlobStoreConfig.shouldUseRevocableFdForReads()) {
+                try {
+                    return new ParcelFileDescriptor(openReadInternal());
+                } catch (IOException e) {
+                    throw ExceptionUtils.wrap(e);
+                }
+            }
         }
 
         FileDescriptor fd = null;
@@ -283,7 +290,6 @@
                 trackRevocableFdLocked(revocableFd);
                 return revocableFd.getRevocableFileDescriptor();
             }
-
         } catch (IOException e) {
             IoUtils.closeQuietly(fd);
             throw ExceptionUtils.wrap(e);
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 887d82c..e15f0f3 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -71,7 +71,7 @@
      */
     void postOneTimeCheckIdleStates();
 
-    void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId);
+    void reportEvent(UsageEvents.Event event, int userId);
 
     void setLastJobRunTime(String packageName, int userId, long elapsedRealtime);
 
@@ -150,9 +150,7 @@
 
     void clearCarrierPrivilegedApps();
 
-    void flushToDisk(int userId);
-
-    void flushDurationsToDisk();
+    void flushToDisk();
 
     void initializeDefaultsForSystemApps(int userId);
 
@@ -162,7 +160,7 @@
 
     void postReportExemptedSyncStart(String packageName, int userId);
 
-    void dumpUser(IndentingPrintWriter idpw, int userId, List<String> pkgs);
+    void dumpUsers(IndentingPrintWriter idpw, int[] userIds, List<String> pkgs);
 
     void dumpState(String[] args, PrintWriter pw);
 
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index 372ec98..70155ee 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -675,6 +675,14 @@
         return Long.parseLong(value);
     }
 
+
+    public void writeAppIdleTimes() {
+        final int size = mIdleHistory.size();
+        for (int i = 0; i < size; i++) {
+            writeAppIdleTimes(mIdleHistory.keyAt(i));
+        }
+    }
+
     public void writeAppIdleTimes(int userId) {
         FileOutputStream fos = null;
         AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
@@ -743,8 +751,18 @@
         }
     }
 
-    public void dump(IndentingPrintWriter idpw, int userId, List<String> pkgs) {
-        idpw.println("App Standby States:");
+    public void dumpUsers(IndentingPrintWriter idpw, int[] userIds, List<String> pkgs) {
+        final int numUsers = userIds.length;
+        for (int i = 0; i < numUsers; i++) {
+            idpw.println();
+            dumpUser(idpw, userIds[i], pkgs);
+        }
+    }
+
+    private void dumpUser(IndentingPrintWriter idpw, int userId, List<String> pkgs) {
+        idpw.print("User ");
+        idpw.print(userId);
+        idpw.println(" App Standby States:");
         idpw.increaseIndent();
         ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
         final long elapsedRealtime = SystemClock.elapsedRealtime();
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 5ceea2a..687b1d4 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -90,6 +90,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
 import android.telephony.TelephonyManager;
@@ -241,6 +242,14 @@
 
     private final CountDownLatch mAdminDataAvailableLatch = new CountDownLatch(1);
 
+    // Cache the active network scorer queried from the network scorer service
+    private volatile String mCachedNetworkScorer = null;
+    // The last time the network scorer service was queried
+    private volatile long mCachedNetworkScorerAtMillis = 0L;
+    // How long before querying the network scorer again. During this time, subsequent queries will
+    // get the cached value
+    private static final long NETWORK_SCORER_CACHE_DURATION_MILLIS = 5000L;
+
     // Messages for the handler
     static final int MSG_INFORM_LISTENERS = 3;
     static final int MSG_FORCE_IDLE_STATE = 4;
@@ -857,7 +866,7 @@
     }
 
     @Override
-    public void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) {
+    public void reportEvent(UsageEvents.Event event, int userId) {
         if (!mAppIdleEnabled) return;
         final int eventType = event.getEventType();
         if ((eventType == UsageEvents.Event.ACTIVITY_RESUMED
@@ -871,6 +880,7 @@
             final String pkg = event.getPackageName();
             final List<UserHandle> linkedProfiles = getCrossProfileTargets(pkg, userId);
             synchronized (mAppIdleLock) {
+                final long elapsedRealtime = mInjector.elapsedRealtime();
                 reportEventLocked(pkg, eventType, elapsedRealtime, userId);
 
                 final int size = linkedProfiles.size();
@@ -1160,6 +1170,8 @@
             return new int[0];
         }
 
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "getIdleUidsForUser");
+
         final long elapsedRealtime = mInjector.elapsedRealtime();
 
         List<ApplicationInfo> apps;
@@ -1195,6 +1207,7 @@
                 uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0));
             }
         }
+
         if (DEBUG) {
             Slog.d(TAG, "getIdleUids took " + (mInjector.elapsedRealtime() - elapsedRealtime));
         }
@@ -1216,6 +1229,8 @@
             }
         }
 
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
         return res;
     }
 
@@ -1582,8 +1597,16 @@
     }
 
     private boolean isActiveNetworkScorer(String packageName) {
-        String activeScorer = mInjector.getActiveNetworkScorer();
-        return packageName != null && packageName.equals(activeScorer);
+        // Validity of network scorer cache is limited to a few seconds. Fetch it again
+        // if longer since query.
+        // This is a temporary optimization until there's a callback mechanism for changes to network scorer.
+        final long now = SystemClock.elapsedRealtime();
+        if (mCachedNetworkScorer == null
+                || mCachedNetworkScorerAtMillis < now - NETWORK_SCORER_CACHE_DURATION_MILLIS) {
+            mCachedNetworkScorer = mInjector.getActiveNetworkScorer();
+            mCachedNetworkScorerAtMillis = now;
+        }
+        return packageName != null && packageName.equals(mCachedNetworkScorer);
     }
 
     private void informListeners(String packageName, int userId, int bucket, int reason,
@@ -1608,18 +1631,11 @@
         }
     }
 
-    @Override
-    public void flushToDisk(int userId) {
-        synchronized (mAppIdleLock) {
-            mAppIdleHistory.writeAppIdleTimes(userId);
-        }
-    }
 
     @Override
-    public void flushDurationsToDisk() {
-        // Persist elapsed and screen on time. If this fails for whatever reason, the apps will be
-        // considered not-idle, which is the safest outcome in such an event.
+    public void flushToDisk() {
         synchronized (mAppIdleLock) {
+            mAppIdleHistory.writeAppIdleTimes();
             mAppIdleHistory.writeAppIdleDurations();
         }
     }
@@ -1796,9 +1812,9 @@
     }
 
     @Override
-    public void dumpUser(IndentingPrintWriter idpw, int userId, List<String> pkgs) {
+    public void dumpUsers(IndentingPrintWriter idpw, int[] userIds, List<String> pkgs) {
         synchronized (mAppIdleLock) {
-            mAppIdleHistory.dump(idpw, userId, pkgs);
+            mAppIdleHistory.dumpUsers(idpw, userIds, pkgs);
         }
     }
 
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 19f578e..8a3fbde 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -31,6 +31,7 @@
 import com.google.android.exoplayer2.Format;
 import com.google.android.exoplayer2.ParserException;
 import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
+import com.google.android.exoplayer2.extractor.ChunkIndex;
 import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
 import com.google.android.exoplayer2.extractor.Extractor;
 import com.google.android.exoplayer2.extractor.ExtractorInput;
@@ -817,6 +818,30 @@
     public static final String PARAMETER_EXPOSE_DUMMY_SEEKMAP =
             "android.media.mediaparser.exposeDummySeekMap";
 
+    /**
+     * Sets whether chunk indices available in the extracted media should be exposed as {@link
+     * MediaFormat MediaFormats}. {@code boolean} expected. Default value is {@link false}.
+     *
+     * <p>When set to true, any information about media segmentation will be exposed as a {@link
+     * MediaFormat} (with track index 0) containing four {@link ByteBuffer} elements under the
+     * following keys:
+     *
+     * <ul>
+     *   <li>"chunk-index-int-sizes": Contains {@code ints} representing the sizes in bytes of each
+     *       of the media segments.
+     *   <li>"chunk-index-long-offsets": Contains {@code longs} representing the byte offsets of
+     *       each segment in the stream.
+     *   <li>"chunk-index-long-us-durations": Contains {@code longs} representing the media duration
+     *       of each segment, in microseconds.
+     *   <li>"chunk-index-long-us-times": Contains {@code longs} representing the start time of each
+     *       segment, in microseconds.
+     * </ul>
+     *
+     * @hide
+     */
+    public static final String PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT =
+            "android.media.mediaParser.exposeChunkIndexAsMediaFormat";
+
     // Private constants.
 
     private static final String TAG = "MediaParser";
@@ -980,6 +1005,7 @@
     private boolean mIgnoreTimestampOffset;
     private boolean mEagerlyExposeTrackType;
     private boolean mExposeDummySeekMap;
+    private boolean mExposeChunkIndexAsMediaFormat;
     private String mParserName;
     private Extractor mExtractor;
     private ExtractorInput mExtractorInput;
@@ -1042,6 +1068,9 @@
         if (PARAMETER_EXPOSE_DUMMY_SEEKMAP.equals(parameterName)) {
             mExposeDummySeekMap = (boolean) value;
         }
+        if (PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT.equals(parameterName)) {
+            mExposeChunkIndexAsMediaFormat = (boolean) value;
+        }
         mParserParameters.put(parameterName, value);
         return this;
     }
@@ -1436,6 +1465,19 @@
 
         @Override
         public void seekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) {
+            if (mExposeChunkIndexAsMediaFormat && exoplayerSeekMap instanceof ChunkIndex) {
+                ChunkIndex chunkIndex = (ChunkIndex) exoplayerSeekMap;
+                MediaFormat mediaFormat = new MediaFormat();
+                mediaFormat.setByteBuffer("chunk-index-int-sizes", toByteBuffer(chunkIndex.sizes));
+                mediaFormat.setByteBuffer(
+                        "chunk-index-long-offsets", toByteBuffer(chunkIndex.offsets));
+                mediaFormat.setByteBuffer(
+                        "chunk-index-long-us-durations", toByteBuffer(chunkIndex.durationsUs));
+                mediaFormat.setByteBuffer(
+                        "chunk-index-long-us-times", toByteBuffer(chunkIndex.timesUs));
+                mOutputConsumer.onTrackDataFound(
+                        /* trackIndex= */ 0, new TrackData(mediaFormat, /* drmInitData= */ null));
+            }
             mOutputConsumer.onSeekMapFound(new SeekMap(exoplayerSeekMap));
         }
     }
@@ -1823,6 +1865,24 @@
         return result;
     }
 
+    private static ByteBuffer toByteBuffer(long[] longArray) {
+        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(longArray.length * Long.BYTES);
+        for (long element : longArray) {
+            byteBuffer.putLong(element);
+        }
+        byteBuffer.flip();
+        return byteBuffer;
+    }
+
+    private static ByteBuffer toByteBuffer(int[] intArray) {
+        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(intArray.length * Integer.BYTES);
+        for (int element : intArray) {
+            byteBuffer.putInt(element);
+        }
+        byteBuffer.flip();
+        return byteBuffer;
+    }
+
     private static String toTypeString(int type) {
         switch (type) {
             case C.TRACK_TYPE_VIDEO:
@@ -1979,6 +2039,8 @@
         expectedTypeByParameterName.put(PARAMETER_IGNORE_TIMESTAMP_OFFSET, Boolean.class);
         expectedTypeByParameterName.put(PARAMETER_EAGERLY_EXPOSE_TRACKTYPE, Boolean.class);
         expectedTypeByParameterName.put(PARAMETER_EXPOSE_DUMMY_SEEKMAP, Boolean.class);
+        expectedTypeByParameterName.put(
+                PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT, Boolean.class);
         EXPECTED_TYPE_BY_PARAMETER_NAME = Collections.unmodifiableMap(expectedTypeByParameterName);
     }
 }
diff --git a/config/preloaded-classes-blacklist b/config/preloaded-classes-blacklist
index 353f786..48d579c 100644
--- a/config/preloaded-classes-blacklist
+++ b/config/preloaded-classes-blacklist
@@ -1,6 +1,8 @@
 android.content.AsyncTaskLoader$LoadTask
 android.net.ConnectivityThread$Singleton
+android.os.AsyncTask
 android.os.FileObserver
+android.os.NullVibrator
 android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask
 android.widget.Magnifier
-sun.nio.fs.UnixChannelFactory
+com.android.server.BootReceiver$2
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 1cc63da..0f31529 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -433,13 +433,21 @@
         }
     }
 
-    /** Returns whether the current UI mode supports error dialogs (ANR, crash, etc). */
-    public static boolean currentUiModeSupportsErrorDialogs(@NonNull Context context) {
-        final Configuration config = context.getResources().getConfiguration();
+    /**
+     * @return whether the UI mode of the given config supports error dialogs (ANR, crash, etc).
+     * @hide
+     */
+    public static boolean currentUiModeSupportsErrorDialogs(@NonNull Configuration config) {
         int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK;
         return (modeType != Configuration.UI_MODE_TYPE_CAR
                 && !(modeType == Configuration.UI_MODE_TYPE_WATCH && Build.IS_USER)
                 && modeType != Configuration.UI_MODE_TYPE_TELEVISION
                 && modeType != Configuration.UI_MODE_TYPE_VR_HEADSET);
     }
+
+    /** @return whether the current UI mode supports error dialogs (ANR, crash, etc). */
+    public static boolean currentUiModeSupportsErrorDialogs(@NonNull Context context) {
+        final Configuration config = context.getResources().getConfiguration();
+        return currentUiModeSupportsErrorDialogs(config);
+    }
 }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 71b866b..3347e04 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -227,7 +227,7 @@
      * {@link #sMessageCollector}, which forces {@link COLLECT_SYNC} mode.
      */
     @GuardedBy("sLock")
-    private static ArrayList<SyncNotedAppOp> sUnforwardedOps = new ArrayList<>();
+    private static ArrayList<AsyncNotedAppOp> sUnforwardedOps = new ArrayList<>();
 
     /**
      * Additional collector that collect accesses and forwards a few of them them via
@@ -8195,7 +8195,10 @@
                         if (sOnOpNotedCallback != null) {
                             sOnOpNotedCallback.onNoted(new SyncNotedAppOp(code, attributionTag));
                         } else {
-                            sUnforwardedOps.add(new SyncNotedAppOp(code, attributionTag));
+                            String message = getFormattedStackTrace();
+                            sUnforwardedOps.add(
+                                    new AsyncNotedAppOp(code, Process.myUid(), attributionTag,
+                                            message, System.currentTimeMillis()));
                             if (sUnforwardedOps.size() > MAX_UNFORWARDED_OPS) {
                                 sUnforwardedOps.remove(0);
                             }
@@ -8268,10 +8271,10 @@
                 synchronized (this) {
                     int numMissedSyncOps = sUnforwardedOps.size();
                     for (int i = 0; i < numMissedSyncOps; i++) {
-                        final SyncNotedAppOp syncNotedAppOp = sUnforwardedOps.get(i);
+                        final AsyncNotedAppOp syncNotedAppOp = sUnforwardedOps.get(i);
                         if (sOnOpNotedCallback != null) {
                             sOnOpNotedCallback.getAsyncNotedExecutor().execute(
-                                    () -> sOnOpNotedCallback.onNoted(syncNotedAppOp));
+                                    () -> sOnOpNotedCallback.onAsyncNoted(syncNotedAppOp));
                         }
                     }
                     sUnforwardedOps.clear();
diff --git a/core/java/android/app/admin/DevicePolicyCache.java b/core/java/android/app/admin/DevicePolicyCache.java
index 4d9970c..15ff531 100644
--- a/core/java/android/app/admin/DevicePolicyCache.java
+++ b/core/java/android/app/admin/DevicePolicyCache.java
@@ -41,7 +41,8 @@
     /**
      * See {@link DevicePolicyManager#getScreenCaptureDisabled}
      */
-    public abstract boolean getScreenCaptureDisabled(@UserIdInt int userHandle);
+    public abstract boolean isScreenCaptureAllowed(@UserIdInt int userHandle,
+            boolean ownerCanAddInternalSystemWindow);
 
     /**
      * Caches {@link DevicePolicyManager#getPasswordQuality(android.content.ComponentName)} of the
@@ -56,8 +57,9 @@
         private static final EmptyDevicePolicyCache INSTANCE = new EmptyDevicePolicyCache();
 
         @Override
-        public boolean getScreenCaptureDisabled(int userHandle) {
-            return false;
+        public boolean isScreenCaptureAllowed(int userHandle,
+                boolean ownerCanAddInternalSystemWindow) {
+            return true;
         }
 
         @Override
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index aa75f60..53fdf38 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -952,19 +952,35 @@
      * <li>{@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference}</li>
      * <li>{@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion}</li>
      * </ul>
-     * <p>The field of view of all non-RAW physical streams must be the same or as close as
-     * possible to that of non-RAW logical streams. If the requested FOV is outside of the
-     * range supported by the physical camera, the physical stream for that physical camera
-     * will use either the maximum or minimum scaler crop region, depending on which one is
-     * closer to the requested FOV. For example, for a logical camera with wide-tele lens
-     * configuration where the wide lens is the default, if the logical camera's crop region
-     * is set to maximum, the physical stream for the tele lens will be configured to its
-     * maximum crop region. On the other hand, if the logical camera has a normal-wide lens
-     * configuration where the normal lens is the default, when the logical camera's crop
-     * region is set to maximum, the FOV of the logical streams will be that of the normal
-     * lens. The FOV of the physical streams for the wide lens will be the same as the
-     * logical stream, by making the crop region smaller than its active array size to
-     * compensate for the smaller focal length.</p>
+     * <p>The field of view of non-RAW physical streams must not be smaller than that of the
+     * non-RAW logical streams, or the maximum field-of-view of the physical camera,
+     * whichever is smaller. The application should check the physical capture result
+     * metadata for how the physical streams are cropped or zoomed. More specifically, given
+     * the physical camera result metadata, the effective horizontal field-of-view of the
+     * physical camera is:</p>
+     * <pre><code>fov = 2 * atan2(cropW * sensorW / (2 * zoomRatio * activeArrayW), focalLength)
+     * </code></pre>
+     * <p>where the equation parameters are the physical camera's crop region width, physical
+     * sensor width, zoom ratio, active array width, and focal length respectively. Typically
+     * the physical stream of active physical camera has the same field-of-view as the
+     * logical streams. However, the same may not be true for physical streams from
+     * non-active physical cameras. For example, if the logical camera has a wide-ultrawide
+     * configuration where the wide lens is the default, when the crop region is set to the
+     * logical camera's active array size, (and the zoom ratio set to 1.0 starting from
+     * Android 11), a physical stream for the ultrawide camera may prefer outputing images
+     * with larger field-of-view than that of the wide camera for better stereo matching
+     * margin or more robust motion tracking. At the same time, the physical non-RAW streams'
+     * field of view must not be smaller than the requested crop region and zoom ratio, as
+     * long as it's within the physical lens' capability. For example, for a logical camera
+     * with wide-tele lens configuration where the wide lens is the default, if the logical
+     * camera's crop region is set to maximum size, and zoom ratio set to 1.0, the physical
+     * stream for the tele lens will be configured to its maximum size crop region (no zoom).</p>
+     * <p><em>Deprecated:</em> Prior to Android 11, the field of view of all non-RAW physical streams
+     * cannot be larger than that of non-RAW logical streams. If the logical camera has a
+     * wide-ultrawide lens configuration where the wide lens is the default, when the logical
+     * camera's crop region is set to maximum size, the FOV of the physical streams for the
+     * ultrawide lens will be the same as the logical stream, by making the crop region
+     * smaller than its active array size to compensate for the smaller focal length.</p>
      * <p>Even if the underlying physical cameras have different RAW characteristics (such as
      * size or CFA pattern), a logical camera can still advertise RAW capability. In this
      * case, when the application configures a RAW stream, the camera device will make sure
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 9154746..fd1ef6c 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1166,7 +1166,6 @@
             checkIfCameraClosedOrInError();
 
             // Make sure that there all requests have at least 1 surface; all surfaces are non-null;
-            // the surface isn't a physical stream surface for reprocessing request
             for (CaptureRequest request : requestList) {
                 if (request.getTargets().isEmpty()) {
                     throw new IllegalArgumentException(
@@ -1177,17 +1176,6 @@
                     if (surface == null) {
                         throw new IllegalArgumentException("Null Surface targets are not allowed");
                     }
-
-                    for (int i = 0; i < mConfiguredOutputs.size(); i++) {
-                        OutputConfiguration configuration = mConfiguredOutputs.valueAt(i);
-                        if (configuration.isForPhysicalCamera()
-                                && configuration.getSurfaces().contains(surface)) {
-                            if (request.isReprocess()) {
-                                throw new IllegalArgumentException(
-                                        "Reprocess request on physical stream is not allowed");
-                            }
-                        }
-                    }
                 }
             }
 
diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java
index 5ab0354..1ef4f17 100644
--- a/core/java/android/net/DhcpResults.java
+++ b/core/java/android/net/DhcpResults.java
@@ -18,12 +18,13 @@
 
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.net.shared.InetAddressUtils;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.net.module.util.InetAddressUtils;
+
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.util.ArrayList;
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 0b92b95..97a7ecc 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -21,13 +21,14 @@
 
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.net.shared.Inet4AddressUtils;
 import android.os.Build;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.net.module.util.Inet4AddressUtils;
+
 import java.io.FileDescriptor;
 import java.math.BigInteger;
 import java.net.Inet4Address;
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index f24a9bd..a973455 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -21,11 +21,11 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.net.shared.InetAddressUtils;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import com.android.internal.util.Preconditions;
+import com.android.net.module.util.InetAddressUtils;
 
 import java.net.InetAddress;
 import java.util.ArrayList;
diff --git a/core/java/android/net/shared/Inet4AddressUtils.java b/core/java/android/net/shared/Inet4AddressUtils.java
deleted file mode 100644
index bec0c84..0000000
--- a/core/java/android/net/shared/Inet4AddressUtils.java
+++ /dev/null
@@ -1,166 +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 android.net.shared;
-
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-/**
- * Collection of utilities to work with IPv4 addresses.
- * @hide
- */
-public class Inet4AddressUtils {
-
-    /**
-     * Convert a IPv4 address from an integer to an InetAddress (0x04030201 -> 1.2.3.4)
-     *
-     * <p>This method uses the higher-order int bytes as the lower-order IPv4 address bytes,
-     * which is an unusual convention. Consider {@link #intToInet4AddressHTH(int)} instead.
-     * @param hostAddress an int coding for an IPv4 address, where higher-order int byte is
-     *                    lower-order IPv4 address byte
-     */
-    public static Inet4Address intToInet4AddressHTL(int hostAddress) {
-        return intToInet4AddressHTH(Integer.reverseBytes(hostAddress));
-    }
-
-    /**
-     * Convert a IPv4 address from an integer to an InetAddress (0x01020304 -> 1.2.3.4)
-     * @param hostAddress an int coding for an IPv4 address
-     */
-    public static Inet4Address intToInet4AddressHTH(int hostAddress) {
-        byte[] addressBytes = { (byte) (0xff & (hostAddress >> 24)),
-                (byte) (0xff & (hostAddress >> 16)),
-                (byte) (0xff & (hostAddress >> 8)),
-                (byte) (0xff & hostAddress) };
-
-        try {
-            return (Inet4Address) InetAddress.getByAddress(addressBytes);
-        } catch (UnknownHostException e) {
-            throw new AssertionError();
-        }
-    }
-
-    /**
-     * Convert an IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x01020304)
-     *
-     * <p>This conversion can help order IP addresses: considering the ordering
-     * 192.0.2.1 < 192.0.2.2 < ..., resulting ints will follow that ordering if read as unsigned
-     * integers with {@link Integer#toUnsignedLong}.
-     * @param inetAddr is an InetAddress corresponding to the IPv4 address
-     * @return the IP address as integer
-     */
-    public static int inet4AddressToIntHTH(Inet4Address inetAddr)
-            throws IllegalArgumentException {
-        byte [] addr = inetAddr.getAddress();
-        return ((addr[0] & 0xff) << 24) | ((addr[1] & 0xff) << 16)
-                | ((addr[2] & 0xff) << 8) | (addr[3] & 0xff);
-    }
-
-    /**
-     * Convert a IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x04030201)
-     *
-     * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes,
-     * which is an unusual convention. Consider {@link #inet4AddressToIntHTH(Inet4Address)} instead.
-     * @param inetAddr is an InetAddress corresponding to the IPv4 address
-     * @return the IP address as integer
-     */
-    public static int inet4AddressToIntHTL(Inet4Address inetAddr) {
-        return Integer.reverseBytes(inet4AddressToIntHTH(inetAddr));
-    }
-
-    /**
-     * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0xffff8000)
-     * @return the IPv4 netmask as an integer
-     */
-    public static int prefixLengthToV4NetmaskIntHTH(int prefixLength)
-            throws IllegalArgumentException {
-        if (prefixLength < 0 || prefixLength > 32) {
-            throw new IllegalArgumentException("Invalid prefix length (0 <= prefix <= 32)");
-        }
-        // (int)a << b is equivalent to a << (b & 0x1f): can't shift by 32 (-1 << 32 == -1)
-        return prefixLength == 0 ? 0 : 0xffffffff << (32 - prefixLength);
-    }
-
-    /**
-     * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0x0080ffff).
-     *
-     * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes,
-     * which is an unusual convention. Consider {@link #prefixLengthToV4NetmaskIntHTH(int)} instead.
-     * @return the IPv4 netmask as an integer
-     */
-    public static int prefixLengthToV4NetmaskIntHTL(int prefixLength)
-            throws IllegalArgumentException {
-        return Integer.reverseBytes(prefixLengthToV4NetmaskIntHTH(prefixLength));
-    }
-
-    /**
-     * Convert an IPv4 netmask to a prefix length, checking that the netmask is contiguous.
-     * @param netmask as a {@code Inet4Address}.
-     * @return the network prefix length
-     * @throws IllegalArgumentException the specified netmask was not contiguous.
-     * @hide
-     */
-    public static int netmaskToPrefixLength(Inet4Address netmask) {
-        // inetAddressToInt returns an int in *network* byte order.
-        int i = inet4AddressToIntHTH(netmask);
-        int prefixLength = Integer.bitCount(i);
-        int trailingZeros = Integer.numberOfTrailingZeros(i);
-        if (trailingZeros != 32 - prefixLength) {
-            throw new IllegalArgumentException("Non-contiguous netmask: " + Integer.toHexString(i));
-        }
-        return prefixLength;
-    }
-
-    /**
-     * Returns the implicit netmask of an IPv4 address, as was the custom before 1993.
-     */
-    public static int getImplicitNetmask(Inet4Address address) {
-        int firstByte = address.getAddress()[0] & 0xff;  // Convert to an unsigned value.
-        if (firstByte < 128) {
-            return 8;
-        } else if (firstByte < 192) {
-            return 16;
-        } else if (firstByte < 224) {
-            return 24;
-        } else {
-            return 32;  // Will likely not end well for other reasons.
-        }
-    }
-
-    /**
-     * Get the broadcast address for a given prefix.
-     *
-     * <p>For example 192.168.0.1/24 -> 192.168.0.255
-     */
-    public static Inet4Address getBroadcastAddress(Inet4Address addr, int prefixLength)
-            throws IllegalArgumentException {
-        final int intBroadcastAddr = inet4AddressToIntHTH(addr)
-                | ~prefixLengthToV4NetmaskIntHTH(prefixLength);
-        return intToInet4AddressHTH(intBroadcastAddr);
-    }
-
-    /**
-     * Get a prefix mask as Inet4Address for a given prefix length.
-     *
-     * <p>For example 20 -> 255.255.240.0
-     */
-    public static Inet4Address getPrefixMaskAsInet4Address(int prefixLength)
-            throws IllegalArgumentException {
-        return intToInet4AddressHTH(prefixLengthToV4NetmaskIntHTH(prefixLength));
-    }
-}
diff --git a/core/java/android/net/shared/InetAddressUtils.java b/core/java/android/net/shared/InetAddressUtils.java
deleted file mode 100644
index c9ee3a7..0000000
--- a/core/java/android/net/shared/InetAddressUtils.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.shared;
-
-import android.os.Parcel;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-/**
- * Collection of utilities to interact with {@link InetAddress}
- * @hide
- */
-public class InetAddressUtils {
-
-    /**
-     * Writes an InetAddress to a parcel. The address may be null. This is likely faster than
-     * calling writeSerializable.
-     * @hide
-     */
-    public static void parcelInetAddress(Parcel parcel, InetAddress address, int flags) {
-        byte[] addressArray = (address != null) ? address.getAddress() : null;
-        parcel.writeByteArray(addressArray);
-    }
-
-    /**
-     * Reads an InetAddress from a parcel. Returns null if the address that was written was null
-     * or if the data is invalid.
-     * @hide
-     */
-    public static InetAddress unparcelInetAddress(Parcel in) {
-        byte[] addressArray = in.createByteArray();
-        if (addressArray == null) {
-            return null;
-        }
-        try {
-            return InetAddress.getByAddress(addressArray);
-        } catch (UnknownHostException e) {
-            return null;
-        }
-    }
-
-    private InetAddressUtils() {}
-}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 545bd98..a7564ee 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1898,6 +1898,15 @@
             "android.settings.ACTION_DEVICE_CONTROLS_SETTINGS";
 
     /**
+     * Activity Action: Show media control settings
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MEDIA_CONTROLS_SETTINGS =
+            "android.settings.ACTION_MEDIA_CONTROLS_SETTINGS";
+
+    /**
      * Activity Action: Show a dialog with disabled by policy message.
      * <p> If an user action is disabled by policy, this dialog can be triggered to let
      * the user know about this.
@@ -8923,6 +8932,15 @@
         public static final String PEOPLE_STRIP = "people_strip";
 
         /**
+         * Whether or not to enable media resumption
+         * When enabled, media controls in quick settings will populate on boot and persist if
+         * resumable via a MediaBrowserService.
+         * @see Settings.Global#SHOW_MEDIA_ON_QUICK_SETTINGS
+         * @hide
+         */
+        public static final String MEDIA_CONTROLS_RESUME = "qs_media_resumption";
+
+        /**
          * Controls if window magnification is enabled.
          * @hide
          */
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 8acf5fa..537498c 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -67,7 +67,6 @@
         DEFAULT_FLAGS.put(SETTINGS_DO_NOT_RESTORE_PRESERVED, "true");
 
         DEFAULT_FLAGS.put("settings_tether_all_in_one", "false");
-        DEFAULT_FLAGS.put("settings_contextual_home2", "true");
     }
 
     /**
diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java
index 2a4b65d..6efe95c 100644
--- a/core/java/android/util/apk/ApkSigningBlockUtils.java
+++ b/core/java/android/util/apk/ApkSigningBlockUtils.java
@@ -420,6 +420,7 @@
     static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
     static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
     static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3;
+    static final int CONTENT_DIGEST_SHA256 = 4;
 
     private static final int[] V4_CONTENT_DIGEST_ALGORITHMS =
             {CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256,
diff --git a/core/java/android/util/apk/SourceStampVerifier.java b/core/java/android/util/apk/SourceStampVerifier.java
index a7ae32d..5fc2423 100644
--- a/core/java/android/util/apk/SourceStampVerifier.java
+++ b/core/java/android/util/apk/SourceStampVerifier.java
@@ -16,6 +16,7 @@
 
 package android.util.apk;
 
+import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_SHA256;
 import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
 import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
@@ -27,12 +28,10 @@
 import android.util.Slog;
 import android.util.jar.StrictJarFile;
 
-import libcore.io.IoUtils;
+import libcore.io.Streams;
 
 import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.RandomAccessFile;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
@@ -49,11 +48,13 @@
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
+import java.util.jar.JarFile;
 import java.util.zip.ZipEntry;
 
 /**
@@ -74,7 +75,11 @@
 
     private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
     private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
-    private static final int SOURCE_STAMP_BLOCK_ID = 0x2b09189e;
+    private static final int SOURCE_STAMP_BLOCK_ID = 0x6dff800d;
+
+    private static final int VERSION_JAR_SIGNATURE_SCHEME = 1;
+    private static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2;
+    private static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3;
 
     /** Name of the SourceStamp certificate hash ZIP entry in APKs. */
     private static final String SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME = "stamp-cert-sha256";
@@ -115,7 +120,8 @@
                 // SourceStamp present.
                 return SourceStampVerificationResult.notPresent();
             }
-            return verify(apk, sourceStampCertificateDigest);
+            byte[] manifestBytes = getManifestBytes(apkJar);
+            return verify(apk, sourceStampCertificateDigest, manifestBytes);
         } catch (IOException e) {
             // Any exception in reading the APK returns a non-present SourceStamp outcome
             // without affecting the outcome of any of the other signature schemes.
@@ -126,22 +132,71 @@
     }
 
     private static SourceStampVerificationResult verify(
-            RandomAccessFile apk, byte[] sourceStampCertificateDigest) {
+            RandomAccessFile apk, byte[] sourceStampCertificateDigest, byte[] manifestBytes) {
         try {
             SignatureInfo signatureInfo =
                     ApkSigningBlockUtils.findSignature(apk, SOURCE_STAMP_BLOCK_ID);
-            Map<Integer, byte[]> apkContentDigests = getApkContentDigests(apk);
-            return verify(signatureInfo, apkContentDigests, sourceStampCertificateDigest);
-        } catch (IOException | SignatureNotFoundException e) {
+            Map<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigests =
+                    getSignatureSchemeApkContentDigests(apk, manifestBytes);
+            return verify(
+                    signatureInfo,
+                    getSignatureSchemeDigests(signatureSchemeApkContentDigests),
+                    sourceStampCertificateDigest);
+        } catch (IOException | SignatureNotFoundException | RuntimeException e) {
             return SourceStampVerificationResult.notVerified();
         }
     }
 
     private static SourceStampVerificationResult verify(
             SignatureInfo signatureInfo,
-            Map<Integer, byte[]> apkContentDigests,
+            Map<Integer, byte[]> signatureSchemeDigests,
             byte[] sourceStampCertificateDigest)
             throws SecurityException, IOException {
+        ByteBuffer sourceStampBlock = signatureInfo.signatureBlock;
+        ByteBuffer sourceStampBlockData =
+                ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlock);
+
+        X509Certificate sourceStampCertificate =
+                verifySourceStampCertificate(sourceStampBlockData, sourceStampCertificateDigest);
+
+        // Parse signed signature schemes block.
+        ByteBuffer signedSignatureSchemes =
+                ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlockData);
+        Map<Integer, ByteBuffer> signedSignatureSchemeData = new HashMap<>();
+        while (signedSignatureSchemes.hasRemaining()) {
+            ByteBuffer signedSignatureScheme =
+                    ApkSigningBlockUtils.getLengthPrefixedSlice(signedSignatureSchemes);
+            int signatureSchemeId = signedSignatureScheme.getInt();
+            signedSignatureSchemeData.put(signatureSchemeId, signedSignatureScheme);
+        }
+
+        for (Map.Entry<Integer, byte[]> signatureSchemeDigest : signatureSchemeDigests.entrySet()) {
+            if (!signedSignatureSchemeData.containsKey(signatureSchemeDigest.getKey())) {
+                throw new SecurityException(
+                        String.format(
+                                "No signatures found for signature scheme %d",
+                                signatureSchemeDigest.getKey()));
+            }
+            verifySourceStampSignature(
+                    signedSignatureSchemeData.get(signatureSchemeDigest.getKey()),
+                    sourceStampCertificate,
+                    signatureSchemeDigest.getValue());
+        }
+
+        return SourceStampVerificationResult.verified(sourceStampCertificate);
+    }
+
+    /**
+     * Verify the SourceStamp certificate found in the signing block is the same as the SourceStamp
+     * certificate found in the APK. It returns the verified certificate.
+     *
+     * @param sourceStampBlockData the source stamp block in the APK signing block which contains
+     *     the certificate used to sign the stamp digests.
+     * @param sourceStampCertificateDigest the source stamp certificate digest found in the APK.
+     */
+    private static X509Certificate verifySourceStampCertificate(
+            ByteBuffer sourceStampBlockData, byte[] sourceStampCertificateDigest)
+            throws IOException {
         CertificateFactory certFactory;
         try {
             certFactory = CertificateFactory.getInstance("X.509");
@@ -149,17 +204,6 @@
             throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
         }
 
-        List<Pair<Integer, byte[]>> digests =
-                apkContentDigests.entrySet().stream()
-                        .sorted(Map.Entry.comparingByKey())
-                        .map(e -> Pair.create(e.getKey(), e.getValue()))
-                        .collect(Collectors.toList());
-        byte[] digestBytes = encodeApkContentDigests(digests);
-
-        ByteBuffer sourceStampBlock = signatureInfo.signatureBlock;
-        ByteBuffer sourceStampBlockData =
-                ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlock);
-
         // Parse the SourceStamp certificate.
         byte[] sourceStampEncodedCertificate =
                 ApkSigningBlockUtils.readLengthPrefixedByteArray(sourceStampBlockData);
@@ -172,24 +216,30 @@
         } catch (CertificateException e) {
             throw new SecurityException("Failed to decode certificate", e);
         }
-        sourceStampCertificate =
-                new VerbatimX509Certificate(sourceStampCertificate, sourceStampEncodedCertificate);
 
-        // Verify the SourceStamp certificate found in the signing block is the same as the
-        // SourceStamp certificate found in the APK.
-        try {
-            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
-            messageDigest.update(sourceStampEncodedCertificate);
-            byte[] sourceStampBlockCertificateDigest = messageDigest.digest();
-            if (!Arrays.equals(sourceStampCertificateDigest, sourceStampBlockCertificateDigest)) {
-                throw new SecurityException("Certificate mismatch between APK and signature block");
-            }
-        } catch (NoSuchAlgorithmException e) {
-            throw new SecurityException("Failed to find SHA-256", e);
+        byte[] sourceStampBlockCertificateDigest =
+                computeSha256Digest(sourceStampEncodedCertificate);
+        if (!Arrays.equals(sourceStampCertificateDigest, sourceStampBlockCertificateDigest)) {
+            throw new SecurityException("Certificate mismatch between APK and signature block");
         }
 
+        return new VerbatimX509Certificate(sourceStampCertificate, sourceStampEncodedCertificate);
+    }
+
+    /**
+     * Verify the SourceStamp signature found in the signing block is signed by the SourceStamp
+     * certificate found in the APK.
+     *
+     * @param signedBlockData the source stamp block in the APK signing block which contains the
+     *     stamp signed digests.
+     * @param sourceStampCertificate the source stamp certificate used to sign the stamp digests.
+     * @param digest the digest to be verified being signed by the source stamp certificate.
+     */
+    private static void verifySourceStampSignature(
+            ByteBuffer signedBlockData, X509Certificate sourceStampCertificate, byte[] digest)
+            throws IOException {
         // Parse the signatures block and identify supported signatures
-        ByteBuffer signatures = ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlockData);
+        ByteBuffer signatures = ApkSigningBlockUtils.getLengthPrefixedSlice(signedBlockData);
         int signatureCount = 0;
         int bestSigAlgorithm = -1;
         byte[] bestSigAlgorithmSignatureBytes = null;
@@ -235,7 +285,7 @@
             if (jcaSignatureAlgorithmParams != null) {
                 sig.setParameter(jcaSignatureAlgorithmParams);
             }
-            sig.update(digestBytes);
+            sig.update(digest);
             sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
         } catch (InvalidKeyException
                 | InvalidAlgorithmParameterException
@@ -247,27 +297,44 @@
         if (!sigVerified) {
             throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
         }
-
-        return SourceStampVerificationResult.verified(sourceStampCertificate);
     }
 
-    private static Map<Integer, byte[]> getApkContentDigests(RandomAccessFile apk)
-            throws IOException, SignatureNotFoundException {
-        // Retrieve APK content digests in V3 signing block. If a V3 signature is not found, the APK
-        // content digests would be re-tried from V2 signature.
+    private static Map<Integer, Map<Integer, byte[]>> getSignatureSchemeApkContentDigests(
+            RandomAccessFile apk, byte[] manifestBytes) throws IOException {
+        Map<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigests = new HashMap<>();
+
+        // Retrieve APK content digests in V3 signing block.
         try {
             SignatureInfo v3SignatureInfo =
                     ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
-            return getApkContentDigestsFromSignatureBlock(v3SignatureInfo.signatureBlock);
+            signatureSchemeApkContentDigests.put(
+                    VERSION_APK_SIGNATURE_SCHEME_V3,
+                    getApkContentDigestsFromSignatureBlock(v3SignatureInfo.signatureBlock));
         } catch (SignatureNotFoundException e) {
             // It's fine not to find a V3 signature.
         }
 
-        // Retrieve APK content digests in V2 signing block. If a V2 signature is not found, the
-        // process of retrieving APK content digests stops, and the stamp is considered un-verified.
-        SignatureInfo v2SignatureInfo =
-                ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
-        return getApkContentDigestsFromSignatureBlock(v2SignatureInfo.signatureBlock);
+        // Retrieve APK content digests in V2 signing block.
+        try {
+            SignatureInfo v2SignatureInfo =
+                    ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
+            signatureSchemeApkContentDigests.put(
+                    VERSION_APK_SIGNATURE_SCHEME_V2,
+                    getApkContentDigestsFromSignatureBlock(v2SignatureInfo.signatureBlock));
+        } catch (SignatureNotFoundException e) {
+            // It's fine not to find a V2 signature.
+        }
+
+        // Retrieve manifest digest.
+        if (manifestBytes != null) {
+            Map<Integer, byte[]> jarSignatureSchemeApkContentDigests = new HashMap<>();
+            jarSignatureSchemeApkContentDigests.put(
+                    CONTENT_DIGEST_SHA256, computeSha256Digest(manifestBytes));
+            signatureSchemeApkContentDigests.put(
+                    VERSION_JAR_SIGNATURE_SCHEME, jarSignatureSchemeApkContentDigests);
+        }
+
+        return signatureSchemeApkContentDigests;
     }
 
     private static Map<Integer, byte[]> getApkContentDigestsFromSignatureBlock(
@@ -289,27 +356,45 @@
         return apkContentDigests;
     }
 
-    private static byte[] getSourceStampCertificateDigest(StrictJarFile apkJar) throws IOException {
-        InputStream inputStream = null;
-        try {
-            ZipEntry zipEntry = apkJar.findEntry(SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME);
-            if (zipEntry == null) {
-                // SourceStamp certificate hash file not found, which means that there is not
-                // SourceStamp present.
-                return null;
-            }
-            inputStream = apkJar.getInputStream(zipEntry);
-            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-
-            // Trying to read the certificate digest, which should be less than 1024 bytes.
-            byte[] buffer = new byte[1024];
-            int count = inputStream.read(buffer, 0, buffer.length);
-            byteArrayOutputStream.write(buffer, 0, count);
-
-            return byteArrayOutputStream.toByteArray();
-        } finally {
-            IoUtils.closeQuietly(inputStream);
+    private static Map<Integer, byte[]> getSignatureSchemeDigests(
+            Map<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigests) {
+        Map<Integer, byte[]> digests = new HashMap<>();
+        for (Map.Entry<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigest :
+                signatureSchemeApkContentDigests.entrySet()) {
+            List<Pair<Integer, byte[]>> apkDigests =
+                    getApkDigests(signatureSchemeApkContentDigest.getValue());
+            digests.put(
+                    signatureSchemeApkContentDigest.getKey(), encodeApkContentDigests(apkDigests));
         }
+        return digests;
+    }
+
+    private static List<Pair<Integer, byte[]>> getApkDigests(
+            Map<Integer, byte[]> apkContentDigests) {
+        List<Pair<Integer, byte[]>> digests = new ArrayList<>();
+        for (Map.Entry<Integer, byte[]> apkContentDigest : apkContentDigests.entrySet()) {
+            digests.add(Pair.create(apkContentDigest.getKey(), apkContentDigest.getValue()));
+        }
+        digests.sort(Comparator.comparing(pair -> pair.first));
+        return digests;
+    }
+
+    private static byte[] getSourceStampCertificateDigest(StrictJarFile apkJar) throws IOException {
+        ZipEntry zipEntry = apkJar.findEntry(SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME);
+        if (zipEntry == null) {
+            // SourceStamp certificate hash file not found, which means that there is not
+            // SourceStamp present.
+            return null;
+        }
+        return Streams.readFully(apkJar.getInputStream(zipEntry));
+    }
+
+    private static byte[] getManifestBytes(StrictJarFile apkJar) throws IOException {
+        ZipEntry zipEntry = apkJar.findEntry(JarFile.MANIFEST_NAME);
+        if (zipEntry == null) {
+            return null;
+        }
+        return Streams.readFully(apkJar.getInputStream(zipEntry));
     }
 
     private static byte[] encodeApkContentDigests(List<Pair<Integer, byte[]>> apkContentDigests) {
@@ -329,6 +414,16 @@
         return result.array();
     }
 
+    private static byte[] computeSha256Digest(byte[] input) {
+        try {
+            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
+            messageDigest.update(input);
+            return messageDigest.digest();
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("Failed to find SHA-256", e);
+        }
+    }
+
     private static void closeApkJar(StrictJarFile apkJar) {
         try {
             if (apkJar == null) {
diff --git a/core/java/android/util/apk/TEST_MAPPING b/core/java/android/util/apk/TEST_MAPPING
new file mode 100644
index 0000000..8544e82
--- /dev/null
+++ b/core/java/android/util/apk/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.util.apk.SourceStampVerifierTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index cd56ca9..baeae2f 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -16,13 +16,14 @@
 
 package android.view;
 
+import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
 import static android.view.InsetsController.AnimationType;
 import static android.view.InsetsController.DEBUG;
 import static android.view.InsetsState.ISIDE_BOTTOM;
-import static android.view.InsetsState.ISIDE_FLOATING;
 import static android.view.InsetsState.ISIDE_LEFT;
 import static android.view.InsetsState.ISIDE_RIGHT;
 import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsState.ITYPE_IME;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
@@ -74,6 +75,8 @@
     private final @InsetsType int mTypes;
     private final InsetsAnimationControlCallbacks mController;
     private final WindowInsetsAnimation mAnimation;
+    /** @see WindowInsetsAnimationController#hasZeroInsetsIme */
+    private final boolean mHasZeroInsetsIme;
     private Insets mCurrentInsets;
     private Insets mPendingInsets;
     private float mPendingFraction;
@@ -102,6 +105,12 @@
                 null /* typeSideMap */);
         mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */,
                 mTypeSideMap);
+        mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME);
+        if (mHasZeroInsetsIme) {
+            // IME has shownInsets of ZERO, and can't map to a side by default.
+            // Map zero insets IME to bottom, making it a special case of bottom insets.
+            mTypeSideMap.put(ITYPE_IME, ISIDE_BOTTOM);
+        }
         buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mControls);
 
         mAnimation = new WindowInsetsAnimation(mTypes, interpolator,
@@ -113,6 +122,11 @@
     }
 
     @Override
+    public boolean hasZeroInsetsIme() {
+        return mHasZeroInsetsIme;
+    }
+
+    @Override
     public Insets getHiddenStateInsets() {
         return mHiddenInsets;
     }
@@ -182,8 +196,6 @@
                 params, state, mPendingAlpha);
         updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mShownInsets.bottom,
                 mPendingInsets.bottom, params, state, mPendingAlpha);
-        updateLeashesForSide(ISIDE_FLOATING, 0 /* offset */, 0 /* inset */, 0 /* maxInset */,
-                params, state, mPendingAlpha);
 
         mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()]));
         mCurrentInsets = mPendingInsets;
@@ -290,6 +302,9 @@
         if (insets == null) {
             insets = getCurrentInsets();
         }
+        if (hasZeroInsetsIme()) {
+            return insets;
+        }
         return Insets.max(Insets.min(insets, mShownInsets), mHiddenInsets);
     }
 
@@ -313,17 +328,19 @@
             mTmpFrame.set(source.getFrame());
             addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame);
 
-            state.getSource(source.getType()).setVisible(side == ISIDE_FLOATING || inset != 0);
+            final boolean visible = mHasZeroInsetsIme && side == ISIDE_BOTTOM
+                    ? (mAnimationType == ANIMATION_TYPE_SHOW ? true : !mFinished)
+                    : inset != 0;
+
+            state.getSource(source.getType()).setVisible(visible);
             state.getSource(source.getType()).setFrame(mTmpFrame);
 
             // If the system is controlling the insets source, the leash can be null.
             if (leash != null) {
                 SurfaceParams params = new SurfaceParams.Builder(leash)
-                        .withAlpha(side == ISIDE_FLOATING ? 1 : alpha)
+                        .withAlpha(alpha)
                         .withMatrix(mTmpMatrix)
-                        .withVisibility(side == ISIDE_FLOATING
-                                ? mShownOnFinish
-                                : inset != 0 /* visible */)
+                        .withVisibility(visible)
                         .build();
                 surfaceParams.add(params);
             }
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index b4dae56..5f99bfe 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -162,6 +162,9 @@
          */
         @Nullable
         String getRootViewTitle();
+
+        /** @see ViewRootImpl#dipToPx */
+        int dipToPx(int dips);
     }
 
     private static final String TAG = "InsetsController";
@@ -254,6 +257,9 @@
     public static class InternalAnimationControlListener
             implements WindowInsetsAnimationControlListener {
 
+        /** The amount IME will move up/down when animating in floating mode. */
+        protected static final int FLOATING_IME_BOTTOM_INSET = -80;
+
         private WindowInsetsAnimationController mController;
         private ValueAnimator mAnimator;
         private final boolean mShow;
@@ -261,6 +267,7 @@
         private final @InsetsType int mRequestedTypes;
         private final long mDurationMs;
         private final boolean mDisable;
+        private final int mFloatingImeBottomInset;
 
         private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
                 new ThreadLocal<AnimationHandler>() {
@@ -273,12 +280,13 @@
         };
 
         public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
-                int requestedTypes, boolean disable) {
+                int requestedTypes, boolean disable, int floatingImeBottomInset) {
             mShow = show;
             mHasAnimationCallbacks = hasAnimationCallbacks;
             mRequestedTypes = requestedTypes;
             mDurationMs = calculateDurationMs();
             mDisable = disable;
+            mFloatingImeBottomInset = floatingImeBottomInset;
         }
 
         @Override
@@ -293,12 +301,19 @@
             mAnimator = ValueAnimator.ofFloat(0f, 1f);
             mAnimator.setDuration(mDurationMs);
             mAnimator.setInterpolator(new LinearInterpolator());
+            Insets hiddenInsets = controller.getHiddenStateInsets();
+            // IME with zero insets is a special case: it will animate-in from offscreen and end
+            // with final insets of zero and vice-versa.
+            hiddenInsets = controller.hasZeroInsetsIme()
+                    ? Insets.of(hiddenInsets.left, hiddenInsets.top, hiddenInsets.right,
+                            mFloatingImeBottomInset)
+                    : hiddenInsets;
             Insets start = mShow
-                    ? controller.getHiddenStateInsets()
+                    ? hiddenInsets
                     : controller.getShownStateInsets();
             Insets end = mShow
                     ? controller.getShownStateInsets()
-                    : controller.getHiddenStateInsets();
+                    : hiddenInsets;
             Interpolator insetsInterpolator = getInterpolator();
             Interpolator alphaInterpolator = getAlphaInterpolator();
             mAnimator.addUpdateListener(animation -> {
@@ -606,12 +621,14 @@
 
     private void updateState(InsetsState newState) {
         mState.setDisplayFrame(newState.getDisplayFrame());
-        for (int i = newState.getSourcesCount() - 1; i >= 0; i--) {
-            InsetsSource source = newState.sourceAt(i);
+        for (int i = 0; i < InsetsState.SIZE; i++) {
+            InsetsSource source = newState.peekSource(i);
+            if (source == null) continue;;
             getSourceConsumer(source.getType()).updateSource(source);
         }
-        for (int i = mState.getSourcesCount() - 1; i >= 0; i--) {
-            InsetsSource source = mState.sourceAt(i);
+        for (int i = 0; i < InsetsState.SIZE; i++) {
+            InsetsSource source = mState.peekSource(i);
+            if (source == null) continue;
             if (newState.peekSource(source.getType()) == null) {
                 mState.removeSource(source.getType());
             }
@@ -707,7 +724,7 @@
         if (hideTypes[0] != 0) {
             applyAnimation(hideTypes[0], false /* show */, false /* fromIme */);
         }
-        if (hasControl && mRequestedState.getSourcesCount() > 0) {
+        if (hasControl && mRequestedState.hasSources()) {
             // We might have changed our requested visibilities while we don't have the control,
             // so we need to update our requested state once we have control. Otherwise, our
             // requested state at the server side might be incorrect.
@@ -1171,7 +1188,8 @@
 
         boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks();
         final InternalAnimationControlListener listener = new InternalAnimationControlListener(
-                show, hasAnimationCallbacks, types, mAnimationsDisabled);
+                show, hasAnimationCallbacks, types, mAnimationsDisabled,
+                mHost.dipToPx(InternalAnimationControlListener.FLOATING_IME_BOTTOM_INSET));
 
         // Show/hide animations always need to be relative to the display frame, in order that shown
         // and hidden state insets are correct.
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 17620fa..9bf2e01 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -50,6 +50,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.StringJoiner;
 
@@ -117,6 +118,7 @@
     public static final int ITYPE_EXTRA_NAVIGATION_BAR = 15;
 
     static final int LAST_TYPE = ITYPE_EXTRA_NAVIGATION_BAR;
+    public static final int SIZE = LAST_TYPE + 1;
 
     // Derived types
 
@@ -140,7 +142,7 @@
     static final int ISIDE_FLOATING = 4;
     static final int ISIDE_UNKNOWN = 5;
 
-    private final ArrayMap<Integer, InsetsSource> mSources = new ArrayMap<>();
+    private InsetsSource[] mSources = new InsetsSource[SIZE];
 
     /**
      * The frame of the display these sources are relative to.
@@ -177,7 +179,7 @@
         final Rect relativeFrame = new Rect(frame);
         final Rect relativeFrameMax = new Rect(frame);
         for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            InsetsSource source = mSources.get(type);
+            InsetsSource source = mSources[type];
             if (source == null) {
                 int index = indexOf(toPublicType(type));
                 if (typeInsetsMap[index] == null) {
@@ -227,7 +229,7 @@
     public Rect calculateVisibleInsets(Rect frame, @SoftInputModeFlags int softInputMode) {
         Insets insets = Insets.NONE;
         for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            InsetsSource source = mSources.get(type);
+            InsetsSource source = mSources[type];
             if (source == null) {
                 continue;
             }
@@ -256,7 +258,7 @@
     public int calculateUncontrollableInsetsFromFrame(Rect frame) {
         int blocked = 0;
         for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            InsetsSource source = mSources.get(type);
+            InsetsSource source = mSources[type];
             if (source == null) {
                 continue;
             }
@@ -350,11 +352,26 @@
     }
 
     public InsetsSource getSource(@InternalInsetsType int type) {
-        return mSources.computeIfAbsent(type, InsetsSource::new);
+        InsetsSource source = mSources[type];
+        if (source != null) {
+            return source;
+        }
+        source = new InsetsSource(type);
+        mSources[type] = source;
+        return source;
     }
 
     public @Nullable InsetsSource peekSource(@InternalInsetsType int type) {
-        return mSources.get(type);
+        return mSources[type];
+    }
+
+    public boolean hasSources() {
+        for (int i = 0; i < SIZE; i++) {
+            if (mSources[i] != null) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -366,7 +383,7 @@
      *         doesn't exist.
      */
     public boolean getSourceOrDefaultVisibility(@InternalInsetsType int type) {
-        final InsetsSource source = mSources.get(type);
+        final InsetsSource source = mSources[type];
         return source != null ? source.isVisible() : getDefaultVisibility(type);
     }
 
@@ -385,7 +402,7 @@
      * @param type The {@link InternalInsetsType} of the source to remove
      */
     public void removeSource(@InternalInsetsType int type) {
-        mSources.remove(type);
+        mSources[type] = null;
     }
 
     /**
@@ -395,7 +412,7 @@
      * @param visible {@code true} for visible
      */
     public void setSourceVisible(@InternalInsetsType int type, boolean visible) {
-        InsetsSource source = mSources.get(type);
+        InsetsSource source = mSources[type];
         if (source != null) {
             source.setVisible(visible);
         }
@@ -407,27 +424,21 @@
 
     public void set(InsetsState other, boolean copySources) {
         mDisplayFrame.set(other.mDisplayFrame);
-        mSources.clear();
         if (copySources) {
-            for (int i = 0; i < other.mSources.size(); i++) {
-                InsetsSource source = other.mSources.valueAt(i);
-                mSources.put(source.getType(), new InsetsSource(source));
+            for (int i = 0; i < SIZE; i++) {
+                InsetsSource source = other.mSources[i];
+                if (source == null) continue;
+                mSources[i] = new InsetsSource(source);
             }
         } else {
-            mSources.putAll(other.mSources);
+            for (int i = 0; i < SIZE; i++) {
+                mSources[i] = other.mSources[i];
+            }
         }
     }
 
     public void addSource(InsetsSource source) {
-        mSources.put(source.getType(), source);
-    }
-
-    public int getSourcesCount() {
-        return mSources.size();
-    }
-
-    public InsetsSource sourceAt(int index) {
-        return mSources.valueAt(index);
+        mSources[source.getType()] = source;
     }
 
     public static @InternalInsetsType ArraySet<Integer> toInternalType(@InsetsType int types) {
@@ -508,8 +519,10 @@
 
     public void dump(String prefix, PrintWriter pw) {
         pw.println(prefix + "InsetsState");
-        for (int i = mSources.size() - 1; i >= 0; i--) {
-            mSources.valueAt(i).dump(prefix + "  ", pw);
+        for (int i = 0; i < SIZE; i++) {
+            InsetsSource source = mSources[i];
+            if (source == null) continue;
+            source.dump(prefix + "  ", pw);
         }
     }
 
@@ -578,26 +591,16 @@
         if (!mDisplayFrame.equals(state.mDisplayFrame)) {
             return false;
         }
-        int size = mSources.size();
-        int otherSize = state.mSources.size();
-        if (excludingCaptionInsets) {
-            if (mSources.get(ITYPE_CAPTION_BAR) != null) {
-                size--;
-            }
-            if (state.mSources.get(ITYPE_CAPTION_BAR) != null) {
-                otherSize--;
-            }
-        }
-        if (size != otherSize) {
-            return false;
-        }
-        for (int i = mSources.size() - 1; i >= 0; i--) {
-            InsetsSource source = mSources.valueAt(i);
+        for (int i = 0; i < SIZE; i++) {
             if (excludingCaptionInsets) {
-                if (source.getType() == ITYPE_CAPTION_BAR) continue;
+                if (i == ITYPE_CAPTION_BAR) continue;
             }
-            InsetsSource otherSource = state.mSources.get(source.getType());
-            if (otherSource == null) {
+            InsetsSource source = mSources[i];
+            InsetsSource otherSource = state.mSources[i];
+            if (source == null && otherSource == null) {
+                continue;
+            }
+            if (source != null && otherSource == null || source == null && otherSource != null) {
                 return false;
             }
             if (!otherSource.equals(source, excludeInvisibleImeFrames)) {
@@ -609,7 +612,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mDisplayFrame, mSources);
+        return Objects.hash(mDisplayFrame, Arrays.hashCode(mSources));
     }
 
     public InsetsState(Parcel in) {
@@ -624,10 +627,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeParcelable(mDisplayFrame, flags);
-        dest.writeInt(mSources.size());
-        for (int i = 0; i < mSources.size(); i++) {
-            dest.writeParcelable(mSources.valueAt(i), flags);
-        }
+        dest.writeParcelableArray(mSources, 0);
     }
 
     public static final @android.annotation.NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
@@ -642,19 +642,15 @@
     };
 
     public void readFromParcel(Parcel in) {
-        mSources.clear();
         mDisplayFrame.set(in.readParcelable(null /* loader */));
-        final int size = in.readInt();
-        for (int i = 0; i < size; i++) {
-            final InsetsSource source = in.readParcelable(null /* loader */);
-            mSources.put(source.getType(), source);
-        }
+        mSources = in.readParcelableArray(null, InsetsSource.class);
     }
 
     @Override
     public String toString() {
         StringJoiner joiner = new StringJoiner(", ");
-        for (InsetsSource source : mSources.values()) {
+        for (int i = 0; i < SIZE; i++) {
+            InsetsSource source = mSources[i];
             if (source != null) {
                 joiner.add(source.toString());
             }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index d6eb706..6859f4f 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -49,6 +49,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.Trace;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -442,7 +443,9 @@
             release();
         }
         if (nativeObject != 0) {
+            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "closeGuard");
             mCloseGuard.open("release");
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
         }
         mNativeObject = nativeObject;
         mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0;
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 3850781..86a4fe1 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -178,6 +178,17 @@
     }
 
     /**
+     * @hide
+     */
+    @Override
+    protected void finalize() throws Throwable {
+        // We aren't on the UI thread here so we need to pass false to
+        // doDie
+        mViewRoot.die(false /* immediate */);
+    }
+
+
+    /**
      * Return a SurfacePackage for the root SurfaceControl of the embedded hierarchy.
      * Rather than be directly reparented using {@link SurfaceControl.Transaction} this
      * SurfacePackage should be passed to {@link SurfaceView#setChildSurfacePackage}
@@ -273,6 +284,6 @@
      */
     public void release() {
         // ViewRoot will release mSurfaceControl for us.
-        mViewRoot.die(false /* immediate */);
+        mViewRoot.die(true /* immediate */);
     }
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8739d9e..877c3f3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -22,6 +22,7 @@
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.InsetsState.LAST_TYPE;
+import static android.view.InsetsState.SIZE;
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -565,7 +566,7 @@
             new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT);
     boolean mPendingAlwaysConsumeSystemBars;
     private final InsetsState mTempInsets = new InsetsState();
-    private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[LAST_TYPE + 1];
+    private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[SIZE];
     final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
             = new ViewTreeObserver.InternalInsetsInfo();
 
@@ -2316,7 +2317,7 @@
                 || lp.type == TYPE_VOLUME_OVERLAY;
     }
 
-    private int dipToPx(int dip) {
+    int dipToPx(int dip) {
         final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
         return (int) (displayMetrics.density * dip + 0.5f);
     }
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index 31a4402..f7ca3c2 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -228,4 +228,12 @@
         }
         return mViewRoot.getTitle().toString();
     }
+
+    @Override
+    public int dipToPx(int dips) {
+        if (mViewRoot != null) {
+            return mViewRoot.dipToPx(dips);
+        }
+        return 0;
+    }
 }
diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java
index fb9d05e..792b974 100644
--- a/core/java/android/view/WindowInsetsAnimationController.java
+++ b/core/java/android/view/WindowInsetsAnimationController.java
@@ -181,4 +181,11 @@
      * @return {@code true} if the instance is cancelled, {@code false} otherwise.
      */
     boolean isCancelled();
+
+    /**
+     * @hide
+     * @return {@code true} when controller controls IME and IME has no insets (floating,
+     *  fullscreen or non-overlapping).
+     */
+    boolean hasZeroInsetsIme();
 }
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 6eb71f7..c43beea 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -52,6 +52,7 @@
 import com.android.internal.os.IResultReceiver;
 
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -146,7 +147,44 @@
      * Binder object used to update the session state.
      */
     @NonNull
-    private final IResultReceiver.Stub mSessionStateReceiver;
+    private final SessionStateReceiver mSessionStateReceiver;
+
+    private static class SessionStateReceiver extends IResultReceiver.Stub {
+        private final WeakReference<MainContentCaptureSession> mMainSession;
+
+        SessionStateReceiver(MainContentCaptureSession session) {
+            mMainSession = new WeakReference<>(session);
+        }
+
+        @Override
+        public void send(int resultCode, Bundle resultData) {
+            final MainContentCaptureSession mainSession = mMainSession.get();
+            if (mainSession == null) {
+                Log.w(TAG, "received result after mina session released");
+                return;
+            }
+            final IBinder binder;
+            if (resultData != null) {
+                // Change in content capture enabled.
+                final boolean hasEnabled = resultData.getBoolean(EXTRA_ENABLED_STATE);
+                if (hasEnabled) {
+                    final boolean disabled = (resultCode == RESULT_CODE_FALSE);
+                    mainSession.mDisabled.set(disabled);
+                    return;
+                }
+                binder = resultData.getBinder(EXTRA_BINDER);
+                if (binder == null) {
+                    Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result");
+                    mainSession.mHandler.post(() -> mainSession.resetSession(
+                            STATE_DISABLED | STATE_INTERNAL_ERROR));
+                    return;
+                }
+            } else {
+                binder = null;
+            }
+            mainSession.mHandler.post(() -> mainSession.onSessionStarted(resultCode, binder));
+        }
+    }
 
     protected MainContentCaptureSession(@NonNull Context context,
             @NonNull ContentCaptureManager manager, @NonNull Handler handler,
@@ -159,32 +197,7 @@
         final int logHistorySize = mManager.mOptions.logHistorySize;
         mFlushHistory = logHistorySize > 0 ? new LocalLog(logHistorySize) : null;
 
-        mSessionStateReceiver = new IResultReceiver.Stub() {
-            @Override
-            public void send(int resultCode, Bundle resultData) {
-                final IBinder binder;
-                if (resultData != null) {
-                    // Change in content capture enabled.
-                    final boolean hasEnabled = resultData.getBoolean(EXTRA_ENABLED_STATE);
-                    if (hasEnabled) {
-                        final boolean disabled = (resultCode == RESULT_CODE_FALSE);
-                        mDisabled.set(disabled);
-                        return;
-                    }
-                    binder = resultData.getBinder(EXTRA_BINDER);
-                    if (binder == null) {
-                        Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result");
-                        mHandler.post(() -> resetSession(
-                                STATE_DISABLED | STATE_INTERNAL_ERROR));
-                        return;
-                    }
-                } else {
-                    binder = null;
-                }
-                mHandler.post(() -> onSessionStarted(resultCode, binder));
-            }
-        };
-
+        mSessionStateReceiver = new SessionStateReceiver(this);
     }
 
     @Override
@@ -543,6 +556,11 @@
             Log.e(TAG, "Error destroying system-service session " + mId + " for "
                     + getDebugState() + ": " + e);
         }
+
+        if (mDirectServiceInterface != null) {
+            mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
+        }
+        mDirectServiceInterface = null;
     }
 
     // TODO(b/122454205): once we support multiple sessions, we might need to move some of these
diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
index 575a532..527286cf0 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -104,17 +104,18 @@
     public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
             boolean isHorizontalDivision, Rect insets) {
         this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets,
-                DOCKED_INVALID, false);
+                DOCKED_INVALID, false /* minimized */, true /* resizable */);
     }
 
     public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
         boolean isHorizontalDivision, Rect insets, int dockSide) {
         this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets,
-            dockSide, false);
+            dockSide, false /* minimized */, true /* resizable */);
     }
 
     public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
-            boolean isHorizontalDivision, Rect insets, int dockSide, boolean isMinimizedMode) {
+            boolean isHorizontalDivision, Rect insets, int dockSide, boolean isMinimizedMode,
+            boolean isHomeResizable) {
         mMinFlingVelocityPxPerSecond =
                 MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
         mMinDismissVelocityPxPerSecond =
@@ -132,8 +133,8 @@
                 com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1);
         mMinimalSizeResizableTask = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.default_minimal_size_resizable_task);
-        mTaskHeightInMinimizedMode = res.getDimensionPixelSize(
-                com.android.internal.R.dimen.task_height_of_minimized_mode);
+        mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize(
+                com.android.internal.R.dimen.task_height_of_minimized_mode) : 0;
         calculateTargets(isHorizontalDivision, dockSide);
         mFirstSplitTarget = mTargets.get(1);
         mLastSplitTarget = mTargets.get(mTargets.size() - 2);
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 997829e..69b32c2 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2678,4 +2678,9 @@
     // CATEGORY: SETTINGS
     // OS: R
     DEVICE_CONTROLS_SETTINGS = 1844;
+
+    // OPEN: Settings > Sound > Media
+    // CATEGORY: SETTINGS
+    // OS: R
+    MEDIA_CONTROLS_SETTINGS = 1845;
 }
diff --git a/core/proto/android/stats/sysui/notification_enums.proto b/core/proto/android/stats/sysui/notification_enums.proto
index 0983702..30bdeca 100644
--- a/core/proto/android/stats/sysui/notification_enums.proto
+++ b/core/proto/android/stats/sysui/notification_enums.proto
@@ -26,4 +26,5 @@
   IMPORTANCE_LOW = 2;  // Shows in shade, maybe status bar, no buzz/beep.
   IMPORTANCE_DEFAULT = 3;  // Shows everywhere, makes noise, no heads-up.
   IMPORTANCE_HIGH = 4;  // Shows everywhere, makes noise, heads-up, may full-screen.
+  IMPORTANCE_IMPORTANT_CONVERSATION = 5;  // High + isImportantConversation().
 }
diff --git a/core/res/res/drawable-car-night/car_dialog_button_background.xml b/core/res/res/drawable-car-night/car_dialog_button_background.xml
new file mode 100644
index 0000000..138cb38
--- /dev/null
+++ b/core/res/res/drawable-car-night/car_dialog_button_background.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true">
+        <ripple android:color="#2371cd">
+            <item android:id="@android:id/mask">
+                <color android:color="@*android:color/car_white_1000"/>
+            </item>
+        </ripple>
+    </item>
+    <item>
+        <ripple android:color="?android:attr/colorControlHighlight">
+            <item android:id="@android:id/mask">
+                <color android:color="@*android:color/car_white_1000"/>
+            </item>
+        </ripple>
+    </item>
+</selector>
diff --git a/core/res/res/drawable-car/car_dialog_button_background.xml b/core/res/res/drawable-car/car_dialog_button_background.xml
index 67506cb..a7d40bcd 100644
--- a/core/res/res/drawable-car/car_dialog_button_background.xml
+++ b/core/res/res/drawable-car/car_dialog_button_background.xml
@@ -14,9 +14,19 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="?android:attr/colorControlHighlight">
-    <item android:id="@android:id/mask">
-        <color android:color="@*android:color/car_white_1000" />
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true">
+        <ripple android:color="#4b9eff">
+            <item android:id="@android:id/mask">
+                <color android:color="@*android:color/car_white_1000"/>
+            </item>
+        </ripple>
     </item>
-</ripple>
+    <item>
+        <ripple android:color="?android:attr/colorControlHighlight">
+            <item android:id="@android:id/mask">
+                <color android:color="@*android:color/car_white_1000"/>
+            </item>
+        </ripple>
+    </item>
+</selector>
diff --git a/core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch.apk b/core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch-v1.apk
similarity index 73%
copy from core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch.apk
copy to core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch-v1.apk
index 1dc1e99..add4aa0 100644
--- a/core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch.apk
+++ b/core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch-v1.apk
Binary files differ
diff --git a/core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch.apk b/core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch-v2.apk
similarity index 73%
copy from core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch.apk
copy to core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch-v2.apk
index 1dc1e99..e55eb90 100644
--- a/core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch.apk
+++ b/core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch-v2.apk
Binary files differ
diff --git a/core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch.apk b/core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch-v3.apk
similarity index 73%
rename from core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch.apk
rename to core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch-v3.apk
index 1dc1e99..de23558 100644
--- a/core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch.apk
+++ b/core/tests/coretests/assets/SourceStampVerifierTest/stamp-apk-hash-mismatch-v3.apk
Binary files differ
diff --git a/core/tests/coretests/assets/SourceStampVerifierTest/stamp-certificate-mismatch.apk b/core/tests/coretests/assets/SourceStampVerifierTest/stamp-certificate-mismatch.apk
index 562805c..f1105f9 100644
--- a/core/tests/coretests/assets/SourceStampVerifierTest/stamp-certificate-mismatch.apk
+++ b/core/tests/coretests/assets/SourceStampVerifierTest/stamp-certificate-mismatch.apk
Binary files differ
diff --git a/core/tests/coretests/assets/SourceStampVerifierTest/stamp-malformed-signature.apk b/core/tests/coretests/assets/SourceStampVerifierTest/stamp-malformed-signature.apk
index 2723cc8..d28774a 100644
--- a/core/tests/coretests/assets/SourceStampVerifierTest/stamp-malformed-signature.apk
+++ b/core/tests/coretests/assets/SourceStampVerifierTest/stamp-malformed-signature.apk
Binary files differ
diff --git a/core/tests/coretests/assets/SourceStampVerifierTest/stamp-without-block.apk b/core/tests/coretests/assets/SourceStampVerifierTest/stamp-without-block.apk
index 9dec2f5..604fe6f 100644
--- a/core/tests/coretests/assets/SourceStampVerifierTest/stamp-without-block.apk
+++ b/core/tests/coretests/assets/SourceStampVerifierTest/stamp-without-block.apk
Binary files differ
diff --git a/core/tests/coretests/assets/SourceStampVerifierTest/valid-stamp.apk b/core/tests/coretests/assets/SourceStampVerifierTest/valid-stamp.apk
index 8056e0b..2f2a592 100644
--- a/core/tests/coretests/assets/SourceStampVerifierTest/valid-stamp.apk
+++ b/core/tests/coretests/assets/SourceStampVerifierTest/valid-stamp.apk
Binary files differ
diff --git a/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java b/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java
index 37b2817..81d54b5 100644
--- a/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java
+++ b/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java
@@ -26,8 +26,11 @@
 
 import android.content.Context;
 
-import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
 
+import libcore.io.Streams;
+
+import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -46,8 +49,20 @@
 @RunWith(JUnit4.class)
 public class SourceStampVerifierTest {
 
-    private final Context mContext =
-            InstrumentationRegistry.getInstrumentation().getTargetContext();
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+
+    private File mPrimaryApk;
+    private File mSecondaryApk;
+
+    @After
+    public void tearDown() throws Exception {
+        if (mPrimaryApk != null) {
+            mPrimaryApk.delete();
+        }
+        if (mSecondaryApk != null) {
+            mSecondaryApk.delete();
+        }
+    }
 
     @Test
     public void testSourceStamp_noStamp() throws Exception {
@@ -63,17 +78,11 @@
 
     @Test
     public void testSourceStamp_correctSignature() throws Exception {
-        File testApk = getApk("SourceStampVerifierTest/valid-stamp.apk");
-        ZipFile apkZipFile = new ZipFile(testApk);
-        ZipEntry stampCertZipEntry = apkZipFile.getEntry("stamp-cert-sha256");
-        int size = (int) stampCertZipEntry.getSize();
-        byte[] expectedStampCertHash = new byte[size];
-        try (InputStream inputStream = apkZipFile.getInputStream(stampCertZipEntry)) {
-            inputStream.read(expectedStampCertHash);
-        }
+        mPrimaryApk = getApk("SourceStampVerifierTest/valid-stamp.apk");
+        byte[] expectedStampCertHash = getSourceStampCertificateHashFromApk(mPrimaryApk);
 
         SourceStampVerificationResult result =
-                SourceStampVerifier.verify(testApk.getAbsolutePath());
+                SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath());
 
         assertTrue(result.isPresent());
         assertTrue(result.isVerified());
@@ -85,10 +94,10 @@
 
     @Test
     public void testSourceStamp_signatureMissing() throws Exception {
-        File testApk = getApk("SourceStampVerifierTest/stamp-without-block.apk");
+        mPrimaryApk = getApk("SourceStampVerifierTest/stamp-without-block.apk");
 
         SourceStampVerificationResult result =
-                SourceStampVerifier.verify(testApk.getAbsolutePath());
+                SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath());
 
         assertTrue(result.isPresent());
         assertFalse(result.isVerified());
@@ -97,10 +106,10 @@
 
     @Test
     public void testSourceStamp_certificateMismatch() throws Exception {
-        File testApk = getApk("SourceStampVerifierTest/stamp-certificate-mismatch.apk");
+        mPrimaryApk = getApk("SourceStampVerifierTest/stamp-certificate-mismatch.apk");
 
         SourceStampVerificationResult result =
-                SourceStampVerifier.verify(testApk.getAbsolutePath());
+                SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath());
 
         assertTrue(result.isPresent());
         assertFalse(result.isVerified());
@@ -108,11 +117,35 @@
     }
 
     @Test
-    public void testSourceStamp_apkHashMismatch() throws Exception {
-        File testApk = getApk("SourceStampVerifierTest/stamp-apk-hash-mismatch.apk");
+    public void testSourceStamp_apkHashMismatch_v1SignatureScheme() throws Exception {
+        mPrimaryApk = getApk("SourceStampVerifierTest/stamp-apk-hash-mismatch-v1.apk");
 
         SourceStampVerificationResult result =
-                SourceStampVerifier.verify(testApk.getAbsolutePath());
+                SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath());
+
+        assertTrue(result.isPresent());
+        assertFalse(result.isVerified());
+        assertNull(result.getCertificate());
+    }
+
+    @Test
+    public void testSourceStamp_apkHashMismatch_v2SignatureScheme() throws Exception {
+        mPrimaryApk = getApk("SourceStampVerifierTest/stamp-apk-hash-mismatch-v2.apk");
+
+        SourceStampVerificationResult result =
+                SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath());
+
+        assertTrue(result.isPresent());
+        assertFalse(result.isVerified());
+        assertNull(result.getCertificate());
+    }
+
+    @Test
+    public void testSourceStamp_apkHashMismatch_v3SignatureScheme() throws Exception {
+        mPrimaryApk = getApk("SourceStampVerifierTest/stamp-apk-hash-mismatch-v3.apk");
+
+        SourceStampVerificationResult result =
+                SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath());
 
         assertTrue(result.isPresent());
         assertFalse(result.isVerified());
@@ -121,10 +154,10 @@
 
     @Test
     public void testSourceStamp_malformedSignature() throws Exception {
-        File testApk = getApk("SourceStampVerifierTest/stamp-malformed-signature.apk");
+        mPrimaryApk = getApk("SourceStampVerifierTest/stamp-malformed-signature.apk");
 
         SourceStampVerificationResult result =
-                SourceStampVerifier.verify(testApk.getAbsolutePath());
+                SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath());
 
         assertTrue(result.isPresent());
         assertFalse(result.isVerified());
@@ -133,21 +166,14 @@
 
     @Test
     public void testSourceStamp_multiApk_validStamps() throws Exception {
-        File testApk1 = getApk("SourceStampVerifierTest/valid-stamp.apk");
-        File testApk2 = getApk("SourceStampVerifierTest/valid-stamp.apk");
-        ZipFile apkZipFile = new ZipFile(testApk1);
-        ZipEntry stampCertZipEntry = apkZipFile.getEntry("stamp-cert-sha256");
-        int size = (int) stampCertZipEntry.getSize();
-        byte[] expectedStampCertHash = new byte[size];
-        try (InputStream inputStream = apkZipFile.getInputStream(stampCertZipEntry)) {
-            inputStream.read(expectedStampCertHash);
-        }
+        mPrimaryApk = getApk("SourceStampVerifierTest/valid-stamp.apk");
+        mSecondaryApk = getApk("SourceStampVerifierTest/valid-stamp.apk");
+        byte[] expectedStampCertHash = getSourceStampCertificateHashFromApk(mPrimaryApk);
         List<String> apkFiles = new ArrayList<>();
-        apkFiles.add(testApk1.getAbsolutePath());
-        apkFiles.add(testApk2.getAbsolutePath());
+        apkFiles.add(mPrimaryApk.getAbsolutePath());
+        apkFiles.add(mSecondaryApk.getAbsolutePath());
 
-        SourceStampVerificationResult result =
-                SourceStampVerifier.verify(apkFiles);
+        SourceStampVerificationResult result = SourceStampVerifier.verify(apkFiles);
 
         assertTrue(result.isPresent());
         assertTrue(result.isVerified());
@@ -159,14 +185,13 @@
 
     @Test
     public void testSourceStamp_multiApk_invalidStamps() throws Exception {
-        File testApk1 = getApk("SourceStampVerifierTest/valid-stamp.apk");
-        File testApk2 = getApk("SourceStampVerifierTest/stamp-apk-hash-mismatch.apk");
+        mPrimaryApk = getApk("SourceStampVerifierTest/valid-stamp.apk");
+        mSecondaryApk = getApk("SourceStampVerifierTest/stamp-apk-hash-mismatch-v3.apk");
         List<String> apkFiles = new ArrayList<>();
-        apkFiles.add(testApk1.getAbsolutePath());
-        apkFiles.add(testApk2.getAbsolutePath());
+        apkFiles.add(mPrimaryApk.getAbsolutePath());
+        apkFiles.add(mSecondaryApk.getAbsolutePath());
 
-        SourceStampVerificationResult result =
-                SourceStampVerifier.verify(apkFiles);
+        SourceStampVerificationResult result = SourceStampVerifier.verify(apkFiles);
 
         assertTrue(result.isPresent());
         assertFalse(result.isVerified());
@@ -174,10 +199,16 @@
     }
 
     private File getApk(String apkPath) throws IOException {
-        File testApk = File.createTempFile("SourceStampApk", ".apk");
+        File apk = File.createTempFile("SourceStampApk", ".apk");
         try (InputStream inputStream = mContext.getAssets().open(apkPath)) {
-            Files.copy(inputStream, testApk.toPath(), REPLACE_EXISTING);
+            Files.copy(inputStream, apk.toPath(), REPLACE_EXISTING);
         }
-        return testApk;
+        return apk;
+    }
+
+    private byte[] getSourceStampCertificateHashFromApk(File apk) throws IOException {
+        ZipFile apkZipFile = new ZipFile(apk);
+        ZipEntry stampCertZipEntry = apkZipFile.getEntry("stamp-cert-sha256");
+        return Streams.readFully(apkZipFile.getInputStream(stampCertZipEntry));
     }
 }
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index e199cf4..93fe06a 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -71,6 +71,7 @@
  */
 public abstract class MediaRoute2ProviderService extends Service {
     private static final String TAG = "MR2ProviderService";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     /**
      * The {@link Intent} action that must be declared as handled by the service.
@@ -238,6 +239,11 @@
             @NonNull RoutingSessionInfo sessionInfo) {
         Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
 
+        if (DEBUG) {
+            Log.d(TAG, "notifySessionCreated: Creating a session. requestId=" + requestId
+                    + ", sessionInfo=" + sessionInfo);
+        }
+
         if (requestId != REQUEST_ID_NONE && !removeRequestId(requestId)) {
             Log.w(TAG, "notifySessionCreated: The requestId doesn't exist. requestId=" + requestId);
             return;
@@ -269,6 +275,10 @@
     public final void notifySessionUpdated(@NonNull RoutingSessionInfo sessionInfo) {
         Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
 
+        if (DEBUG) {
+            Log.d(TAG, "notifySessionUpdated: Updating session id=" + sessionInfo);
+        }
+
         String sessionId = sessionInfo.getId();
         synchronized (mSessionLock) {
             if (mSessionInfo.containsKey(sessionId)) {
@@ -299,6 +309,10 @@
         if (TextUtils.isEmpty(sessionId)) {
             throw new IllegalArgumentException("sessionId must not be empty");
         }
+        if (DEBUG) {
+            Log.d(TAG, "notifySessionReleased: Releasing session id=" + sessionId);
+        }
+
         RoutingSessionInfo sessionInfo;
         synchronized (mSessionLock) {
             sessionInfo = mSessionInfo.remove(sessionId);
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 470c52a..6fe48a4 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -378,6 +378,7 @@
      */
     public void transferTo(@NonNull MediaRoute2Info route) {
         Objects.requireNonNull(route, "route must not be null");
+        Log.v(TAG, "Transferring to route: " + route);
         transfer(getCurrentController(), route);
     }
 
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 5a7c87e0..7732638 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -321,6 +321,8 @@
         Objects.requireNonNull(packageName, "packageName must not be null");
         Objects.requireNonNull(route, "route must not be null");
 
+        Log.v(TAG, "Selecting route. packageName= " + packageName + ", route=" + route);
+
         List<RoutingSessionInfo> sessionInfos = getRoutingSessions(packageName);
         RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
         transfer(targetSession, route);
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index af63070..57a04fd 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -29,6 +29,7 @@
 @SystemApi
 public class MediaEvent extends FilterEvent {
     private long mNativeContext;
+    private boolean mReleased = false;
     private final Object mLock = new Object();
 
     private native Long nativeGetAudioHandle();
@@ -181,7 +182,21 @@
      */
     @Override
     protected void finalize() {
-        nativeFinalize();
-        mNativeContext = 0;
+        release();
+    }
+
+    /**
+     * Releases the MediaEvent object.
+     * @hide
+     */
+    public void release() {
+        synchronized (mLock) {
+            if (mReleased) {
+                return;
+            }
+            nativeFinalize();
+            mNativeContext = 0;
+            mReleased = true;
+        }
     }
 }
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index f970b59..0b0e162 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -235,7 +235,10 @@
 void JMediaCodec::releaseAsync() {
     std::call_once(mAsyncReleaseFlag, [this] {
         if (mCodec != NULL) {
-            mCodec->releaseAsync(new AMessage(kWhatAsyncReleaseComplete, this));
+            sp<AMessage> notify = new AMessage(kWhatAsyncReleaseComplete, this);
+            // Hold strong reference to this until async release is complete
+            notify->setObject("this", this);
+            mCodec->releaseAsync(notify);
         }
         mInitStatus = NO_INIT;
     });
@@ -1088,8 +1091,11 @@
         }
         case kWhatAsyncReleaseComplete:
         {
-            mCodec.clear();
-            mLooper->stop();
+            if (mLooper != NULL) {
+                mLooper->unregisterHandler(id());
+                mLooper->stop();
+                mLooper.clear();
+            }
             break;
         }
         default:
@@ -1104,7 +1110,7 @@
 using namespace android;
 
 static sp<JMediaCodec> setMediaCodec(
-        JNIEnv *env, jobject thiz, const sp<JMediaCodec> &codec) {
+        JNIEnv *env, jobject thiz, const sp<JMediaCodec> &codec, bool release = true) {
     sp<JMediaCodec> old = (JMediaCodec *)env->CallLongMethod(thiz, gFields.lockAndGetContextID);
     if (codec != NULL) {
         codec->incStrong(thiz);
@@ -1115,7 +1121,9 @@
          * its message handler, doing release() from there will deadlock
          * (as MediaCodec::release() post synchronous message to the same looper)
          */
-        old->release();
+        if (release) {
+            old->release();
+        }
         old->decStrong(thiz);
     }
     env->CallVoidMethod(thiz, gFields.setAndUnlockContextID, (jlong)codec.get());
@@ -1130,7 +1138,8 @@
 }
 
 static void android_media_MediaCodec_release(JNIEnv *env, jobject thiz) {
-    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+    // Clear Java native reference.
+    sp<JMediaCodec> codec = setMediaCodec(env, thiz, nullptr, false /* release */);
     if (codec != NULL) {
         codec->releaseAsync();
     }
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 7e72140..e8f18a5 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -314,8 +314,9 @@
     if (mIonHandle != NULL) {
         delete mIonHandle;
     }
-    if (mC2Buffer != NULL) {
-        mC2Buffer->unregisterOnDestroyNotify(&DestroyCallback, this);
+    std::shared_ptr<C2Buffer> pC2Buffer = mC2Buffer.lock();
+    if (pC2Buffer != NULL) {
+        pC2Buffer->unregisterOnDestroyNotify(&DestroyCallback, this);
     }
 }
 
@@ -340,15 +341,17 @@
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock};
     context->mBlock = block;
-    mC2Buffer = context->toC2Buffer(0, mDataLength);
+    std::shared_ptr<C2Buffer> pC2Buffer = context->toC2Buffer(0, mDataLength);
+    context->mBuffer = pC2Buffer;
+    mC2Buffer = pC2Buffer;
     if (mAvHandle->numInts > 0) {
         // use first int in the native_handle as the index
         int index = mAvHandle->data[mAvHandle->numFds];
         std::shared_ptr<C2Param> c2param = std::make_shared<C2DataIdInfo>(index, mDataId);
         std::shared_ptr<C2Info> info(std::static_pointer_cast<C2Info>(c2param));
-        mC2Buffer->setInfo(info);
+        pC2Buffer->setInfo(info);
     }
-    mC2Buffer->registerOnDestroyNotify(&DestroyCallback, this);
+    pC2Buffer->registerOnDestroyNotify(&DestroyCallback, this);
     jobject linearBlock =
             env->NewObject(
                     env->FindClass("android/media/MediaCodec$LinearBlock"),
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index c469a3a..83e9db7 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -130,7 +130,7 @@
     jweak mMediaEventObj;
     jweak mLinearBlockObj;
     C2HandleIon* mIonHandle;
-    std::shared_ptr<C2Buffer> mC2Buffer;
+    std::weak_ptr<C2Buffer> mC2Buffer;
 };
 
 struct Filter : public RefBase {
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index 1738091..1eead62 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -48,17 +48,21 @@
 import com.android.systemui.car.window.OverlayPanelViewController;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.StatusBarState;
 
+import java.util.concurrent.Executor;
+
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
 /** View controller for the notification panel. */
 @Singleton
-public class NotificationPanelViewController extends OverlayPanelViewController {
+public class NotificationPanelViewController extends OverlayPanelViewController
+        implements CommandQueue.Callbacks {
 
     private static final boolean DEBUG = true;
     private static final String TAG = "NotificationPanelViewController";
@@ -68,12 +72,14 @@
     private final CarServiceProvider mCarServiceProvider;
     private final IStatusBarService mBarService;
     private final CommandQueue mCommandQueue;
+    private final Executor mUiBgExecutor;
     private final NotificationDataManager mNotificationDataManager;
     private final CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
     private final CarNotificationListener mCarNotificationListener;
     private final NotificationClickHandlerFactory mNotificationClickHandlerFactory;
     private final StatusBarStateController mStatusBarStateController;
     private final boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen;
+    private final NotificationVisibilityLogger mNotificationVisibilityLogger;
 
     private float mInitialBackgroundAlpha;
     private float mBackgroundAlphaDiff;
@@ -98,6 +104,7 @@
             @Main Resources resources,
             OverlayViewGlobalStateController overlayViewGlobalStateController,
             FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
+            @UiBackground Executor uiBgExecutor,
 
             /* Other things */
             CarServiceProvider carServiceProvider,
@@ -110,6 +117,7 @@
             CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
             CarNotificationListener carNotificationListener,
             NotificationClickHandlerFactory notificationClickHandlerFactory,
+            NotificationVisibilityLogger notificationVisibilityLogger,
 
             /* Things that need to be replaced */
             StatusBarStateController statusBarStateController
@@ -121,12 +129,15 @@
         mCarServiceProvider = carServiceProvider;
         mBarService = barService;
         mCommandQueue = commandQueue;
+        mUiBgExecutor = uiBgExecutor;
         mNotificationDataManager = notificationDataManager;
         mCarUxRestrictionManagerWrapper = carUxRestrictionManagerWrapper;
         mCarNotificationListener = carNotificationListener;
         mNotificationClickHandlerFactory = notificationClickHandlerFactory;
         mStatusBarStateController = statusBarStateController;
+        mNotificationVisibilityLogger = notificationVisibilityLogger;
 
+        mCommandQueue.addCallback(this);
         // Notification background setup.
         mInitialBackgroundAlpha = (float) mResources.getInteger(
                 R.integer.config_initialNotificationBackgroundAlpha) / 100;
@@ -151,12 +162,36 @@
                         .config_enableHeadsUpNotificationWhenNotificationShadeOpen);
     }
 
+    // CommandQueue.Callbacks
+
+    @Override
+    public void animateExpandNotificationsPanel() {
+        if (!isPanelExpanded()) {
+            toggle();
+        }
+    }
+
+    @Override
+    public void animateCollapsePanels(int flags, boolean force) {
+        if (isPanelExpanded()) {
+            toggle();
+        }
+    }
+
+    // OverlayViewController
+
     @Override
     protected void onFinishInflate() {
         reinflate();
     }
 
     @Override
+    protected void hideInternal() {
+        super.hideInternal();
+        mNotificationVisibilityLogger.stop();
+    }
+
+    @Override
     protected boolean shouldShowNavigationBar() {
         return true;
     }
@@ -197,6 +232,11 @@
                 mUnseenCountUpdateListener.onUnseenCountUpdate(
                         mNotificationDataManager.getUnseenNotificationCount());
             }
+            mCarNotificationListener.setNotificationsShown(
+                    mNotificationDataManager.getSeenNotifications());
+            // This logs both when the notification panel is expanded and when the notification
+            // panel is scrolled.
+            mNotificationVisibilityLogger.log(isPanelExpanded());
         });
 
         mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
@@ -332,6 +372,8 @@
         mNotificationDataManager.clearAll();
     }
 
+    // OverlayPanelViewController
+
     @Override
     protected boolean shouldAnimateCollapsePanel() {
         return true;
@@ -364,6 +406,30 @@
     }
 
     @Override
+    protected void onPanelVisible(boolean visible) {
+        super.onPanelVisible(visible);
+        mUiBgExecutor.execute(() -> {
+            try {
+                if (visible) {
+                    // When notification panel is open even just a bit, we want to clear
+                    // notification effects.
+                    boolean clearNotificationEffects =
+                            mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
+                    mBarService.onPanelRevealed(clearNotificationEffects,
+                            mNotificationDataManager.getVisibleNotifications().size());
+                } else {
+                    mBarService.onPanelHidden();
+                }
+            } catch (RemoteException ex) {
+                // Won't fail unless the world has ended.
+                Log.e(TAG, String.format(
+                        "Unable to notify StatusBarService of panel visibility: %s", visible));
+            }
+        });
+
+    }
+
+    @Override
     protected void onPanelExpanded(boolean expand) {
         super.onPanelExpanded(expand);
 
@@ -373,6 +439,9 @@
             }
             clearNotificationEffects();
         }
+        if (!expand) {
+            mNotificationVisibilityLogger.log(isPanelExpanded());
+        }
     }
 
     /**
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationVisibilityLogger.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationVisibilityLogger.java
new file mode 100644
index 0000000..44c8197
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationVisibilityLogger.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.notification;
+
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.car.notification.AlertEntry;
+import com.android.car.notification.NotificationDataManager;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.dagger.qualifiers.UiBackground;
+
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Handles notification logging, in particular, logging which notifications are visible and which
+ * are not.
+ */
+@Singleton
+public class NotificationVisibilityLogger {
+
+    private static final String TAG = "NotificationVisibilityLogger";
+
+    private final ArraySet<NotificationVisibility> mCurrentlyVisible = new ArraySet<>();
+    private final ArraySet<NotificationVisibility> mNewlyVisible = new ArraySet<>();
+    private final ArraySet<NotificationVisibility> mPreviouslyVisible = new ArraySet<>();
+    private final ArraySet<NotificationVisibility> mTmpCurrentlyVisible = new ArraySet<>();
+
+    private final IStatusBarService mBarService;
+    private final Executor mUiBgExecutor;
+    private final NotificationDataManager mNotificationDataManager;
+
+    private boolean mIsVisible;
+
+    private final Runnable mVisibilityReporter = new Runnable() {
+
+        @Override
+        public void run() {
+            if (mIsVisible) {
+                int count = mNotificationDataManager.getVisibleNotifications().size();
+                for (AlertEntry alertEntry : mNotificationDataManager.getVisibleNotifications()) {
+                    NotificationVisibility visObj = NotificationVisibility.obtain(
+                            alertEntry.getKey(),
+                            /* rank= */ -1,
+                            count,
+                            mIsVisible,
+                            NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA);
+                    mTmpCurrentlyVisible.add(visObj);
+                    if (!mCurrentlyVisible.contains(visObj)) {
+                        mNewlyVisible.add(visObj);
+                    }
+                }
+            }
+            mPreviouslyVisible.addAll(mCurrentlyVisible);
+            mPreviouslyVisible.removeAll(mTmpCurrentlyVisible);
+            onNotificationVisibilityChanged(mNewlyVisible, mPreviouslyVisible);
+
+            recycleAllVisibilityObjects(mCurrentlyVisible);
+            mCurrentlyVisible.addAll(mTmpCurrentlyVisible);
+
+            recycleAllVisibilityObjects(mPreviouslyVisible);
+            recycleAllVisibilityObjects(mNewlyVisible);
+            recycleAllVisibilityObjects(mTmpCurrentlyVisible);
+        }
+    };
+
+    @Inject
+    public NotificationVisibilityLogger(
+            @UiBackground Executor uiBgExecutor,
+            IStatusBarService barService,
+            NotificationDataManager notificationDataManager) {
+        mUiBgExecutor = uiBgExecutor;
+        mBarService = barService;
+        mNotificationDataManager = notificationDataManager;
+    }
+
+    /** Triggers a visibility report update to be sent to StatusBarService. */
+    public void log(boolean isVisible) {
+        mIsVisible = isVisible;
+        mUiBgExecutor.execute(mVisibilityReporter);
+    }
+
+    /** Stops logging, clearing all visibility objects. */
+    public void stop() {
+        recycleAllVisibilityObjects(mCurrentlyVisible);
+    }
+
+    /**
+     * Notify StatusBarService of change in notifications' visibility.
+     */
+    private void onNotificationVisibilityChanged(
+            Set<NotificationVisibility> newlyVisible, Set<NotificationVisibility> noLongerVisible) {
+        if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
+            return;
+        }
+
+        try {
+            mBarService.onNotificationVisibilityChanged(
+                    cloneVisibilitiesAsArr(newlyVisible), cloneVisibilitiesAsArr(noLongerVisible));
+        } catch (RemoteException e) {
+            // Won't fail unless the world has ended.
+            Log.e(TAG, "Failed to notify StatusBarService of notification visibility change");
+        }
+    }
+
+    /**
+     * Clears array and recycles NotificationVisibility objects for reuse.
+     */
+    private static void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
+        for (int i = 0; i < array.size(); i++) {
+            array.valueAt(i).recycle();
+        }
+        array.clear();
+    }
+
+    /**
+     * Converts Set of NotificationVisibility objects to primitive array.
+     */
+    private static NotificationVisibility[] cloneVisibilitiesAsArr(Set<NotificationVisibility> c) {
+        NotificationVisibility[] array = new NotificationVisibility[c.size()];
+        int i = 0;
+        for (NotificationVisibility nv : c) {
+            if (nv != null) {
+                array[i] = nv.clone();
+            }
+            i++;
+        }
+        return array;
+    }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java
new file mode 100644
index 0000000..89dac58
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.car.notification.AlertEntry;
+import com.android.car.notification.NotificationDataManager;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class NotificationVisibilityLoggerTest extends SysuiTestCase {
+
+    private static final String PKG = "package_1";
+    private static final String OP_PKG = "OpPackage";
+    private static final int ID = 1;
+    private static final String TAG = "Tag";
+    private static final int UID = 2;
+    private static final int INITIAL_PID = 3;
+    private static final String CHANNEL_ID = "CHANNEL_ID";
+    private static final String CONTENT_TITLE = "CONTENT_TITLE";
+    private static final String OVERRIDE_GROUP_KEY = "OVERRIDE_GROUP_KEY";
+    private static final long POST_TIME = 12345L;
+    private static final UserHandle USER_HANDLE = new UserHandle(12);
+
+    @Mock
+    private IStatusBarService mBarService;
+    @Mock
+    private NotificationDataManager mNotificationDataManager;
+
+    private NotificationVisibilityLogger mNotificationVisibilityLogger;
+    private FakeExecutor mUiBgExecutor;
+    private AlertEntry mMessageNotification;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(/* testClass= */this);
+
+        mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
+        Notification.Builder mNotificationBuilder1 = new Notification.Builder(mContext, CHANNEL_ID)
+                .setContentTitle(CONTENT_TITLE);
+        mMessageNotification = new AlertEntry(new StatusBarNotification(PKG, OP_PKG,
+                ID, TAG, UID, INITIAL_PID, mNotificationBuilder1.build(), USER_HANDLE,
+                OVERRIDE_GROUP_KEY, POST_TIME));
+
+        when(mNotificationDataManager.getVisibleNotifications()).thenReturn(
+                Collections.singletonList(mMessageNotification));
+
+        mNotificationVisibilityLogger = new NotificationVisibilityLogger(
+                mUiBgExecutor, mBarService, mNotificationDataManager);
+    }
+
+    @Test
+    public void log_notifiesStatusBarService() throws RemoteException {
+        mNotificationVisibilityLogger.log(/* isVisible= */ true);
+        mUiBgExecutor.runNextReady();
+
+        verify(mBarService).onNotificationVisibilityChanged(
+                any(NotificationVisibility[].class), any(NotificationVisibility[].class));
+    }
+
+    @Test
+    public void log_isVisibleIsTrue_notifiesOfNewlyVisibleItems() throws RemoteException {
+        ArgumentCaptor<NotificationVisibility[]> newlyVisibleCaptor =
+                ArgumentCaptor.forClass(NotificationVisibility[].class);
+        ArgumentCaptor<NotificationVisibility[]> previouslyVisibleCaptor =
+                ArgumentCaptor.forClass(NotificationVisibility[].class);
+
+        mNotificationVisibilityLogger.log(/* isVisible= */ true);
+        mUiBgExecutor.runNextReady();
+
+        verify(mBarService).onNotificationVisibilityChanged(
+                newlyVisibleCaptor.capture(), previouslyVisibleCaptor.capture());
+        assertThat(newlyVisibleCaptor.getValue().length).isEqualTo(1);
+        assertThat(previouslyVisibleCaptor.getValue().length).isEqualTo(0);
+    }
+
+    @Test
+    public void log_isVisibleIsFalse_notifiesOfPreviouslyVisibleItems() throws RemoteException {
+        ArgumentCaptor<NotificationVisibility[]> newlyVisibleCaptor =
+                ArgumentCaptor.forClass(NotificationVisibility[].class);
+        ArgumentCaptor<NotificationVisibility[]> previouslyVisibleCaptor =
+                ArgumentCaptor.forClass(NotificationVisibility[].class);
+        mNotificationVisibilityLogger.log(/* isVisible= */ true);
+        mUiBgExecutor.runNextReady();
+        reset(mBarService);
+
+        mNotificationVisibilityLogger.log(/* isVisible= */ false);
+        mUiBgExecutor.runNextReady();
+
+        verify(mBarService).onNotificationVisibilityChanged(
+                newlyVisibleCaptor.capture(), previouslyVisibleCaptor.capture());
+        assertThat(previouslyVisibleCaptor.getValue().length).isEqualTo(1);
+        assertThat(newlyVisibleCaptor.getValue().length).isEqualTo(0);
+    }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index c04a1ba..d05e6e1 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -164,7 +164,8 @@
         Settings.Secure.AWARE_TAP_PAUSE_GESTURE_COUNT,
         Settings.Secure.AWARE_TAP_PAUSE_TOUCH_COUNT,
         Settings.Secure.PEOPLE_STRIP,
+        Settings.Secure.MEDIA_CONTROLS_RESUME,
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
-        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 76746e5..fa810bd 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -243,6 +243,7 @@
         VALIDATORS.put(Secure.DISPLAY_DENSITY_FORCED, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.TAP_GESTURE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.PEOPLE_STRIP, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.MEDIA_CONTROLS_RESUME, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
                 new InclusiveIntegerRangeValidator(
                         Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 4e17062..b64a9e7 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -39,6 +39,8 @@
     <uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <!-- ACCESS_BACKGROUND_LOCATION is needed for testing purposes only. -->
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
diff --git a/packages/SoundPicker/AndroidManifest.xml b/packages/SoundPicker/AndroidManifest.xml
index 9d08182..73d99a5 100644
--- a/packages/SoundPicker/AndroidManifest.xml
+++ b/packages/SoundPicker/AndroidManifest.xml
@@ -11,6 +11,7 @@
 
     <application
             android:allowBackup="false"
+            android:label="@string/app_label"
             android:supportsRtl="true">
         <receiver android:name="RingtoneReceiver">
             <intent-filter>
diff --git a/packages/SoundPicker/res/values/strings.xml b/packages/SoundPicker/res/values/strings.xml
index 56ed5fd..04a2c2b 100644
--- a/packages/SoundPicker/res/values/strings.xml
+++ b/packages/SoundPicker/res/values/strings.xml
@@ -37,4 +37,7 @@
     <string name="unable_to_add_ringtone">Unable to add custom ringtone</string>
     <!-- Text for the Toast displayed when deleting a custom ringtone fails. -->
     <string name="unable_to_delete_ringtone">Unable to delete custom ringtone</string>
+
+    <!-- Text for the name of the app. [CHAR LIMIT=12] -->
+    <string name="app_label">Sounds</string>
 </resources>
diff --git a/packages/SystemUI/res/drawable/bubble_stack_user_education_bg_rtl.xml b/packages/SystemUI/res/drawable/bubble_stack_user_education_bg_rtl.xml
new file mode 100644
index 0000000..c7baba1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bubble_stack_user_education_bg_rtl.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <solid android:color="?android:attr/colorAccent"/>
+    <corners
+        android:bottomLeftRadius="360dp"
+        android:topLeftRadius="360dp" />
+</shape>
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index 6b61046..a87c7b3 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -50,8 +50,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:paddingHorizontal="24dp"
-        android:paddingTop="16dp"
-        android:paddingBottom="24dp"
+        android:paddingVertical="12dp"
         android:textSize="12sp"
         android:gravity="center_horizontal"
         android:accessibilityLiveRegion="polite"
@@ -60,22 +59,23 @@
     <LinearLayout
         android:id="@+id/button_bar"
         android:layout_width="match_parent"
-        android:layout_height="72dip"
-        android:paddingTop="24dp"
-        android:layout_gravity="center_vertical"
+        android:layout_height="88dp"
         style="?android:attr/buttonBarStyle"
-        android:orientation="horizontal">
+        android:orientation="horizontal"
+        android:paddingTop="16dp">
         <Space android:id="@+id/leftSpacer"
-            android:layout_width="12dp"
+            android:layout_width="8dp"
             android:layout_height="match_parent"
             android:visibility="visible" />
         <!-- Negative Button -->
         <Button android:id="@+id/button_negative"
             android:layout_width="wrap_content"
-            android:layout_height="match_parent"
+            android:layout_height="wrap_content"
             style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-            android:gravity="center"
-            android:maxLines="2"/>
+            android:layout_gravity="center_vertical"
+            android:ellipsize="end"
+            android:maxLines="2"
+            android:maxWidth="@dimen/biometric_dialog_button_negative_max_width"/>
         <Space android:id="@+id/middleSpacer"
             android:layout_width="0dp"
             android:layout_height="match_parent"
@@ -84,23 +84,27 @@
         <!-- Positive Button -->
         <Button android:id="@+id/button_positive"
             android:layout_width="wrap_content"
-            android:layout_height="match_parent"
+            android:layout_height="wrap_content"
             style="@*android:style/Widget.DeviceDefault.Button.Colored"
-            android:gravity="center"
+            android:layout_gravity="center_vertical"
+            android:ellipsize="end"
             android:maxLines="2"
+            android:maxWidth="@dimen/biometric_dialog_button_positive_max_width"
             android:text="@string/biometric_dialog_confirm"
             android:visibility="gone"/>
         <!-- Try Again Button -->
         <Button android:id="@+id/button_try_again"
             android:layout_width="wrap_content"
-            android:layout_height="match_parent"
+            android:layout_height="wrap_content"
             style="@*android:style/Widget.DeviceDefault.Button.Colored"
-            android:gravity="center"
+            android:layout_gravity="center_vertical"
+            android:ellipsize="end"
             android:maxLines="2"
+            android:maxWidth="@dimen/biometric_dialog_button_positive_max_width"
             android:text="@string/biometric_dialog_try_again"
             android:visibility="gone"/>
         <Space android:id="@+id/rightSpacer"
-            android:layout_width="12dip"
+            android:layout_width="8dp"
             android:layout_height="match_parent"
             android:visibility="visible" />
     </LinearLayout>
diff --git a/packages/SystemUI/res/layout/bubble_manage_menu.xml b/packages/SystemUI/res/layout/bubble_manage_menu.xml
index 129282d..8494882 100644
--- a/packages/SystemUI/res/layout/bubble_manage_menu.xml
+++ b/packages/SystemUI/res/layout/bubble_manage_menu.xml
@@ -32,8 +32,8 @@
         android:orientation="horizontal">
 
         <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
             android:src="@drawable/ic_remove_no_shadow"
             android:tint="@color/global_actions_text"/>
 
@@ -57,8 +57,8 @@
         android:orientation="horizontal">
 
         <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
             android:src="@drawable/ic_stop_bubble"
             android:tint="@color/global_actions_text"/>
 
diff --git a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml
index 0f561cb..87dd58e 100644
--- a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml
+++ b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml
@@ -31,7 +31,6 @@
         android:layout_marginEnd="24dp"
         android:orientation="vertical"
         android:background="@drawable/bubble_stack_user_education_bg"
-        android:alpha="0.9"
         >
 
         <TextView
@@ -61,6 +60,7 @@
         <LinearLayout
             android:layout_height="wrap_content"
             android:layout_width="wrap_content"
+            android:id="@+id/button_layout"
             android:orientation="horizontal" >
 
             <com.android.systemui.statusbar.AlphaOptimizedButton
@@ -73,7 +73,6 @@
                 android:clickable="false"
                 android:text="@string/manage_bubbles_text"
                 android:textColor="?attr/wallpaperTextColor"
-                android:alpha="0.89"
                 />
 
             <com.android.systemui.statusbar.AlphaOptimizedButton
@@ -88,4 +87,4 @@
                 />
         </LinearLayout>
     </LinearLayout>
-</com.android.systemui.bubbles.BubbleManageEducationView>
\ No newline at end of file
+</com.android.systemui.bubbles.BubbleManageEducationView>
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index a722fbf..d0f9332 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -154,7 +154,7 @@
         android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
         android:singleLine="true"
         android:textColor="@color/media_primary_text"
-        android:textSize="18sp" />
+        android:textSize="16sp" />
 
     <!-- Artist name -->
     <TextView
@@ -163,7 +163,7 @@
         android:layout_height="wrap_content"
         android:fontFamily="@*android:string/config_bodyFontFamily"
         android:singleLine="true"
-        android:textColor="@color/media_primary_text"
+        android:textColor="@color/media_secondary_text"
         android:textSize="14sp" />
 
     <com.android.internal.widget.CachingIconView
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 2d42ce6..2c08925 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -41,4 +41,7 @@
     <dimen name="bubble_padding_top">4dp</dimen>
 
     <dimen name="controls_activity_view_top_offset">25dp</dimen>
+
+    <dimen name="biometric_dialog_button_negative_max_width">140dp</dimen>
+    <dimen name="biometric_dialog_button_positive_max_width">116dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 40a4b50..d8a3c8c 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -245,6 +245,7 @@
 
     <!-- media -->
     <color name="media_primary_text">@android:color/white</color>
+    <color name="media_secondary_text">#99ffffff</color> <!-- 60% -->
     <color name="media_seekbar_progress">#c0ffffff</color>
     <color name="media_disabled">#80ffffff</color>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 38ca8a1..3f04231 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1116,6 +1116,8 @@
 
     <!-- Biometric Dialog values -->
     <dimen name="biometric_dialog_biometric_icon_size">64dp</dimen>
+    <dimen name="biometric_dialog_button_negative_max_width">160dp</dimen>
+    <dimen name="biometric_dialog_button_positive_max_width">136dp</dimen>
     <dimen name="biometric_dialog_corner_size">4dp</dimen>
     <!-- Y translation when showing/dismissing the dialog-->
     <dimen name="biometric_dialog_animation_translation_offset">350dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b95af26..0e3fa1e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1091,11 +1091,11 @@
     <!-- Message shown when face authentication fails and the pin pad is visible. [CHAR LIMIT=60] -->
     <string name="keyguard_retry">Swipe up to try again</string>
 
-    <!-- Text on keyguard screen and in Quick Settings footer indicating that the device is enterprise-managed by a Device Owner [CHAR LIMIT=60] -->
-    <string name="do_disclosure_generic">This device is managed by your organization</string>
+    <!-- Text on keyguard screen and in Quick Settings footer indicating that the user's device belongs to their organization. [CHAR LIMIT=60] -->
+    <string name="do_disclosure_generic">This device belongs to your organization</string>
 
-    <!-- Text on keyguard screen and in Quick Settings footer indicating that the device is enterprise-managed by a Device Owner [CHAR LIMIT=40] -->
-    <string name="do_disclosure_with_name">This device is managed by <xliff:g id="organization_name" example="Foo, Inc.">%s</xliff:g></string>
+    <!-- Text on keyguard screen and in Quick Settings footer indicating that user's device belongs to their organization. [CHAR LIMIT=40] -->
+    <string name="do_disclosure_with_name">This device belongs to <xliff:g id="organization_name" example="Foo, Inc.">%s</xliff:g></string>
 
     <!-- Shows when people have clicked on the phone icon [CHAR LIMIT=60] -->
     <string name="phone_hint">Swipe from icon for phone</string>
@@ -1288,29 +1288,29 @@
     <!-- Footer vpn present text [CHAR LIMIT=50] -->
     <string name="branded_vpn_footer">Network may be monitored</string>
 
-    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner that can monitor the network traffic [CHAR LIMIT=100] -->
-    <string name="quick_settings_disclosure_management_monitoring">Your organization manages this device and may monitor network traffic</string>
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the user's device belongs to their organization, and the organization can monitor network traffic on that device. [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_management_monitoring">Your organization owns this device and may monitor network traffic</string>
 
-    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner that can monitor the network traffic [CHAR LIMIT=100] -->
-    <string name="quick_settings_disclosure_named_management_monitoring"><xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> manages this device and may monitor network traffic</string>
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the user's device belongs to their organization, and the organization can monitor network traffic on that device. The placeholder is the organization's name. [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_named_management_monitoring"><xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> owns this device and may monitor network traffic</string>
 
-    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner and is connected to a VPN [CHAR LIMIT=100] -->
-    <string name="quick_settings_disclosure_management_named_vpn">Device is managed by your organization and connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the user's device belongs to their organization, and the device is connected to a VPN. The placeholder is the VPN name. [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_management_named_vpn">This device belongs to your organization and is connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
 
-    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner and is connected to a VPN [CHAR LIMIT=100] -->
-    <string name="quick_settings_disclosure_named_management_named_vpn">Device is managed by <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> and connected to <xliff:g id="vpn_app" example="Foo VPN App">%2$s</xliff:g></string>
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the user's device belongs to their organization, and the device is connected to a VPN. The first placeholder is the organization name, and the second placeholder is the VPN name. [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_named_management_named_vpn">This device belongs to <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> and is connected to <xliff:g id="vpn_app" example="Foo VPN App">%2$s</xliff:g></string>
 
-    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner [CHAR LIMIT=100] -->
-    <string name="quick_settings_disclosure_management">Device is managed by your organization</string>
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the user's device belongs to their organization. [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_management">This device belongs to your organization</string>
 
-    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner [CHAR LIMIT=100] -->
-    <string name="quick_settings_disclosure_named_management">Device is managed by <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g></string>
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the user's device belongs to their organization. The placeholder is the organization's name. [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_named_management">This device belongs to <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g></string>
 
-    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner and is connected to two VPNs, one in the current user, one in the managed profile [CHAR LIMIT=100] -->
-    <string name="quick_settings_disclosure_management_vpns">Device is managed by your organization and connected to VPNs</string>
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the user's device belongs to their organization, and the device is connected to multiple VPNs. [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_management_vpns">This device belongs to your organization and is connected to VPNs</string>
 
-    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner and is connected to two VPNs, one in the current user, one in the managed profile [CHAR LIMIT=100] -->
-    <string name="quick_settings_disclosure_named_management_vpns">Device is managed by <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> and connected to VPNs</string>
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the user's device belongs to their organization, and the device is connected to multiple VPNs. The placeholder is the organization's name. [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_named_management_vpns">This device belongs to <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> and is connected to VPNs</string>
 
     <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a managed profile which can be monitored by the profile owner [CHAR LIMIT=100] -->
     <string name="quick_settings_disclosure_managed_profile_monitoring">Your organization may monitor network traffic in your work profile</string>
@@ -1321,17 +1321,17 @@
     <!-- Disclosure at the bottom of Quick Settings that indicates that a certificate authorithy is installed on this device and the traffic might be monitored [CHAR LIMIT=100] -->
     <string name="quick_settings_disclosure_monitoring">Network may be monitored</string>
 
-    <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to two VPNs, one in the current user, one in the managed profile [CHAR LIMIT=100] -->
-    <string name="quick_settings_disclosure_vpns">Device connected to VPNs</string>
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to multiple VPNs. [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_vpns">This device is connected to VPNs</string>
 
-    <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to a VPN in the work profile [CHAR LIMIT=100] -->
-    <string name="quick_settings_disclosure_managed_profile_named_vpn">Work profile connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to a VPN in the work profile. The placeholder is the VPN name. [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_managed_profile_named_vpn">Your work profile is connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
 
-    <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to a VPN in the personal profile (as opposed to the work profile) [CHAR LIMIT=100] -->
-    <string name="quick_settings_disclosure_personal_profile_named_vpn">Personal profile connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to a VPN in the personal profile (instead of the work profile). The placeholder is the VPN name. [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_personal_profile_named_vpn">Your personal profile is connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
 
-    <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to a VPN [CHAR LIMIT=100] -->
-    <string name="quick_settings_disclosure_named_vpn">Device connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to a VPN. The placeholder is the VPN name. [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_named_vpn">This device is connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
 
     <!-- Monitoring dialog title for device owned devices [CHAR LIMIT=35] -->
     <string name="monitoring_title_device_owned">Device management</string>
@@ -1361,11 +1361,11 @@
     <!-- Monitoring dialog label for button opening a page with more information on the admin's abilities [CHAR LIMIT=30] -->
     <string name="monitoring_button_view_policies">View Policies</string>
 
-    <!-- Monitoring dialog: Description of the device owner by name. [CHAR LIMIT=NONE]-->
-    <string name="monitoring_description_named_management">Your device is managed by <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g>.\n\nYour admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nFor more information, contact your admin.</string>
+    <!-- Dialog that a user can access via Quick Settings. The dialog describes what the IT admin can monitor (and the changes they can make) on the user's device. [CHAR LIMIT=NONE]-->
+    <string name="monitoring_description_named_management">This device belongs to <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g>.\n\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nFor more information, contact your IT admin.</string>
 
-    <!-- Monitoring dialog: Description of a device owner. [CHAR LIMIT=NONE]-->
-    <string name="monitoring_description_management">Your device is managed by your organization.\n\nYour admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nFor more information, contact your admin.</string>
+    <!-- Dialog that a user can access via Quick Settings. The dialog describes what the IT admin can monitor (and the changes they can make) on the user's device. [CHAR LIMIT=NONE]-->
+    <string name="monitoring_description_management">This device belongs to your organization.\n\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nFor more information, contact your IT admin.</string>
 
     <!-- Monitoring dialog: Description of a CA Certificate. [CHAR LIMIT=NONE]-->
     <string name="monitoring_description_management_ca_certificate">Your organization installed a certificate authority on this device. Your secure network traffic may be monitored or modified.</string>
@@ -2784,10 +2784,16 @@
          recommended controls [CHAR_LIMIT=60] -->
     <string name="controls_seeding_in_progress">Loading recommendations</string>
 
-    <!-- Close the controls associated with a specific media session [CHAR_LIMIT=NONE] -->
-    <string name="controls_media_close_session">Close this media session</string>
+    <!-- Title for media controls [CHAR_LIMIT=50] -->
+    <string name="controls_media_title">Media</string>
+    <!-- Explanation for closing controls associated with a specific media session [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_close_session">Hide the current session.</string>
+    <!-- Label for a button that will hide media controls [CHAR_LIMIT=30] -->
+    <string name="controls_media_dismiss_button">Hide</string>
     <!-- Label for button to resume media playback [CHAR_LIMIT=NONE] -->
     <string name="controls_media_resume">Resume</string>
+    <!-- Label for button to go to media control settings screen [CHAR_LIMIT=30] -->
+    <string name="controls_media_settings_button">Settings</string>
 
     <!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] -->
     <string name="controls_error_timeout">Inactive, check app</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f126bfe..db30afc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -79,7 +79,6 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
-import android.util.EventLog;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -1076,17 +1075,6 @@
                 != LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
     }
 
-    private boolean isUserEncryptedOrLockdown(int userId) {
-        // Biometrics should not be started in this case. Think carefully before modifying this
-        // method, see b/79776455
-        final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(userId);
-        final boolean isLockDown =
-                containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
-                        || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
-        final boolean isEncrypted = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT);
-        return isLockDown || isEncrypted;
-    }
-
     private boolean containsFlag(int haystack, int needle) {
         return (haystack & needle) != 0;
     }
@@ -1916,7 +1904,6 @@
     private boolean shouldListenForFingerprint() {
         final boolean allowedOnBouncer =
                 !(mFingerprintLockedOut && mBouncer && mCredentialAttempted);
-        final int user = getCurrentUser();
 
         // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
@@ -1925,7 +1912,7 @@
                 shouldListenForFingerprintAssistant() || (mKeyguardOccluded && mIsDreaming))
                 && !mSwitchingUser && !isFingerprintDisabled(getCurrentUser())
                 && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser
-                && allowedOnBouncer && !isUserEncryptedOrLockdown(user);
+                && allowedOnBouncer;
         return shouldListen;
     }
 
@@ -1939,8 +1926,12 @@
                 && !statusBarShadeLocked;
         final int user = getCurrentUser();
         final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
-        final boolean isTimedOut =
-                containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
+        final boolean isLockDown =
+                containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
+                        || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+        final boolean isEncryptedOrTimedOut =
+                containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT)
+                        || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
 
         boolean canBypass = mKeyguardBypassController != null
                 && mKeyguardBypassController.canBypass();
@@ -1949,9 +1940,10 @@
         // TrustAgents or biometrics are keeping the device unlocked.
         boolean becauseCannotSkipBouncer = !getUserCanSkipBouncer(user) || canBypass;
 
-        // Scan even when timeout to show a preemptive bouncer when bypassing.
+        // Scan even when encrypted or timeout to show a preemptive bouncer when bypassing.
         // Lock-down mode shouldn't scan, since it is more explicit.
-        boolean strongAuthAllowsScanning = (!isTimedOut || canBypass && !mBouncer);
+        boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass && !mBouncer)
+                && !isLockDown;
 
         // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
@@ -1961,7 +1953,7 @@
                 && !mSwitchingUser && !isFaceDisabled(user) && becauseCannotSkipBouncer
                 && !mKeyguardGoingAway && mFaceSettingEnabledForUser.get(user) && !mLockIconPressed
                 && strongAuthAllowsScanning && mIsPrimaryUser
-                && !mSecureCameraLaunched && !isUserEncryptedOrLockdown(user);
+                && !mSecureCameraLaunched;
 
         // Aggregate relevant fields for debug logging.
         if (DEBUG_FACE || DEBUG_SPEW) {
@@ -2034,11 +2026,6 @@
             if (mFingerprintCancelSignal != null) {
                 mFingerprintCancelSignal.cancel();
             }
-
-            if (isUserEncryptedOrLockdown(userId)) {
-                // If this happens, shouldListenForFingerprint() is wrong. SafetyNet for b/79776455
-                EventLog.writeEvent(0x534e4554, "79776455", "startListeningForFingerprint");
-            }
             mFingerprintCancelSignal = new CancellationSignal();
             mFpm.authenticate(null, mFingerprintCancelSignal, 0, mFingerprintAuthenticationCallback,
                     null, userId);
@@ -2057,11 +2044,6 @@
             if (mFaceCancelSignal != null) {
                 mFaceCancelSignal.cancel();
             }
-
-            if (isUserEncryptedOrLockdown(userId)) {
-                // If this happens, shouldListenForFace() is wrong. SafetyNet for b/79776455
-                EventLog.writeEvent(0x534e4554, "79776455", "startListeningForFace");
-            }
             mFaceCancelSignal = new CancellationSignal();
             mFaceManager.authenticate(null, mFaceCancelSignal, 0,
                     mFaceAuthenticationCallback, null, userId);
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
index e651392..08edad3 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
@@ -63,7 +63,7 @@
     }
 
     fun reportAssistantInvocationEvent(
-        invocationEvent: AssistantInvocationEvent,
+        invocationEvent: UiEventLogger.UiEventEnum,
         assistantComponent: ComponentName? = null,
         deviceState: Int? = null
     ) {
@@ -86,7 +86,7 @@
         reportAssistantInvocationExtraData()
     }
 
-    fun reportAssistantSessionEvent(sessionEvent: AssistantSessionEvent) {
+    fun reportAssistantSessionEvent(sessionEvent: UiEventLogger.UiEventEnum) {
         val assistantComponent = getAssistantComponentForCurrentUser()
         val assistantUid = getAssistantUid(assistantComponent)
         uiEventLogger.logWithInstanceId(
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt b/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
new file mode 100644
index 0000000..434b03d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.broadcast
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.util.ArraySet
+import com.android.systemui.Dumpable
+import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
+import com.android.systemui.util.indentIfPossible
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicInteger
+
+/**
+ * Receiver for a given action-userId pair to be used by [UserBroadcastDispatcher].
+ *
+ * Each object of this class will take care of a single Action. It will register if it has at least
+ * one [BroadcastReceiver] added to it, and unregister when none are left.
+ *
+ * It will also re-register if filters with new categories are added. But this should not happen
+ * often.
+ *
+ * This class has no sync controls, so make sure to only make modifications from the background
+ * thread.
+ */
+class ActionReceiver(
+    private val action: String,
+    private val userId: Int,
+    private val registerAction: BroadcastReceiver.(IntentFilter) -> Unit,
+    private val unregisterAction: BroadcastReceiver.() -> Unit,
+    private val bgExecutor: Executor,
+    private val logger: BroadcastDispatcherLogger
+) : BroadcastReceiver(), Dumpable {
+
+    companion object {
+        val index = AtomicInteger(0)
+    }
+
+    var registered = false
+        private set
+    private val receiverDatas = ArraySet<ReceiverData>()
+    private val activeCategories = ArraySet<String>()
+
+    @Throws(IllegalArgumentException::class)
+    fun addReceiverData(receiverData: ReceiverData) {
+        if (!receiverData.filter.hasAction(action)) {
+            throw(IllegalArgumentException("Trying to attach to $action without correct action," +
+                "receiver: ${receiverData.receiver}"))
+        }
+        val addedCategories = activeCategories
+                .addAll(receiverData.filter.categoriesIterator()?.asSequence() ?: emptySequence())
+
+        if (receiverDatas.add(receiverData) && receiverDatas.size == 1) {
+            registerAction(createFilter())
+            registered = true
+        } else if (addedCategories) {
+            unregisterAction()
+            registerAction(createFilter())
+        }
+    }
+
+    fun hasReceiver(receiver: BroadcastReceiver): Boolean {
+        return receiverDatas.any { it.receiver == receiver }
+    }
+
+    private fun createFilter(): IntentFilter {
+        val filter = IntentFilter(action)
+        activeCategories.forEach(filter::addCategory)
+        return filter
+    }
+
+    fun removeReceiver(receiver: BroadcastReceiver) {
+        if (receiverDatas.removeAll { it.receiver == receiver } &&
+                receiverDatas.isEmpty() && registered) {
+            unregisterAction()
+            registered = false
+            activeCategories.clear()
+        }
+    }
+
+    @Throws(IllegalStateException::class)
+    override fun onReceive(context: Context, intent: Intent) {
+        if (intent.action != action) {
+            throw(IllegalStateException("Received intent for ${intent.action} " +
+                "in receiver for $action}"))
+        }
+        val id = index.getAndIncrement()
+        logger.logBroadcastReceived(id, userId, intent)
+        // Immediately return control to ActivityManager
+        bgExecutor.execute {
+            receiverDatas.forEach {
+                if (it.filter.matchCategories(intent.categories) == null) {
+                    it.executor.execute {
+                        it.receiver.pendingResult = pendingResult
+                        it.receiver.onReceive(context, intent)
+                        logger.logBroadcastDispatched(id, action, it.receiver)
+                    }
+                }
+            }
+        }
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        pw.indentIfPossible {
+            println("Registered: $registered")
+            println("Receivers:")
+            pw.indentIfPossible {
+                receiverDatas.forEach {
+                    println(it.receiver)
+                }
+            }
+            println("Categories: ${activeCategories.joinToString(", ")}")
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 67c0c62..b9b849b 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -29,6 +29,7 @@
 import android.text.TextUtils
 import android.util.SparseArray
 import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.util.IndentingPrintWriter
 import com.android.systemui.Dumpable
 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
 import com.android.systemui.dump.DumpManager
@@ -65,6 +66,7 @@
 open class BroadcastDispatcher constructor (
     private val context: Context,
     private val bgLooper: Looper,
+    private val bgExecutor: Executor,
     private val dumpManager: DumpManager,
     private val logger: BroadcastDispatcherLogger
 ) : Dumpable, BroadcastReceiver() {
@@ -173,15 +175,18 @@
 
     @VisibleForTesting
     protected open fun createUBRForUser(userId: Int) =
-            UserBroadcastDispatcher(context, userId, bgLooper, logger)
+            UserBroadcastDispatcher(context, userId, bgLooper, bgExecutor, logger)
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
         pw.println("Broadcast dispatcher:")
-        pw.println("  Current user: ${handler.currentUser}")
+        val ipw = IndentingPrintWriter(pw, "  ")
+        ipw.increaseIndent()
+        ipw.println("Current user: ${handler.currentUser}")
         for (index in 0 until receiversByUser.size()) {
-            pw.println("  User ${receiversByUser.keyAt(index)}")
-            receiversByUser.valueAt(index).dump(fd, pw, args)
+            ipw.println("User ${receiversByUser.keyAt(index)}")
+            receiversByUser.valueAt(index).dump(fd, ipw, args)
         }
+        ipw.decreaseIndent()
     }
 
     private val handler = object : Handler(bgLooper) {
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index 96f5a1f..11da920 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -18,8 +18,6 @@
 
 import android.content.BroadcastReceiver
 import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
 import android.os.Handler
 import android.os.Looper
 import android.os.Message
@@ -31,11 +29,10 @@
 import com.android.internal.util.Preconditions
 import com.android.systemui.Dumpable
 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
+import com.android.systemui.util.indentIfPossible
 import java.io.FileDescriptor
 import java.io.PrintWriter
-import java.lang.IllegalArgumentException
-import java.lang.IllegalStateException
-import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.Executor
 import java.util.concurrent.atomic.AtomicInteger
 
 private const val MSG_REGISTER_RECEIVER = 0
@@ -48,16 +45,14 @@
  *
  * Created by [BroadcastDispatcher] as needed by users. The value of [userId] can be
  * [UserHandle.USER_ALL].
- *
- * Each instance of this class will register itself exactly once with [Context]. Updates to the
- * [IntentFilter] will be done in the background thread.
  */
-class UserBroadcastDispatcher(
+open class UserBroadcastDispatcher(
     private val context: Context,
     private val userId: Int,
     private val bgLooper: Looper,
+    private val bgExecutor: Executor,
     private val logger: BroadcastDispatcherLogger
-) : BroadcastReceiver(), Dumpable {
+) : Dumpable {
 
     companion object {
         // Used only for debugging. If not debugging, this variable will not be accessed and all
@@ -76,47 +71,16 @@
         }
     }
 
-    private val registered = AtomicBoolean(false)
-
-    internal fun isRegistered() = registered.get()
-
     // Only modify in BG thread
-    private val actionsToReceivers = ArrayMap<String, MutableSet<ReceiverData>>()
-    private val receiverToReceiverData = ArrayMap<BroadcastReceiver, MutableSet<ReceiverData>>()
+    @VisibleForTesting
+    internal val actionsToActionsReceivers = ArrayMap<String, ActionReceiver>()
+    private val receiverToActions = ArrayMap<BroadcastReceiver, MutableSet<String>>()
 
     @VisibleForTesting
     internal fun isReceiverReferenceHeld(receiver: BroadcastReceiver): Boolean {
-        return receiverToReceiverData.contains(receiver) ||
-                actionsToReceivers.any {
-            it.value.any { it.receiver == receiver }
-        }
-    }
-
-    // Only call on BG thread as it reads from the maps
-    private fun createFilter(): IntentFilter {
-        Preconditions.checkState(bgHandler.looper.isCurrentThread,
-                "This method should only be called from BG thread")
-        val categories = mutableSetOf<String>()
-        receiverToReceiverData.values.flatten().forEach {
-            it.filter.categoriesIterator()?.asSequence()?.let {
-                categories.addAll(it)
-            }
-        }
-        val intentFilter = IntentFilter().apply {
-            // The keys of the arrayMap are of type String! so null check is needed
-            actionsToReceivers.keys.forEach { if (it != null) addAction(it) else Unit }
-            categories.forEach { addCategory(it) }
-        }
-        return intentFilter
-    }
-
-    override fun onReceive(context: Context, intent: Intent) {
-        val id = index.getAndIncrement()
-        if (DEBUG) Log.w(TAG, "[$id] Received $intent")
-        logger.logBroadcastReceived(id, userId, intent)
-        bgHandler.post(
-                HandleBroadcastRunnable(
-                        actionsToReceivers, context, intent, pendingResult, id, logger))
+        return actionsToActionsReceivers.values.any {
+            it.hasReceiver(receiver)
+        } || (receiver in receiverToActions)
     }
 
     /**
@@ -137,109 +101,57 @@
         Preconditions.checkState(bgHandler.looper.isCurrentThread,
                 "This method should only be called from BG thread")
         if (DEBUG) Log.w(TAG, "Register receiver: ${receiverData.receiver}")
-        receiverToReceiverData.getOrPut(receiverData.receiver, { ArraySet() }).add(receiverData)
-        var changed = false
-        // Index the BroadcastReceiver by all its actions, that way it's easier to dispatch given
-        // a received intent.
+        receiverToActions
+                .getOrPut(receiverData.receiver, { ArraySet() })
+                .addAll(receiverData.filter.actionsIterator()?.asSequence() ?: emptySequence())
         receiverData.filter.actionsIterator().forEach {
-            actionsToReceivers.getOrPut(it) {
-                changed = true
-                ArraySet()
-            }.add(receiverData)
+            actionsToActionsReceivers
+                    .getOrPut(it, { createActionReceiver(it) })
+                    .addReceiverData(receiverData)
         }
         logger.logReceiverRegistered(userId, receiverData.receiver)
-        if (changed) {
-            createFilterAndRegisterReceiverBG()
-        }
+    }
+
+    @VisibleForTesting
+    internal open fun createActionReceiver(action: String): ActionReceiver {
+        return ActionReceiver(
+                action,
+                userId,
+                {
+                    context.registerReceiverAsUser(this, UserHandle.of(userId), it, null, bgHandler)
+                    logger.logContextReceiverRegistered(userId, it)
+                },
+                {
+                    try {
+                        context.unregisterReceiver(this)
+                        logger.logContextReceiverUnregistered(userId, action)
+                    } catch (e: IllegalArgumentException) {
+                        Log.e(TAG, "Trying to unregister unregistered receiver for user $userId, " +
+                                "action $action",
+                                IllegalStateException(e))
+                    }
+                },
+                bgExecutor,
+                logger
+        )
     }
 
     private fun handleUnregisterReceiver(receiver: BroadcastReceiver) {
         Preconditions.checkState(bgHandler.looper.isCurrentThread,
                 "This method should only be called from BG thread")
         if (DEBUG) Log.w(TAG, "Unregister receiver: $receiver")
-        val actions = receiverToReceiverData.getOrElse(receiver) { return }
-                .flatMap { it.filter.actionsIterator().asSequence().asIterable() }.toSet()
-        receiverToReceiverData.remove(receiver)?.clear()
-        var changed = false
-        actions.forEach { action ->
-            actionsToReceivers.get(action)?.removeIf { it.receiver == receiver }
-            if (actionsToReceivers.get(action)?.isEmpty() ?: false) {
-                changed = true
-                actionsToReceivers.remove(action)
-            }
+        receiverToActions.getOrDefault(receiver, mutableSetOf()).forEach {
+            actionsToActionsReceivers.get(it)?.removeReceiver(receiver)
         }
+        receiverToActions.remove(receiver)
         logger.logReceiverUnregistered(userId, receiver)
-        if (changed) {
-            createFilterAndRegisterReceiverBG()
-        }
-    }
-
-    // Only call this from a BG thread
-    private fun createFilterAndRegisterReceiverBG() {
-        val intentFilter = createFilter()
-        bgHandler.post(RegisterReceiverRunnable(intentFilter))
     }
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
-        pw.println("  Registered=${registered.get()}")
-        actionsToReceivers.forEach { (action, list) ->
-            pw.println("    $action:")
-            list.forEach { pw.println("      ${it.receiver}") }
-        }
-    }
-
-    private class HandleBroadcastRunnable(
-        val actionsToReceivers: Map<String, Set<ReceiverData>>,
-        val context: Context,
-        val intent: Intent,
-        val pendingResult: PendingResult,
-        val index: Int,
-        val logger: BroadcastDispatcherLogger
-    ) : Runnable {
-        override fun run() {
-            if (DEBUG) Log.w(TAG, "[$index] Dispatching $intent")
-            actionsToReceivers.get(intent.action)
-                    ?.filter {
-                        it.filter.hasAction(intent.action) &&
-                            it.filter.matchCategories(intent.categories) == null }
-                    ?.forEach {
-                        it.executor.execute {
-                            if (DEBUG) Log.w(TAG,
-                                    "[$index] Dispatching ${intent.action} to ${it.receiver}")
-                            logger.logBroadcastDispatched(index, intent.action, it.receiver)
-                            it.receiver.pendingResult = pendingResult
-                            it.receiver.onReceive(context, intent)
-                        }
-                    }
-        }
-    }
-
-    private inner class RegisterReceiverRunnable(val intentFilter: IntentFilter) : Runnable {
-
-        /*
-         * Registers and unregisters the BroadcastReceiver
-         */
-        override fun run() {
-            if (registered.get()) {
-                try {
-                    context.unregisterReceiver(this@UserBroadcastDispatcher)
-                    logger.logContextReceiverUnregistered(userId)
-                } catch (e: IllegalArgumentException) {
-                    Log.e(TAG, "Trying to unregister unregistered receiver for user $userId",
-                            IllegalStateException(e))
-                }
-                registered.set(false)
-            }
-            // Short interval without receiver, this can be problematic
-            if (intentFilter.countActions() > 0 && !registered.get()) {
-                context.registerReceiverAsUser(
-                        this@UserBroadcastDispatcher,
-                        UserHandle.of(userId),
-                        intentFilter,
-                        null,
-                        bgHandler)
-                registered.set(true)
-                logger.logContextReceiverRegistered(userId, intentFilter)
+        pw.indentIfPossible {
+            actionsToActionsReceivers.forEach { (action, actionReceiver) ->
+                println("$action:")
+                actionReceiver.dump(fd, pw, args)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
index 123a8ae..6ba88f4 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
@@ -99,11 +99,12 @@
         })
     }
 
-    fun logContextReceiverUnregistered(user: Int) {
+    fun logContextReceiverUnregistered(user: Int, action: String) {
         log(INFO, {
             int1 = user
+            str1 = action
         }, {
-            "Receiver unregistered with Context for user $int1."
+            "Receiver unregistered with Context for user $int1, action $str1"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index c429209..aa41719 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -52,13 +52,16 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.ZenModeConfig;
@@ -130,7 +133,7 @@
     @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
             DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
             DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
-            DISMISS_OVERFLOW_MAX_REACHED})
+            DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED})
     @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
     @interface DismissReason {}
 
@@ -145,6 +148,8 @@
     static final int DISMISS_GROUP_CANCELLED = 9;
     static final int DISMISS_INVALID_INTENT = 10;
     static final int DISMISS_OVERFLOW_MAX_REACHED = 11;
+    static final int DISMISS_SHORTCUT_REMOVED = 12;
+    static final int DISMISS_PACKAGE_REMOVED = 13;
 
     private final Context mContext;
     private final NotificationEntryManager mNotificationEntryManager;
@@ -183,6 +188,12 @@
     // Only load overflow data from disk once
     private boolean mOverflowDataLoaded = false;
 
+    /**
+     * When the shade status changes to SHADE (from anything but SHADE, like LOCKED) we'll select
+     * this bubble and expand the stack.
+     */
+    @Nullable private NotificationEntry mNotifEntryToExpandOnShadeUnlock;
+
     private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private IStatusBarService mBarService;
     private WindowManager mWindowManager;
@@ -207,6 +218,9 @@
      */
     private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
 
+    /** Last known direction, used to detect layout direction changes @link #onConfigChanged}. */
+    private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
+
     private boolean mInflateSynchronously;
 
     // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
@@ -292,6 +306,12 @@
             if (shouldCollapse) {
                 collapseStack();
             }
+
+            if (mNotifEntryToExpandOnShadeUnlock != null) {
+                expandStackAndSelectBubble(mNotifEntryToExpandOnShadeUnlock);
+                mNotifEntryToExpandOnShadeUnlock = null;
+            }
+
             updateStack();
         }
     }
@@ -319,7 +339,8 @@
             SysUiState sysUiState,
             INotificationManager notificationManager,
             @Nullable IStatusBarService statusBarService,
-            WindowManager windowManager) {
+            WindowManager windowManager,
+            LauncherApps launcherApps) {
         dumpManager.registerDumpable(TAG, this);
         mContext = context;
         mShadeController = shadeController;
@@ -411,6 +432,47 @@
                 });
 
         mBubbleIconFactory = new BubbleIconFactory(context);
+
+        launcherApps.registerCallback(new LauncherApps.Callback() {
+            @Override
+            public void onPackageAdded(String s, UserHandle userHandle) {}
+
+            @Override
+            public void onPackageChanged(String s, UserHandle userHandle) {}
+
+            @Override
+            public void onPackageRemoved(String s, UserHandle userHandle) {
+                // Remove bubbles with this package name, since it has been uninstalled and attempts
+                // to open a bubble from an uninstalled app can cause issues.
+                mBubbleData.removeBubblesWithPackageName(s, DISMISS_PACKAGE_REMOVED);
+            }
+
+            @Override
+            public void onPackagesAvailable(String[] strings, UserHandle userHandle,
+                    boolean b) {
+
+            }
+
+            @Override
+            public void onPackagesUnavailable(String[] packages, UserHandle userHandle,
+                    boolean b) {
+                for (String packageName : packages) {
+                    // Remove bubbles from unavailable apps. This can occur when the app is on
+                    // external storage that has been removed.
+                    mBubbleData.removeBubblesWithPackageName(packageName, DISMISS_PACKAGE_REMOVED);
+                }
+            }
+
+            @Override
+            public void onShortcutsChanged(String packageName, List<ShortcutInfo> validShortcuts,
+                    UserHandle user) {
+                super.onShortcutsChanged(packageName, validShortcuts, user);
+
+                // Remove bubbles whose shortcuts aren't in the latest list of valid shortcuts.
+                mBubbleData.removeBubblesWithInvalidShortcuts(
+                        packageName, validShortcuts, DISMISS_SHORTCUT_REMOVED);
+            }
+        });
     }
 
     /**
@@ -832,8 +894,10 @@
                 mBubbleIconFactory = new BubbleIconFactory(mContext);
                 mStackView.onDisplaySizeChanged();
             }
-
-            mStackView.onLayoutDirectionChanged();
+            if (newConfig.getLayoutDirection() != mLayoutDirection) {
+                mLayoutDirection = newConfig.getLayoutDirection();
+                mStackView.onLayoutDirectionChanged(mLayoutDirection);
+            }
         }
     }
 
@@ -930,20 +994,49 @@
      * @param entry the notification for the bubble to be selected
      */
     public void expandStackAndSelectBubble(NotificationEntry entry) {
-        String key = entry.getKey();
-        Bubble bubble = mBubbleData.getBubbleInStackWithKey(key);
-        if (bubble != null) {
-            mBubbleData.setSelectedBubble(bubble);
-            mBubbleData.setExpanded(true);
-        } else {
-            bubble = mBubbleData.getOverflowBubbleWithKey(key);
+        if (mStatusBarStateListener.getCurrentState() == SHADE) {
+            mNotifEntryToExpandOnShadeUnlock = null;
+
+            String key = entry.getKey();
+            Bubble bubble = mBubbleData.getBubbleInStackWithKey(key);
             if (bubble != null) {
-                promoteBubbleFromOverflow(bubble);
-            } else if (entry.canBubble()) {
-                // It can bubble but it's not -- it got aged out of the overflow before it
-                // was dismissed or opened, make it a bubble again.
-                setIsBubble(entry, true /* isBubble */, true /* autoExpand */);
+                mBubbleData.setSelectedBubble(bubble);
+                mBubbleData.setExpanded(true);
+            } else {
+                bubble = mBubbleData.getOverflowBubbleWithKey(key);
+                if (bubble != null) {
+                    promoteBubbleFromOverflow(bubble);
+                } else if (entry.canBubble()) {
+                    // It can bubble but it's not -- it got aged out of the overflow before it
+                    // was dismissed or opened, make it a bubble again.
+                    setIsBubble(entry, true /* isBubble */, true /* autoExpand */);
+                }
             }
+        } else {
+            // Wait until we're unlocked to expand, so that the user can see the expand animation
+            // and also to work around bugs with expansion animation + shade unlock happening at the
+            // same time.
+            mNotifEntryToExpandOnShadeUnlock = entry;
+        }
+    }
+
+    /**
+     * When a notification is marked Priority, expand the stack if needed,
+     * then (maybe create and) select the given bubble.
+     *
+     * @param entry the notification for the bubble to show
+     */
+    public void onUserChangedImportance(NotificationEntry entry) {
+        try {
+            int flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
+            flags |= Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE;
+            mBarService.onNotificationBubbleChanged(entry.getKey(), true, flags);
+        } catch (RemoteException e) {
+            Log.e(TAG, e.getMessage());
+        }
+        mShadeController.collapsePanel(true);
+        if (entry.getRow() != null) {
+            entry.getRow().updateBubbleButton();
         }
     }
 
@@ -1076,7 +1169,7 @@
     @MainThread
     void removeBubble(String key, int reason) {
         if (mBubbleData.hasAnyBubbleWithKey(key)) {
-            mBubbleData.notificationEntryRemoved(key, reason);
+            mBubbleData.dismissBubbleWithKey(key, reason);
         }
     }
 
@@ -1134,7 +1227,7 @@
             rankingMap.getRanking(key, mTmpRanking);
             boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
             if (isActiveBubble && !mTmpRanking.canBubble()) {
-                mBubbleData.notificationEntryRemoved(entry.getKey(),
+                mBubbleData.dismissBubbleWithKey(entry.getKey(),
                         BubbleController.DISMISS_BLOCKED);
             } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
                 entry.setFlagBubble(true);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index c870612..7020f1c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.content.pm.ShortcutInfo;
 import android.util.Log;
 import android.util.Pair;
 import android.view.View;
@@ -42,8 +43,12 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -286,9 +291,9 @@
     }
 
     /**
-     * Called when a notification associated with a bubble is removed.
+     * Dismisses the bubble with the matching key, if it exists.
      */
-    public void notificationEntryRemoved(String key, @DismissReason int reason) {
+    public void dismissBubbleWithKey(String key, @DismissReason int reason) {
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "notificationEntryRemoved: key=" + key + " reason=" + reason);
         }
@@ -349,6 +354,44 @@
         return bubbleChildren;
     }
 
+    /**
+     * Removes bubbles from the given package whose shortcut are not in the provided list of valid
+     * shortcuts.
+     */
+    public void removeBubblesWithInvalidShortcuts(
+            String packageName, List<ShortcutInfo> validShortcuts, int reason) {
+
+        final Set<String> validShortcutIds = new HashSet<String>();
+        for (ShortcutInfo info : validShortcuts) {
+            validShortcutIds.add(info.getId());
+        }
+
+        final Predicate<Bubble> invalidBubblesFromPackage = bubble ->
+                packageName.equals(bubble.getPackageName())
+                        && (bubble.getShortcutInfo() == null
+                            || !bubble.getShortcutInfo().isEnabled()
+                            || !validShortcutIds.contains(bubble.getShortcutInfo().getId()));
+
+        final Consumer<Bubble> removeBubble = bubble ->
+                dismissBubbleWithKey(bubble.getKey(), reason);
+
+        performActionOnBubblesMatching(getBubbles(), invalidBubblesFromPackage, removeBubble);
+        performActionOnBubblesMatching(
+                getOverflowBubbles(), invalidBubblesFromPackage, removeBubble);
+    }
+
+    /** Dismisses all bubbles from the given package. */
+    public void removeBubblesWithPackageName(String packageName, int reason) {
+        final Predicate<Bubble> bubbleMatchesPackage = bubble ->
+                bubble.getPackageName().equals(packageName);
+
+        final Consumer<Bubble> removeBubble = bubble ->
+                dismissBubbleWithKey(bubble.getKey(), reason);
+
+        performActionOnBubblesMatching(getBubbles(), bubbleMatchesPackage, removeBubble);
+        performActionOnBubblesMatching(getOverflowBubbles(), bubbleMatchesPackage, removeBubble);
+    }
+
     private void doAdd(Bubble bubble) {
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "doAdd: " + bubble);
@@ -388,6 +431,21 @@
         }
     }
 
+    /** Runs the given action on Bubbles that match the given predicate. */
+    private void performActionOnBubblesMatching(
+            List<Bubble> bubbles, Predicate<Bubble> predicate, Consumer<Bubble> action) {
+        final List<Bubble> matchingBubbles = new ArrayList<>();
+        for (Bubble bubble : bubbles) {
+            if (predicate.test(bubble)) {
+                matchingBubbles.add(bubble);
+            }
+        }
+
+        for (Bubble matchingBubble : matchingBubbles) {
+            action.accept(matchingBubble);
+        }
+    }
+
     private void doRemove(String key, @DismissReason int reason) {
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "doRemove: " + key);
@@ -400,9 +458,11 @@
         if (indexToRemove == -1) {
             if (hasOverflowBubbleWithKey(key)
                 && (reason == BubbleController.DISMISS_NOTIF_CANCEL
-                || reason == BubbleController.DISMISS_GROUP_CANCELLED
-                || reason == BubbleController.DISMISS_NO_LONGER_BUBBLE
-                || reason == BubbleController.DISMISS_BLOCKED)) {
+                    || reason == BubbleController.DISMISS_GROUP_CANCELLED
+                    || reason == BubbleController.DISMISS_NO_LONGER_BUBBLE
+                    || reason == BubbleController.DISMISS_BLOCKED
+                    || reason == BubbleController.DISMISS_SHORTCUT_REMOVED
+                    || reason == BubbleController.DISMISS_PACKAGE_REMOVED)) {
 
                 Bubble b = getOverflowBubbleWithKey(key);
                 if (DEBUG_BUBBLE_DATA) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
index 0c25d14..390f706 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
@@ -162,7 +162,7 @@
         // into Bubble.
         val bubbles = entities.mapNotNull { entity ->
             shortcutMap[ShortcutKey(entity.userId, entity.packageName)]
-                    ?.first { shortcutInfo -> entity.shortcutId == shortcutInfo.id }
+                    ?.firstOrNull { shortcutInfo -> entity.shortcutId == shortcutInfo.id }
                     ?.let { shortcutInfo -> Bubble(
                             entity.key,
                             shortcutInfo,
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 959130b..7c3e027 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -120,6 +120,8 @@
     private int mPointerWidth;
     private int mPointerHeight;
     private ShapeDrawable mPointerDrawable;
+    private int mExpandedViewPadding;
+
 
     @Nullable private Bubble mBubble;
 
@@ -345,11 +347,9 @@
             return view.onApplyWindowInsets(insets);
         });
 
-        final int expandedViewPadding =
-                res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
-
-        setPadding(
-                expandedViewPadding, expandedViewPadding, expandedViewPadding, expandedViewPadding);
+        mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
+        setPadding(mExpandedViewPadding, mExpandedViewPadding, mExpandedViewPadding,
+                mExpandedViewPadding);
         setOnTouchListener((view, motionEvent) -> {
             if (!usingActivityView()) {
                 return false;
@@ -494,6 +494,11 @@
                     + " bubble=" + getBubbleKey());
         }
         final float alpha = visibility ? 1f : 0f;
+
+        if (alpha == mActivityView.getAlpha()) {
+            return;
+        }
+
         mPointerView.setAlpha(alpha);
         if (mActivityView != null) {
             mActivityView.setAlpha(alpha);
@@ -729,7 +734,7 @@
      */
     public void setPointerPosition(float x) {
         float halfPointerWidth = mPointerWidth / 2f;
-        float pointerLeft = x - halfPointerWidth;
+        float pointerLeft = x - halfPointerWidth - mExpandedViewPadding;
         mPointerView.setTranslationX(pointerLeft);
         mPointerView.setVisibility(VISIBLE);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java
index 4712012..86244ba 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java
@@ -20,6 +20,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.TextView;
@@ -34,6 +35,8 @@
 public class BubbleManageEducationView extends LinearLayout {
 
     private View mManageView;
+    private TextView mTitleTextView;
+    private TextView mDescTextView;
 
     public BubbleManageEducationView(Context context) {
         this(context, null);
@@ -57,6 +60,8 @@
         super.onFinishInflate();
 
         mManageView = findViewById(R.id.manage_education_view);
+        mTitleTextView = findViewById(R.id.user_education_title);
+        mDescTextView = findViewById(R.id.user_education_description);
 
         final TypedArray ta = mContext.obtainStyledAttributes(
                 new int[] {android.R.attr.colorAccent,
@@ -66,8 +71,8 @@
         ta.recycle();
 
         textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true);
-        ((TextView) findViewById(R.id.user_education_title)).setTextColor(textColor);
-        ((TextView) findViewById(R.id.user_education_description)).setTextColor(textColor);
+        mTitleTextView.setTextColor(textColor);
+        mDescTextView.setTextColor(textColor);
     }
 
     /**
@@ -84,4 +89,18 @@
     public int getManageViewHeight() {
         return mManageView.getHeight();
     }
+
+    @Override
+    public void setLayoutDirection(int direction) {
+        super.setLayoutDirection(direction);
+        if (getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+            mManageView.setBackgroundResource(R.drawable.bubble_stack_user_education_bg_rtl);
+            mTitleTextView.setGravity(Gravity.RIGHT);
+            mDescTextView.setGravity(Gravity.RIGHT);
+        } else {
+            mManageView.setBackgroundResource(R.drawable.bubble_stack_user_education_bg);
+            mTitleTextView.setGravity(Gravity.LEFT);
+            mDescTextView.setGravity(Gravity.LEFT);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 7408fb3..9926f2e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -57,6 +57,8 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
 
     private LinearLayout mEmptyState;
+    private TextView mEmptyStateTitle;
+    private TextView mEmptyStateSubtitle;
     private ImageView mEmptyStateImage;
     private BubbleController mBubbleController;
     private BubbleOverflowAdapter mAdapter;
@@ -100,8 +102,10 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.bubble_overflow_activity);
 
-        mEmptyState = findViewById(R.id.bubble_overflow_empty_state);
         mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
+        mEmptyState = findViewById(R.id.bubble_overflow_empty_state);
+        mEmptyStateTitle = findViewById(R.id.bubble_overflow_empty_title);
+        mEmptyStateSubtitle = findViewById(R.id.bubble_overflow_empty_subtitle);
         mEmptyStateImage = findViewById(R.id.bubble_overflow_empty_state_image);
 
         updateDimensions();
@@ -141,21 +145,27 @@
     void updateTheme() {
         Resources res = getResources();
         final int mode = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-        switch (mode) {
-            case Configuration.UI_MODE_NIGHT_YES:
-                mEmptyStateImage.setImageDrawable(
-                        res.getDrawable(R.drawable.ic_empty_bubble_overflow_dark));
-                findViewById(android.R.id.content)
-                        .setBackgroundColor(res.getColor(R.color.bubbles_dark));
-                break;
+        final boolean isNightMode = (mode == Configuration.UI_MODE_NIGHT_YES);
 
-            case Configuration.UI_MODE_NIGHT_NO:
-                mEmptyStateImage.setImageDrawable(
-                        res.getDrawable(R.drawable.ic_empty_bubble_overflow_light));
-                findViewById(android.R.id.content)
-                        .setBackgroundColor(res.getColor(R.color.bubbles_light));
-                break;
-        }
+        mEmptyStateImage.setImageDrawable(isNightMode
+                ? res.getDrawable(R.drawable.ic_empty_bubble_overflow_dark)
+                : res.getDrawable(R.drawable.ic_empty_bubble_overflow_light));
+
+        findViewById(android.R.id.content)
+                .setBackgroundColor(isNightMode
+                        ? res.getColor(R.color.bubbles_dark)
+                        : res.getColor(R.color.bubbles_light));
+
+        final TypedArray typedArray = getApplicationContext().obtainStyledAttributes(
+                new int[]{android.R.attr.colorBackgroundFloating,
+                        android.R.attr.textColorSecondary});
+        int bgColor = typedArray.getColor(0, isNightMode ? Color.BLACK : Color.WHITE);
+        int textColor = typedArray.getColor(1, isNightMode ? Color.WHITE : Color.BLACK);
+        textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, isNightMode);
+        typedArray.recycle();
+
+        mEmptyStateTitle.setTextColor(textColor);
+        mEmptyStateSubtitle.setTextColor(textColor);
     }
 
     void onDataChanged(List<Bubble> bubbles) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 640475e..c985b08 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -51,7 +51,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.provider.Settings;
-import android.service.notification.StatusBarNotification;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.DisplayCutout;
@@ -71,6 +70,7 @@
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import androidx.annotation.MainThread;
@@ -1107,6 +1107,7 @@
             title.setTextColor(textColor);
             description.setTextColor(textColor);
 
+            updateUserEducationForLayoutDirection();
             addView(mUserEducationView);
         }
 
@@ -1123,7 +1124,7 @@
                             false /* attachToRoot */);
             mManageEducationView.setVisibility(GONE);
             mManageEducationView.setElevation(mBubbleElevation);
-
+            mManageEducationView.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
             addView(mManageEducationView);
         }
     }
@@ -1205,13 +1206,17 @@
     }
 
     /** Tells the views with locale-dependent layout direction to resolve the new direction. */
-    public void onLayoutDirectionChanged() {
-        mManageMenu.resolveLayoutDirection();
-        mFlyout.resolveLayoutDirection();
-
-        if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-            mExpandedBubble.getExpandedView().resolveLayoutDirection();
+    public void onLayoutDirectionChanged(int direction) {
+        mManageMenu.setLayoutDirection(direction);
+        mFlyout.setLayoutDirection(direction);
+        if (mUserEducationView != null) {
+            mUserEducationView.setLayoutDirection(direction);
+            updateUserEducationForLayoutDirection();
         }
+        if (mManageEducationView != null) {
+            mManageEducationView.setLayoutDirection(direction);
+        }
+        updateExpandedViewDirection(direction);
     }
 
     /** Respond to the display size change by recalculating view size and location. */
@@ -1286,6 +1291,18 @@
         });
     }
 
+    void updateExpandedViewDirection(int direction) {
+        final List<Bubble> bubbles = mBubbleData.getBubbles();
+        if (bubbles.isEmpty()) {
+            return;
+        }
+        bubbles.forEach(bubble -> {
+            if (bubble.getExpandedView() != null) {
+                bubble.getExpandedView().setLayoutDirection(direction);
+            }
+        });
+    }
+
     void setupLocalMenu(AccessibilityNodeInfo info) {
         Resources res = mContext.getResources();
 
@@ -1633,6 +1650,8 @@
         if (mShouldShowUserEducation && mUserEducationView.getVisibility() != VISIBLE) {
             mUserEducationView.setAlpha(0);
             mUserEducationView.setVisibility(VISIBLE);
+            updateUserEducationForLayoutDirection();
+
             // Post so we have height of mUserEducationView
             mUserEducationView.post(() -> {
                 final int viewHeight = mUserEducationView.getHeight();
@@ -1650,6 +1669,28 @@
         return false;
     }
 
+    private void updateUserEducationForLayoutDirection() {
+        if (mUserEducationView == null) {
+            return;
+        }
+        LinearLayout textLayout =  mUserEducationView.findViewById(R.id.user_education_view);
+        TextView title = mUserEducationView.findViewById(R.id.user_education_title);
+        TextView description = mUserEducationView.findViewById(R.id.user_education_description);
+        boolean isLtr =
+                getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_LTR;
+        if (isLtr) {
+            mUserEducationView.setLayoutDirection(LAYOUT_DIRECTION_LTR);
+            textLayout.setBackgroundResource(R.drawable.bubble_stack_user_education_bg);
+            title.setGravity(Gravity.LEFT);
+            description.setGravity(Gravity.LEFT);
+        } else {
+            mUserEducationView.setLayoutDirection(LAYOUT_DIRECTION_RTL);
+            textLayout.setBackgroundResource(R.drawable.bubble_stack_user_education_bg_rtl);
+            title.setGravity(Gravity.RIGHT);
+            description.setGravity(Gravity.RIGHT);
+        }
+    }
+
     /**
      * If necessary, hides the user education view for the bubble stack.
      *
@@ -1861,6 +1902,8 @@
                                     if (mExpandedBubble != null
                                             && mExpandedBubble.getExpandedView() != null) {
                                         mExpandedBubble.getExpandedView()
+                                                .setContentVisibility(true);
+                                        mExpandedBubble.getExpandedView()
                                                 .setSurfaceZOrderedOnTop(false);
                                     }
                                 })
@@ -2004,6 +2047,7 @@
                     })
                     .withEndActions(() -> {
                         if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+                            mExpandedBubble.getExpandedView().setContentVisibility(true);
                             mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
                         }
 
@@ -2188,7 +2232,7 @@
 
     private void dismissBubbleIfExists(@Nullable Bubble bubble) {
         if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
-            mBubbleData.notificationEntryRemoved(
+            mBubbleData.dismissBubbleWithKey(
                     bubble.getKey(), BubbleController.DISMISS_USER_GESTURE);
         }
     }
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 3ef2044..b378469 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -940,8 +940,12 @@
 
     /** Returns the default stack position, which is on the top left. */
     public PointF getDefaultStartPosition() {
-        return new PointF(
-                getAllowableStackPositionRegion().left,
+        boolean isRtl = mLayout != null
+                && mLayout.getResources().getConfiguration().getLayoutDirection()
+                == View.LAYOUT_DIRECTION_RTL;
+        return new PointF(isRtl
+                        ? getAllowableStackPositionRegion().right
+                        : getAllowableStackPositionRegion().left,
                 getAllowableStackPositionRegion().top + mStackStartingVerticalOffset);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
index 097932e..10d301d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -18,6 +18,7 @@
 
 import android.app.INotificationManager;
 import android.content.Context;
+import android.content.pm.LauncherApps;
 import android.view.WindowManager;
 
 import com.android.internal.statusbar.IStatusBarService;
@@ -72,7 +73,8 @@
             SysUiState sysUiState,
             INotificationManager notifManager,
             IStatusBarService statusBarService,
-            WindowManager windowManager) {
+            WindowManager windowManager,
+            LauncherApps launcherApps) {
         return new BubbleController(
                 context,
                 notificationShadeWindowController,
@@ -94,6 +96,7 @@
                 sysUiState,
                 notifManager,
                 statusBarService,
-                windowManager);
+                windowManager,
+                launcherApps);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
index d4d4d2a..eed5531 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
@@ -20,7 +20,7 @@
 import android.service.controls.Control
 import android.service.controls.ControlsProviderService
 import android.service.controls.actions.ControlAction
-import com.android.systemui.controls.UserAwareController
+import com.android.systemui.util.UserAwareController
 import java.util.function.Consumer
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index 45ba1e6..496741b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -21,7 +21,7 @@
 import android.service.controls.ControlsProviderService
 import android.service.controls.actions.ControlAction
 import com.android.systemui.controls.ControlStatus
-import com.android.systemui.controls.UserAwareController
+import com.android.systemui.util.UserAwareController
 import com.android.systemui.controls.management.ControlsFavoritingActivity
 import com.android.systemui.controls.ui.ControlsUiController
 import java.util.function.Consumer
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
index 647dacc..b9f1666 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
@@ -18,7 +18,7 @@
 
 import android.content.ComponentName
 import com.android.systemui.controls.ControlsServiceInfo
-import com.android.systemui.controls.UserAwareController
+import com.android.systemui.util.UserAwareController
 import com.android.systemui.statusbar.policy.CallbackController
 
 /**
@@ -26,7 +26,7 @@
  */
 interface ControlsListingController :
         CallbackController<ControlsListingController.ControlsListingCallback>,
-        UserAwareController {
+    UserAwareController {
 
     /**
      * @return the current list of services that satisfies the [ServiceListing].
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index d2501fa..c073642 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -35,7 +35,6 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.R
 
 import javax.inject.Inject
 import javax.inject.Singleton
@@ -164,8 +163,7 @@
                         it.show()
                     }
                 } else {
-                    cvh.setTransientStatus(
-                        cvh.context.resources.getString(R.string.controls_error_failed))
+                    cvh.setErrorStatus()
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 4eb7527..865a38a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -67,7 +67,6 @@
 
     companion object {
         const val STATE_ANIMATION_DURATION = 700L
-        private const val UPDATE_DELAY_IN_MILLIS = 3000L
         private const val ALPHA_ENABLED = 255
         private const val ALPHA_DISABLED = 0
         private const val STATUS_ALPHA_ENABLED = 1f
@@ -113,7 +112,6 @@
     val context: Context = layout.getContext()
     val clipLayer: ClipDrawable
     lateinit var cws: ControlWithState
-    var cancelUpdate: Runnable? = null
     var behavior: Behavior? = null
     var lastAction: ControlAction? = null
     var isLoading = false
@@ -148,8 +146,6 @@
 
         this.cws = cws
 
-        cancelUpdate?.run()
-
         // For the following statuses only, assume the title/subtitle could not be set properly
         // by the app and instead use the last known information from favorites
         if (controlStatus == Control.STATUS_UNKNOWN || controlStatus == Control.STATUS_NOT_FOUND) {
@@ -188,11 +184,11 @@
                 lastChallengeDialog = null
             ControlAction.RESPONSE_UNKNOWN -> {
                 lastChallengeDialog = null
-                setTransientStatus(context.resources.getString(R.string.controls_error_failed))
+                setErrorStatus()
             }
             ControlAction.RESPONSE_FAIL -> {
                 lastChallengeDialog = null
-                setTransientStatus(context.resources.getString(R.string.controls_error_failed))
+                setErrorStatus()
             }
             ControlAction.RESPONSE_CHALLENGE_PIN -> {
                 lastChallengeDialog = ChallengeDialogs.createPinDialog(
@@ -219,17 +215,10 @@
         visibleDialog = null
     }
 
-    fun setTransientStatus(tempStatus: String) {
-        val previousText = status.getText()
-
-        cancelUpdate = uiExecutor.executeDelayed({
-            animateStatusChange(/* animated */ true, {
-                setStatusText(previousText, /* immediately */ true)
-            })
-        }, UPDATE_DELAY_IN_MILLIS)
-
+    fun setErrorStatus() {
+        val text = context.resources.getString(R.string.controls_error_failed)
         animateStatusChange(/* animated */ true, {
-            setStatusText(tempStatus, /* immediately */ true)
+            setStatusText(text, /* immediately */ true)
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
index dac5537..116f3ca 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
@@ -77,8 +77,7 @@
                         cws.control?.getAppIntent()?.send()
                         context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
                     } catch (e: PendingIntent.CanceledException) {
-                        cvh.setTransientStatus(
-                            cvh.context.resources.getString(R.string.controls_error_failed))
+                        cvh.setErrorStatus()
                     }
                     dialog.dismiss()
             })
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 1f30305..fedc9e31 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -61,6 +61,8 @@
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.util.leak.LeakDetector;
 
+import java.util.concurrent.Executor;
+
 import javax.inject.Named;
 import javax.inject.Singleton;
 
@@ -188,11 +190,12 @@
     public BroadcastDispatcher providesBroadcastDispatcher(
             Context context,
             @Background Looper backgroundLooper,
+            @Background Executor backgroundExecutor,
             DumpManager dumpManager,
             BroadcastDispatcherLogger logger
     ) {
-        BroadcastDispatcher bD =
-                new BroadcastDispatcher(context, backgroundLooper, dumpManager, logger);
+        BroadcastDispatcher bD = new BroadcastDispatcher(context, backgroundLooper,
+                backgroundExecutor, dumpManager, logger);
         bD.initialize();
         return bD;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
index 1dbbb4d..ac4fc62 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
@@ -54,6 +54,7 @@
 
         // required to show above the global actions dialog
         setWindowLayoutType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+        setInputMethodMode(INPUT_METHOD_NOT_NEEDED);
         setModal(true);
 
         mGlobalActionsSidePadding = res.getDimensionPixelSize(R.dimen.global_actions_side_margin);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPowerDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPowerDialog.java
index 9dec3ab..caa88a3 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPowerDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPowerDialog.java
@@ -54,6 +54,7 @@
         window.setTitle(""); // prevent Talkback from speaking first item name twice
         window.setBackgroundDrawable(res.getDrawable(
                 com.android.systemui.R.drawable.control_background, context.getTheme()));
+        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
 
         return dialog;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 1ec285a..c59a548 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -44,7 +44,6 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.notification.MediaNotificationProcessor
 import com.android.systemui.statusbar.notification.row.HybridGroupManager
 import com.android.systemui.util.Assert
@@ -96,7 +95,7 @@
     dumpManager: DumpManager,
     mediaTimeoutListener: MediaTimeoutListener,
     mediaResumeListener: MediaResumeListener,
-    private val useMediaResumption: Boolean,
+    private var useMediaResumption: Boolean,
     private val useQsMediaPlayer: Boolean
 ) : Dumpable {
 
@@ -149,18 +148,9 @@
         mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
             setTimedOut(token, timedOut) }
         addListener(mediaTimeoutListener)
-        if (useMediaResumption) {
-            mediaResumeListener.addTrackToResumeCallback = { desc: MediaDescription,
-                resumeAction: Runnable, token: MediaSession.Token, appName: String,
-                appIntent: PendingIntent, packageName: String ->
-                addResumptionControls(desc, resumeAction, token, appName, appIntent, packageName)
-            }
-            mediaResumeListener.resumeComponentFoundCallback = { key: String, action: Runnable? ->
-                mediaEntries.get(key)?.resumeAction = action
-                mediaEntries.get(key)?.hasCheckedForResume = true
-            }
-            addListener(mediaResumeListener)
-        }
+
+        mediaResumeListener.setManager(this)
+        addListener(mediaResumeListener)
 
         val userFilter = IntentFilter(Intent.ACTION_USER_SWITCHED)
         broadcastDispatcher.registerReceiver(userChangeReceiver, userFilter, null, UserHandle.ALL)
@@ -223,7 +213,14 @@
         }
     }
 
-    private fun addResumptionControls(
+    fun setResumeAction(key: String, action: Runnable?) {
+        mediaEntries.get(key)?.let {
+            it.resumeAction = action
+            it.hasCheckedForResume = true
+        }
+    }
+
+    fun addResumptionControls(
         desc: MediaDescription,
         action: Runnable,
         token: MediaSession.Token,
@@ -233,7 +230,8 @@
     ) {
         // Resume controls don't have a notification key, so store by package name instead
         if (!mediaEntries.containsKey(packageName)) {
-            val resumeData = LOADING.copy(packageName = packageName, resumeAction = action)
+            val resumeData = LOADING.copy(packageName = packageName, resumeAction = action,
+                hasCheckedForResume = true)
             mediaEntries.put(packageName, resumeData)
         }
         backgroundExecutor.execute {
@@ -301,6 +299,8 @@
     ) {
         if (TextUtils.isEmpty(desc.title)) {
             Log.e(TAG, "Description incomplete")
+            // Delete the placeholder entry
+            mediaEntries.remove(packageName)
             return
         }
 
@@ -323,7 +323,8 @@
             onMediaDataLoaded(packageName, null, MediaData(true, bgColor, appName,
                     null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
                     packageName, token, appIntent, device = null, active = false,
-                    resumeAction = resumeAction))
+                    resumeAction = resumeAction, notificationKey = packageName,
+                    hasCheckedForResume = true))
         }
     }
 
@@ -432,11 +433,12 @@
         }
 
         val resumeAction: Runnable? = mediaEntries.get(key)?.resumeAction
+        val hasCheckedForResume = mediaEntries.get(key)?.hasCheckedForResume == true
         foregroundExecutor.execute {
             onMediaDataLoaded(key, oldKey, MediaData(true, bgColor, app, smallIconDrawable, artist,
                     song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
                     notif.contentIntent, null, active = true, resumeAction = resumeAction,
-                    notificationKey = key))
+                    notificationKey = key, hasCheckedForResume = hasCheckedForResume))
         }
     }
 
@@ -540,7 +542,7 @@
             val data = mediaEntries.remove(key)!!
             val resumeAction = getResumeMediaAction(data.resumeAction!!)
             val updated = data.copy(token = null, actions = listOf(resumeAction),
-                actionsToShowInCompact = listOf(0))
+                actionsToShowInCompact = listOf(0), active = false)
             mediaEntries.put(data.packageName, updated)
             // Notify listeners of "new" controls
             val listenersCopy = listeners.toSet()
@@ -563,20 +565,33 @@
      */
     fun hasActiveMedia() = mediaEntries.any { it.value.active }
 
-    fun isActive(token: MediaSession.Token?): Boolean {
-        if (token == null) {
-            return false
-        }
-        val controller = mediaControllerFactory.create(token)
-        val state = controller?.playbackState?.state
-        return state != null && NotificationMediaManager.isActiveState(state)
-    }
-
     /**
-     * Are there any media entries, including resume controls?
+     * Are there any media entries we should display?
+     * If resumption is enabled, this will include inactive players
+     * If resumption is disabled, we only want to show active players
      */
     fun hasAnyMedia() = if (useMediaResumption) mediaEntries.isNotEmpty() else hasActiveMedia()
 
+    fun setMediaResumptionEnabled(isEnabled: Boolean) {
+        if (useMediaResumption == isEnabled) {
+            return
+        }
+
+        useMediaResumption = isEnabled
+
+        if (!useMediaResumption) {
+            // Remove any existing resume controls
+            val listenersCopy = listeners.toSet()
+            val filtered = mediaEntries.filter { !it.value.active }
+            filtered.forEach {
+                mediaEntries.remove(it.key)
+                listenersCopy.forEach { listener ->
+                    listener.onMediaDataRemoved(it.key)
+                }
+            }
+        }
+    }
+
     interface Listener {
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index b86e1d0..26fa296 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -193,6 +193,8 @@
             override fun onDozingChanged(isDozing: Boolean) {
                 if (!isDozing) {
                     dozeAnimationRunning = false
+                } else {
+                    updateDesiredLocation()
                 }
             }
         })
@@ -510,12 +512,19 @@
                 (statusbarState == StatusBarState.KEYGUARD ||
                         statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
         val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications()
-        return when {
+        val location = when {
             qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS
             qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
             onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN
             else -> LOCATION_QQS
         }
+        // When we're on lock screen and the player is not active, we should keep it in QS.
+        // Otherwise it will try to animate a transition that doesn't make sense.
+        if (location == LOCATION_LOCKSCREEN && getHost(location)?.visible != true &&
+                !statusBarStateController.isDozing) {
+            return LOCATION_QS
+        }
+        return location
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index e8a4b1e..0cc1e7b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.media
 
-import android.app.PendingIntent
 import android.content.BroadcastReceiver
 import android.content.ComponentName
 import android.content.Context
@@ -25,12 +24,13 @@
 import android.content.pm.PackageManager
 import android.media.MediaDescription
 import android.media.session.MediaController
-import android.media.session.MediaSession
 import android.os.UserHandle
+import android.provider.Settings
 import android.service.media.MediaBrowserService
 import android.util.Log
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.Utils
 import java.util.concurrent.ConcurrentLinkedQueue
 import java.util.concurrent.Executor
@@ -46,21 +46,14 @@
 class MediaResumeListener @Inject constructor(
     private val context: Context,
     private val broadcastDispatcher: BroadcastDispatcher,
-    @Background private val backgroundExecutor: Executor
+    @Background private val backgroundExecutor: Executor,
+    private val tunerService: TunerService
 ) : MediaDataManager.Listener {
 
-    private val useMediaResumption: Boolean = Utils.useMediaResumption(context)
+    private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
     private val resumeComponents: ConcurrentLinkedQueue<ComponentName> = ConcurrentLinkedQueue()
 
-    lateinit var addTrackToResumeCallback: (
-        MediaDescription,
-        Runnable,
-        MediaSession.Token,
-        String,
-        PendingIntent,
-        String
-    ) -> Unit
-    lateinit var resumeComponentFoundCallback: (String, Runnable?) -> Unit
+    private lateinit var mediaDataManager: MediaDataManager
 
     private var mediaBrowser: ResumeMediaBrowser? = null
     private var currentUserId: Int
@@ -96,8 +89,8 @@
             }
 
             Log.d(TAG, "Adding resume controls $desc")
-            addTrackToResumeCallback(desc, resumeAction, token, appName.toString(), appIntent,
-                component.packageName)
+            mediaDataManager.addResumptionControls(desc, resumeAction, token, appName.toString(),
+                appIntent, component.packageName)
         }
     }
 
@@ -113,6 +106,18 @@
         }
     }
 
+    fun setManager(manager: MediaDataManager) {
+        mediaDataManager = manager
+
+        // Add listener for resumption setting changes
+        tunerService.addTunable(object : TunerService.Tunable {
+            override fun onTuningChanged(key: String?, newValue: String?) {
+                useMediaResumption = Utils.useMediaResumption(context)
+                mediaDataManager.setMediaResumptionEnabled(useMediaResumption)
+            }
+        }, Settings.Secure.MEDIA_CONTROLS_RESUME)
+    }
+
     private fun loadSavedComponents() {
         // Make sure list is empty (if we switched users)
         resumeComponents.clear()
@@ -165,7 +170,7 @@
                     }
                 } else {
                     // No service found
-                    resumeComponentFoundCallback(key, null)
+                    mediaDataManager.setResumeAction(key, null)
                 }
             }
         }
@@ -182,7 +187,7 @@
                 object : ResumeMediaBrowser.Callback() {
                     override fun onConnected() {
                         Log.d(TAG, "yes we can resume with $componentName")
-                        resumeComponentFoundCallback(key, getResumeAction(componentName))
+                        mediaDataManager.setResumeAction(key, getResumeAction(componentName))
                         updateResumptionList(componentName)
                         mediaBrowser?.disconnect()
                         mediaBrowser = null
@@ -190,7 +195,7 @@
 
                     override fun onError() {
                         Log.e(TAG, "Cannot resume with $componentName")
-                        resumeComponentFoundCallback(key, null)
+                        mediaDataManager.setResumeAction(key, null)
                         mediaBrowser?.disconnect()
                         mediaBrowser = null
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
index 6462f07..1842564 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
@@ -30,8 +30,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.systemui.util.Utils;
-
 import java.util.List;
 
 /**
@@ -46,7 +44,6 @@
     public static final String DELIMITER = ":";
 
     private static final String TAG = "ResumeMediaBrowser";
-    private boolean mIsEnabled = false;
     private final Context mContext;
     private final Callback mCallback;
     private MediaBrowser mMediaBrowser;
@@ -59,7 +56,6 @@
      * @param componentName Component name of the MediaBrowserService this browser will connect to
      */
     public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName) {
-        mIsEnabled = Utils.useMediaResumption(context);
         mContext = context;
         mCallback = callback;
         mComponentName = componentName;
@@ -74,9 +70,6 @@
      * ResumeMediaBrowser#disconnect will be called automatically with this function.
      */
     public void findRecentMedia() {
-        if (!mIsEnabled) {
-            return;
-        }
         Log.d(TAG, "Connecting to " + mComponentName);
         disconnect();
         Bundle rootHints = new Bundle();
@@ -186,9 +179,6 @@
      * ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed.
      */
     public void restart() {
-        if (!mIsEnabled) {
-            return;
-        }
         disconnect();
         Bundle rootHints = new Bundle();
         rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
@@ -250,9 +240,6 @@
      * ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed.
      */
     public void testConnection() {
-        if (!mIsEnabled) {
-            return;
-        }
         disconnect();
         final MediaBrowser.ConnectionCallback connectionCallback =
                 new MediaBrowser.ConnectionCallback() {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index e38bfb4..7a18ec3 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -41,6 +41,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.model.SysUiState;
 import com.android.systemui.pip.BasePipManager;
 import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.pip.PipSnapAlgorithm;
@@ -81,12 +82,12 @@
     private InputConsumerController mInputConsumerController;
     private PipMediaController mMediaController;
     private PipTouchHandler mTouchHandler;
+    private PipTaskOrganizer mPipTaskOrganizer;
     private PipAppOpsListener mAppOpsListener;
     private IPinnedStackAnimationListener mPinnedStackAnimationRecentsListener;
     private boolean mIsInFixedRotation;
 
     protected PipMenuActivityController mMenuController;
-    protected PipTaskOrganizer mPipTaskOrganizer;
 
     /**
      * Handler for display rotation changes.
@@ -229,7 +230,8 @@
             DeviceConfigProxy deviceConfig,
             PipBoundsHandler pipBoundsHandler,
             PipSnapAlgorithm pipSnapAlgorithm,
-            PipTaskOrganizer pipTaskOrganizer) {
+            PipTaskOrganizer pipTaskOrganizer,
+            SysUiState sysUiState) {
         mContext = context;
         mActivityManager = ActivityManager.getService();
 
@@ -250,7 +252,7 @@
                 mInputConsumerController);
         mTouchHandler = new PipTouchHandler(context, mActivityManager,
                 mMenuController, mInputConsumerController, mPipBoundsHandler, mPipTaskOrganizer,
-                floatingContentCoordinator, deviceConfig, pipSnapAlgorithm);
+                floatingContentCoordinator, deviceConfig, pipSnapAlgorithm, sysUiState);
         mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
                 mTouchHandler.getMotionHelper());
         displayController.addDisplayChangingController(mRotationController);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 6ab73fc..2043546 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -110,6 +110,8 @@
 
     private static final float DISABLED_ACTION_ALPHA = 0.54f;
 
+    private static final boolean ENABLE_RESIZE_HANDLE = false;
+
     private int mMenuState;
     private boolean mResize = true;
     private boolean mAllowMenuTimeout = true;
@@ -388,7 +390,8 @@
             ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
                     mDismissButton.getAlpha(), 1f);
             ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
-                    mResizeHandle.getAlpha(), menuState == MENU_STATE_CLOSE && showResizeHandle
+                    mResizeHandle.getAlpha(),
+                    ENABLE_RESIZE_HANDLE && menuState == MENU_STATE_CLOSE && showResizeHandle
                             ? 1f : 0f);
             if (menuState == MENU_STATE_FULL) {
                 mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 06c98d0..2680505 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -206,6 +206,7 @@
     void synchronizePinnedStackBounds() {
         cancelAnimations();
         mBounds.set(mPipTaskOrganizer.getLastReportedBounds());
+        mTemporaryBounds.setEmpty();
 
         if (mPipTaskOrganizer.isInPip()) {
             mFloatingContentCoordinator.onContentMoved(this);
@@ -274,6 +275,11 @@
         final float destinationX = targetCenter.x - (desiredWidth / 2f);
         final float destinationY = targetCenter.y - (desiredHeight / 2f);
 
+        // If we're already in the dismiss target area, then there won't be a move to set the
+        // temporary bounds, so just initialize it to the current bounds
+        if (mTemporaryBounds.isEmpty()) {
+            mTemporaryBounds.set(mBounds);
+        }
         mTemporaryBoundsPhysicsAnimator
                 .spring(FloatProperties.RECT_X, destinationX, velX, mSpringConfig)
                 .spring(FloatProperties.RECT_Y, destinationY, velY, mSpringConfig)
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
index c151715..a4edace 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
@@ -21,6 +21,13 @@
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -43,6 +50,7 @@
 
 import com.android.internal.policy.TaskResizingAlgorithm;
 import com.android.systemui.R;
+import com.android.systemui.model.SysUiState;
 import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.pip.PipTaskOrganizer;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -58,11 +66,21 @@
 
     private static final String TAG = "PipResizeGestureHandler";
 
+    private static final int INVALID_SYSUI_STATE_MASK =
+            SYSUI_STATE_GLOBAL_ACTIONS_SHOWING
+            | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+            | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
+            | SYSUI_STATE_BOUNCER_SHOWING
+            | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+            | SYSUI_STATE_BUBBLES_EXPANDED
+            | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+
     private final Context mContext;
     private final PipBoundsHandler mPipBoundsHandler;
     private final PipMotionHelper mMotionHelper;
     private final int mDisplayId;
     private final Executor mMainExecutor;
+    private final SysUiState mSysUiState;
     private final Region mTmpRegion = new Region();
 
     private final PointF mDownPoint = new PointF();
@@ -96,7 +114,7 @@
     public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler,
             PipMotionHelper motionHelper, DeviceConfigProxy deviceConfig,
             PipTaskOrganizer pipTaskOrganizer, Supplier<Rect> movementBoundsSupplier,
-            Runnable updateMovementBoundsRunnable) {
+            Runnable updateMovementBoundsRunnable, SysUiState sysUiState) {
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = context.getMainExecutor();
@@ -105,6 +123,7 @@
         mPipTaskOrganizer = pipTaskOrganizer;
         mMovementBoundsSupplier = movementBoundsSupplier;
         mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
+        mSysUiState = sysUiState;
 
         context.getDisplay().getRealSize(mMaxSize);
         reloadResources();
@@ -258,13 +277,17 @@
         }
     }
 
+    private boolean isInValidSysUiState() {
+        return (mSysUiState.getFlags() & INVALID_SYSUI_STATE_MASK) == 0;
+    }
+
     private void onMotionEvent(MotionEvent ev) {
         int action = ev.getActionMasked();
         float x = ev.getX();
         float y = ev.getY();
         if (action == MotionEvent.ACTION_DOWN) {
             mLastResizeBounds.setEmpty();
-            mAllowGesture = isWithinTouchRegion((int) x, (int) y);
+            mAllowGesture = isInValidSysUiState() && isWithinTouchRegion((int) x, (int) y);
             if (mAllowGesture) {
                 setCtrlType((int) x, (int) y);
                 mDownPoint.set(x, y);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 199e3fa..a4d7bad 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -57,6 +57,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.logging.MetricsLoggerWrapper;
 import com.android.systemui.R;
+import com.android.systemui.model.SysUiState;
 import com.android.systemui.pip.PipAnimationController;
 import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.pip.PipSnapAlgorithm;
@@ -82,6 +83,9 @@
     /** Duration of the dismiss scrim fading in/out. */
     private static final int DISMISS_TRANSITION_DURATION_MS = 200;
 
+    /* The multiplier to apply scale the target size by when applying the magnetic field radius */
+    private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f;
+
     // Allow dragging the PIP to a location to close it
     private final boolean mEnableDismissDragToEdge;
     // Allow PIP to resize to a slightly bigger state upon touch
@@ -219,7 +223,8 @@
             PipTaskOrganizer pipTaskOrganizer,
             FloatingContentCoordinator floatingContentCoordinator,
             DeviceConfigProxy deviceConfig,
-            PipSnapAlgorithm pipSnapAlgorithm) {
+            PipSnapAlgorithm pipSnapAlgorithm,
+            SysUiState sysUiState) {
         // Initialize the Pip input consumer
         mContext = context;
         mActivityManager = activityManager;
@@ -234,7 +239,7 @@
         mPipResizeGestureHandler =
                 new PipResizeGestureHandler(context, pipBoundsHandler, mMotionHelper,
                         deviceConfig, pipTaskOrganizer, this::getMovementBounds,
-                        this::updateMovementBounds);
+                        this::updateMovementBounds, sysUiState);
         mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler,
                 () -> mMenuController.showMenuWithDelay(MENU_STATE_FULL, mMotionHelper.getBounds(),
                         true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle()));
@@ -330,8 +335,9 @@
                 R.dimen.floating_dismiss_bottom_margin);
         mTargetView.setLayoutParams(newParams);
 
-        // Set the magnetic field radius equal to twice the size of the target.
-        mMagneticTarget.setMagneticFieldRadiusPx(targetSize * 2);
+        // Set the magnetic field radius equal to the target size from the center of the target
+        mMagneticTarget.setMagneticFieldRadiusPx(
+                (int) (targetSize * MAGNETIC_FIELD_RADIUS_MULTIPLIER));
     }
 
     private boolean shouldShowResizeHandle() {
@@ -632,6 +638,7 @@
                 && !mMotionHelper.isAnimating()
                 && mPipResizeGestureHandler.isWithinTouchRegion(
                         (int) ev.getRawX(), (int) ev.getRawY())) {
+            mTouchState.onTouchEvent(ev);
             return true;
         }
 
@@ -953,7 +960,6 @@
             }
 
             final PointF vel = touchState.getVelocity();
-            final float velocity = PointF.length(vel.x, vel.y);
 
             if (touchState.isDragging()) {
                 if (mMenuState != MENU_STATE_NONE) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
index 2365e67..0a84f5e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.os.Handler;
+import android.os.UserHandle;
 import android.provider.Settings.Secure;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -30,6 +31,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Prefs;
 import com.android.systemui.Prefs.Key;
+import com.android.systemui.util.UserAwareController;
 
 import java.util.Arrays;
 import java.util.Collection;
@@ -37,7 +39,7 @@
 
 import javax.inject.Inject;
 
-public class AutoAddTracker {
+public class AutoAddTracker implements UserAwareController {
 
     private static final String[][] CONVERT_PREFS = {
             {Key.QS_HOTSPOT_ADDED, HOTSPOT},
@@ -49,20 +51,39 @@
 
     private final ArraySet<String> mAutoAdded;
     private final Context mContext;
+    private int mUserId;
 
-    @Inject
-    public AutoAddTracker(Context context) {
+    public AutoAddTracker(Context context, int userId) {
         mContext = context;
+        mUserId = userId;
         mAutoAdded = new ArraySet<>(getAdded());
         // TODO: remove migration code and shared preferences keys after P release
-        for (String[] convertPref : CONVERT_PREFS) {
-            if (Prefs.getBoolean(context, convertPref[0], false)) {
-                setTileAdded(convertPref[1]);
-                Prefs.remove(context, convertPref[0]);
+        if (mUserId == UserHandle.USER_SYSTEM) {
+            for (String[] convertPref : CONVERT_PREFS) {
+                if (Prefs.getBoolean(context, convertPref[0], false)) {
+                    setTileAdded(convertPref[1]);
+                    Prefs.remove(context, convertPref[0]);
+                }
             }
         }
         mContext.getContentResolver().registerContentObserver(
-                Secure.getUriFor(Secure.QS_AUTO_ADDED_TILES), false, mObserver);
+                Secure.getUriFor(Secure.QS_AUTO_ADDED_TILES), false, mObserver,
+                UserHandle.USER_ALL);
+    }
+
+    @Override
+    public void changeUser(UserHandle newUser) {
+        if (newUser.getIdentifier() == mUserId) {
+            return;
+        }
+        mUserId = newUser.getIdentifier();
+        mAutoAdded.clear();
+        mAutoAdded.addAll(getAdded());
+    }
+
+    @Override
+    public int getCurrentUserId() {
+        return mUserId;
     }
 
     public boolean isAdded(String tile) {
@@ -86,12 +107,13 @@
     }
 
     private void saveTiles() {
-        Secure.putString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES,
-                TextUtils.join(",", mAutoAdded));
+        Secure.putStringForUser(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES,
+                TextUtils.join(",", mAutoAdded), mUserId);
     }
 
     private Collection<String> getAdded() {
-        String current = Secure.getString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES);
+        String current = Secure.getStringForUser(mContext.getContentResolver(),
+                Secure.QS_AUTO_ADDED_TILES, mUserId);
         if (current == null) {
             return Collections.emptyList();
         }
@@ -102,7 +124,27 @@
     protected final ContentObserver mObserver = new ContentObserver(new Handler()) {
         @Override
         public void onChange(boolean selfChange) {
+            mAutoAdded.clear();
             mAutoAdded.addAll(getAdded());
         }
     };
+
+    public static class Builder {
+        private final Context mContext;
+        private int mUserId;
+
+        @Inject
+        public Builder(Context context) {
+            mContext = context;
+        }
+
+        public Builder setUserId(int userId) {
+            mUserId = userId;
+            return this;
+        }
+
+        public AutoAddTracker build() {
+            return new AutoAddTracker(mContext, mUserId);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 7bcaa72..51eca67 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -148,14 +148,19 @@
         final String vpnName = mSecurityController.getPrimaryVpnName();
         final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName();
         final CharSequence organizationName = mSecurityController.getDeviceOwnerOrganizationName();
-        final CharSequence workProfileName = mSecurityController.getWorkProfileOrganizationName();
+        final CharSequence workProfileOrganizationName =
+                mSecurityController.getWorkProfileOrganizationName();
+        final boolean isProfileOwnerOfOrganizationOwnedDevice =
+                mSecurityController.isProfileOwnerOfOrganizationOwnedDevice();
         // Update visibility of footer
-        mIsVisible = (isDeviceManaged && !isDemoDevice) || hasCACerts || hasCACertsInWorkProfile ||
-            vpnName != null || vpnNameWorkProfile != null;
+        mIsVisible = (isDeviceManaged && !isDemoDevice) || hasCACerts || hasCACertsInWorkProfile
+                || vpnName != null || vpnNameWorkProfile != null
+                || isProfileOwnerOfOrganizationOwnedDevice;
         // Update the string
         mFooterTextContent = getFooterText(isDeviceManaged, hasWorkProfile,
                 hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName,
-                vpnNameWorkProfile, organizationName, workProfileName);
+                vpnNameWorkProfile, organizationName, workProfileOrganizationName,
+                isProfileOwnerOfOrganizationOwnedDevice);
         // Update the icon
         int footerIconId = R.drawable.ic_info_outline;
         if (vpnName != null || vpnNameWorkProfile != null) {
@@ -175,7 +180,8 @@
     protected CharSequence getFooterText(boolean isDeviceManaged, boolean hasWorkProfile,
             boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
             String vpnName, String vpnNameWorkProfile, CharSequence organizationName,
-            CharSequence workProfileName) {
+            CharSequence workProfileOrganizationName,
+            boolean isProfileOwnerOfOrganizationOwnedDevice) {
         if (isDeviceManaged || DEBUG_FORCE_VISIBLE) {
             if (hasCACerts || hasCACertsInWorkProfile || isNetworkLoggingEnabled) {
                 if (organizationName == null) {
@@ -211,13 +217,13 @@
                     organizationName);
         } // end if(isDeviceManaged)
         if (hasCACertsInWorkProfile) {
-            if (workProfileName == null) {
+            if (workProfileOrganizationName == null) {
                 return mContext.getString(
                         R.string.quick_settings_disclosure_managed_profile_monitoring);
             }
             return mContext.getString(
                     R.string.quick_settings_disclosure_named_managed_profile_monitoring,
-                    workProfileName);
+                    workProfileOrganizationName);
         }
         if (hasCACerts) {
             return mContext.getString(R.string.quick_settings_disclosure_monitoring);
@@ -238,6 +244,13 @@
             return mContext.getString(R.string.quick_settings_disclosure_named_vpn,
                     vpnName);
         }
+        if (isProfileOwnerOfOrganizationOwnedDevice) {
+            if (workProfileOrganizationName == null) {
+                return mContext.getString(R.string.quick_settings_disclosure_management);
+            }
+            return mContext.getString(R.string.quick_settings_disclosure_named_management,
+                    workProfileOrganizationName);
+        }
         return null;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 65d3572..1f3967c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -255,6 +255,9 @@
         int currentUser = ActivityManager.getCurrentUser();
         if (currentUser != mCurrentUser) {
             mUserContext = mContext.createContextAsUser(UserHandle.of(currentUser), 0);
+            if (mAutoTiles != null) {
+                mAutoTiles.changeUser(UserHandle.of(currentUser));
+            }
         }
         if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
         mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
@@ -474,7 +477,7 @@
 
         tiles.addAll(Arrays.asList(defaultTileList.split(",")));
         if (Build.IS_DEBUGGABLE
-                && GarbageMonitor.MemoryTile.ADD_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) {
+                && GarbageMonitor.ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) {
             tiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
         }
         return tiles;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index f476ddd7..33f0f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -622,7 +622,7 @@
     /**
      * Clears current screenshot
      */
-    private void dismissScreenshot(String reason, boolean immediate) {
+    void dismissScreenshot(String reason, boolean immediate) {
         Log.v(TAG, "clearing screenshot: " + reason);
         mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
         mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index c05c823..9f8a9bb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -16,12 +16,17 @@
 
 package com.android.systemui.screenshot;
 
+import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
+
 import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE;
 import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI;
 
 import android.app.Service;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.Bitmap;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -51,6 +56,16 @@
     private final UserManager mUserManager;
     private final UiEventLogger mUiEventLogger;
 
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction()) && mScreenshot != null) {
+                mScreenshot.dismissScreenshot("close system dialogs", true);
+            }
+        }
+    };
+
     private Handler mHandler = new Handler(Looper.myLooper()) {
         @Override
         public void handleMessage(Message msg) {
@@ -119,12 +134,18 @@
 
     @Override
     public IBinder onBind(Intent intent) {
+        // register broadcast receiver
+        IntentFilter filter = new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS);
+        registerReceiver(mBroadcastReceiver, filter);
+
         return new Messenger(mHandler).getBinder();
+
     }
 
     @Override
     public boolean onUnbind(Intent intent) {
         if (mScreenshot != null) mScreenshot.stopScreenshot();
+        unregisterReceiver(mBroadcastReceiver);
         return true;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index fab120e..03a0d93 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -127,7 +127,7 @@
                         snap.calculateNonDismissingSnapTarget(position);
                 sdl.resizeSplits(target.position, t);
 
-                if (isSplitActive()) {
+                if (isSplitActive() && mHomeStackResizable) {
                     WindowManagerProxy.applyHomeTasksMinimized(sdl, mSplits.mSecondary.token, t);
                 }
                 if (mWindowManagerProxy.queueSyncTransactionIfWaiting(t)) {
@@ -428,7 +428,7 @@
     }
 
     private void updateTouchable() {
-        mWindowManager.setTouchable((mHomeStackResizable || !mMinimized) && !mAdjustedForIme);
+        mWindowManager.setTouchable(!mAdjustedForIme);
     }
 
     /**
@@ -523,7 +523,7 @@
     }
 
     void ensureMinimizedSplit() {
-        setHomeMinimized(true /* minimized */, mSplits.mSecondary.isResizeable);
+        setHomeMinimized(true /* minimized */, mHomeStackResizable);
         if (!isDividerVisible()) {
             // Wasn't in split-mode yet, so enter now.
             if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
index aea87f2..d782a3ca 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
@@ -126,16 +126,16 @@
     @Override
     public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
             boolean imeShouldShow, SurfaceControl.Transaction t) {
+        mHiddenTop = hiddenTop;
+        mShownTop = shownTop;
+        mTargetShown = imeShouldShow;
         if (!isDividerVisible()) {
             return;
         }
         final boolean splitIsVisible = !getView().isHidden();
         mSecondaryHasFocus = getSecondaryHasFocus(displayId);
         final boolean targetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus
-                && !getLayout().mDisplayLayout.isLandscape();
-        mHiddenTop = hiddenTop;
-        mShownTop = shownTop;
-        mTargetShown = imeShouldShow;
+                && !getLayout().mDisplayLayout.isLandscape() && !mSplits.mDivider.isMinimized();
         if (mLastAdjustTop < 0) {
             mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop;
         } else if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 2271f6d..42d8c95 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -320,8 +320,7 @@
         super.onAttachedToWindow();
 
         // Save the current target if not minimized once attached to window
-        if (mHomeStackResizable && mDockSide != WindowManager.DOCKED_INVALID
-                && !mIsInMinimizeInteraction) {
+        if (mDockSide != WindowManager.DOCKED_INVALID && !mIsInMinimizeInteraction) {
             saveSnapTargetBeforeMinimized(mSnapTargetBeforeMinimized);
         }
         mFirstLayout = true;
@@ -470,8 +469,7 @@
     }
 
     public DividerSnapAlgorithm getSnapAlgorithm() {
-        return mDockedStackMinimized
-                && mHomeStackResizable ? mSplitLayout.getMinimizedSnapAlgorithm()
+        return mDockedStackMinimized ? mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
                         : mSplitLayout.getSnapAlgorithm();
     }
 
@@ -629,7 +627,7 @@
             }
 
             // Record last snap target the divider moved to
-            if (mHomeStackResizable && !mIsInMinimizeInteraction) {
+            if (!mIsInMinimizeInteraction) {
                 // The last snapTarget position can be negative when the last divider position was
                 // offscreen. In that case, save the middle (default) SnapTarget so calculating next
                 // position isn't negative.
@@ -774,7 +772,8 @@
         mSplitLayout.resizeSplits(midPos);
         Transaction t = mTiles.getTransaction();
         if (mDockedStackMinimized) {
-            int position = mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget().position;
+            int position = mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
+                    .getMiddleTarget().position;
             calculateBoundsForPosition(position, mDockSide, mDockedRect);
             calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
                     mOtherRect);
@@ -811,23 +810,9 @@
         updateDockSide();
         if (!minimized) {
             resetBackground();
-        } else if (!isHomeStackResizable) {
-            if (mDockSide == WindowManager.DOCKED_TOP) {
-                mBackground.setPivotY(0);
-                mBackground.setScaleY(MINIMIZE_DOCK_SCALE);
-            } else if (mDockSide == WindowManager.DOCKED_LEFT
-                    || mDockSide == WindowManager.DOCKED_RIGHT) {
-                mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
-                        ? 0
-                        : mBackground.getWidth());
-                mBackground.setScaleX(MINIMIZE_DOCK_SCALE);
-            }
         }
         mMinimizedShadow.setAlpha(minimized ? 1f : 0f);
-        if (!isHomeStackResizable) {
-            mHandle.setAlpha(minimized ? 0f : 1f);
-            mDockedStackMinimized = minimized;
-        } else if (mDockedStackMinimized != minimized) {
+        if (mDockedStackMinimized != minimized) {
             mDockedStackMinimized = minimized;
             if (mSplitLayout.mDisplayLayout.rotation() != mDefaultDisplay.getRotation()) {
                 // Splitscreen to minimize is about to starts after rotating landscape to seascape,
@@ -840,8 +825,8 @@
                     // Relayout to recalculate the divider shadow when minimizing
                     requestLayout();
                     mIsInMinimizeInteraction = true;
-                    resizeStackSurfaces(
-                            mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget(), t);
+                    resizeStackSurfaces(mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
+                            .getMiddleTarget(), t);
                 } else {
                     resizeStackSurfaces(mSnapTargetBeforeMinimized, t);
                     mIsInMinimizeInteraction = false;
@@ -860,11 +845,11 @@
             t.show(sc).apply();
             mTiles.releaseTransaction(t);
         });
-        if (isHomeStackResizable) {
-            SnapTarget miniMid = mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget();
-            if (mDockedStackMinimized) {
-                mDividerPositionY = mDividerPositionX = miniMid.position;
-            }
+
+        SnapTarget miniMid =
+                mSplitLayout.getMinimizedSnapAlgorithm(isHomeStackResizable).getMiddleTarget();
+        if (mDockedStackMinimized) {
+            mDividerPositionY = mDividerPositionX = miniMid.position;
         }
     }
 
@@ -903,38 +888,15 @@
         if (DEBUG) Slog.d(TAG, "setMinDock: " + mDockedStackMinimized + "->" + minimized);
         mHomeStackResizable = isHomeStackResizable;
         updateDockSide();
-        if (!isHomeStackResizable) {
-            mMinimizedShadow.animate()
-                    .alpha(minimized ? 1f : 0f)
-                    .setInterpolator(Interpolators.ALPHA_IN)
-                    .setDuration(animDuration)
-                    .start();
-            mHandle.animate()
-                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                    .setDuration(animDuration)
-                    .alpha(minimized ? 0f : 1f)
-                    .start();
-            if (mDockSide == WindowManager.DOCKED_TOP) {
-                mBackground.setPivotY(0);
-                mBackground.animate()
-                        .scaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f);
-            } else if (mDockSide == WindowManager.DOCKED_LEFT
-                    || mDockSide == WindowManager.DOCKED_RIGHT) {
-                mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
-                        ? 0
-                        : mBackground.getWidth());
-                mBackground.animate()
-                        .scaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f);
-            }
-            mDockedStackMinimized = minimized;
-        } else if (mDockedStackMinimized != minimized) {
+        if (mDockedStackMinimized != minimized) {
             mIsInMinimizeInteraction = true;
             mDockedStackMinimized = minimized;
             stopDragging(minimized
                             ? mSnapTargetBeforeMinimized.position
                             : getCurrentPosition(),
                     minimized
-                            ? mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget()
+                            ? mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
+                                    .getMiddleTarget()
                             : mSnapTargetBeforeMinimized,
                     animDuration, Interpolators.FAST_OUT_SLOW_IN, 0);
             setAdjustedForIme(false, animDuration);
@@ -1127,7 +1089,7 @@
         final boolean ownTransaction = transaction == null;
         final Transaction t = ownTransaction ? mTiles.getTransaction() : transaction;
         mLastResizeRect.set(mDockedRect);
-        if (mHomeStackResizable && mIsInMinimizeInteraction) {
+        if (mIsInMinimizeInteraction) {
             calculateBoundsForPosition(mSnapTargetBeforeMinimized.position, mDockSide,
                     mDockedTaskRect);
             calculateBoundsForPosition(mSnapTargetBeforeMinimized.position,
@@ -1138,8 +1100,7 @@
                 mDockedTaskRect.offset(Math.max(position, -mDividerSize)
                         - mDockedTaskRect.left + mDividerSize, 0);
             }
-            resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect,
-                    mOtherTaskRect);
+            resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
             if (ownTransaction) {
                 t.apply();
                 mTiles.releaseTransaction(t);
@@ -1420,7 +1381,7 @@
 
     void onUndockingTask() {
         int dockSide = mSplitLayout.getPrimarySplitSide();
-        if (inSplitMode() && (mHomeStackResizable || !mDockedStackMinimized)) {
+        if (inSplitMode()) {
             startDragging(false /* animate */, false /* touching */);
             SnapTarget target = dockSideTopLeft(dockSide)
                     ? mSplitLayout.getSnapAlgorithm().getDismissEndTarget()
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java
index 92f6b4a..69095f7 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.stackdivider;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.WindowManager.DOCKED_BOTTOM;
@@ -113,11 +111,6 @@
         }
     }
 
-    boolean isMinimized() {
-        return mTiles.mSecondary.topActivityType == ACTIVITY_TYPE_HOME
-                || mTiles.mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS;
-    }
-
     DividerSnapAlgorithm getSnapAlgorithm() {
         if (mSnapAlgorithm == null) {
             updateResources();
@@ -129,14 +122,14 @@
         return mSnapAlgorithm;
     }
 
-    DividerSnapAlgorithm getMinimizedSnapAlgorithm() {
+    DividerSnapAlgorithm getMinimizedSnapAlgorithm(boolean homeStackResizable) {
         if (mMinimizedSnapAlgorithm == null) {
             updateResources();
             boolean isHorizontalDivision = !mDisplayLayout.isLandscape();
             mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(mContext.getResources(),
                     mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize,
                     isHorizontalDivision, mDisplayLayout.stableInsets(), getPrimarySplitSide(),
-                    true /* isMinimized */);
+                    true /* isMinimized */, homeStackResizable);
         }
         return mMinimizedSnapAlgorithm;
     }
@@ -168,8 +161,9 @@
                 mDisplayLayout.height(), mDividerSize);
     }
 
-    Rect calcMinimizedHomeStackBounds() {
-        DividerSnapAlgorithm.SnapTarget miniMid = getMinimizedSnapAlgorithm().getMiddleTarget();
+    Rect calcResizableMinimizedHomeStackBounds() {
+        DividerSnapAlgorithm.SnapTarget miniMid =
+                getMinimizedSnapAlgorithm(true /* resizable */).getMiddleTarget();
         Rect homeBounds = new Rect();
         DockedDividerUtils.calculateBoundsForPosition(miniMid.position,
                 DockedDividerUtils.invertDockSide(getPrimarySplitSide()), homeBounds,
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
index db32482..6751e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
@@ -165,8 +165,9 @@
             Log.e(TAG, "Got handleTaskInfoChanged when not initialized: " + info);
             return;
         }
-        final boolean secondaryWasHomeOrRecents = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
-                || mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS;
+        final boolean secondaryImpliedMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
+                || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS
+                        && mDivider.isHomeStackResizable());
         final boolean primaryWasEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
         final boolean secondaryWasEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
         if (info.token.asBinder() == mPrimary.token.asBinder()) {
@@ -176,13 +177,14 @@
         }
         final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
         final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
-        final boolean secondaryIsHomeOrRecents = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
-                || mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS;
+        final boolean secondaryImpliesMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
+                || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS
+                        && mDivider.isHomeStackResizable());
         if (DEBUG) {
             Log.d(TAG, "onTaskInfoChanged " + mPrimary + "  " + mSecondary);
         }
         if (primaryIsEmpty == primaryWasEmpty && secondaryWasEmpty == secondaryIsEmpty
-                && secondaryWasHomeOrRecents == secondaryIsHomeOrRecents) {
+                && secondaryImpliedMinimize == secondaryImpliesMinimize) {
             // No relevant changes
             return;
         }
@@ -211,7 +213,7 @@
                 }
                 mDivider.startEnterSplit();
             }
-        } else if (secondaryIsHomeOrRecents) {
+        } else if (secondaryImpliesMinimize) {
             // Both splits are populated but the secondary split has a home/recents stack on top,
             // so enter minimized mode.
             mDivider.ensureMinimizedSplit();
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index da1880a..410e3dd 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -19,6 +19,9 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.annotation.NonNull;
@@ -115,7 +118,7 @@
         WindowOrganizer.applyTransaction(t);
     }
 
-    private static boolean getHomeAndRecentsTasks(List<WindowContainerToken> out,
+    private static boolean getHomeAndRecentsTasks(List<ActivityManager.RunningTaskInfo> out,
             WindowContainerToken parent) {
         boolean resizable = false;
         List<ActivityManager.RunningTaskInfo> rootTasks = parent == null
@@ -123,7 +126,7 @@
                 : TaskOrganizer.getChildTasks(parent, HOME_AND_RECENTS);
         for (int i = 0, n = rootTasks.size(); i < n; ++i) {
             final ActivityManager.RunningTaskInfo ti = rootTasks.get(i);
-            out.add(ti.token);
+            out.add(ti);
             if (ti.topActivityType == ACTIVITY_TYPE_HOME) {
                 resizable = ti.isResizeable;
             }
@@ -140,16 +143,37 @@
             @NonNull WindowContainerTransaction wct) {
         // Resize the home/recents stacks to the larger minimized-state size
         final Rect homeBounds;
-        final ArrayList<WindowContainerToken> homeStacks = new ArrayList<>();
+        final ArrayList<ActivityManager.RunningTaskInfo> homeStacks = new ArrayList<>();
         boolean isHomeResizable = getHomeAndRecentsTasks(homeStacks, parent);
         if (isHomeResizable) {
-            homeBounds = layout.calcMinimizedHomeStackBounds();
+            homeBounds = layout.calcResizableMinimizedHomeStackBounds();
         } else {
-            homeBounds = new Rect(0, 0, layout.mDisplayLayout.width(),
-                    layout.mDisplayLayout.height());
+            // home is not resizable, so lock it to its inherent orientation size.
+            homeBounds = new Rect(0, 0, 0, 0);
+            for (int i = homeStacks.size() - 1; i >= 0; --i) {
+                if (homeStacks.get(i).topActivityType == ACTIVITY_TYPE_HOME) {
+                    final int orient = homeStacks.get(i).configuration.orientation;
+                    final boolean displayLandscape = layout.mDisplayLayout.isLandscape();
+                    final boolean isLandscape = orient == ORIENTATION_LANDSCAPE
+                            || (orient == ORIENTATION_UNDEFINED && displayLandscape);
+                    homeBounds.right = isLandscape == displayLandscape
+                            ? layout.mDisplayLayout.width() : layout.mDisplayLayout.height();
+                    homeBounds.bottom = isLandscape == displayLandscape
+                            ? layout.mDisplayLayout.height() : layout.mDisplayLayout.width();
+                    break;
+                }
+            }
         }
         for (int i = homeStacks.size() - 1; i >= 0; --i) {
-            wct.setBounds(homeStacks.get(i), homeBounds);
+            // For non-resizable homes, the minimized size is actually the fullscreen-size. As a
+            // result, we don't minimize for recents since it only shows half-size screenshots.
+            if (!isHomeResizable) {
+                if (homeStacks.get(i).topActivityType == ACTIVITY_TYPE_RECENTS) {
+                    continue;
+                }
+                wct.setWindowingMode(homeStacks.get(i).token, WINDOWING_MODE_FULLSCREEN);
+            }
+            wct.setBounds(homeStacks.get(i).token, homeBounds);
         }
         layout.mTiles.mHomeBounds.set(homeBounds);
         return isHomeResizable;
@@ -175,11 +199,13 @@
             return false;
         }
         ActivityManager.RunningTaskInfo topHomeTask = null;
-        boolean homeIsTop = false;
         for (int i = rootTasks.size() - 1; i >= 0; --i) {
             final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i);
-            // Only move resizeable task to split secondary. WM will just ignore this anyways...
-            if (!rootTask.isResizeable) continue;
+            // Only move resizeable task to split secondary. However, we have an exception
+            // for non-resizable home because we will minimize to show it.
+            if (!rootTask.isResizeable && rootTask.topActivityType != ACTIVITY_TYPE_HOME) {
+                continue;
+            }
             // Only move fullscreen tasks to split secondary.
             if (rootTask.configuration.windowConfiguration.getWindowingMode()
                     != WINDOWING_MODE_FULLSCREEN) {
@@ -193,7 +219,7 @@
         // Move the secondary split-forward.
         wct.reorder(tiles.mSecondary.token, true /* onTop */);
         boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct);
-        if (isHomeResizable && topHomeTask != null) {
+        if (topHomeTask != null) {
             // Translate/update-crop of secondary out-of-band with sync transaction -- Until BALST
             // is enabled, this temporarily syncs the home surface position with offset until
             // sync transaction finishes.
@@ -253,6 +279,7 @@
                 wct.reparent(ti.token, null /* parent */, true /* onTop */);
                 if (isHomeOrRecentTask(ti)) {
                     wct.setBounds(ti.token, null);
+                    wct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED);
                     if (i == 0) {
                         homeOnTop = true;
                     }
@@ -293,8 +320,9 @@
                 final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
                 if (isHomeOrRecentTask(ti)) {
                     wct.reparent(ti.token, null /* parent */, true /* onTop */);
-                    // reset bounds too
+                    // reset bounds and mode too
                     wct.setBounds(ti.token, null);
+                    wct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED);
                 }
             }
             for (int i = primaryChildren.size() - 1; i >= 0; --i) {
@@ -304,6 +332,7 @@
         }
         for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) {
             wct.setBounds(freeHomeAndRecents.get(i).token, null);
+            wct.setWindowingMode(freeHomeAndRecents.get(i).token, WINDOWING_MODE_UNDEFINED);
         }
         // Reset focusable to true
         wct.setFocusable(tiles.mPrimary.token, true /* focusable */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java
index 261ae07..e595dd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java
@@ -39,11 +39,14 @@
  */
 public class HideNotifsForOtherUsersCoordinator implements Coordinator {
     private final NotificationLockscreenUserManager mLockscreenUserManager;
+    private final SharedCoordinatorLogger mLogger;
 
     @Inject
     public HideNotifsForOtherUsersCoordinator(
-            NotificationLockscreenUserManager lockscreenUserManager) {
+            NotificationLockscreenUserManager lockscreenUserManager,
+            SharedCoordinatorLogger logger) {
         mLockscreenUserManager = lockscreenUserManager;
+        mLogger = logger;
     }
 
     @Override
@@ -61,9 +64,27 @@
     };
 
     private final UserChangedListener mUserChangedListener = new UserChangedListener() {
+        // This listener is fired both when the list of profiles changes and when the current user
+        // changes
         @Override
         public void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles) {
+            mLogger.logUserOrProfileChanged(
+                    mLockscreenUserManager.getCurrentUserId(),
+                    profileIdsToStr(currentProfiles));
             mFilter.invalidateList();
         }
     };
+
+    private String profileIdsToStr(SparseArray<UserInfo> currentProfiles) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("{");
+        for (int i = 0; i < currentProfiles.size(); i++) {
+            sb.append(currentProfiles.keyAt(i));
+            if (i < currentProfiles.size() - 1) {
+                sb.append(",");
+            }
+        }
+        sb.append("}");
+        return sb.toString();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SharedCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SharedCoordinatorLogger.kt
new file mode 100644
index 0000000..c85fc1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SharedCoordinatorLogger.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotificationLog
+import javax.inject.Inject
+
+/**
+ * Shared logging class for coordinators that don't log enough to merit their own logger.
+ */
+class SharedCoordinatorLogger @Inject constructor(
+    @NotificationLog private val buffer: LogBuffer
+) {
+    fun logUserOrProfileChanged(userId: Int, profiles: String) {
+        buffer.log("NotCurrentUserFilter", LogLevel.INFO, {
+            int1 = userId
+            str1 = profiles
+        }, {
+            "Current user or profiles changed. Current user is $int1; profiles are $str1"
+        })
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 033a638..5c802bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -25,6 +25,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -114,7 +115,8 @@
             ShortcutManager shortcutManager,
             ChannelEditorDialogController channelEditorDialogController,
             CurrentUserContextTracker contextTracker,
-            Provider<PriorityOnboardingDialogController.Builder> builderProvider) {
+            Provider<PriorityOnboardingDialogController.Builder> builderProvider,
+            BubbleController bubbleController) {
         return new NotificationGutsManager(
                 context,
                 visualStabilityManager,
@@ -128,7 +130,8 @@
                 shortcutManager,
                 channelEditorDialogController,
                 contextTracker,
-                builderProvider);
+                builderProvider,
+                bubbleController);
     }
 
     /** Provides an instance of {@link VisualStabilityManager} */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index bee2f70..f543db7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -65,6 +65,7 @@
 import com.android.settingslib.notification.ConversationIconFactory;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.notification.NotificationChannelHelper;
@@ -91,7 +92,7 @@
     private VisualStabilityManager mVisualStabilityManager;
     private Handler mMainHandler;
     private Handler mBgHandler;
-
+    private BubbleController mBubbleController;
     private String mPackageName;
     private String mAppName;
     private int mAppUid;
@@ -219,7 +220,8 @@
             boolean isDeviceProvisioned,
             @Main Handler mainHandler,
             @Background Handler bgHandler,
-            OnConversationSettingsClickListener onConversationSettingsClickListener) {
+            OnConversationSettingsClickListener onConversationSettingsClickListener,
+            BubbleController bubbleController) {
         mSelectedAction = -1;
         mINotificationManager = iNotificationManager;
         mVisualStabilityManager = visualStabilityManager;
@@ -238,6 +240,7 @@
         mIconFactory = conversationIconFactory;
         mUserContext = userContext;
         mBubbleMetadata = bubbleMetadata;
+        mBubbleController = bubbleController;
         mBuilderProvider = builderProvider;
         mMainHandler = mainHandler;
         mBgHandler = bgHandler;
@@ -627,6 +630,9 @@
                                 mINotificationManager.setBubblesAllowed(mAppPkg, mAppUid,
                                         BUBBLE_PREFERENCE_SELECTED);
                             }
+                            post(() -> {
+                                mBubbleController.onUserChangedImportance(mEntry);
+                            });
                         }
                         mChannelToUpdate.setImportance(Math.max(
                                 mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 1074adc..8337cbe4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -46,6 +46,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -113,6 +114,7 @@
     private final Lazy<StatusBar> mStatusBarLazy;
     private final Handler mMainHandler;
     private final Handler mBgHandler;
+    private final BubbleController mBubbleController;
     private Runnable mOpenRunnable;
     private final INotificationManager mNotificationManager;
     private final LauncherApps mLauncherApps;
@@ -132,7 +134,8 @@
             ShortcutManager shortcutManager,
             ChannelEditorDialogController channelEditorDialogController,
             CurrentUserContextTracker contextTracker,
-            Provider<PriorityOnboardingDialogController.Builder> builderProvider) {
+            Provider<PriorityOnboardingDialogController.Builder> builderProvider,
+            BubbleController bubbleController) {
         mContext = context;
         mVisualStabilityManager = visualStabilityManager;
         mStatusBarLazy = statusBarLazy;
@@ -146,6 +149,7 @@
         mContextTracker = contextTracker;
         mBuilderProvider = builderProvider;
         mChannelEditorDialogController = channelEditorDialogController;
+        mBubbleController = bubbleController;
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
@@ -480,7 +484,8 @@
                 mDeviceProvisionedController.isDeviceProvisioned(),
                 mMainHandler,
                 mBgHandler,
-                onConversationSettingsListener);
+                onConversationSettingsListener,
+                mBubbleController);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index fc8c8db..825919f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -19,6 +19,7 @@
 import android.hardware.display.ColorDisplayManager;
 import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
+import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -34,6 +35,7 @@
 import com.android.systemui.statusbar.policy.DataSaverController.Listener;
 import com.android.systemui.statusbar.policy.HotspotController;
 import com.android.systemui.statusbar.policy.HotspotController.Callback;
+import com.android.systemui.util.UserAwareController;
 
 import java.util.ArrayList;
 import java.util.Objects;
@@ -43,7 +45,7 @@
 /**
  * Manages which tiles should be automatically added to QS.
  */
-public class AutoTileManager {
+public class AutoTileManager implements UserAwareController {
     private static final String TAG = "AutoTileManager";
 
     public static final String HOTSPOT = "hotspot";
@@ -52,7 +54,9 @@
     public static final String WORK = "work";
     public static final String NIGHT = "night";
     public static final String CAST = "cast";
-    public static final String SETTING_SEPARATOR = ":";
+    static final String SETTING_SEPARATOR = ":";
+
+    private UserHandle mCurrentUser;
 
     private final Context mContext;
     private final QSTileHost mHost;
@@ -66,43 +70,56 @@
     private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>();
 
     @Inject
-    public AutoTileManager(Context context, AutoAddTracker autoAddTracker, QSTileHost host,
+    public AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder,
+            QSTileHost host,
             @Background Handler handler,
             HotspotController hotspotController,
             DataSaverController dataSaverController,
             ManagedProfileController managedProfileController,
             NightDisplayListener nightDisplayListener,
             CastController castController) {
-        mAutoTracker = autoAddTracker;
         mContext = context;
         mHost = host;
+        mCurrentUser = mHost.getUserContext().getUser();
+        mAutoTracker = autoAddTrackerBuilder.setUserId(mCurrentUser.getIdentifier()).build();
         mHandler = handler;
         mHotspotController = hotspotController;
         mDataSaverController = dataSaverController;
         mManagedProfileController = managedProfileController;
         mNightDisplayListener = nightDisplayListener;
         mCastController = castController;
+
+        populateSettingsList();
+        startControllersAndSettingsListeners();
+    }
+
+    protected void startControllersAndSettingsListeners() {
         if (!mAutoTracker.isAdded(HOTSPOT)) {
-            hotspotController.addCallback(mHotspotCallback);
+            mHotspotController.addCallback(mHotspotCallback);
         }
         if (!mAutoTracker.isAdded(SAVER)) {
-            dataSaverController.addCallback(mDataSaverListener);
+            mDataSaverController.addCallback(mDataSaverListener);
         }
         if (!mAutoTracker.isAdded(WORK)) {
-            managedProfileController.addCallback(mProfileCallback);
+            mManagedProfileController.addCallback(mProfileCallback);
         }
         if (!mAutoTracker.isAdded(NIGHT)
                 && ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            nightDisplayListener.setCallback(mNightDisplayCallback);
+            mNightDisplayListener.setCallback(mNightDisplayCallback);
         }
         if (!mAutoTracker.isAdded(CAST)) {
-            castController.addCallback(mCastCallback);
+            mCastController.addCallback(mCastCallback);
         }
-        populateSettingsList();
+
+        int settingsN = mAutoAddSettingList.size();
+        for (int i = 0; i < settingsN; i++) {
+            if (!mAutoTracker.isAdded(mAutoAddSettingList.get(i).mSpec)) {
+                mAutoAddSettingList.get(i).setListening(true);
+            }
+        }
     }
 
-    public void destroy() {
-        mAutoTracker.destroy();
+    protected void stopListening() {
         mHotspotController.removeCallback(mHotspotCallback);
         mDataSaverController.removeCallback(mDataSaverListener);
         mManagedProfileController.removeCallback(mProfileCallback);
@@ -116,6 +133,11 @@
         }
     }
 
+    public void destroy() {
+        stopListening();
+        mAutoTracker.destroy();
+    }
+
     /**
      * Populates a list with the pairs setting:spec in the config resource.
      * <p>
@@ -137,17 +159,39 @@
             if (split.length == 2) {
                 String setting = split[0];
                 String spec = split[1];
-                if (!mAutoTracker.isAdded(spec)) {
-                    AutoAddSetting s = new AutoAddSetting(mContext, mHandler, setting, spec);
-                    mAutoAddSettingList.add(s);
-                    s.setListening(true);
-                }
+                // Populate all the settings. As they may not have been added in other users
+                AutoAddSetting s = new AutoAddSetting(mContext, mHandler, setting, spec);
+                mAutoAddSettingList.add(s);
             } else {
                 Log.w(TAG, "Malformed item in array: " + tile);
             }
         }
     }
 
+    @Override
+    public void changeUser(UserHandle newUser) {
+        if (!Thread.currentThread().equals(mHandler.getLooper().getThread())) {
+            mHandler.post(() -> changeUser(newUser));
+            return;
+        }
+        if (newUser.getIdentifier() == mCurrentUser.getIdentifier()) {
+            return;
+        }
+        stopListening();
+        mCurrentUser = newUser;
+        int settingsN = mAutoAddSettingList.size();
+        for (int i = 0; i < settingsN; i++) {
+            mAutoAddSettingList.get(i).setUserId(newUser.getIdentifier());
+        }
+        mAutoTracker.changeUser(newUser);
+        startControllersAndSettingsListeners();
+    }
+
+    @Override
+    public int getCurrentUserId() {
+        return mCurrentUser.getIdentifier();
+    }
+
     public void unmarkTileAsAutoAdded(String tabSpec) {
         mAutoTracker.setTileRemoved(tabSpec);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 00eab95..60fc17d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -113,6 +113,12 @@
      * Scrim opacity when the phone is about to wake-up.
      */
     public static final float WAKE_SENSOR_SCRIM_ALPHA = 0.6f;
+
+    /**
+     * Scrim opacity when bubbles are expanded.
+     */
+    public static final float BUBBLE_SCRIM_ALPHA = 0.6f;
+
     /**
      * The default scrim under the shade and dialogs.
      * This should not be lower than 0.54, otherwise we won't pass GAR.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index ade642c..2db36f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -235,7 +235,7 @@
 
             mFrontAlpha = 0f;
             mBehindAlpha = mDefaultScrimAlpha;
-            mBubbleAlpha = mDefaultScrimAlpha;
+            mBubbleAlpha = ScrimController.BUBBLE_SCRIM_ALPHA;
 
             mAnimationDuration = ScrimController.ANIMATION_DURATION;
             mBlankScreen = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index 1fb9b69..79d264c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -24,6 +24,8 @@
     boolean isDeviceManaged();
     boolean hasProfileOwner();
     boolean hasWorkProfile();
+    /** Whether this device is organization-owned with a work profile **/
+    boolean isProfileOwnerOfOrganizationOwnedDevice();
     String getDeviceOwnerName();
     String getProfileOwnerName();
     CharSequence getDeviceOwnerOrganizationName();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index d29f4fc..309d4b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -207,6 +207,11 @@
     }
 
     @Override
+    public boolean isProfileOwnerOfOrganizationOwnedDevice() {
+        return mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
+    }
+
+    @Override
     public String getWorkProfileVpnName() {
         final int profileId = getWorkProfileUserId(mVpnUserId);
         if (profileId == UserHandle.USER_NULL) return null;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 248bdc8..9ad2aa2 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -62,7 +62,8 @@
     // shouldn't be reset with tuner settings.
     private static final String[] RESET_BLACKLIST = new String[] {
             QSTileHost.TILES_SETTING,
-            Settings.Secure.DOZE_ALWAYS_ON
+            Settings.Secure.DOZE_ALWAYS_ON,
+            Settings.Secure.MEDIA_CONTROLS_RESUME
     };
 
     private final Observer mObserver = new Observer();
diff --git a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
index 631ea9d..ff53a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.util
 
 import android.view.ViewGroup
+import com.android.internal.util.IndentingPrintWriter
+import java.io.PrintWriter
 
 /** [Sequence] that yields all of the direct children of this [ViewGroup] */
 val ViewGroup.children
@@ -32,4 +34,15 @@
             break
         }
     }
+}
+
+/**
+ * If `this` is an [IndentingPrintWriter], it will process block inside an indentation level.
+ *
+ * If not, this will just process block.
+ */
+inline fun PrintWriter.indentIfPossible(block: PrintWriter.() -> Unit) {
+    if (this is IndentingPrintWriter) increaseIndent()
+    block()
+    if (this is IndentingPrintWriter) decreaseIndent()
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt b/packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt
rename to packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt
index d2776d2..693c270 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.controls
+package com.android.systemui.util
 
 import android.os.UserHandle
 
@@ -23,6 +23,8 @@
  * changes.
  */
 interface UserAwareController {
+    @JvmDefault
     fun changeUser(newUser: UserHandle) {}
+
     val currentUserId: Int
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index 5c9db54..e5f30cf 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -139,7 +139,8 @@
      * Off by default, but can be enabled by setting to 1
      */
     public static boolean useMediaResumption(Context context) {
-        int flag = Settings.System.getInt(context.getContentResolver(), "qs_media_resumption", 0);
+        int flag = Settings.Secure.getInt(context.getContentResolver(),
+                Settings.Secure.MEDIA_CONTROLS_RESUME, 1);
         return useQsMediaPlayer(context) && flag > 0;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index 2e0c035..b12224b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -62,23 +62,39 @@
 import javax.inject.Singleton;
 
 /**
+ * Suite of tools to periodically inspect the System UI heap and possibly prompt the user to
+ * capture heap dumps and report them. Includes the implementation of the "Dump SysUI Heap"
+ * quick settings tile.
  */
 @Singleton
 public class GarbageMonitor implements Dumpable {
-    private static final boolean LEAK_REPORTING_ENABLED =
-            Build.IS_DEBUGGABLE
-                    && SystemProperties.getBoolean("debug.enable_leak_reporting", false);
-    private static final String FORCE_ENABLE_LEAK_REPORTING = "sysui_force_enable_leak_reporting";
+    // Feature switches
+    // ================
 
-    private static final boolean HEAP_TRACKING_ENABLED = Build.IS_DEBUGGABLE;
+    // Whether to use TrackedGarbage to trigger LeakReporter. Off by default unless you set the
+    // appropriate sysprop on a userdebug device.
+    public static final boolean LEAK_REPORTING_ENABLED = Build.IS_DEBUGGABLE
+            && SystemProperties.getBoolean("debug.enable_leak_reporting", false);
+    public static final String FORCE_ENABLE_LEAK_REPORTING = "sysui_force_enable_leak_reporting";
 
-    // whether to use ActivityManager.setHeapLimit
-    private static final boolean ENABLE_AM_HEAP_LIMIT = Build.IS_DEBUGGABLE;
-    // heap limit value, in KB (overrides R.integer.watch_heap_limit)
+    // Heap tracking: watch the current memory levels and update the MemoryTile if available.
+    // On for all userdebug devices.
+    public static final boolean HEAP_TRACKING_ENABLED = Build.IS_DEBUGGABLE;
+
+    // Tell QSTileHost.java to toss this into the default tileset?
+    public static final boolean ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS = true;
+
+    // whether to use ActivityManager.setHeapLimit (and post a notification to the user asking
+    // to dump the heap). Off by default unless you set the appropriate sysprop on userdebug
+    private static final boolean ENABLE_AM_HEAP_LIMIT = Build.IS_DEBUGGABLE
+            && SystemProperties.getBoolean("debug.enable_sysui_heap_limit", false);
+
+    // Tuning params
+    // =============
+
+    // threshold for setHeapLimit(), in KB (overrides R.integer.watch_heap_limit)
     private static final String SETTINGS_KEY_AM_HEAP_LIMIT = "systemui_am_heap_limit";
 
-    private static final String TAG = "GarbageMonitor";
-
     private static final long GARBAGE_INSPECTION_INTERVAL =
             15 * DateUtils.MINUTE_IN_MILLIS; // 15 min
     private static final long HEAP_TRACK_INTERVAL = 1 * DateUtils.MINUTE_IN_MILLIS; // 1 min
@@ -89,6 +105,7 @@
 
     private static final int GARBAGE_ALLOWANCE = 5;
 
+    private static final String TAG = "GarbageMonitor";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final Handler mHandler;
@@ -378,9 +395,6 @@
     public static class MemoryTile extends QSTileImpl<QSTile.State> {
         public static final String TILE_SPEC = "dbg:mem";
 
-        // Tell QSTileHost.java to toss this into the default tileset?
-        public static final boolean ADD_TO_DEFAULT_ON_DEBUGGABLE_BUILDS = true;
-
         private final GarbageMonitor gm;
         private final ActivityStarter mActivityStarter;
         private ProcessMemInfo pmi;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 0238799..7bc453a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -452,6 +452,12 @@
     }
 
     @Test
+    public void requiresAuthentication_whenEncryptedKeyguard_andBypass() {
+        testStrongAuthExceptOnBouncer(
+                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT);
+    }
+
+    @Test
     public void requiresAuthentication_whenTimeoutKeyguard_andBypass() {
         testStrongAuthExceptOnBouncer(
                 KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
@@ -507,20 +513,10 @@
 
     @Test
     public void testIgnoresAuth_whenLockdown() {
-        testIgnoresAuth(
-                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
-    }
-
-    @Test
-    public void testIgnoresAuth_whenEncrypted() {
-        testIgnoresAuth(
-                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT);
-    }
-
-    private void testIgnoresAuth(int strongAuth) {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
-        when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
+        when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
+                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
 
         mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
         verify(mFaceManager, never()).authenticate(any(), any(), anyInt(), any(), any(), anyInt());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 713aef9..dd3a785 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -49,6 +49,7 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
 import java.util.concurrent.Future;
 
 /**
@@ -74,7 +75,8 @@
         SystemUIFactory.createFromConfig(mContext);
         mDependency = new TestableDependency(mContext);
         mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(mContext, mock(Looper.class),
-                mock(DumpManager.class), mock(BroadcastDispatcherLogger.class));
+                mock(Executor.class), mock(DumpManager.class),
+                mock(BroadcastDispatcherLogger.class));
 
         mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
         Instrumentation inst = spy(mRealInstrumentation);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
new file mode 100644
index 0000000..e95eb4e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.broadcast
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.lang.IllegalArgumentException
+import java.lang.IllegalStateException
+import java.util.concurrent.Executor
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class ActionReceiverTest : SysuiTestCase() {
+
+    companion object {
+        private const val ACTION1 = "TEST_ACTION1"
+        private const val ACTION2 = "TEST_ACTION2"
+        private const val CATEGORY = "TEST_CATEGORY"
+        private val USER = UserHandle.of(0)
+        private fun <T : Any> sameNotNull(arg: T): T = Mockito.same(arg) ?: arg
+
+        fun IntentFilter.matchesOther(it: IntentFilter): Boolean {
+            val actions = actionsIterator()?.asSequence()?.toSet() ?: emptySet()
+            val categories = categoriesIterator()?.asSequence()?.toSet() ?: emptySet()
+            return (it.actionsIterator()?.asSequence()?.toSet() ?: emptySet()) == actions &&
+                    (it.categoriesIterator()?.asSequence()?.toSet() ?: emptySet()) == categories &&
+                    it.countDataAuthorities() == 0 &&
+                    it.countDataPaths() == 0 &&
+                    it.countDataSchemes() == 0 &&
+                    it.countDataTypes() == 0 &&
+                    it.countMimeGroups() == 0 &&
+                    it.priority == 0
+        }
+    }
+
+    @Mock
+    private lateinit var registerFunction: BroadcastReceiver.(IntentFilter) -> Unit
+    @Mock
+    private lateinit var unregisterFunction: BroadcastReceiver.() -> Unit
+    @Mock
+    private lateinit var receiver1: BroadcastReceiver
+    @Mock
+    private lateinit var receiver2: BroadcastReceiver
+    @Mock
+    private lateinit var logger: BroadcastDispatcherLogger
+    @Captor
+    private lateinit var intentFilterCaptor: ArgumentCaptor<IntentFilter>
+
+    private lateinit var executor: FakeExecutor
+    private lateinit var actionReceiver: ActionReceiver
+    private val directExecutor = Executor { it.run() }
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        executor = FakeExecutor(FakeSystemClock())
+
+        actionReceiver = ActionReceiver(
+                ACTION1,
+                USER.identifier,
+                registerFunction,
+                unregisterFunction,
+                executor,
+                logger
+        )
+    }
+
+    @Test
+    fun testStartsUnregistered() {
+        assertFalse(actionReceiver.registered)
+        verify(registerFunction, never()).invoke(sameNotNull(actionReceiver),
+                any(IntentFilter::class.java))
+    }
+
+    @Test
+    fun testRegistersOnFirstAdd() {
+        val receiverData = ReceiverData(receiver1, IntentFilter(ACTION1), directExecutor, USER)
+
+        actionReceiver.addReceiverData(receiverData)
+
+        assertTrue(actionReceiver.registered)
+        verify(registerFunction).invoke(sameNotNull(actionReceiver), capture(intentFilterCaptor))
+
+        assertTrue(IntentFilter(ACTION1).matchesOther(intentFilterCaptor.value))
+    }
+
+    @Test
+    fun testRegistersOnlyOnce() {
+        val receiverData1 = ReceiverData(receiver1, IntentFilter(ACTION1), directExecutor, USER)
+        val receiverData2 = ReceiverData(receiver2, IntentFilter(ACTION1), directExecutor, USER)
+
+        actionReceiver.addReceiverData(receiverData1)
+        actionReceiver.addReceiverData(receiverData2)
+
+        verify(registerFunction).invoke(sameNotNull(actionReceiver), any(IntentFilter::class.java))
+    }
+
+    @Test
+    fun testRemovingLastReceiverUnregisters() {
+        val receiverData = ReceiverData(receiver1, IntentFilter(ACTION1), directExecutor, USER)
+
+        actionReceiver.addReceiverData(receiverData)
+
+        actionReceiver.removeReceiver(receiver1)
+
+        assertFalse(actionReceiver.registered)
+        verify(unregisterFunction).invoke(sameNotNull(actionReceiver))
+    }
+
+    @Test
+    fun testRemovingWhileOtherReceiversDoesntUnregister() {
+        val receiverData1 = ReceiverData(receiver1, IntentFilter(ACTION1), directExecutor, USER)
+        val receiverData2 = ReceiverData(receiver2, IntentFilter(ACTION1), directExecutor, USER)
+
+        actionReceiver.addReceiverData(receiverData1)
+        actionReceiver.addReceiverData(receiverData2)
+
+        actionReceiver.removeReceiver(receiver1)
+
+        assertTrue(actionReceiver.registered)
+        verify(unregisterFunction, never()).invoke(any(BroadcastReceiver::class.java))
+    }
+
+    @Test
+    fun testReceiverHasCategories() {
+        val filter = IntentFilter(ACTION1)
+        filter.addCategory(CATEGORY)
+
+        val receiverData = ReceiverData(receiver1, filter, directExecutor, USER)
+
+        actionReceiver.addReceiverData(receiverData)
+
+        verify(registerFunction).invoke(sameNotNull(actionReceiver), capture(intentFilterCaptor))
+        assertTrue(intentFilterCaptor.value.hasCategory(CATEGORY))
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun testNotRegisteredWithWrongAction_throwsException() {
+        val receiverData = ReceiverData(receiver1, IntentFilter(ACTION2), directExecutor, USER)
+
+        actionReceiver.addReceiverData(receiverData)
+    }
+
+    @Test
+    fun testReceiverGetsBroadcast() {
+        val receiverData = ReceiverData(receiver1, IntentFilter(ACTION1), directExecutor, USER)
+        actionReceiver.addReceiverData(receiverData)
+
+        val intent = Intent(ACTION1)
+
+        actionReceiver.onReceive(mContext, intent)
+
+        executor.runAllReady()
+
+        verify(receiver1).onReceive(any(Context::class.java), sameNotNull(intent))
+    }
+
+    @Test
+    fun testReceiverGetsPendingResult() {
+        val receiverData = ReceiverData(receiver1, IntentFilter(ACTION1), directExecutor, USER)
+        actionReceiver.addReceiverData(receiverData)
+
+        val intent = Intent(ACTION1)
+        val pendingResult = mock(BroadcastReceiver.PendingResult::class.java)
+
+        actionReceiver.pendingResult = pendingResult
+        actionReceiver.onReceive(mContext, intent)
+
+        executor.runAllReady()
+        verify(receiver1).pendingResult = pendingResult
+    }
+
+    @Test
+    fun testBroadcastIsDispatchedInExecutor() {
+        val executor = FakeExecutor(FakeSystemClock())
+        val receiverData = ReceiverData(receiver1, IntentFilter(ACTION1), executor, USER)
+        actionReceiver.addReceiverData(receiverData)
+
+        val intent = Intent(ACTION1)
+        actionReceiver.onReceive(mContext, intent)
+
+        this.executor.runAllReady()
+
+        verify(receiver1, never()).onReceive(mContext, intent)
+
+        executor.runAllReady()
+        // Dispatched after executor is processed
+        verify(receiver1).onReceive(mContext, intent)
+    }
+
+    @Test
+    fun testBroadcastReceivedDispatched_logger() {
+        val receiverData = ReceiverData(receiver1, IntentFilter(ACTION1), directExecutor, USER)
+
+        actionReceiver.addReceiverData(receiverData)
+
+        val intent = Intent(ACTION1)
+        actionReceiver.onReceive(mContext, intent)
+        verify(logger).logBroadcastReceived(anyInt(), eq(USER.identifier), eq(intent))
+
+        verify(logger, never()).logBroadcastDispatched(anyInt(), anyString(),
+                any(BroadcastReceiver::class.java))
+
+        executor.runAllReady()
+
+        verify(logger).logBroadcastDispatched(anyInt(), eq(ACTION1), sameNotNull(receiver1))
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun testBroadcastWithWrongAction_throwsException() {
+        actionReceiver.onReceive(mContext, Intent(ACTION2))
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 4ed284e..22e9594 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -98,6 +98,7 @@
         broadcastDispatcher = TestBroadcastDispatcher(
                 mockContext,
                 testableLooper.looper,
+                mock(Executor::class.java),
                 mock(DumpManager::class.java),
                 logger,
                 mapOf(0 to mockUBRUser0, 1 to mockUBRUser1))
@@ -246,10 +247,11 @@
     private class TestBroadcastDispatcher(
         context: Context,
         bgLooper: Looper,
+        executor: Executor,
         dumpManager: DumpManager,
         logger: BroadcastDispatcherLogger,
         var mockUBRMap: Map<Int, UserBroadcastDispatcher>
-    ) : BroadcastDispatcher(context, bgLooper, dumpManager, logger) {
+    ) : BroadcastDispatcher(context, bgLooper, executor, dumpManager, logger) {
         override fun createUBRForUser(userId: Int): UserBroadcastDispatcher {
             return mockUBRMap.getOrDefault(userId, mock(UserBroadcastDispatcher::class.java))
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index 09a0916..949932d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -31,9 +31,10 @@
 class FakeBroadcastDispatcher(
     context: SysuiTestableContext,
     looper: Looper,
+    executor: Executor,
     dumpManager: DumpManager,
     logger: BroadcastDispatcherLogger
-) : BroadcastDispatcher(context, looper, dumpManager, logger) {
+) : BroadcastDispatcher(context, looper, executor, dumpManager, logger) {
 
     private val registeredReceivers = ArraySet<BroadcastReceiver>()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
index 4433576..dfe1432 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -18,7 +18,6 @@
 
 import android.content.BroadcastReceiver
 import android.content.Context
-import android.content.Intent
 import android.content.IntentFilter
 import android.os.Handler
 import android.os.UserHandle
@@ -29,26 +28,18 @@
 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
-import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
-import junit.framework.Assert.assertTrue
+import junit.framework.Assert.assertNotNull
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
-import org.mockito.Mockito.anyString
-import org.mockito.Mockito.atLeastOnce
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.times
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
 
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
@@ -58,8 +49,6 @@
     companion object {
         private const val ACTION_1 = "com.android.systemui.tests.ACTION_1"
         private const val ACTION_2 = "com.android.systemui.tests.ACTION_2"
-        private const val CATEGORY_1 = "com.android.systemui.tests.CATEGORY_1"
-        private const val CATEGORY_2 = "com.android.systemui.tests.CATEGORY_2"
         private const val USER_ID = 0
         private val USER_HANDLE = UserHandle.of(USER_ID)
 
@@ -75,13 +64,8 @@
     @Mock
     private lateinit var mockContext: Context
     @Mock
-    private lateinit var mPendingResult: BroadcastReceiver.PendingResult
-    @Mock
     private lateinit var logger: BroadcastDispatcherLogger
 
-    @Captor
-    private lateinit var argumentCaptor: ArgumentCaptor<IntentFilter>
-
     private lateinit var testableLooper: TestableLooper
     private lateinit var userBroadcastDispatcher: UserBroadcastDispatcher
     private lateinit var intentFilter: IntentFilter
@@ -96,46 +80,25 @@
         handler = Handler(testableLooper.looper)
         fakeExecutor = FakeExecutor(FakeSystemClock())
 
-        userBroadcastDispatcher = UserBroadcastDispatcher(
-                mockContext, USER_ID, testableLooper.looper, logger)
-        userBroadcastDispatcher.pendingResult = mPendingResult
-    }
-
-    @Test
-    fun testNotRegisteredOnStart() {
-        testableLooper.processAllMessages()
-        verify(mockContext, never()).registerReceiver(any(), any())
-        verify(mockContext, never()).registerReceiver(any(), any(), anyInt())
-        verify(mockContext, never()).registerReceiver(any(), any(), anyString(), any())
-        verify(mockContext, never()).registerReceiver(any(), any(), anyString(), any(), anyInt())
-        verify(mockContext, never()).registerReceiverAsUser(any(), any(), any(), anyString(), any())
-    }
-
-    @Test
-    fun testNotRegisteredOnStart_logging() {
-        testableLooper.processAllMessages()
-
-        verify(logger, never()).logContextReceiverRegistered(anyInt(), any())
+        userBroadcastDispatcher = object : UserBroadcastDispatcher(
+                mockContext, USER_ID, testableLooper.looper, mock(Executor::class.java), logger) {
+            override fun createActionReceiver(action: String): ActionReceiver {
+                return mock(ActionReceiver::class.java)
+            }
+        }
     }
 
     @Test
     fun testSingleReceiverRegistered() {
         intentFilter = IntentFilter(ACTION_1)
+        val receiverData = ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)
 
-        userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
+        userBroadcastDispatcher.registerReceiver(receiverData)
         testableLooper.processAllMessages()
 
-        assertTrue(userBroadcastDispatcher.isRegistered())
-        verify(mockContext).registerReceiverAsUser(
-                any(),
-                eq(USER_HANDLE),
-                capture(argumentCaptor),
-                any(),
-                any())
-        assertEquals(1, argumentCaptor.value.countActions())
-        assertTrue(argumentCaptor.value.hasAction(ACTION_1))
-        assertEquals(0, argumentCaptor.value.countCategories())
+        val actionReceiver = userBroadcastDispatcher.getActionReceiver(ACTION_1)
+        assertNotNull(actionReceiver)
+        verify(actionReceiver)?.addReceiverData(receiverData)
     }
 
     @Test
@@ -147,7 +110,6 @@
         testableLooper.processAllMessages()
 
         verify(logger).logReceiverRegistered(USER_HANDLE.identifier, broadcastReceiver)
-        verify(logger).logContextReceiverRegistered(eq(USER_HANDLE.identifier), any())
     }
 
     @Test
@@ -157,16 +119,13 @@
         userBroadcastDispatcher.registerReceiver(
                 ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         testableLooper.processAllMessages()
-        reset(mockContext)
-
-        assertTrue(userBroadcastDispatcher.isRegistered())
 
         userBroadcastDispatcher.unregisterReceiver(broadcastReceiver)
         testableLooper.processAllMessages()
 
-        verify(mockContext, atLeastOnce()).unregisterReceiver(any())
-        verify(mockContext, never()).registerReceiverAsUser(any(), any(), any(), any(), any())
-        assertFalse(userBroadcastDispatcher.isRegistered())
+        val actionReceiver = userBroadcastDispatcher.getActionReceiver(ACTION_1)
+        assertNotNull(actionReceiver)
+        verify(actionReceiver)?.removeReceiver(broadcastReceiver)
     }
 
     @Test
@@ -181,139 +140,6 @@
         testableLooper.processAllMessages()
 
         verify(logger).logReceiverUnregistered(USER_HANDLE.identifier, broadcastReceiver)
-        verify(logger).logContextReceiverUnregistered(USER_HANDLE.identifier)
-    }
-
-    @Test
-    fun testFilterHasAllActionsAndCategories_twoReceivers() {
-        intentFilter = IntentFilter(ACTION_1)
-        intentFilterOther = IntentFilter(ACTION_2).apply {
-            addCategory(CATEGORY_1)
-            addCategory(CATEGORY_2)
-        }
-
-        userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
-        userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
-
-        testableLooper.processAllMessages()
-        assertTrue(userBroadcastDispatcher.isRegistered())
-
-        verify(mockContext, times(2)).registerReceiverAsUser(
-                any(),
-                eq(USER_HANDLE),
-                capture(argumentCaptor),
-                any(),
-                any())
-
-        val lastFilter = argumentCaptor.value
-
-        assertTrue(lastFilter.hasAction(ACTION_1))
-        assertTrue(lastFilter.hasAction(ACTION_2))
-        assertTrue(lastFilter.hasCategory(CATEGORY_1))
-        assertTrue(lastFilter.hasCategory(CATEGORY_1))
-    }
-
-    @Test
-    fun testDispatchToCorrectReceiver() {
-        intentFilter = IntentFilter(ACTION_1)
-        intentFilterOther = IntentFilter(ACTION_2)
-
-        userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
-        userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
-
-        val intent = Intent(ACTION_2)
-
-        userBroadcastDispatcher.onReceive(mockContext, intent)
-        testableLooper.processAllMessages()
-        fakeExecutor.runAllReady()
-
-        verify(broadcastReceiver, never()).onReceive(any(), any())
-        verify(broadcastReceiverOther).onReceive(mockContext, intent)
-    }
-
-    @Test
-    fun testDispatch_logger() {
-        intentFilter = IntentFilter(ACTION_1)
-        intentFilterOther = IntentFilter(ACTION_2)
-
-        userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
-        userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
-
-        val intent = Intent(ACTION_2)
-
-        userBroadcastDispatcher.onReceive(mockContext, intent)
-        testableLooper.processAllMessages()
-        fakeExecutor.runAllReady()
-
-        val captor = ArgumentCaptor.forClass(Int::class.java)
-        verify(logger)
-                .logBroadcastReceived(captor.capture(), eq(USER_HANDLE.identifier), eq(intent))
-        verify(logger).logBroadcastDispatched(captor.value, ACTION_2, broadcastReceiverOther)
-        verify(logger, never())
-                .logBroadcastDispatched(eq(captor.value), any(), eq(broadcastReceiver))
-    }
-
-    @Test
-    fun testDispatchToCorrectReceiver_differentFiltersSameReceiver() {
-        intentFilter = IntentFilter(ACTION_1)
-        intentFilterOther = IntentFilter(ACTION_2)
-
-        userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
-        userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilterOther, fakeExecutor, USER_HANDLE))
-
-        val intent = Intent(ACTION_2)
-
-        userBroadcastDispatcher.onReceive(mockContext, intent)
-        testableLooper.processAllMessages()
-        fakeExecutor.runAllReady()
-
-        verify(broadcastReceiver).onReceive(mockContext, intent)
-    }
-
-    @Test
-    fun testDispatchIntentWithoutCategories() {
-        intentFilter = IntentFilter(ACTION_1)
-        intentFilter.addCategory(CATEGORY_1)
-        intentFilterOther = IntentFilter(ACTION_1)
-        intentFilterOther.addCategory(CATEGORY_2)
-
-        userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
-        userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
-
-        val intent = Intent(ACTION_1)
-
-        userBroadcastDispatcher.onReceive(mockContext, intent)
-        testableLooper.processAllMessages()
-        fakeExecutor.runAllReady()
-
-        verify(broadcastReceiver).onReceive(mockContext, intent)
-        verify(broadcastReceiverOther).onReceive(mockContext, intent)
-    }
-
-    @Test
-    fun testPendingResult() {
-        intentFilter = IntentFilter(ACTION_1)
-        userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
-
-        val intent = Intent(ACTION_1)
-        userBroadcastDispatcher.onReceive(mockContext, intent)
-
-        testableLooper.processAllMessages()
-        fakeExecutor.runAllReady()
-
-        verify(broadcastReceiver).onReceive(mockContext, intent)
-        verify(broadcastReceiver).pendingResult = mPendingResult
     }
 
     @Test
@@ -333,4 +159,8 @@
 
         assertFalse(userBroadcastDispatcher.isReceiverReferenceHeld(broadcastReceiver))
     }
+
+    private fun UserBroadcastDispatcher.getActionReceiver(action: String): ActionReceiver? {
+        return actionsToActionsReceivers.get(action)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 36398a6..5b46b7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -44,6 +44,7 @@
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.PendingIntent;
+import android.content.pm.LauncherApps;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.face.FaceManager;
 import android.os.Handler;
@@ -179,6 +180,8 @@
     private NotificationShadeWindowView mNotificationShadeWindowView;
     @Mock
     private IStatusBarService mStatusBarService;
+    @Mock
+    private LauncherApps mLauncherApps;
 
     private BubbleData mBubbleData;
 
@@ -256,7 +259,8 @@
                 mSysUiState,
                 mock(INotificationManager.class),
                 mStatusBarService,
-                mWindowManager);
+                mWindowManager,
+                mLauncherApps);
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
         // Get a reference to the BubbleController's entry listener
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 1ca2f02..ed4e686 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -177,7 +177,7 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
+        mBubbleData.dismissBubbleWithKey(
                 mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
 
         // Verify
@@ -300,13 +300,13 @@
         mBubbleData.setListener(mListener);
 
         mBubbleData.setMaxOverflowBubbles(1);
-        mBubbleData.notificationEntryRemoved(
+        mBubbleData.dismissBubbleWithKey(
                 mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
 
         // Overflow max of 1 is reached; A1 is oldest, so it gets removed
-        mBubbleData.notificationEntryRemoved(
+        mBubbleData.dismissBubbleWithKey(
                 mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
@@ -328,13 +328,13 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(mEntryA1.getKey(),
+        mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(),
                 BubbleController.DISMISS_NOTIF_CANCEL);
         verifyUpdateReceived();
         assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
 
         // Test
-        mBubbleData.notificationEntryRemoved(mEntryA2.getKey(),
+        mBubbleData.dismissBubbleWithKey(mEntryA2.getKey(),
                 BubbleController.DISMISS_GROUP_CANCELLED);
         verifyUpdateReceived();
         assertOverflowChangedTo(ImmutableList.of());
@@ -415,7 +415,7 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
+        mBubbleData.dismissBubbleWithKey(
                 mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         // TODO: this should fail if things work as I expect them to?
@@ -436,7 +436,7 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
+        mBubbleData.dismissBubbleWithKey(
                 mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertOrderNotChanged();
@@ -456,7 +456,7 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
+        mBubbleData.dismissBubbleWithKey(
                 mEntryA2.getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
         verifyUpdateReceived();
         assertSelectionChangedTo(mBubbleB2);
@@ -531,7 +531,7 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
+        mBubbleData.dismissBubbleWithKey(
                 mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
 
         // Verify the selection was cleared.
@@ -632,7 +632,7 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
+        mBubbleData.dismissBubbleWithKey(
                 mEntryB2.getKey(), BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertOrderChangedTo(mBubbleA2, mBubbleB1, mBubbleA1);
@@ -657,12 +657,12 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
+        mBubbleData.dismissBubbleWithKey(
                 mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertSelectionChangedTo(mBubbleB1);
 
-        mBubbleData.notificationEntryRemoved(
+        mBubbleData.dismissBubbleWithKey(
                 mEntryB1.getKey(), BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertSelectionChangedTo(mBubbleA1);
@@ -777,7 +777,7 @@
         mBubbleData.setListener(mListener);
 
         // Test
-        mBubbleData.notificationEntryRemoved(
+        mBubbleData.dismissBubbleWithKey(
                 mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertExpandedChangedTo(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 1c70db3..52c64cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -40,6 +40,7 @@
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.PendingIntent;
+import android.content.pm.LauncherApps;
 import android.content.res.Resources;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.face.FaceManager;
@@ -174,6 +175,8 @@
     private LockscreenLockIconController mLockIconController;
     @Mock
     private IStatusBarService mStatusBarService;
+    @Mock
+    private LauncherApps mLauncherApps;
 
     private BubbleData mBubbleData;
 
@@ -241,7 +244,8 @@
                 mSysUiState,
                 mock(INotificationManager.class),
                 mStatusBarService,
-                mWindowManager);
+                mWindowManager,
+                mLauncherApps);
         mBubbleController.addNotifCallback(mNotifCallback);
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
index ab49134..0a6d071 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
@@ -18,6 +18,7 @@
 
 import android.app.INotificationManager;
 import android.content.Context;
+import android.content.pm.LauncherApps;
 import android.view.WindowManager;
 
 import com.android.internal.statusbar.IStatusBarService;
@@ -61,14 +62,15 @@
             SysUiState sysUiState,
             INotificationManager notificationManager,
             IStatusBarService statusBarService,
-            WindowManager windowManager) {
+            WindowManager windowManager,
+            LauncherApps launcherApps) {
         super(context,
                 notificationShadeWindowController, statusBarStateController, shadeController,
                 data, Runnable::run, configurationController, interruptionStateProvider,
                 zenModeController, lockscreenUserManager, groupManager, entryManager,
                 notifPipeline, featureFlags, dumpManager, floatingContentCoordinator,
                 dataRepository, sysUiState, notificationManager, statusBarService,
-                windowManager);
+                windowManager, launcherApps);
         setInflateSynchronously(true);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
index f38c722..8b254e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
@@ -60,8 +60,13 @@
     public void testGlobalActions_doesntStealImeControl() throws Exception {
         turnScreenOn();
         final TestActivity activity = mActivityTestRule.launchActivity(null);
-
-        waitUntil("Ime is visible", activity::isImeVisible);
+        boolean isImeVisible = waitUntil(activity::isImeVisible);
+        if (!isImeVisible) {
+            // Sometimes the keyboard is dismissed when run with other tests. Bringing it up again
+            // should improve test reliability
+            activity.showIme();
+            waitUntil("Ime is not visible", activity::isImeVisible);
+        }
 
         executeShellCommand("input keyevent --longpress POWER");
 
@@ -91,17 +96,23 @@
 
     private static void waitUntil(String message, BooleanSupplier predicate)
             throws Exception {
+        if (!waitUntil(predicate)) {
+            fail(message);
+        }
+    }
+
+    private static boolean waitUntil(BooleanSupplier predicate) throws Exception {
         int sleep = 125;
         final long timeout = SystemClock.uptimeMillis() + 10_000;  // 10 second timeout
         while (SystemClock.uptimeMillis() < timeout) {
             if (predicate.getAsBoolean()) {
-                return; // okay
+                return true;
             }
             Thread.sleep(sleep);
             sleep *= 5;
             sleep = Math.min(2000, sleep);
         }
-        fail(message);
+        return false;
     }
 
     private static void executeShellCommand(String cmd) {
@@ -130,6 +141,7 @@
             WindowInsetsController.OnControllableInsetsChangedListener,
             View.OnApplyWindowInsetsListener {
 
+        private EditText mEditText;
         boolean mHasFocus;
         boolean mControlsIme;
         boolean mImeVisible;
@@ -137,14 +149,16 @@
         @Override
         protected void onCreate(@Nullable Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
+            mEditText = new EditText(this);
+            mEditText.setCursorVisible(false);  // Otherwise, main thread doesn't go idle.
+            setContentView(mEditText);
+            showIme();
+        }
 
-            EditText content = new EditText(this);
-            content.setCursorVisible(false);  // Otherwise, main thread doesn't go idle.
-            setContentView(content);
-            content.requestFocus();
-
+        private void showIme() {
+            mEditText.requestFocus();
             getWindow().getDecorView().setOnApplyWindowInsetsListener(this);
-            WindowInsetsController wic = content.getWindowInsetsController();
+            WindowInsetsController wic = mEditText.getWindowInsetsController();
             wic.addOnControllableInsetsChangedListener(this);
             wic.show(ime());
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java
index 601fad6..96bb521 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java
@@ -23,7 +23,6 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.IActivityManager;
-import android.app.IActivityTaskManager;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
@@ -34,6 +33,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.model.SysUiState;
 import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.pip.PipSnapAlgorithm;
 import com.android.systemui.pip.PipTaskOrganizer;
@@ -82,6 +82,9 @@
     @Mock
     private DeviceConfigProxy mDeviceConfigProxy;
 
+    @Mock
+    private SysUiState mSysUiState;
+
     private PipSnapAlgorithm mPipSnapAlgorithm;
     private PipMotionHelper mMotionHelper;
     private PipResizeGestureHandler mPipResizeGestureHandler;
@@ -101,7 +104,7 @@
         mPipTouchHandler = new PipTouchHandler(mContext, mActivityManager,
                 mPipMenuActivityController, mInputConsumerController, mPipBoundsHandler,
                 mPipTaskOrganizer, mFloatingContentCoordinator, mDeviceConfigProxy,
-                mPipSnapAlgorithm);
+                mPipSnapAlgorithm, mSysUiState);
         mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
         mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler());
         mPipTouchHandler.setPipMotionHelper(mMotionHelper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
index 0ae9461..61f5a7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.os.UserHandle;
 import android.provider.Settings.Secure;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
@@ -40,6 +41,8 @@
 @SmallTest
 public class AutoAddTrackerTest extends SysuiTestCase {
 
+    private static final int USER = 0;
+
     private AutoAddTracker mAutoTracker;
 
     @Before
@@ -51,7 +54,7 @@
     public void testMigration() {
         Prefs.putBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true);
         Prefs.putBoolean(mContext, Key.QS_WORK_ADDED, true);
-        mAutoTracker = new AutoAddTracker(mContext);
+        mAutoTracker = new AutoAddTracker(mContext, USER);
 
         assertTrue(mAutoTracker.isAdded(SAVER));
         assertTrue(mAutoTracker.isAdded(WORK));
@@ -68,7 +71,7 @@
 
     @Test
     public void testChangeFromBackup() {
-        mAutoTracker = new AutoAddTracker(mContext);
+        mAutoTracker = new AutoAddTracker(mContext, USER);
 
         assertFalse(mAutoTracker.isAdded(SAVER));
 
@@ -82,7 +85,7 @@
 
     @Test
     public void testSetAdded() {
-        mAutoTracker = new AutoAddTracker(mContext);
+        mAutoTracker = new AutoAddTracker(mContext, USER);
 
         assertFalse(mAutoTracker.isAdded(SAVER));
         mAutoTracker.setTileAdded(SAVER);
@@ -94,16 +97,35 @@
 
     @Test
     public void testPersist() {
-        mAutoTracker = new AutoAddTracker(mContext);
+        mAutoTracker = new AutoAddTracker(mContext, USER);
 
         assertFalse(mAutoTracker.isAdded(SAVER));
         mAutoTracker.setTileAdded(SAVER);
 
         mAutoTracker.destroy();
-        mAutoTracker = new AutoAddTracker(mContext);
+        mAutoTracker = new AutoAddTracker(mContext, USER);
 
         assertTrue(mAutoTracker.isAdded(SAVER));
 
         mAutoTracker.destroy();
     }
+
+    @Test
+    public void testIndependentUsers() {
+        mAutoTracker = new AutoAddTracker(mContext, USER);
+        mAutoTracker.setTileAdded(SAVER);
+
+        mAutoTracker = new AutoAddTracker(mContext, USER + 1);
+        assertFalse(mAutoTracker.isAdded(SAVER));
+    }
+
+    @Test
+    public void testChangeUser() {
+        mAutoTracker = new AutoAddTracker(mContext, USER);
+        mAutoTracker.setTileAdded(SAVER);
+
+        mAutoTracker = new AutoAddTracker(mContext, USER + 1);
+        mAutoTracker.changeUser(UserHandle.of(USER));
+        assertTrue(mAutoTracker.isAdded(SAVER));
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index e58a3a6..ea5449b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -96,6 +96,7 @@
     @Test
     public void testUnmanaged() {
         when(mSecurityController.isDeviceManaged()).thenReturn(false);
+        when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(false);
         mFooter.refreshState();
 
         TestableLooper.get(this).processAllMessages();
@@ -317,6 +318,33 @@
     }
 
     @Test
+    public void testProfileOwnerOfOrganizationOwnedDeviceNoName() {
+        when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
+
+        mFooter.refreshState();
+        TestableLooper.get(this).processAllMessages();
+
+        assertEquals(mContext.getString(
+                R.string.quick_settings_disclosure_management),
+                mFooterText.getText());
+    }
+
+    @Test
+    public void testProfileOwnerOfOrganizationOwnedDeviceWithName() {
+        when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
+        when(mSecurityController.getWorkProfileOrganizationName())
+                .thenReturn(MANAGING_ORGANIZATION);
+
+        mFooter.refreshState();
+        TestableLooper.get(this).processAllMessages();
+
+        assertEquals(mContext.getString(
+                R.string.quick_settings_disclosure_named_management,
+                MANAGING_ORGANIZATION),
+                mFooterText.getText());
+    }
+
+    @Test
     public void testVpnEnabled() {
         when(mSecurityController.isVpnEnabled()).thenReturn(true);
         when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java
index 87fc020..d21053b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java
@@ -51,6 +51,7 @@
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock private NotifPipeline mNotifPipeline;
     @Mock private PluggableListener<NotifFilter> mInvalidationListener;
+    @Mock private SharedCoordinatorLogger mLogger;
 
     @Captor private ArgumentCaptor<UserChangedListener> mUserChangedListenerCaptor;
     @Captor private ArgumentCaptor<NotifFilter> mNotifFilterCaptor;
@@ -65,7 +66,7 @@
         MockitoAnnotations.initMocks(this);
 
         HideNotifsForOtherUsersCoordinator coordinator =
-                new HideNotifsForOtherUsersCoordinator(mLockscreenUserManager);
+                new HideNotifsForOtherUsersCoordinator(mLockscreenUserManager, mLogger);
         coordinator.attach(mNotifPipeline);
 
         verify(mLockscreenUserManager).addUserChangedListener(mUserChangedListenerCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 4122cf5..83fc826 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -256,7 +256,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
         final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
         assertEquals(mIconDrawable, view.getDrawable());
     }
@@ -280,7 +280,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
         final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
         assertTrue(textView.getText().toString().contains("App Name"));
         assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -331,7 +331,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
         final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
         assertTrue(textView.getText().toString().contains(group.getName()));
         assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -356,7 +356,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
         final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
         assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
         assertEquals(GONE, textView.getVisibility());
@@ -380,7 +380,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
         final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
         assertEquals(GONE, nameView.getVisibility());
     }
@@ -415,7 +415,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
         final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
         assertEquals(VISIBLE, nameView.getVisibility());
         assertTrue(nameView.getText().toString().contains("Proxied"));
@@ -443,7 +443,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
 
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         settingsButton.performClick();
@@ -469,7 +469,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertTrue(settingsButton.getVisibility() != View.VISIBLE);
     }
@@ -496,7 +496,7 @@
                 mBuilderProvider,
                 false,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertTrue(settingsButton.getVisibility() != View.VISIBLE);
     }
@@ -521,7 +521,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
         View view = mNotificationInfo.findViewById(R.id.silence);
         assertThat(view.isSelected()).isTrue();
     }
@@ -549,7 +549,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
         View view = mNotificationInfo.findViewById(R.id.default_behavior);
         assertThat(view.isSelected()).isTrue();
         assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo(
@@ -580,7 +580,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
         View view = mNotificationInfo.findViewById(R.id.default_behavior);
         assertThat(view.isSelected()).isTrue();
         assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo(
@@ -610,7 +610,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
 
         View fave = mNotificationInfo.findViewById(R.id.priority);
         fave.performClick();
@@ -654,7 +654,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
 
         mNotificationInfo.findViewById(R.id.default_behavior).performClick();
         mTestableLooper.processAllMessages();
@@ -697,7 +697,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
 
         View silence = mNotificationInfo.findViewById(R.id.silence);
 
@@ -741,7 +741,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
 
         View fave = mNotificationInfo.findViewById(R.id.priority);
         fave.performClick();
@@ -778,7 +778,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
 
         View fave = mNotificationInfo.findViewById(R.id.priority);
         fave.performClick();
@@ -814,7 +814,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
 
         View fave = mNotificationInfo.findViewById(R.id.priority);
         fave.performClick();
@@ -852,7 +852,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
 
         mNotificationInfo.findViewById(R.id.default_behavior).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -888,7 +888,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
 
         mNotificationInfo.findViewById(R.id.default_behavior).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -924,7 +924,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
 
         mNotificationInfo.findViewById(R.id.default_behavior).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -959,7 +959,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
 
         View silence = mNotificationInfo.findViewById(R.id.silence);
         silence.performClick();
@@ -993,7 +993,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
 
         verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage(
                 anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
@@ -1018,7 +1018,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
 
         verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
                 anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
@@ -1053,7 +1053,7 @@
                 () -> b,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
 
         // WHEN user clicks "priority"
         mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
@@ -1093,7 +1093,7 @@
                 () -> b,
                 true,
                 mTestHandler,
-                mTestHandler, null);
+                mTestHandler, null, mBubbleController);
 
         // WHEN user clicks "priority"
         mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index da7d249..5b5873b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -65,6 +65,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.settings.CurrentUserContextTracker;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -126,6 +127,7 @@
     @Mock private ChannelEditorDialogController mChannelEditorDialogController;
     @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
     @Mock private CurrentUserContextTracker mContextTracker;
+    @Mock private BubbleController mBubbleController;
     @Mock(answer = Answers.RETURNS_SELF)
     private PriorityOnboardingDialogController.Builder mBuilder;
     private Provider<PriorityOnboardingDialogController.Builder> mProvider = () -> mBuilder;
@@ -138,6 +140,7 @@
                 mDeviceProvisionedController);
         mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
         mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+        mDependency.injectTestDependency(BubbleController.class, mBubbleController);
         mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
         mHandler = Handler.createAsync(mTestableLooper.getLooper());
         mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
@@ -146,7 +149,7 @@
         mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager,
                 () -> mStatusBar, mHandler, mHandler, mAccessibilityManager, mHighPriorityProvider,
                 mINotificationManager, mLauncherApps, mShortcutManager,
-                mChannelEditorDialogController, mContextTracker, mProvider);
+                mChannelEditorDialogController, mContextTracker, mProvider, mBubbleController);
         mGutsManager.setUpWithPresenter(mPresenter, mStackScroller,
                 mCheckSaveListener, mOnSettingsClickListener);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 05cdd80..0a959d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -16,18 +16,34 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNotNull;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
 import android.hardware.display.ColorDisplayManager;
 import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableContentResolver;
+import android.testing.TestableContext;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
@@ -38,14 +54,18 @@
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.statusbar.phone.AutoTileManagerTest.MyContextWrapper;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.HotspotController;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -64,9 +84,18 @@
     private static final String TEST_CUSTOM_SPEC = "custom(" + TEST_COMPONENT + ")";
     private static final String SEPARATOR = AutoTileManager.SETTING_SEPARATOR;
 
+    private static final int USER = 0;
+
     @Mock private QSTileHost mQsTileHost;
     @Mock private AutoAddTracker mAutoAddTracker;
     @Mock private CastController mCastController;
+    @Mock private HotspotController mHotspotController;
+    @Mock private DataSaverController mDataSaverController;
+    @Mock private ManagedProfileController mManagedProfileController;
+    @Mock private NightDisplayListener mNightDisplayListener;
+    @Mock(answer = Answers.RETURNS_SELF)
+    private AutoAddTracker.Builder mAutoAddTrackerBuilder;
+    @Mock private Context mUserContext;
 
     private AutoTileManager mAutoTileManager;
 
@@ -82,20 +111,114 @@
                 }
         );
 
-        mAutoTileManager = createAutoTileManager();
+        when(mAutoAddTrackerBuilder.build()).thenReturn(mAutoAddTracker);
+        when(mQsTileHost.getUserContext()).thenReturn(mUserContext);
+        when(mUserContext.getUser()).thenReturn(UserHandle.of(USER));
+
+        mAutoTileManager = createAutoTileManager(new
+                MyContextWrapper(mContext));
     }
 
-    private AutoTileManager createAutoTileManager() {
-        return new AutoTileManager(mContext, mAutoAddTracker, mQsTileHost,
+    @After
+    public void tearDown() {
+        mAutoTileManager.destroy();
+    }
+
+    private AutoTileManager createAutoTileManager(Context context) {
+        return new AutoTileManager(context, mAutoAddTrackerBuilder, mQsTileHost,
                 Handler.createAsync(TestableLooper.get(this).getLooper()),
-                mock(HotspotController.class),
-                mock(DataSaverController.class),
-                mock(ManagedProfileController.class),
-                mock(NightDisplayListener.class),
+                mHotspotController,
+                mDataSaverController,
+                mManagedProfileController,
+                mNightDisplayListener,
                 mCastController);
     }
 
     @Test
+    public void testChangeUserCallbacksStoppedAndStarted() throws Exception {
+        TestableLooper.get(this).runWithLooper(() ->
+                mAutoTileManager.changeUser(UserHandle.of(USER + 1))
+        );
+
+        InOrder inOrderHotspot = inOrder(mHotspotController);
+        inOrderHotspot.verify(mHotspotController).removeCallback(any());
+        inOrderHotspot.verify(mHotspotController).addCallback(any());
+
+        InOrder inOrderDataSaver = inOrder(mDataSaverController);
+        inOrderDataSaver.verify(mDataSaverController).removeCallback(any());
+        inOrderDataSaver.verify(mDataSaverController).addCallback(any());
+
+        InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
+        inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
+        inOrderManagedProfile.verify(mManagedProfileController).addCallback(any());
+
+        if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
+            InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
+            inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull());
+            inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull());
+        }
+
+        InOrder inOrderCast = inOrder(mCastController);
+        inOrderCast.verify(mCastController).removeCallback(any());
+        inOrderCast.verify(mCastController).addCallback(any());
+
+        SecureSetting setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
+        assertEquals(USER + 1, setting.getCurrentUser());
+        assertTrue(setting.isListening());
+    }
+
+    @Test
+    public void testChangeUserSomeCallbacksNotAdded() throws Exception {
+        when(mAutoAddTracker.isAdded("hotspot")).thenReturn(true);
+        when(mAutoAddTracker.isAdded("work")).thenReturn(true);
+        when(mAutoAddTracker.isAdded("cast")).thenReturn(true);
+        when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true);
+
+        TestableLooper.get(this).runWithLooper(() ->
+                mAutoTileManager.changeUser(UserHandle.of(USER + 1))
+        );
+
+        verify(mAutoAddTracker).changeUser(UserHandle.of(USER + 1));
+
+        InOrder inOrderHotspot = inOrder(mHotspotController);
+        inOrderHotspot.verify(mHotspotController).removeCallback(any());
+        inOrderHotspot.verify(mHotspotController, never()).addCallback(any());
+
+        InOrder inOrderDataSaver = inOrder(mDataSaverController);
+        inOrderDataSaver.verify(mDataSaverController).removeCallback(any());
+        inOrderDataSaver.verify(mDataSaverController).addCallback(any());
+
+        InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
+        inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
+        inOrderManagedProfile.verify(mManagedProfileController, never()).addCallback(any());
+
+        if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
+            InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
+            inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull());
+            inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull());
+        }
+
+        InOrder inOrderCast = inOrder(mCastController);
+        inOrderCast.verify(mCastController).removeCallback(any());
+        inOrderCast.verify(mCastController, never()).addCallback(any());
+
+        SecureSetting setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
+        assertEquals(USER + 1, setting.getCurrentUser());
+        assertFalse(setting.isListening());
+    }
+
+    @Test
+    public void testGetCurrentUserId() throws Exception {
+        assertEquals(USER, mAutoTileManager.getCurrentUserId());
+
+        TestableLooper.get(this).runWithLooper(() ->
+                mAutoTileManager.changeUser(UserHandle.of(USER + 100))
+        );
+
+        assertEquals(USER + 100, mAutoTileManager.getCurrentUserId());
+    }
+
+    @Test
     public void nightTileAdded_whenActivated() {
         if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
             return;
@@ -213,14 +336,14 @@
     public void testEmptyArray_doesNotCrash() {
         mContext.getOrCreateTestableResources().addOverride(
                 R.array.config_quickSettingsAutoAdd, new String[0]);
-        createAutoTileManager();
+        createAutoTileManager(mContext).destroy();
     }
 
     @Test
     public void testMissingConfig_doesNotCrash() {
         mContext.getOrCreateTestableResources().addOverride(
                 R.array.config_quickSettingsAutoAdd, null);
-        createAutoTileManager();
+        createAutoTileManager(mContext).destroy();
     }
 
     // Will only notify if it's listening
@@ -231,4 +354,22 @@
             s.onChange(false);
         }
     }
+
+    class MyContextWrapper extends ContextWrapper {
+
+        private TestableContentResolver mSpiedTCR;
+
+        MyContextWrapper(TestableContext context) {
+            super(context);
+            mSpiedTCR = spy(context.getContentResolver());
+            doNothing().when(mSpiedTCR).registerContentObserver(any(), anyBoolean(), any(),
+                    anyInt());
+            doNothing().when(mSpiedTCR).unregisterContentObserver(any());
+        }
+
+        @Override
+        public ContentResolver getContentResolver() {
+            return mSpiedTCR;
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
index fee5e32..e8911a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
@@ -41,6 +41,11 @@
     }
 
     @Override
+    public boolean isProfileOwnerOfOrganizationOwnedDevice() {
+        return false;
+    }
+
+    @Override
     public String getDeviceOwnerName() {
         return null;
     }
diff --git a/packages/Tethering/OWNERS b/packages/Tethering/OWNERS
new file mode 100644
index 0000000..5b42d49
--- /dev/null
+++ b/packages/Tethering/OWNERS
@@ -0,0 +1,2 @@
+include platform/packages/modules/NetworkStack/:/OWNERS
+markchien@google.com
diff --git a/packages/Tethering/jarjar-rules.txt b/packages/Tethering/jarjar-rules.txt
index 8f072e4..2d3108a 100644
--- a/packages/Tethering/jarjar-rules.txt
+++ b/packages/Tethering/jarjar-rules.txt
@@ -1,17 +1,8 @@
 # These must be kept in sync with the framework-tethering-shared-srcs filegroup.
-# If there are files in that filegroup that do not appear here, the classes in the
+# Classes from the framework-tethering-shared-srcs filegroup.
+# If there are files in that filegroup that are not covered below, the classes in the
 # module will be overwritten by the ones in the framework.
-# Don't jar-jar the entire package because tethering still use some internal classes
-# (like TrafficStatsConstants in com.android.internal.util)
-# TODO: simply these when tethering is built as system_current.
-rule com.android.internal.util.BitUtils* com.android.networkstack.tethering.util.BitUtils@1
-rule com.android.internal.util.IndentingPrintWriter.java* com.android.networkstack.tethering.util.IndentingPrintWriter.java@1
-rule com.android.internal.util.IState.java* com.android.networkstack.tethering.util.IState.java@1
-rule com.android.internal.util.MessageUtils* com.android.networkstack.tethering.util.MessageUtils@1
-rule com.android.internal.util.State* com.android.networkstack.tethering.util.State@1
-rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1
-rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.tethering.util.TrafficStatsConstants@1
-
+rule com.android.internal.util.** com.android.networkstack.tethering.util.@1
 rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1
 
 rule android.net.shared.Inet4AddressUtils* com.android.networkstack.tethering.shared.Inet4AddressUtils@1
diff --git a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
index 4710a30..aaaec17 100644
--- a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
+++ b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
@@ -16,7 +16,7 @@
 
 package android.net.dhcp;
 
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
 
 import android.net.LinkAddress;
 import android.util.ArraySet;
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index fc9e808..4f13f6b 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -19,13 +19,14 @@
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.TetheringManager.TetheringRequest.checkStaticAddressConfiguration;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
 import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
 import static android.net.util.NetworkConstants.asByte;
 import static android.net.util.PrefixUtils.asIpPrefix;
 import static android.net.util.TetheringMessageBase.BASE_IPSERVER;
 import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
+
 import android.net.INetd;
 import android.net.INetworkStackStatusCallback;
 import android.net.IpPrefix;
@@ -616,8 +617,9 @@
         final Boolean setIfaceUp;
         if (mInterfaceType == TetheringManager.TETHERING_WIFI
                 || mInterfaceType == TetheringManager.TETHERING_WIFI_P2P
-                || mInterfaceType == TetheringManager.TETHERING_WIGIG) {
-            // The WiFi stack has ownership of the interface up/down state.
+                || mInterfaceType == TetheringManager.TETHERING_WIGIG
+                || mInterfaceType == TetheringManager.TETHERING_ETHERNET) {
+            // The WiFi and Ethernet stack has ownership of the interface up/down state.
             // It is unclear whether the Bluetooth or USB stacks will manage their own
             // state.
             setIfaceUp = null;
@@ -1322,6 +1324,7 @@
     class UnavailableState extends State {
         @Override
         public void enter() {
+            mIpNeighborMonitor.stop();
             mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
             sendInterfaceState(STATE_UNAVAILABLE);
         }
diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 4f88605..30a9d22 100644
--- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -36,7 +36,8 @@
 import static android.net.netlink.StructNdMsg.NUD_FAILED;
 import static android.net.netlink.StructNdMsg.NUD_REACHABLE;
 import static android.net.netlink.StructNdMsg.NUD_STALE;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
+
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -859,6 +860,7 @@
         verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer);
         verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA));
         verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB));
+        verify(mIpNeighborMonitor).stop();
         resetNetdAndBpfCoordinator();
     }
 
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 5261992..64538c7 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -40,7 +40,6 @@
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
@@ -49,6 +48,7 @@
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
 import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
 import static com.android.networkstack.tethering.UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES;
 
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 4a4b7dd..ea94ad0 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -953,7 +953,7 @@
         }
 
         @Override
-        public void accept(@NonNull IDataShareReadAdapter serviceAdapter) throws RemoteException {
+        public void accept(@NonNull IDataShareReadAdapter serviceAdapter) {
             Slog.i(TAG, "Data share request accepted by Content Capture service");
             logServiceEvent(CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ACCEPT_DATA_SHARE_REQUEST);
 
@@ -961,8 +961,8 @@
             if (clientPipe == null) {
                 logServiceEvent(
                         CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CLIENT_PIPE_FAIL);
-                mClientAdapter.error(ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
-                serviceAdapter.error(ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
+                sendErrorSignal(mClientAdapter, serviceAdapter,
+                        ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
                 return;
             }
 
@@ -975,8 +975,8 @@
                         CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_SERVICE_PIPE_FAIL);
                 bestEffortCloseFileDescriptors(sourceIn, sinkIn);
 
-                mClientAdapter.error(ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
-                serviceAdapter.error(ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
+                sendErrorSignal(mClientAdapter, serviceAdapter,
+                        ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
                 return;
             }
 
@@ -985,8 +985,26 @@
 
             mParentService.mPackagesWithShareRequests.add(mDataShareRequest.getPackageName());
 
-            mClientAdapter.write(sourceIn);
-            serviceAdapter.start(sinkOut);
+            try {
+                mClientAdapter.write(sourceIn);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to call write() the client operation", e);
+                sendErrorSignal(mClientAdapter, serviceAdapter,
+                        ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
+                logServiceEvent(
+                        CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CLIENT_PIPE_FAIL);
+                return;
+            }
+            try {
+                serviceAdapter.start(sinkOut);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to call start() the service operation", e);
+                sendErrorSignal(mClientAdapter, serviceAdapter,
+                        ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
+                logServiceEvent(
+                        CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_SERVICE_PIPE_FAIL);
+                return;
+            }
 
             // File descriptors received by remote apps will be copies of the current one. Close
             // the ones that belong to the system server, so there's only 1 open left for the
@@ -1061,11 +1079,20 @@
         }
 
         @Override
-        public void reject() throws RemoteException {
+        public void reject() {
             Slog.i(TAG, "Data share request rejected by Content Capture service");
             logServiceEvent(CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__REJECT_DATA_SHARE_REQUEST);
 
-            mClientAdapter.rejected();
+            try {
+                mClientAdapter.rejected();
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to call rejected() the client operation", e);
+                try {
+                    mClientAdapter.error(ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
+                } catch (RemoteException e2) {
+                    Slog.w(TAG, "Failed to call error() the client operation", e2);
+                }
+            }
         }
 
         private void enforceDataSharingTtl(ParcelFileDescriptor sourceIn,
diff --git a/services/core/java/android/os/UserManagerInternal.java b/services/core/java/android/os/UserManagerInternal.java
index 94f5741..fbe8c04 100644
--- a/services/core/java/android/os/UserManagerInternal.java
+++ b/services/core/java/android/os/UserManagerInternal.java
@@ -27,6 +27,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.List;
 
 /**
  * @hide Only for use within the system server.
@@ -219,6 +220,13 @@
     public abstract int[] getUserIds();
 
     /**
+     * Internal implementation of getUsers does not check permissions.
+     * This improves performance for calls from inside system server which already have permissions
+     * checked.
+     */
+    public abstract @NonNull List<UserInfo> getUsers(boolean excludeDying);
+
+    /**
      * Checks if the {@code callingUserId} and {@code targetUserId} are same or in same group
      * and that the {@code callingUserId} is not a profile and {@code targetUserId} is enabled.
      *
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 4009caf..d51ca6e 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -104,6 +104,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.text.SimpleDateFormat;
 import java.time.DateTimeException;
 import java.util.ArrayList;
@@ -146,6 +147,12 @@
     static final boolean DEBUG_WAKELOCK = localLOGV || false;
     static final boolean DEBUG_BG_LIMIT = localLOGV || false;
     static final boolean DEBUG_STANDBY = localLOGV || false;
+
+    // TODO (b/157782538): Turn off once bug is fixed.
+    static final boolean DEBUG_PER_UID_LIMIT = true;
+    // TODO (b/157782538): Turn off once bug is fixed.
+    static final boolean WARN_SYSTEM_ON_ALARM_LIMIT = true;
+
     static final boolean RECORD_ALARMS_IN_HISTORY = true;
     static final boolean RECORD_DEVICE_IDLE_ALARMS = false;
     static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
@@ -1767,7 +1774,21 @@
                         "Maximum limit of concurrent alarms " + mConstants.MAX_ALARMS_PER_UID
                                 + " reached for uid: " + UserHandle.formatUid(callingUid)
                                 + ", callingPackage: " + callingPackage;
-                Slog.w(TAG, errorMsg);
+
+                if (WARN_SYSTEM_ON_ALARM_LIMIT && UserHandle.isCore(callingUid)) {
+                    final StringWriter logWriter = new StringWriter();
+                    final PrintWriter pw = new PrintWriter(logWriter);
+                    pw.println(errorMsg);
+                    pw.println("Next 20 alarms for " + callingUid + ":");
+                    dumpUpcomingNAlarmsForUid(pw, callingUid, 20);
+                    pw.flush();
+                    Slog.wtf(TAG, logWriter.toString());
+                } else {
+                    Slog.w(TAG, errorMsg);
+                }
+                if (DEBUG_PER_UID_LIMIT) {
+                    logAllAlarmsForUidLocked(callingUid);
+                }
                 throw new IllegalStateException(errorMsg);
             }
             setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
@@ -1776,6 +1797,36 @@
         }
     }
 
+    private void logAllAlarmsForUidLocked(int uid) {
+        final StringWriter logWriter = new StringWriter();
+        final PrintWriter pw = new PrintWriter(logWriter);
+
+        pw.println("List of all pending alarms for " + UserHandle.formatUid(uid) + ":");
+        dumpUpcomingNAlarmsForUid(pw, uid, mConstants.MAX_ALARMS_PER_UID);
+        pw.flush();
+        Slog.d(TAG, logWriter.toString());
+    }
+
+    private void dumpUpcomingNAlarmsForUid(PrintWriter pw, int uid, int n) {
+        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        final long nowElapsed = mInjector.getElapsedRealtime();
+        final long nowRtc = mInjector.getCurrentTimeMillis();
+
+        int count = 0;
+        for (int i = 0; i < mAlarmBatches.size() && count < n; i++) {
+            final Batch b = mAlarmBatches.get(i);
+            for (int j = 0; j < b.size() && count < n; j++) {
+                final Alarm a = b.get(j);
+                if (a.uid == uid) {
+                    final String label = labelForType(a.type);
+                    pw.print(label + " #" + (++count) + ": ");
+                    pw.println(a);
+                    a.dump(pw, "  ", nowElapsed, nowRtc, sdf);
+                }
+            }
+        }
+    }
+
     private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
             long maxWhen, long interval, PendingIntent operation, IAlarmListener directReceiver,
             String listenerTag, int flags, boolean doValidate, WorkSource workSource,
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 97c5bb1..87ddf54 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1142,8 +1142,6 @@
             Slog.wtf(TAG, e);
         }
 
-        onKeyguardStateChanged(false);
-
         mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId).sendToTarget();
     }
 
@@ -1155,6 +1153,8 @@
             mPmInternal.migrateLegacyObbData();
         }
 
+        onKeyguardStateChanged(false);
+
         // Record user as started so newly mounted volumes kick off events
         // correctly, then synthesize events for any already-mounted volumes.
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/gpu/GpuService.java b/services/core/java/com/android/server/gpu/GpuService.java
index 955f177..8a3c963 100644
--- a/services/core/java/com/android/server/gpu/GpuService.java
+++ b/services/core/java/com/android/server/gpu/GpuService.java
@@ -39,6 +39,7 @@
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Slog;
 
@@ -62,15 +63,19 @@
     public static final String TAG = "GpuService";
     public static final boolean DEBUG = false;
 
-    private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0";
+    private static final String PROD_DRIVER_PROPERTY = "ro.gfx.driver.0";
+    private static final String DEV_DRIVER_PROPERTY = "ro.gfx.driver.1";
     private static final String GAME_DRIVER_WHITELIST_FILENAME = "whitelist.txt";
     private static final int BASE64_FLAGS = Base64.NO_PADDING | Base64.NO_WRAP;
 
     private final Context mContext;
-    private final String mDriverPackageName;
+    private final String mProdDriverPackageName;
+    private final String mDevDriverPackageName;
     private final PackageManager mPackageManager;
     private final Object mLock = new Object();
     private final Object mDeviceConfigLock = new Object();
+    private final boolean mHasProdDriver;
+    private final boolean mHasDevDriver;
     private ContentResolver mContentResolver;
     private long mGameDriverVersionCode;
     private SettingsObserver mSettingsObserver;
@@ -82,10 +87,13 @@
         super(context);
 
         mContext = context;
-        mDriverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER);
+        mProdDriverPackageName = SystemProperties.get(PROD_DRIVER_PROPERTY);
         mGameDriverVersionCode = -1;
+        mDevDriverPackageName = SystemProperties.get(DEV_DRIVER_PROPERTY);
         mPackageManager = context.getPackageManager();
-        if (mDriverPackageName != null && !mDriverPackageName.isEmpty()) {
+        mHasProdDriver = !TextUtils.isEmpty(mProdDriverPackageName);
+        mHasDevDriver = !TextUtils.isEmpty(mDevDriverPackageName);
+        if (mHasDevDriver || mHasProdDriver) {
             final IntentFilter packageFilter = new IntentFilter();
             packageFilter.addAction(ACTION_PACKAGE_ADDED);
             packageFilter.addAction(ACTION_PACKAGE_CHANGED);
@@ -104,7 +112,7 @@
     public void onBootPhase(int phase) {
         if (phase == PHASE_BOOT_COMPLETED) {
             mContentResolver = mContext.getContentResolver();
-            if (mDriverPackageName == null || mDriverPackageName.isEmpty()) {
+            if (!mHasProdDriver && !mHasDevDriver) {
                 return;
             }
             mSettingsObserver = new SettingsObserver();
@@ -112,6 +120,7 @@
             fetchGameDriverPackageProperties();
             processBlacklists();
             setBlacklist();
+            fetchDeveloperDriverPackageProperties();
         }
     }
 
@@ -166,18 +175,22 @@
                 return;
             }
             final String packageName = data.getSchemeSpecificPart();
-            if (!packageName.equals(mDriverPackageName)) {
+            final boolean isProdDriver = packageName.equals(mProdDriverPackageName);
+            final boolean isDevDriver = packageName.equals(mDevDriverPackageName);
+            if (!isProdDriver && !isDevDriver) {
                 return;
             }
 
-            final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-
             switch (intent.getAction()) {
                 case ACTION_PACKAGE_ADDED:
                 case ACTION_PACKAGE_CHANGED:
                 case ACTION_PACKAGE_REMOVED:
-                    fetchGameDriverPackageProperties();
-                    setBlacklist();
+                    if (isProdDriver) {
+                        fetchGameDriverPackageProperties();
+                        setBlacklist();
+                    } else if (isDevDriver) {
+                        fetchDeveloperDriverPackageProperties();
+                    }
                     break;
                 default:
                     // do nothing
@@ -208,11 +221,11 @@
     private void fetchGameDriverPackageProperties() {
         final ApplicationInfo driverInfo;
         try {
-            driverInfo = mPackageManager.getApplicationInfo(mDriverPackageName,
+            driverInfo = mPackageManager.getApplicationInfo(mProdDriverPackageName,
                                                             PackageManager.MATCH_SYSTEM_ONLY);
         } catch (PackageManager.NameNotFoundException e) {
             if (DEBUG) {
-                Slog.e(TAG, "driver package '" + mDriverPackageName + "' not installed");
+                Slog.e(TAG, "driver package '" + mProdDriverPackageName + "' not installed");
             }
             return;
         }
@@ -232,14 +245,14 @@
         mGameDriverVersionCode = driverInfo.longVersionCode;
 
         try {
-            final Context driverContext = mContext.createPackageContext(mDriverPackageName,
+            final Context driverContext = mContext.createPackageContext(mProdDriverPackageName,
                                                                         Context.CONTEXT_RESTRICTED);
 
             assetToSettingsGlobal(mContext, driverContext, GAME_DRIVER_WHITELIST_FILENAME,
                     Settings.Global.GAME_DRIVER_WHITELIST, ",");
         } catch (PackageManager.NameNotFoundException e) {
             if (DEBUG) {
-                Slog.w(TAG, "driver package '" + mDriverPackageName + "' not installed");
+                Slog.w(TAG, "driver package '" + mProdDriverPackageName + "' not installed");
             }
         }
     }
@@ -291,4 +304,40 @@
             }
         }
     }
+
+    private void fetchDeveloperDriverPackageProperties() {
+        final ApplicationInfo driverInfo;
+        try {
+            driverInfo = mPackageManager.getApplicationInfo(mDevDriverPackageName,
+                                                            PackageManager.MATCH_SYSTEM_ONLY);
+        } catch (PackageManager.NameNotFoundException e) {
+            if (DEBUG) {
+                Slog.e(TAG, "driver package '" + mDevDriverPackageName + "' not installed");
+            }
+            return;
+        }
+
+        // O drivers are restricted to the sphal linker namespace, so don't try to use
+        // packages unless they declare they're compatible with that restriction.
+        if (driverInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+            if (DEBUG) {
+                Slog.w(TAG, "Driver package is not known to be compatible with O");
+            }
+            return;
+        }
+
+        setUpdatableDriverPath(driverInfo);
+    }
+
+    private void setUpdatableDriverPath(ApplicationInfo ai) {
+        if (ai.primaryCpuAbi == null) {
+            nSetUpdatableDriverPath("");
+            return;
+        }
+        final StringBuilder sb = new StringBuilder();
+        sb.append(ai.sourceDir).append("!/lib/");
+        nSetUpdatableDriverPath(sb.toString());
+    }
+
+    private static native void nSetUpdatableDriverPath(String driverPath);
 }
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index e17cca4..3fb713b 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -17,9 +17,8 @@
 package com.android.server.location.gnss;
 
 import android.content.Context;
+import android.database.Cursor;
 import android.net.ConnectivityManager;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
@@ -27,26 +26,25 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.PowerManager;
-import android.telephony.PhoneStateListener;
-import android.telephony.PreciseCallState;
+import android.provider.Telephony.Carriers;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
+import android.telephony.PreciseCallState;
+import android.telephony.PhoneStateListener;
 import android.util.Log;
 
 import com.android.internal.location.GpsNetInitiatedHandler;
 
-import java.net.Inet4Address;
-import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.Arrays;
+import java.util.Map;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
-
+import java.util.Iterator;
 
 /**
  * Handles network connection requests and network state change updates for AGPS data download.
@@ -387,10 +385,10 @@
     private ConnectivityManager.NetworkCallback createSuplConnectivityCallback() {
         return new ConnectivityManager.NetworkCallback() {
             @Override
-            public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
+            public void onAvailable(Network network) {
                 if (DEBUG) Log.d(TAG, "SUPL network connection available.");
                 // Specific to a change to a SUPL enabled network becoming ready
-                handleSuplConnectionAvailable(network, linkProperties);
+                handleSuplConnectionAvailable(network);
             }
 
             @Override
@@ -498,7 +496,7 @@
         return networkAttributes;
     }
 
-    private void handleSuplConnectionAvailable(Network network, LinkProperties linkProperties) {
+    private void handleSuplConnectionAvailable(Network network) {
         // TODO: The synchronous method ConnectivityManager.getNetworkInfo() should not be called
         //       inside the asynchronous ConnectivityManager.NetworkCallback methods.
         NetworkInfo info = mConnMgr.getNetworkInfo(network);
@@ -530,7 +528,7 @@
                 setRouting();
             }
 
-            int apnIpType = getLinkIpType(linkProperties);
+            int apnIpType = getApnIpType(apn);
             if (DEBUG) {
                 String message = String.format(
                         "native_agps_data_conn_open: mAgpsApn=%s, mApnIpType=%s",
@@ -706,32 +704,74 @@
         }
     }
 
-    private int getLinkIpType(LinkProperties linkProperties) {
+    private int getApnIpType(String apn) {
         ensureInHandlerThread();
-        boolean isIPv4 = false;
-        boolean isIPv6 = false;
-
-        List<LinkAddress> linkAddresses = linkProperties.getLinkAddresses();
-        for (LinkAddress linkAddress : linkAddresses) {
-            InetAddress inetAddress = linkAddress.getAddress();
-            if (inetAddress instanceof Inet4Address) {
-                isIPv4 = true;
-            } else if (inetAddress instanceof Inet6Address) {
-                isIPv6 = true;
+        if (apn == null) {
+            return APN_INVALID;
+        }
+        TelephonyManager phone = (TelephonyManager)
+                mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        // During an emergency call with an active sub id, get the Telephony Manager specific
+        // to the active sub to get the correct value from getServiceState and getNetworkType
+        if (mNiHandler.getInEmergency() && mActiveSubId >= 0) {
+            TelephonyManager subIdTelManager =
+                    phone.createForSubscriptionId(mActiveSubId);
+            if (subIdTelManager != null) {
+                phone = subIdTelManager;
             }
-            if (DEBUG) Log.d(TAG, "LinkAddress : " + inetAddress.toString());
+        }
+        ServiceState serviceState = phone.getServiceState();
+        String projection = null;
+        String selection = null;
+
+        // Carrier configuration may override framework roaming state, we need to use the actual
+        // modem roaming state instead of the framework roaming state.
+        if (serviceState != null && serviceState.getDataRoamingFromRegistration()) {
+            projection = Carriers.ROAMING_PROTOCOL;
+        } else {
+            projection = Carriers.PROTOCOL;
+        }
+        // No SIM case for emergency
+        if (TelephonyManager.NETWORK_TYPE_UNKNOWN == phone.getNetworkType()
+                && AGPS_TYPE_EIMS == mAGpsType) {
+            selection = String.format(
+                "type like '%%emergency%%' and apn = '%s' and carrier_enabled = 1", apn);
+        } else {
+            selection = String.format("current = 1 and apn = '%s' and carrier_enabled = 1", apn);
+        }
+        try (Cursor cursor = mContext.getContentResolver().query(
+                Carriers.CONTENT_URI,
+                new String[]{projection},
+                selection,
+                null,
+                Carriers.DEFAULT_SORT_ORDER)) {
+            if (null != cursor && cursor.moveToFirst()) {
+                return translateToApnIpType(cursor.getString(0), apn);
+            } else {
+                Log.e(TAG, "No entry found in query for APN: " + apn);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Error encountered on APN query for: " + apn, e);
         }
 
-        if (isIPv4 && isIPv6) {
-            return APN_IPV4V6;
-        }
-        if (isIPv4) {
+        return APN_IPV4V6;
+    }
+
+    private int translateToApnIpType(String ipProtocol, String apn) {
+        if ("IP".equals(ipProtocol)) {
             return APN_IPV4;
         }
-        if (isIPv6) {
+        if ("IPV6".equals(ipProtocol)) {
             return APN_IPV6;
         }
-        return APN_INVALID;
+        if ("IPV4V6".equals(ipProtocol)) {
+            return APN_IPV4V6;
+        }
+
+        // we hit the default case so the ipProtocol is not recognized
+        String message = String.format("Unknown IP Protocol: %s, for APN: %s", ipProtocol, apn);
+        Log.e(TAG, message);
+        return APN_IPV4V6;
     }
 
     // AGPS support
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 9625041..242132c 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -2572,6 +2572,9 @@
                     }
                 } else {
                     if (mKeyType == KEY_TYPE_VOLUME) {
+                        if (isFirstLongPressKeyEvent(keyEvent)) {
+                            dispatchVolumeKeyLongPressLocked(mTrackingFirstDownKeyEvent);
+                        }
                         dispatchVolumeKeyLongPressLocked(keyEvent);
                     } else if (isFirstLongPressKeyEvent(keyEvent)
                             && isVoiceKey(keyEvent.getKeyCode())) {
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index bd39caa..e01f365 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -45,6 +45,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -58,8 +59,7 @@
 // TODO: check thread safety. We may need to use lock to protect variables.
 class SystemMediaRoute2Provider extends MediaRoute2Provider {
     private static final String TAG = "MR2SystemProvider";
-    // TODO(b/156996903): Revert it when releasing the framework.
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
     static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 3283fd9..87f0fb1 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -578,6 +578,10 @@
 
     private final NetworkPolicyLogger mLogger = new NetworkPolicyLogger();
 
+    /** List of apps indexed by appId and whether they have the internet permission */
+    @GuardedBy("mUidRulesFirstLock")
+    private final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray();
+
     // TODO: keep whitelist of system-critical services that should never have
     // rules enforced, such as system, phone, and radio UIDs.
 
@@ -958,7 +962,9 @@
                 // update rules for UID, since it might be subject to
                 // global background data policy
                 if (LOGV) Slog.v(TAG, "ACTION_PACKAGE_ADDED for uid=" + uid);
+                // Clear the cache for the app
                 synchronized (mUidRulesFirstLock) {
+                    mInternetPermissionMap.delete(UserHandle.getAppId(uid));
                     updateRestrictionRulesForUidUL(uid);
                 }
             }
@@ -1000,7 +1006,7 @@
                     synchronized (mUidRulesFirstLock) {
                         // Remove any persistable state for the given user; both cleaning up after a
                         // USER_REMOVED, and one last sanity check during USER_ADDED
-                        removeUserStateUL(userId, true);
+                        removeUserStateUL(userId, true, false);
                         // Removing outside removeUserStateUL since that can also be called when
                         // user resets app preferences.
                         mMeteredRestrictedUids.remove(userId);
@@ -2613,7 +2619,7 @@
         setUidPolicyUncheckedUL(uid, policy, false);
 
         final boolean notifyApp;
-        if (!isUidValidForWhitelistRules(uid)) {
+        if (!isUidValidForWhitelistRulesUL(uid)) {
             notifyApp = false;
         } else {
             final boolean wasBlacklisted = oldPolicy == POLICY_REJECT_METERED_BACKGROUND;
@@ -2689,7 +2695,7 @@
      * if any changes that are made.
      */
     @GuardedBy("mUidRulesFirstLock")
-    boolean removeUserStateUL(int userId, boolean writePolicy) {
+    boolean removeUserStateUL(int userId, boolean writePolicy, boolean updateGlobalRules) {
 
         mLogger.removingUserState(userId);
         boolean changed = false;
@@ -2719,7 +2725,9 @@
             changed = true;
         }
         synchronized (mNetworkPoliciesSecondLock) {
-            updateRulesForGlobalChangeAL(true);
+            if (updateGlobalRules) {
+                updateRulesForGlobalChangeAL(true);
+            }
             if (writePolicy && changed) {
                 writePolicyAL();
             }
@@ -3859,7 +3867,7 @@
                         // quick check: if this uid doesn't have INTERNET permission, it
                         // doesn't have network access anyway, so it is a waste to mess
                         // with it here.
-                        if (hasInternetPermissions(uid)) {
+                        if (hasInternetPermissionUL(uid)) {
                             uidRules.put(uid, FIREWALL_RULE_DENY);
                         }
                     }
@@ -3874,7 +3882,7 @@
 
     @GuardedBy("mUidRulesFirstLock")
     void updateRuleForAppIdleUL(int uid) {
-        if (!isUidValidForBlacklistRules(uid)) return;
+        if (!isUidValidForBlacklistRulesUL(uid)) return;
 
         if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRuleForAppIdleUL: " + uid );
@@ -4056,18 +4064,20 @@
 
     // TODO: the MEDIA / DRM restriction might not be needed anymore, in which case both
     // methods below could be merged into a isUidValidForRules() method.
-    private boolean isUidValidForBlacklistRules(int uid) {
+    @GuardedBy("mUidRulesFirstLock")
+    private boolean isUidValidForBlacklistRulesUL(int uid) {
         // allow rules on specific system services, and any apps
         if (uid == android.os.Process.MEDIA_UID || uid == android.os.Process.DRM_UID
-            || (UserHandle.isApp(uid) && hasInternetPermissions(uid))) {
+                || isUidValidForWhitelistRulesUL(uid)) {
             return true;
         }
 
         return false;
     }
 
-    private boolean isUidValidForWhitelistRules(int uid) {
-        return UserHandle.isApp(uid) && hasInternetPermissions(uid);
+    @GuardedBy("mUidRulesFirstLock")
+    private boolean isUidValidForWhitelistRulesUL(int uid) {
+        return UserHandle.isApp(uid) && hasInternetPermissionUL(uid);
     }
 
     /**
@@ -4143,12 +4153,20 @@
      * <p>
      * Useful for the cases where the lack of network access can simplify the rules.
      */
-    private boolean hasInternetPermissions(int uid) {
+    @GuardedBy("mUidRulesFirstLock")
+    private boolean hasInternetPermissionUL(int uid) {
         try {
-            if (mIPm.checkUidPermission(Manifest.permission.INTERNET, uid)
-                    != PackageManager.PERMISSION_GRANTED) {
-                return false;
+            final int appId = UserHandle.getAppId(uid);
+            final boolean hasPermission;
+            if (mInternetPermissionMap.indexOfKey(appId) < 0) {
+                hasPermission =
+                        mIPm.checkUidPermission(Manifest.permission.INTERNET, uid)
+                                == PackageManager.PERMISSION_GRANTED;
+                mInternetPermissionMap.put(appId, hasPermission);
+            } else {
+                hasPermission = mInternetPermissionMap.get(appId);
             }
+            return hasPermission;
         } catch (RemoteException e) {
         }
         return true;
@@ -4253,7 +4271,7 @@
     }
 
     private void updateRulesForDataUsageRestrictionsULInner(int uid) {
-        if (!isUidValidForWhitelistRules(uid)) {
+        if (!isUidValidForWhitelistRulesUL(uid)) {
             if (LOGD) Slog.d(TAG, "no need to update restrict data rules for uid " + uid);
             return;
         }
@@ -4400,6 +4418,7 @@
      *
      * @return the new computed rules for the uid
      */
+    @GuardedBy("mUidRulesFirstLock")
     private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules, boolean paroled) {
         if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
@@ -4413,8 +4432,9 @@
         }
     }
 
+    @GuardedBy("mUidRulesFirstLock")
     private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules, boolean paroled) {
-        if (!isUidValidForBlacklistRules(uid)) {
+        if (!isUidValidForBlacklistRulesUL(uid)) {
             if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
             return RULE_NONE;
         }
@@ -5198,7 +5218,7 @@
         @Override
         public void resetUserState(int userId) {
             synchronized (mUidRulesFirstLock) {
-                boolean changed = removeUserStateUL(userId, false);
+                boolean changed = removeUserStateUL(userId, false, true);
                 changed = addDefaultRestrictBackgroundWhitelistUidsUL(userId) || changed;
                 if (changed) {
                     synchronized (mNetworkPoliciesSecondLock) {
diff --git a/services/core/java/com/android/server/notification/BadgeExtractor.java b/services/core/java/com/android/server/notification/BadgeExtractor.java
index d323d80..0681d95 100644
--- a/services/core/java/com/android/server/notification/BadgeExtractor.java
+++ b/services/core/java/com/android/server/notification/BadgeExtractor.java
@@ -17,9 +17,9 @@
 
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
 
+import android.app.Notification;
 import android.content.Context;
 import android.util.Slog;
-import android.app.Notification;
 
 /**
  * Determines whether a badge should be shown for this notification
@@ -66,6 +66,17 @@
         if (metadata != null && metadata.isNotificationSuppressed()) {
             record.setShowBadge(false);
         }
+
+        if (mConfig.isMediaNotificationFilteringEnabled()) {
+            final Notification notif = record.getNotification();
+            if (notif.hasMediaSession()) {
+                Class<? extends Notification.Style> notifStyle = notif.getNotificationStyle();
+                if (Notification.DecoratedMediaCustomViewStyle.class.equals(notifStyle)
+                        || Notification.MediaStyle.class.equals(notifStyle)) {
+                    record.setShowBadge(false);
+                }
+            }
+        }
         return null;
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationChannelLogger.java b/services/core/java/com/android/server/notification/NotificationChannelLogger.java
index 5c127c3..51faac7 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelLogger.java
@@ -16,9 +16,12 @@
 
 package com.android.server.notification;
 
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+
 import android.annotation.NonNull;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
+import android.stats.sysui.NotificationEnums;
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
@@ -42,7 +45,7 @@
             String pkg) {
         logNotificationChannel(
                 NotificationChannelEvent.getCreated(channel),
-                channel, uid, pkg, 0, 0);
+                channel, uid, pkg, 0, getLoggingImportance(channel));
     }
 
     /**
@@ -55,7 +58,7 @@
             String pkg) {
         logNotificationChannel(
                 NotificationChannelEvent.getDeleted(channel),
-                channel, uid, pkg, 0, 0);
+                channel, uid, pkg, getLoggingImportance(channel), 0);
     }
 
     /**
@@ -63,13 +66,13 @@
      * @param channel The channel.
      * @param uid UID of app that owns the channel.
      * @param pkg Package of app that owns the channel.
-     * @param oldImportance Previous importance level of the channel.
+     * @param oldLoggingImportance Previous logging importance level of the channel.
      * @param byUser True if the modification was user-specified.
      */
     default void logNotificationChannelModified(@NonNull NotificationChannel channel, int uid,
-            String pkg, int oldImportance, boolean byUser) {
+            String pkg, int oldLoggingImportance, boolean byUser) {
         logNotificationChannel(NotificationChannelEvent.getUpdated(byUser),
-                channel, uid, pkg, oldImportance, channel.getImportance());
+                channel, uid, pkg, oldLoggingImportance, getLoggingImportance(channel));
     }
 
     /**
@@ -219,6 +222,27 @@
     }
 
     /**
+     * @return Logging importance for a channel: the regular importance, or
+     *     IMPORTANCE_IMPORTANT_CONVERSATION for a HIGH-importance conversation tagged important.
+     */
+    static int getLoggingImportance(@NonNull NotificationChannel channel) {
+        return getLoggingImportance(channel, channel.getImportance());
+    }
+
+    /**
+     * @return Logging importance for a channel or notification: the regular importance, or
+     *     IMPORTANCE_IMPORTANT_CONVERSATION for a HIGH-importance conversation tagged important.
+     */
+    static int getLoggingImportance(@NonNull NotificationChannel channel, int importance) {
+        if (channel.getConversationId() == null || importance < IMPORTANCE_HIGH) {
+            return importance;
+        }
+        return (channel.isImportantConversation())
+                ? NotificationEnums.IMPORTANCE_IMPORTANT_CONVERSATION
+                : importance;
+    }
+
+    /**
      * @return "Importance" for a channel group
      */
     static int getImportance(@NonNull NotificationChannelGroup channelGroup) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b64e991..18635e8 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1621,7 +1621,8 @@
                 = Settings.Global.getUriFor(Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE);
         private final Uri NOTIFICATION_HISTORY_ENABLED
                 = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_HISTORY_ENABLED);
-
+        private final Uri NOTIFICATION_SHOW_MEDIA_ON_QUICK_SETTINGS_URI
+                = Settings.Global.getUriFor(Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS);
 
         SettingsObserver(Handler handler) {
             super(handler);
@@ -1639,6 +1640,8 @@
                     false, this, UserHandle.USER_ALL);
             resolver.registerContentObserver(NOTIFICATION_HISTORY_ENABLED,
                     false, this, UserHandle.USER_ALL);
+            resolver.registerContentObserver(NOTIFICATION_SHOW_MEDIA_ON_QUICK_SETTINGS_URI,
+                    false, this, UserHandle.USER_ALL);
             update(null);
         }
 
@@ -1675,6 +1678,9 @@
                             Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0) == 1);
                 }
             }
+            if (uri == null || NOTIFICATION_SHOW_MEDIA_ON_QUICK_SETTINGS_URI.equals(uri)) {
+                mPreferencesHelper.updateMediaNotificationFilteringEnabled();
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index e06e01e..0e2ff75 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -20,8 +20,10 @@
 import static android.service.notification.NotificationListenerService.REASON_CLICK;
 import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.Person;
 import android.os.Bundle;
 import android.service.notification.NotificationListenerService;
@@ -346,7 +348,8 @@
                         == old.getSbn().getNotification().isGroupSummary())
                     && Objects.equals(r.getSbn().getNotification().category,
                         old.getSbn().getNotification().category)
-                    && (r.getImportance() == old.getImportance()));
+                    && (r.getImportance() == old.getImportance())
+                    && (getLoggingImportance(r) == getLoggingImportance(old)));
         }
 
         /**
@@ -413,5 +416,17 @@
 
     }
 
+    /**
+     * @param r NotificationRecord
+     * @return Logging importance of record, taking important conversation channels into account.
+     */
+    static int getLoggingImportance(@NonNull NotificationRecord r) {
+        final int importance = r.getImportance();
+        final NotificationChannel channel = r.getChannel();
+        if (channel == null) {
+            return importance;
+        }
+        return NotificationChannelLogger.getLoggingImportance(channel, importance);
+    }
 
 }
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index 2b8ee92..1a99fb0e 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -51,7 +51,8 @@
                 /* int32 style = 11 */ p.getStyle(),
                 /* int32 num_people = 12 */ p.getNumPeople(),
                 /* int32 position = 13 */ position,
-                /* android.stats.sysui.NotificationImportance importance = 14 */ r.getImportance(),
+                /* android.stats.sysui.NotificationImportance importance = 14 */
+                NotificationRecordLogger.getLoggingImportance(r),
                 /* int32 alerting = 15 */ buzzBeepBlink,
                 /* NotificationImportanceExplanation importance_source = 16 */
                 r.getImportanceExplanationCode(),
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index b047970..dd6a83b 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -134,6 +134,7 @@
     static final boolean DEFAULT_GLOBAL_ALLOW_BUBBLE = true;
     @VisibleForTesting
     static final int DEFAULT_BUBBLE_PREFERENCE = BUBBLE_PREFERENCE_NONE;
+    static final boolean DEFAULT_MEDIA_NOTIFICATION_FILTERING = true;
 
     /**
      * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
@@ -165,6 +166,7 @@
 
     private SparseBooleanArray mBadgingEnabled;
     private boolean mBubblesEnabledGlobally = DEFAULT_GLOBAL_ALLOW_BUBBLE;
+    private boolean mIsMediaNotificationFilteringEnabled = DEFAULT_MEDIA_NOTIFICATION_FILTERING;
     private boolean mAreChannelsBypassingDnd;
     private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
 
@@ -184,6 +186,7 @@
 
         updateBadgingEnabled();
         updateBubblesEnabled();
+        updateMediaNotificationFilteringEnabled();
         syncChannelsBypassingDnd(mContext.getUserId());
     }
 
@@ -838,6 +841,8 @@
                 // Apps are allowed to downgrade channel importance if the user has not changed any
                 // fields on this channel yet.
                 final int previousExistingImportance = existing.getImportance();
+                final int previousLoggingImportance =
+                        NotificationChannelLogger.getLoggingImportance(existing);
                 if (existing.getUserLockedFields() == 0 &&
                         channel.getImportance() < existing.getImportance()) {
                     existing.setImportance(channel.getImportance());
@@ -867,7 +872,7 @@
                 updateConfig();
                 if (needsPolicyFileChange && !wasUndeleted) {
                     mNotificationChannelLogger.logNotificationChannelModified(existing, uid, pkg,
-                            previousExistingImportance, false);
+                            previousLoggingImportance, false);
                 }
                 return needsPolicyFileChange;
             }
@@ -985,7 +990,7 @@
                 MetricsLogger.action(getChannelLog(updatedChannel, pkg)
                         .setSubtype(fromUser ? 1 : 0));
                 mNotificationChannelLogger.logNotificationChannelModified(updatedChannel, uid, pkg,
-                        channel.getImportance(), fromUser);
+                        NotificationChannelLogger.getLoggingImportance(channel), fromUser);
             }
 
             if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
@@ -2287,6 +2292,21 @@
         return mBubblesEnabledGlobally;
     }
 
+    /** Requests check of the feature setting for showing media notifications in quick settings. */
+    public void updateMediaNotificationFilteringEnabled() {
+        final boolean newValue = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1) > 0;
+        if (newValue != mIsMediaNotificationFilteringEnabled) {
+            mIsMediaNotificationFilteringEnabled = newValue;
+            updateConfig();
+        }
+    }
+
+    /** Returns true if the setting is enabled for showing media notifications in quick settings. */
+    public boolean isMediaNotificationFilteringEnabled() {
+        return mIsMediaNotificationFilteringEnabled;
+    }
+
     public void updateBadgingEnabled() {
         if (mBadgingEnabled == null) {
             mBadgingEnabled = new SparseBooleanArray();
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 7fc79e6..b2827ba 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -31,6 +31,8 @@
     boolean badgingEnabled(UserHandle userHandle);
     int getBubblePreference(String packageName, int uid);
     boolean bubblesEnabled();
+    /** Returns true when feature is enabled that shows media notifications in quick settings. */
+    boolean isMediaNotificationFilteringEnabled();
     boolean isGroupBlocked(String packageName, int uid, String groupId);
 
     Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 9203122..8a7702e 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -817,7 +817,7 @@
                 }
             }
 
-            if (!setting.pkg.getProtectedBroadcasts().isEmpty()) {
+            if (setting.pkg != null && !setting.pkg.getProtectedBroadcasts().isEmpty()) {
                 final String removingPackageName = setting.pkg.getPackageName();
                 final Set<String> protectedBroadcasts = mProtectedBroadcasts;
                 mProtectedBroadcasts = collectProtectedBroadcasts(settings, removingPackageName);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 30ce9ae..980697f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2992,7 +2992,7 @@
             t.traceEnd();
 
             t.traceBegin("read user settings");
-            mFirstBoot = !mSettings.readLPw(mUserManager.getUsers(false));
+            mFirstBoot = !mSettings.readLPw(mInjector.getUserManagerInternal().getUsers(false));
             t.traceEnd();
 
             // Clean up orphaned packages for which the code path doesn't exist
@@ -3435,7 +3435,7 @@
             // boot, then we need to initialize the default preferred apps across
             // all defined users.
             if (!mOnlyCore && (mPromoteSystemApps || mFirstBoot)) {
-                for (UserInfo user : mUserManager.getUsers(true)) {
+                for (UserInfo user : mInjector.getUserManagerInternal().getUsers(true)) {
                     mSettings.applyDefaultPreferredAppsLPw(user.id);
                     primeDomainVerificationsLPw(user.id);
                 }
@@ -22225,7 +22225,7 @@
         }
         for (String packageName : apkList) {
             setSystemAppHiddenUntilInstalled(packageName, true);
-            for (UserInfo user : mUserManager.getUsers(false)) {
+            for (UserInfo user : mInjector.getUserManagerInternal().getUsers(false)) {
                 setSystemAppInstallState(packageName, false, user.id);
             }
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 398ff4b..27924a6 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -751,13 +751,19 @@
     }
 
     public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
-        return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */ true);
+        return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */
+                true);
     }
 
     @Override
     public @NonNull List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying,
             boolean excludePreCreated) {
         checkManageOrCreateUsersPermission("query users");
+        return getUsersInternal(excludePartial, excludeDying, excludePreCreated);
+    }
+
+    private @NonNull List<UserInfo> getUsersInternal(boolean excludePartial, boolean excludeDying,
+            boolean excludePreCreated) {
         synchronized (mUsersLock) {
             ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
             final int userSize = mUsers.size();
@@ -5045,6 +5051,12 @@
         }
 
         @Override
+        public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
+            return UserManagerService.this.getUsersInternal(/*excludePartial= */ true,
+                    excludeDying, /* excludePreCreated= */ true);
+        }
+
+        @Override
         public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
             int state;
             synchronized (mUserStates) {
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index 8b6e9a4..2e5c0f4 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -510,7 +510,8 @@
             if (getPowerSaveModeChangedListenerPackage().isPresent()) {
                 intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)
                         .setPackage(getPowerSaveModeChangedListenerPackage().get())
-                        .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+                        .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+                                | Intent.FLAG_RECEIVER_FOREGROUND);
                 mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
             }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 61a315d..ead74f2 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4352,9 +4352,8 @@
      *         screenshot.
      */
     boolean shouldUseAppThemeSnapshot() {
-        return mDisablePreviewScreenshots || forAllWindows(w -> {
-                    return mWmService.isSecureLocked(w);
-                }, true /* topToBottom */);
+        return mDisablePreviewScreenshots || forAllWindows(WindowState::isSecureLocked,
+                true /* topToBottom */);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a1680dd..7374184 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3824,7 +3824,7 @@
             }
             userId = activity.mUserId;
         }
-        return !DevicePolicyCache.getInstance().getScreenCaptureDisabled(userId);
+        return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId, false);
     }
 
     @Override
@@ -5413,7 +5413,7 @@
         final boolean hideDialogsSet = Settings.Global.getInt(mContext.getContentResolver(),
                 HIDE_ERROR_DIALOGS, 0) != 0;
         mShowDialogs = inputMethodExists
-                && ActivityTaskManager.currentUiModeSupportsErrorDialogs(mContext)
+                && ActivityTaskManager.currentUiModeSupportsErrorDialogs(config)
                 && !hideDialogsSet;
     }
 
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index efcb558..6305fda 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -16,17 +16,19 @@
 
 package com.android.server.wm;
 
-import android.view.SurfaceControl;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 
-import java.util.HashMap;
+import java.util.Set;
 
 /**
- * Utility class for collecting and merging transactions from various sources asynchronously.
+ * Utility class for collecting WindowContainers that will merge transactions.
  * For example to use to synchronously resize all the children of a window container
  *   1. Open a new sync set, and pass the listener that will be invoked
  *        int id startSyncSet(TransactionReadyListener)
  *      the returned ID will be eventually passed to the TransactionReadyListener in combination
- *      with the prepared transaction. You also use it to refer to the operation in future steps.
+ *      with a set of WindowContainers that are ready, meaning onTransactionReady was called for
+ *      those WindowContainers. You also use it to refer to the operation in future steps.
  *   2. Ask each child to participate:
  *       addToSyncSet(int id, WindowContainer wc)
  *      if the child thinks it will be affected by a configuration change (a.k.a. has a visible
@@ -38,35 +40,37 @@
  *       setReady(int id)
  *   5. If there were no sub windows anywhere in the hierarchy to wait on, then
  *      transactionReady is immediately invoked, otherwise all the windows are poked
- *      to redraw and to deliver a buffer to WMS#finishDrawing.
- *      Once all this drawing is complete the combined transaction of all the buffers
- *      and pending transaction hierarchy changes will be delivered to the TransactionReadyListener
+ *      to redraw and to deliver a buffer to {@link WindowState#finishDrawing}.
+ *      Once all this drawing is complete the WindowContainer that's ready will be added to the
+ *      set of ready WindowContainers. When the final onTransactionReady is called, it will merge
+ *      the transactions of the all the WindowContainers and will be delivered to the
+ *      TransactionReadyListener
  */
 class BLASTSyncEngine {
     private static final String TAG = "BLASTSyncEngine";
 
     interface TransactionReadyListener {
-        void onTransactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction);
+        void onTransactionReady(int mSyncId, Set<WindowContainer> windowContainersReady);
     };
 
     // Holds state associated with a single synchronous set of operations.
     class SyncState implements TransactionReadyListener {
         int mSyncId;
-        SurfaceControl.Transaction mMergedTransaction;
         int mRemainingTransactions;
         TransactionReadyListener mListener;
         boolean mReady = false;
+        Set<WindowContainer> mWindowContainersReady = new ArraySet<>();
 
         private void tryFinish() {
             if (mRemainingTransactions == 0 && mReady) {
-                mListener.onTransactionReady(mSyncId, mMergedTransaction);
+                mListener.onTransactionReady(mSyncId, mWindowContainersReady);
                 mPendingSyncs.remove(mSyncId);
             }
         }
 
-        public void onTransactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) {
+        public void onTransactionReady(int mSyncId, Set<WindowContainer> windowContainersReady) {
             mRemainingTransactions--;
-            mMergedTransaction.merge(mergedTransaction);
+            mWindowContainersReady.addAll(windowContainersReady);
             tryFinish();
         }
 
@@ -86,14 +90,13 @@
         SyncState(TransactionReadyListener l, int id) {
             mListener = l;
             mSyncId = id;
-            mMergedTransaction = new SurfaceControl.Transaction();
             mRemainingTransactions = 0;
         }
     };
 
-    int mNextSyncId = 0;
+    private int mNextSyncId = 0;
 
-    final HashMap<Integer, SyncState> mPendingSyncs = new HashMap();
+    private final ArrayMap<Integer, SyncState> mPendingSyncs = new ArrayMap<>();
 
     BLASTSyncEngine() {
     }
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index b2fab9a..8260cb3 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -290,7 +290,7 @@
                 mDimmer.resetDimStates();
             }
 
-            if (mDimmer.updateDims(getSyncTransaction(), mTmpDimBoundsRect)) {
+            if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
                 scheduleAnimation();
             }
         }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 30070a3..5e36880 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3782,7 +3782,7 @@
     }
 
     boolean hasSecureWindowOnScreen() {
-        final WindowState win = getWindow(w -> w.isOnScreen() && mWmService.isSecureLocked(w));
+        final WindowState win = getWindow(w -> w.isOnScreen() && w.isSecureLocked());
         return win != null;
     }
 
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 3d7873a..c7b9106 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -111,15 +111,20 @@
             abortTransient();
         }
         mFocusedWin = focusedWin;
-        mStateController.onBarControlTargetChanged(getStatusControlTarget(focusedWin),
-                getFakeStatusControlTarget(focusedWin),
-                getNavControlTarget(focusedWin),
-                getFakeNavControlTarget(focusedWin));
+        boolean forceShowsSystemBarsForWindowingMode = forceShowsSystemBarsForWindowingMode();
+        InsetsControlTarget statusControlTarget = getStatusControlTarget(focusedWin,
+                forceShowsSystemBarsForWindowingMode);
+        InsetsControlTarget navControlTarget = getNavControlTarget(focusedWin,
+                forceShowsSystemBarsForWindowingMode);
+        mStateController.onBarControlTargetChanged(statusControlTarget,
+                getFakeControlTarget(focusedWin, statusControlTarget),
+                navControlTarget,
+                getFakeControlTarget(focusedWin, navControlTarget));
         if (ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL) {
             return;
         }
-        mStatusBar.updateVisibility(getStatusControlTarget(focusedWin), ITYPE_STATUS_BAR);
-        mNavBar.updateVisibility(getNavControlTarget(focusedWin), ITYPE_NAVIGATION_BAR);
+        mStatusBar.updateVisibility(statusControlTarget, ITYPE_STATUS_BAR);
+        mNavBar.updateVisibility(navControlTarget, ITYPE_NAVIGATION_BAR);
         mPolicy.updateHideNavInputEventReceiver();
     }
 
@@ -237,16 +242,13 @@
         updateBarControlTarget(mFocusedWin);
     }
 
-    private @Nullable InsetsControlTarget getFakeStatusControlTarget(
-            @Nullable WindowState focused) {
-        return getStatusControlTarget(focused) == mDummyControlTarget ? focused : null;
+    private @Nullable InsetsControlTarget getFakeControlTarget(@Nullable WindowState focused,
+            InsetsControlTarget realControlTarget) {
+        return realControlTarget == mDummyControlTarget ? focused : null;
     }
 
-    private @Nullable InsetsControlTarget getFakeNavControlTarget(@Nullable WindowState focused) {
-        return getNavControlTarget(focused) == mDummyControlTarget ? focused : null;
-    }
-
-    private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin) {
+    private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin,
+            boolean forceShowsSystemBarsForWindowingMode) {
         if (mShowingTransientTypes.indexOf(ITYPE_STATUS_BAR) != -1) {
             return mDummyControlTarget;
         }
@@ -254,7 +256,7 @@
             // Notification shade has control anyways, no reason to force anything.
             return focusedWin;
         }
-        if (forceShowsSystemBarsForWindowingMode()) {
+        if (forceShowsSystemBarsForWindowingMode) {
             // Status bar is forcibly shown for the windowing mode which is a steady state.
             // We don't want the client to control the status bar, and we will dispatch the real
             // visibility of status bar to the client.
@@ -274,7 +276,8 @@
         return focusedWin;
     }
 
-    private @Nullable InsetsControlTarget getNavControlTarget(@Nullable WindowState focusedWin) {
+    private @Nullable InsetsControlTarget getNavControlTarget(@Nullable WindowState focusedWin,
+            boolean forceShowsSystemBarsForWindowingMode) {
         if (mShowingTransientTypes.indexOf(ITYPE_NAVIGATION_BAR) != -1) {
             return mDummyControlTarget;
         }
@@ -282,7 +285,7 @@
             // Notification shade has control anyways, no reason to force anything.
             return focusedWin;
         }
-        if (forceShowsSystemBarsForWindowingMode()) {
+        if (forceShowsSystemBarsForWindowingMode) {
             // Navigation bar is forcibly shown for the windowing mode which is a steady state.
             // We don't want the client to control the navigation bar, and we will dispatch the real
             // visibility of navigation bar to the client.
@@ -384,7 +387,10 @@
         InsetsPolicyAnimationControlCallbacks mControlCallbacks;
 
         InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
-            super(show, false /* hasCallbacks */, types, false /* disable */);
+
+            super(show, false /* hasCallbacks */, types, false /* disable */,
+                    (int) (mDisplayContent.getDisplayMetrics().density * FLOATING_IME_BOTTOM_INSET
+                            + 0.5f));
             mFinishCallback = finishCallback;
             mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
         }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index bf9a784..e80d593 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -254,8 +254,9 @@
 
     void onInsetsModified(InsetsControlTarget windowState, InsetsState state) {
         boolean changed = false;
-        for (int i = state.getSourcesCount() - 1; i >= 0; i--) {
-            final InsetsSource source = state.sourceAt(i);
+        for (int i = 0; i < InsetsState.SIZE; i++) {
+            final InsetsSource source = state.peekSource(i);
+            if (source == null) continue;
             final InsetsSourceProvider provider = mProviders.get(source.getType());
             if (provider == null) {
                 continue;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 274f859..00959e8 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -662,10 +662,10 @@
         }
     }
 
-    void setSecureSurfaceState(int userId, boolean disabled) {
+    void setSecureSurfaceState(int userId) {
         forAllWindows((w) -> {
             if (w.mHasSurface && userId == w.mShowUserId) {
-                w.mWinAnimator.setSecureLocked(disabled);
+                w.mWinAnimator.setSecureLocked(w.isSecureLocked());
             }
         }, true /* traverseTopToBottom */);
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3cf64c4..fec4849 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3507,7 +3507,7 @@
 
         updateShadowsRadius(isFocused(), getSyncTransaction());
 
-        if (mDimmer.updateDims(getSyncTransaction(), mTmpDimBoundsRect)) {
+        if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
             scheduleAnimation();
         }
     }
@@ -3686,20 +3686,6 @@
             final int otherWindowingMode = other.getWindowingMode();
 
             if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
-                // In this case the home stack isn't resizeable even though we are in split-screen
-                // mode. We still want the primary splitscreen stack to be visible as there will be
-                // a slight hint of it in the status bar area above the non-resizeable home
-                // activity. In addition, if the fullscreen assistant is over primary splitscreen
-                // stack, the stack should still be visible in the background as long as the recents
-                // animation is running.
-                final int activityType = other.getActivityType();
-                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-                    if (activityType == ACTIVITY_TYPE_HOME
-                            || (activityType == ACTIVITY_TYPE_ASSISTANT
-                            && mWmService.getRecentsAnimationController() != null)) {
-                        break;
-                    }
-                }
                 if (other.isTranslucent(starting)) {
                     // Can be visible behind a translucent fullscreen stack.
                     gotTranslucentFullscreen = true;
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 429c421..83d739b 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -580,14 +580,16 @@
             // Apps and their containers are not allowed to specify an orientation while using
             // root tasks...except for the home stack if it is not resizable and currently
             // visible (top of) its root task.
-            if (mRootHomeTask != null && mRootHomeTask.isVisible()) {
-                final Task topMost = mRootHomeTask.getTopMostTask();
-                final boolean resizable = topMost != null && topMost.isResizeable();
-                if (!(resizable && mRootHomeTask.matchParentBounds())) {
-                    final int orientation = mRootHomeTask.getOrientation();
-                    if (orientation != SCREEN_ORIENTATION_UNSET) {
-                        return orientation;
-                    }
+            if (mRootHomeTask != null && mRootHomeTask.isVisible()
+                    && !mRootHomeTask.isResizeable()) {
+                // Manually nest one-level because because getOrientation() checks fillsParent()
+                // which checks that requestedOverrideBounds() is empty. However, in this case,
+                // it is not empty because it's been overridden to maintain the fullscreen size
+                // within a smaller split-root.
+                final Task topHomeTask = mRootHomeTask.getTopMostTask();
+                final int orientation = topHomeTask.getOrientation();
+                if (orientation != SCREEN_ORIENTATION_UNSET) {
+                    return orientation;
                 }
             }
             return SCREEN_ORIENTATION_UNSPECIFIED;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index dd08f42..3a1619b 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -93,6 +93,7 @@
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.LinkedList;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -179,6 +180,8 @@
      * Applied as part of the animation pass in "prepareSurfaces".
      */
     protected final SurfaceAnimator mSurfaceAnimator;
+    private boolean mAnyParentAnimating;
+
     final SurfaceFreezer mSurfaceFreezer;
     protected final WindowManagerService mWmService;
 
@@ -2460,21 +2463,16 @@
      */
     @Nullable
     WindowContainer getAnimatingContainer(int flags, int typesToCheck) {
-        int animationType = mSurfaceAnimator.getAnimationType();
-        if (mSurfaceAnimator.isAnimating() && (animationType & typesToCheck) > 0) {
-            return this;
-        }
-        if ((flags & TRANSITION) != 0 && isWaitingForTransitionStart()) {
+        if (isSelfAnimating(flags, typesToCheck)) {
             return this;
         }
         if ((flags & PARENTS) != 0) {
-            final WindowContainer parent = getParent();
-            if (parent != null) {
-                final WindowContainer wc = parent.getAnimatingContainer(
-                        flags & ~CHILDREN, typesToCheck);
-                if (wc != null) {
-                    return wc;
+            WindowContainer parent = getParent();
+            while (parent != null) {
+                if (parent.isSelfAnimating(flags, typesToCheck)) {
+                    return parent;
                 }
+                parent = parent.getParent();
             }
         }
         if ((flags & CHILDREN) != 0) {
@@ -2490,6 +2488,21 @@
     }
 
     /**
+     * Internal method only to be used during {@link #getAnimatingContainer(int, int)}.DO NOT CALL
+     * FROM OUTSIDE.
+     */
+    protected boolean isSelfAnimating(int flags, int typesToCheck) {
+        if (mSurfaceAnimator.isAnimating()
+                && (mSurfaceAnimator.getAnimationType() & typesToCheck) > 0) {
+            return true;
+        }
+        if ((flags & TRANSITION) != 0 && isWaitingForTransitionStart()) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * @deprecated Use {@link #getAnimatingContainer(int, int)} instead.
      */
     @Nullable
@@ -2673,11 +2686,13 @@
     }
 
     @Override
-    public void onTransactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) {
-        mergedTransaction.merge(mBLASTSyncTransaction);
-        mUsingBLASTSyncTransaction = false;
+    public void onTransactionReady(int mSyncId, Set<WindowContainer> windowContainersReady) {
+        if (mWaitingListener == null) {
+            return;
+        }
 
-        mWaitingListener.onTransactionReady(mWaitingSyncId, mergedTransaction);
+        windowContainersReady.add(this);
+        mWaitingListener.onTransactionReady(mWaitingSyncId, windowContainersReady);
 
         mWaitingListener = null;
         mWaitingSyncId = -1;
@@ -2728,4 +2743,9 @@
     boolean useBLASTSync() {
         return mUsingBLASTSyncTransaction;
     }
+
+    void mergeBlastSyncTransaction(Transaction t) {
+        t.merge(mBLASTSyncTransaction);
+        mUsingBLASTSyncTransaction = false;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 600d57c..58a8708 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -139,7 +139,6 @@
 import android.app.IActivityTaskManager;
 import android.app.IAssistDataReceiver;
 import android.app.WindowConfiguration;
-import android.app.admin.DevicePolicyCache;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -1886,16 +1885,6 @@
         }
     }
 
-    boolean isSecureLocked(WindowState w) {
-        if ((w.mAttrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) {
-            return true;
-        }
-        if (DevicePolicyCache.getInstance().getScreenCaptureDisabled(w.mShowUserId)) {
-            return true;
-        }
-        return false;
-    }
-
     /**
      * Set whether screen capture is disabled for all windows of a specific user from
      * the device policy cache.
@@ -1909,8 +1898,7 @@
 
         synchronized (mGlobalLock) {
             // Update secure surface for all windows belonging to this user.
-            mRoot.setSecureSurfaceState(userId,
-                    DevicePolicyCache.getInstance().getScreenCaptureDisabled(userId));
+            mRoot.setSecureSurfaceState(userId);
         }
     }
 
@@ -2260,7 +2248,7 @@
                     && (win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
             wallpaperMayMove |= (flagChanges & FLAG_SHOW_WALLPAPER) != 0;
             if ((flagChanges & FLAG_SECURE) != 0 && winAnimator.mSurfaceController != null) {
-                winAnimator.mSurfaceController.setSecure(isSecureLocked(win));
+                winAnimator.mSurfaceController.setSecure(win.isSecureLocked());
             }
 
             win.mRelayoutCalled = true;
@@ -2882,15 +2870,13 @@
                 aspectRatio);
     }
 
-    public void getStackBounds(int windowingMode, int activityType, Rect bounds) {
-        synchronized (mGlobalLock) {
-            final ActivityStack stack = mRoot.getStack(windowingMode, activityType);
-            if (stack != null) {
-                stack.getBounds(bounds);
-                return;
-            }
-            bounds.setEmpty();
+    void getStackBounds(int windowingMode, int activityType, Rect bounds) {
+        final ActivityStack stack = mRoot.getStack(windowingMode, activityType);
+        if (stack != null) {
+            stack.getBounds(bounds);
+            return;
         }
+        bounds.setEmpty();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a3faa86..fbc5afa 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -44,6 +44,7 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledLambda;
 
@@ -51,6 +52,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Server side implementation for the interface for organizing windows
@@ -142,7 +144,7 @@
                         // operations so we don't end up splitting effects between the WM
                         // pending transaction and the BLASTSync transaction.
                         if (syncId >= 0) {
-                            mBLASTSyncEngine.addToSyncSet(syncId, wc);
+                            addToSyncSet(syncId, wc);
                         }
 
                         int containerEffect = applyWindowContainerChange(wc, entry.getValue());
@@ -164,7 +166,7 @@
                             continue;
                         }
                         if (syncId >= 0) {
-                            mBLASTSyncEngine.addToSyncSet(syncId, wc);
+                            addToSyncSet(syncId, wc);
                         }
                         effects |= sanitizeAndApplyHierarchyOp(wc, hop);
                     }
@@ -396,21 +398,33 @@
         return mDisplayAreaOrganizerController;
     }
 
+    @VisibleForTesting
     int startSyncWithOrganizer(IWindowContainerTransactionCallback callback) {
         int id = mBLASTSyncEngine.startSyncSet(this);
         mTransactionCallbacksByPendingSyncId.put(id, callback);
         return id;
     }
 
+    @VisibleForTesting
     void setSyncReady(int id) {
         mBLASTSyncEngine.setReady(id);
     }
 
+    @VisibleForTesting
+    void addToSyncSet(int syncId, WindowContainer wc) {
+        mBLASTSyncEngine.addToSyncSet(syncId, wc);
+    }
+
     @Override
-    public void onTransactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) {
+    public void onTransactionReady(int mSyncId, Set<WindowContainer> windowContainersReady) {
         final IWindowContainerTransactionCallback callback =
                 mTransactionCallbacksByPendingSyncId.get(mSyncId);
 
+        SurfaceControl.Transaction mergedTransaction = new SurfaceControl.Transaction();
+        for (WindowContainer container : windowContainersReady) {
+            container.mergeBlastSyncTransaction(mergedTransaction);
+        }
+
         try {
             callback.onTransactionReady(mSyncId, mergedTransaction);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5fc519c..26a1fea 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -176,6 +176,7 @@
 import android.annotation.CallSuper;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyCache;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Matrix;
@@ -241,8 +242,10 @@
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Predicate;
 
 /** A window in the window manager. */
@@ -655,6 +658,15 @@
     private static final StringBuilder sTmpSB = new StringBuilder();
 
     /**
+     * Whether the next surfacePlacement call should notify that the blast sync is ready.
+     * This is set to true when {@link #finishDrawing(Transaction)} is called so
+     * {@link #onTransactionReady(int, Set)} is called after the next surfacePlacement. This allows
+     * Transactions to get flushed into the syncTransaction before notifying {@link BLASTSyncEngine}
+     * that this WindowState is ready.
+     */
+    private boolean mNotifyBlastOnSurfacePlacement;
+
+    /**
      * Compares two window sub-layers and returns -1 if the first is lesser than the second in terms
      * of z-order and 1 otherwise.
      */
@@ -705,6 +717,8 @@
 
     static final int BLAST_TIMEOUT_DURATION = 5000; /* milliseconds */
 
+    private final WindowProcessController mWpcForDisplayConfigChanges;
+
     /**
      * @return The insets state as requested by the client, i.e. the dispatched insets state
      *         for which the visibilities are overridden with what the client requested.
@@ -719,8 +733,9 @@
     void updateRequestedInsetsState(InsetsState state) {
 
         // Only update the sources the client is actually controlling.
-        for (int i = state.getSourcesCount() - 1; i >= 0; i--) {
-            final InsetsSource source = state.sourceAt(i);
+        for (int i = 0; i < InsetsState.SIZE; i++) {
+            final InsetsSource source = state.peekSource(i);
+            if (source == null) continue;
             mRequestedInsetsState.addSource(source);
         }
     }
@@ -872,6 +887,7 @@
             mSubLayer = 0;
             mInputWindowHandle = null;
             mWinAnimator = null;
+            mWpcForDisplayConfigChanges = null;
             return;
         }
         mDeathRecipient = deathRecipient;
@@ -927,6 +943,11 @@
             ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", this, parentWindow);
             parentWindow.addChild(this, sWindowSubLayerComparator);
         }
+
+        // System process or invalid process cannot register to display config change.
+        mWpcForDisplayConfigChanges = (s.mPid == MY_PID || s.mPid < 0)
+                ? null
+                : service.mAtmService.getProcessController(s.mPid, s.mUid);
     }
 
     void attach() {
@@ -1744,6 +1765,14 @@
                && mActivityRecord.getActivityType() == ACTIVITY_TYPE_DREAM;
     }
 
+    boolean isSecureLocked() {
+        if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+            return true;
+        }
+        return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId,
+                mOwnerCanAddInternalSystemWindow);
+    }
+
     /**
      * Whether this window's drawn state might affect the drawn states of the app token.
      *
@@ -3505,13 +3534,10 @@
     /** @return {@code true} if the process registered to a display as a config listener. */
     private boolean registeredForDisplayConfigChanges() {
         final WindowState parentWindow = getParentWindow();
-        final Session session = parentWindow != null ? parentWindow.mSession : mSession;
-        // System process or invalid process cannot register to display config change.
-        if (session.mPid == MY_PID || session.mPid < 0) return false;
-        WindowProcessController app =
-                mWmService.mAtmService.getProcessController(session.mPid, session.mUid);
-        if (app == null || !app.registeredForDisplayConfigChanges()) return false;
-        return true;
+        final WindowProcessController wpc = parentWindow != null
+                ? parentWindow.mWpcForDisplayConfigChanges
+                : mWpcForDisplayConfigChanges;
+        return wpc != null && wpc.registeredForDisplayConfigChanges();
     }
 
     void reportResized() {
@@ -5094,13 +5120,12 @@
         mWindowFrames.updateLastInsetValues();
     }
 
-    @Nullable
     @Override
-    WindowContainer<WindowState> getAnimatingContainer(int flags, int typesToCheck) {
+    protected boolean isSelfAnimating(int flags, int typesToCheck) {
         if (mControllableInsetProvider != null) {
-            return null;
+            return false;
         }
-        return super.getAnimatingContainer(flags, typesToCheck);
+        return super.isSelfAnimating(flags, typesToCheck);
     }
 
     void startAnimation(Animation anim) {
@@ -5290,10 +5315,10 @@
         return mWillReplaceWindow;
     }
 
-    private void applyDims(Dimmer dimmer) {
+    private void applyDims() {
         if (!mAnimatingExit && mAppDied) {
             mIsDimming = true;
-            dimmer.dimAbove(getSyncTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
+            getDimmer().dimAbove(getSyncTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
         } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0 && isVisibleNow() && !mHidden) {
             // Only show a dim behind when the following is satisfied:
             // 1. The window has the flag FLAG_DIM_BEHIND
@@ -5301,7 +5326,7 @@
             // 3. The WS is considered visible according to the isVisible() method
             // 4. The WS is not hidden.
             mIsDimming = true;
-            dimmer.dimBelow(getSyncTransaction(), this, mAttrs.dimAmount);
+            getDimmer().dimBelow(getSyncTransaction(), this, mAttrs.dimAmount);
         }
     }
 
@@ -5325,16 +5350,14 @@
 
     @Override
     void prepareSurfaces() {
-        final Dimmer dimmer = getDimmer();
         mIsDimming = false;
-        if (dimmer != null) {
-            applyDims(dimmer);
-        }
+        applyDims();
         updateSurfacePosition();
         // Send information to SufaceFlinger about the priority of the current window.
         updateFrameRateSelectionPriorityIfNeeded();
 
         mWinAnimator.prepareSurfaceLocked(true);
+        notifyBlastSyncTransaction();
         super.prepareSurfaces();
     }
 
@@ -5826,6 +5849,17 @@
             mBLASTSyncTransaction.merge(postDrawTransaction);
         }
 
+        mNotifyBlastOnSurfacePlacement = true;
+        return mWinAnimator.finishDrawingLocked(null);
+    }
+
+    @VisibleForTesting
+    void notifyBlastSyncTransaction() {
+        if (!mNotifyBlastOnSurfacePlacement || mWaitingListener == null) {
+            mNotifyBlastOnSurfacePlacement = false;
+            return;
+        }
+
         // If localSyncId is >0 then we are syncing with children and will
         // invoke transaction ready from our own #transactionReady callback
         // we just need to signal our side of the sync (setReady). But if we
@@ -5833,15 +5867,14 @@
         // be invoked and we need to invoke it ourself.
         if (mLocalSyncId >= 0) {
             mBLASTSyncEngine.setReady(mLocalSyncId);
-            return mWinAnimator.finishDrawingLocked(null);
+            return;
         }
 
-        mWaitingListener.onTransactionReady(mWaitingSyncId, mBLASTSyncTransaction);
-        mUsingBLASTSyncTransaction = false;
+        mWaitingListener.onTransactionReady(mWaitingSyncId,  Collections.singleton(this));
 
         mWaitingSyncId = 0;
         mWaitingListener = null;
-        return mWinAnimator.finishDrawingLocked(null);
+        mNotifyBlastOnSurfacePlacement = false;
     }
 
     private boolean requestResizeForBlastSync() {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 8115ac8..508d2d4 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -476,7 +476,7 @@
         int flags = SurfaceControl.HIDDEN;
         final WindowManager.LayoutParams attrs = w.mAttrs;
 
-        if (mService.isSecureLocked(w)) {
+        if (w.isSecureLocked()) {
             flags |= SurfaceControl.SECURE;
         }
 
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 09c0778..d47341f 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -29,6 +29,7 @@
         "com_android_server_connectivity_Vpn.cpp",
         "com_android_server_ConsumerIrService.cpp",
         "com_android_server_devicepolicy_CryptoTestHelper.cpp",
+        "com_android_server_gpu_GpuService.cpp",
         "com_android_server_HardwarePropertiesManagerService.cpp",
         "com_android_server_hdmi_HdmiCecController.cpp",
         "com_android_server_input_InputManagerService.cpp",
@@ -99,6 +100,7 @@
         "libcutils",
         "libcrypto",
         "liblog",
+        "libgraphicsenv",
         "libhardware",
         "libhardware_legacy",
         "libhidlbase",
diff --git a/services/core/jni/com_android_server_gpu_GpuService.cpp b/services/core/jni/com_android_server_gpu_GpuService.cpp
new file mode 100644
index 0000000..2359e80
--- /dev/null
+++ b/services/core/jni/com_android_server_gpu_GpuService.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "GpuService-JNI"
+
+#include <binder/IServiceManager.h>
+#include <graphicsenv/IGpuService.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/scoped_utf_chars.h>
+
+namespace {
+
+static android::sp<android::IGpuService> getGpuService() {
+    static const android::sp<android::IBinder> binder =
+            android::defaultServiceManager()->checkService(android::String16("gpu"));
+    if (!binder) {
+        ALOGE("Failed to get gpu service");
+        return nullptr;
+    }
+
+    return interface_cast<android::IGpuService>(binder);
+}
+
+void setUpdatableDriverPath_native(JNIEnv* env, jobject clazz, jstring jDriverPath) {
+    if (jDriverPath == nullptr) {
+        return;
+    }
+    const android::sp<android::IGpuService> gpuService = getGpuService();
+    if (!gpuService) {
+        return;
+    }
+    ScopedUtfChars driverPath(env, jDriverPath);
+    gpuService->setUpdatableDriverPath(driverPath.c_str());
+}
+
+static const JNINativeMethod gGpuServiceMethods[] = {
+        /* name, signature, funcPtr */
+        {"nSetUpdatableDriverPath", "(Ljava/lang/String;)V",
+         reinterpret_cast<void*>(setUpdatableDriverPath_native)},
+};
+
+const char* const kGpuServiceName = "com/android/server/gpu/GpuService";
+
+} // anonymous namespace
+
+namespace android {
+
+int register_android_server_GpuService(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, kGpuServiceName, gGpuServiceMethods,
+                                    NELEM(gGpuServiceMethods));
+}
+
+} /* namespace android */
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 8528b72..ec104ec 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -63,6 +63,7 @@
 int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
 int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env);
 int register_android_server_AdbDebuggingManager(JNIEnv* env);
+int register_android_server_GpuService(JNIEnv* env);
 int register_android_server_ActivityTriggerService(JNIEnv* env);
 };
 
@@ -120,6 +121,7 @@
     register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
     register_android_server_stats_pull_StatsPullAtomService(env);
     register_android_server_AdbDebuggingManager(env);
+    register_android_server_GpuService(env);
     register_android_server_ActivityTriggerService(env);
     return JNI_VERSION_1_4;
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
index f3a6935..d616ed3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
@@ -51,15 +51,15 @@
     }
 
     @Override
-    public boolean getScreenCaptureDisabled(int userHandle) {
+    public boolean isScreenCaptureAllowed(int userHandle, boolean ownerCanAddInternalSystemWindow) {
         synchronized (mLock) {
-            return mScreenCaptureDisabled.get(userHandle);
+            return !mScreenCaptureDisabled.get(userHandle) || ownerCanAddInternalSystemWindow;
         }
     }
 
-    public void setScreenCaptureDisabled(int userHandle, boolean disabled) {
+    public void setScreenCaptureAllowed(int userHandle, boolean allowed) {
         synchronized (mLock) {
-            mScreenCaptureDisabled.put(userHandle, disabled);
+            mScreenCaptureDisabled.put(userHandle, !allowed);
         }
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 401649a..c6b93d6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7990,7 +7990,7 @@
     }
 
     private void updateScreenCaptureDisabled(int userHandle, boolean disabled) {
-        mPolicyCache.setScreenCaptureDisabled(userHandle, disabled);
+        mPolicyCache.setScreenCaptureAllowed(userHandle, !disabled);
         mHandler.post(() -> {
             try {
                 mInjector.getIWindowManager().refreshScreenCaptureDisabled(userHandle);
diff --git a/services/net/java/android/net/ip/IpClientCallbacks.java b/services/net/java/android/net/ip/IpClientCallbacks.java
index b172c4b..b17fcaa 100644
--- a/services/net/java/android/net/ip/IpClientCallbacks.java
+++ b/services/net/java/android/net/ip/IpClientCallbacks.java
@@ -68,12 +68,13 @@
      */
     public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
         // In general callbacks would not use a parcelable directly (DhcpResultsParcelable), and
-        // would use a wrapper instead. But there are already two classes in the tree for DHCP
-        // information: DhcpInfo and DhcpResults, and each of them do not expose an appropriate API
-        // (they are bags of mutable fields and can't be changed because they are public API and
-        // @UnsupportedAppUsage). Adding a third class would cost more than the gain considering
-        // that the only client of this callback is WiFi, which will end up converting the results
-        // to DhcpInfo anyway.
+        // would use a wrapper instead, because of the lack of safety of stable parcelables. But
+        // there are already two classes in the tree for DHCP information: DhcpInfo and DhcpResults,
+        // and neither of them exposes an appropriate API (they are bags of mutable fields and can't
+        // be changed because they are public API and @UnsupportedAppUsage, being no better than the
+        // stable parcelable). Adding a third class would cost more than the gain considering that
+        // the only client of this callback is WiFi, which will end up converting the results to
+        // DhcpInfo anyway.
     }
 
     /**
diff --git a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk
index 211e064..bd871ae 100644
--- a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk
+++ b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk
Binary files differ
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 7d20da1..1a04d2f 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -516,7 +516,7 @@
         UsageEvents.Event ev = new UsageEvents.Event();
         ev.mPackage = packageName;
         ev.mEventType = eventType;
-        controller.reportEvent(ev, elapsedTime, USER_ID);
+        controller.reportEvent(ev, USER_ID);
     }
 
     private int getStandbyBucket(AppStandbyController controller, String packageName) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java
index c9c31bf..36ac5d5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java
@@ -32,7 +32,7 @@
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.graphics.drawable.Icon;
-
+import android.media.session.MediaSession;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -114,6 +114,31 @@
         return r;
     }
 
+    private NotificationRecord getNotificationRecordWithMedia(boolean excludeSession) {
+        NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_UNSPECIFIED);
+        channel.setShowBadge(/* showBadge */ true);
+        when(mConfig.getNotificationChannel(mPkg, mUid, "a", false)).thenReturn(channel);
+
+        Notification.MediaStyle style = new Notification.MediaStyle();
+        if (!excludeSession) {
+            MediaSession session = new MediaSession(getContext(), "BadgeExtractorTestSession");
+            style.setMediaSession(session.getSessionToken());
+        }
+
+        final Builder builder = new Builder(getContext())
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setDefaults(Notification.DEFAULT_SOUND)
+                .setStyle(style);
+
+        Notification n = builder.build();
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, mId, mTag, mUid,
+                mPid, n, mUser, null, System.currentTimeMillis());
+        NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
+        return r;
+    }
+
     //
     // Tests
     //
@@ -203,6 +228,66 @@
     }
 
     @Test
+    public void testHideMediaNotifOverridesYes() throws Exception {
+        BadgeExtractor extractor = new BadgeExtractor();
+        extractor.setConfig(mConfig);
+        when(mConfig.badgingEnabled(mUser)).thenReturn(true);
+        when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true);
+
+        when(mConfig.isMediaNotificationFilteringEnabled()).thenReturn(true);
+        NotificationRecord r = getNotificationRecordWithMedia(/* excludeSession */ false);
+
+        extractor.process(r);
+
+        assertFalse(r.canShowBadge());
+    }
+
+    @Test
+    public void testHideMediaNotifDisabledOverridesNo() throws Exception {
+        BadgeExtractor extractor = new BadgeExtractor();
+        extractor.setConfig(mConfig);
+        when(mConfig.badgingEnabled(mUser)).thenReturn(true);
+        when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true);
+
+        when(mConfig.isMediaNotificationFilteringEnabled()).thenReturn(false);
+        NotificationRecord r = getNotificationRecordWithMedia(/* excludeSession */ false);
+
+        extractor.process(r);
+
+        assertTrue(r.canShowBadge());
+    }
+
+    @Test
+    public void testHideMediaNotifNoSessionOverridesNo() throws Exception {
+        BadgeExtractor extractor = new BadgeExtractor();
+        extractor.setConfig(mConfig);
+        when(mConfig.badgingEnabled(mUser)).thenReturn(true);
+        when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true);
+
+        when(mConfig.isMediaNotificationFilteringEnabled()).thenReturn(true);
+        NotificationRecord r = getNotificationRecordWithMedia(/* excludeSession */ true);
+
+        extractor.process(r);
+
+        assertTrue(r.canShowBadge());
+    }
+
+    @Test
+    public void testHideMediaNotifNotMediaStyleOverridesNo() throws Exception {
+        BadgeExtractor extractor = new BadgeExtractor();
+        extractor.setConfig(mConfig);
+        when(mConfig.badgingEnabled(mUser)).thenReturn(true);
+        when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true);
+
+        when(mConfig.isMediaNotificationFilteringEnabled()).thenReturn(true);
+        NotificationRecord r = getNotificationRecord(true, IMPORTANCE_UNSPECIFIED);
+
+        extractor.process(r);
+
+        assertTrue(r.canShowBadge());
+    }
+
+    @Test
     public void testDndOverridesYes() {
         BadgeExtractor extractor = new BadgeExtractor();
         extractor.setConfig(mConfig);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 2e49929..5b0a7fb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2088,6 +2088,22 @@
     }
 
     @Test
+    public void testShowQSMediaOverrideTrue() {
+        Global.putInt(getContext().getContentResolver(),
+                Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1);
+        mHelper.updateMediaNotificationFilteringEnabled(); // would be called by settings observer
+        assertTrue(mHelper.isMediaNotificationFilteringEnabled());
+    }
+
+    @Test
+    public void testShowQSMediaOverrideFalse() {
+        Global.putInt(getContext().getContentResolver(),
+                Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 0);
+        mHelper.updateMediaNotificationFilteringEnabled(); // would be called by settings observer
+        assertFalse(mHelper.isMediaNotificationFilteringEnabled());
+    }
+
+    @Test
     public void testOnLocaleChanged_updatesDefaultChannels() throws Exception {
         String newLabel = "bananas!";
         final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG_N_MR1,
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 2af98d8..976ac23 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -87,10 +87,7 @@
                 .setWindow(statusBar, null, null);
         statusBar.setControllableInsetProvider(getController().getSourceProvider(ITYPE_STATUS_BAR));
         final InsetsState state = getController().getInsetsForDispatch(statusBar);
-        for (int i = state.getSourcesCount() - 1; i >= 0; i--) {
-            final InsetsSource source = state.sourceAt(i);
-            assertNotEquals(ITYPE_STATUS_BAR, source.getType());
-        }
+        assertNull(state.peekSource(ITYPE_STATUS_BAR));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 71dabc5..7ce0c1e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -67,6 +67,7 @@
 import android.view.Display;
 import android.view.SurfaceControl;
 import android.window.ITaskOrganizer;
+import android.window.IWindowContainerTransactionCallback;
 import android.window.WindowContainerTransaction;
 
 import androidx.test.filters.SmallTest;
@@ -728,7 +729,7 @@
         // We should be rejected from the second sync since we are already
         // in one.
         assertEquals(false, bse.addToSyncSet(id2, task));
-        w.finishDrawing(null);
+        finishAndNotifyDrawing(w);
         assertEquals(true, bse.addToSyncSet(id2, task));
         bse.setReady(id2);
     }
@@ -752,7 +753,7 @@
         // Since we have a window we have to wait for it to draw to finish sync.
         verify(transactionListener, never())
             .onTransactionReady(anyInt(), any());
-        w.finishDrawing(null);
+        finishAndNotifyDrawing(w);
         verify(transactionListener)
             .onTransactionReady(anyInt(), any());
     }
@@ -820,14 +821,14 @@
         int id = bse.startSyncSet(transactionListener);
         assertEquals(true, bse.addToSyncSet(id, task));
         bse.setReady(id);
-        w.finishDrawing(null);
+        finishAndNotifyDrawing(w);
 
         // Since we have a child window we still shouldn't be done.
         verify(transactionListener, never())
             .onTransactionReady(anyInt(), any());
         reset(transactionListener);
 
-        child.finishDrawing(null);
+        finishAndNotifyDrawing(child);
         // Ah finally! Done
         verify(transactionListener)
                 .onTransactionReady(anyInt(), any());
@@ -979,4 +980,42 @@
                 new IRequestFinishCallback.Default());
         verify(organizer, times(1)).onBackPressedOnTaskRoot(any());
     }
+
+    @Test
+    public void testBLASTCallbackWithMultipleWindows() throws Exception {
+        final ActivityStack stackController = createStack();
+        final Task task = createTask(stackController);
+        final ITaskOrganizer organizer = registerMockOrganizer();
+        final WindowState w1 = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window 1");
+        final WindowState w2 = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window 2");
+        makeWindowVisible(w1);
+        makeWindowVisible(w2);
+
+        IWindowContainerTransactionCallback mockCallback =
+                mock(IWindowContainerTransactionCallback.class);
+        int id = mWm.mAtmService.mWindowOrganizerController.startSyncWithOrganizer(mockCallback);
+
+        mWm.mAtmService.mWindowOrganizerController.addToSyncSet(id, task);
+        mWm.mAtmService.mWindowOrganizerController.setSyncReady(id);
+
+        // Since we have a window we have to wait for it to draw to finish sync.
+        verify(mockCallback, never()).onTransactionReady(anyInt(), any());
+        assertTrue(w1.useBLASTSync());
+        assertTrue(w2.useBLASTSync());
+        finishAndNotifyDrawing(w1);
+
+        // Even though one Window finished drawing, both windows should still be using blast sync
+        assertTrue(w1.useBLASTSync());
+        assertTrue(w2.useBLASTSync());
+
+        finishAndNotifyDrawing(w2);
+        verify(mockCallback).onTransactionReady(anyInt(), any());
+        assertFalse(w1.useBLASTSync());
+        assertFalse(w2.useBLASTSync());
+    }
+
+    private void finishAndNotifyDrawing(WindowState ws) {
+        ws.finishDrawing(null);
+        ws.notifyBlastSyncTransaction();
+    }
 }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index a98da95..321657d 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -681,6 +681,8 @@
             reportEventToAllUserId(event);
             flushToDiskLocked();
         }
+
+        mAppStandby.flushToDisk();
     }
 
     /**
@@ -806,8 +808,6 @@
                 return;
             }
 
-            final long elapsedRealtime = SystemClock.elapsedRealtime();
-
             switch (event.mEventType) {
                 case Event.ACTIVITY_RESUMED:
                     FrameworkStatsLog.write(
@@ -914,9 +914,9 @@
                 return; // user was stopped or removed
             }
             service.reportEvent(event);
-
-            mAppStandby.reportEvent(event, elapsedRealtime, userId);
         }
+
+        mAppStandby.reportEvent(event, userId);
     }
 
     /**
@@ -948,6 +948,7 @@
             reportEventToAllUserId(event);
             flushToDiskLocked();
         }
+        mAppStandby.flushToDisk();
     }
 
     /**
@@ -957,9 +958,9 @@
         synchronized (mLock) {
             Slog.i(TAG, "Removing user " + userId + " and all data.");
             mUserState.remove(userId);
-            mAppStandby.onUserRemoved(userId);
             mAppTimeLimit.onUserRemoved(userId);
         }
+        mAppStandby.onUserRemoved(userId);
         // Cancel any scheduled jobs for this user since the user is being removed.
         UsageStatsIdleService.cancelJob(getContext(), userId);
         UsageStatsIdleService.cancelUpdateMappingsJob(getContext());
@@ -1158,10 +1159,7 @@
             if (service != null) {
                 service.persistActiveStats();
             }
-            mAppStandby.flushToDisk(userId);
         }
-        mAppStandby.flushDurationsToDisk();
-
         mHandler.removeMessages(MSG_FLUSH_TO_DISK);
     }
 
@@ -1169,28 +1167,31 @@
      * Called by the Binder stub.
      */
     void dump(String[] args, PrintWriter pw) {
-        synchronized (mLock) {
-            IndentingPrintWriter idpw = new IndentingPrintWriter(pw, "  ");
+        IndentingPrintWriter idpw = new IndentingPrintWriter(pw, "  ");
 
-            boolean checkin = false;
-            boolean compact = false;
-            final ArrayList<String> pkgs = new ArrayList<>();
+        boolean checkin = false;
+        boolean compact = false;
+        final ArrayList<String> pkgs = new ArrayList<>();
 
-            if (args != null) {
-                for (int i = 0; i < args.length; i++) {
-                    String arg = args[i];
-                    if ("--checkin".equals(arg)) {
-                        checkin = true;
-                    } else if ("-c".equals(arg)) {
-                        compact = true;
-                    } else if ("flush".equals(arg)) {
+        if (args != null) {
+            for (int i = 0; i < args.length; i++) {
+                String arg = args[i];
+                if ("--checkin".equals(arg)) {
+                    checkin = true;
+                } else if ("-c".equals(arg)) {
+                    compact = true;
+                } else if ("flush".equals(arg)) {
+                    synchronized (mLock) {
                         flushToDiskLocked();
-                        pw.println("Flushed stats to disk");
-                        return;
-                    } else if ("is-app-standby-enabled".equals(arg)) {
-                        pw.println(mAppStandby.isAppIdleEnabled());
-                        return;
-                    } else if ("apptimelimit".equals(arg)) {
+                    }
+                    mAppStandby.flushToDisk();
+                    pw.println("Flushed stats to disk");
+                    return;
+                } else if ("is-app-standby-enabled".equals(arg)) {
+                    pw.println(mAppStandby.isAppIdleEnabled());
+                    return;
+                } else if ("apptimelimit".equals(arg)) {
+                    synchronized (mLock) {
                         if (i + 1 >= args.length) {
                             mAppTimeLimit.dump(null, pw);
                         } else {
@@ -1199,8 +1200,10 @@
                             mAppTimeLimit.dump(remainingArgs, pw);
                         }
                         return;
-                    } else if ("file".equals(arg)) {
-                        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+                    }
+                } else if ("file".equals(arg)) {
+                    final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+                    synchronized (mLock) {
                         if (i + 1 >= args.length) {
                             // dump everything for all users
                             final int numUsers = mUserState.size();
@@ -1224,8 +1227,10 @@
                             }
                         }
                         return;
-                    } else if ("database-info".equals(arg)) {
-                        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+                    }
+                } else if ("database-info".equals(arg)) {
+                    final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+                    synchronized (mLock) {
                         if (i + 1 >= args.length) {
                             // dump info for all users
                             final int numUsers = mUserState.size();
@@ -1247,34 +1252,43 @@
                             }
                         }
                         return;
-                    } else if ("appstandby".equals(arg)) {
-                        mAppStandby.dumpState(args, pw);
-                        return;
-                    } else if ("stats-directory".equals(arg)) {
-                        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+                    }
+                } else if ("appstandby".equals(arg)) {
+                    mAppStandby.dumpState(args, pw);
+                    return;
+                } else if ("stats-directory".equals(arg)) {
+                    final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+                    synchronized (mLock) {
                         final int userId = parseUserIdFromArgs(args, i, ipw);
                         if (userId != UserHandle.USER_NULL) {
                             ipw.println(new File(Environment.getDataSystemCeDirectory(userId),
                                     "usagestats").getAbsolutePath());
                         }
                         return;
-                    } else if ("mappings".equals(arg)) {
-                        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
-                        final int userId = parseUserIdFromArgs(args, i, ipw);
+                    }
+                } else if ("mappings".equals(arg)) {
+                    final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+                    final int userId = parseUserIdFromArgs(args, i, ipw);
+                    synchronized (mLock) {
                         if (userId != UserHandle.USER_NULL) {
                             mUserState.get(userId).dumpMappings(ipw);
                         }
                         return;
-                    } else if (arg != null && !arg.startsWith("-")) {
-                        // Anything else that doesn't start with '-' is a pkg to filter
-                        pkgs.add(arg);
                     }
+                } else if (arg != null && !arg.startsWith("-")) {
+                    // Anything else that doesn't start with '-' is a pkg to filter
+                    pkgs.add(arg);
                 }
             }
+        }
 
+        final int[] userIds;
+        synchronized (mLock) {
             final int userCount = mUserState.size();
+            userIds = new int[userCount];
             for (int i = 0; i < userCount; i++) {
-                int userId = mUserState.keyAt(i);
+                final int userId = mUserState.keyAt(i);
+                userIds[i] = userId;
                 idpw.printPair("user", userId);
                 idpw.println();
                 idpw.increaseIndent();
@@ -1286,21 +1300,22 @@
                         idpw.println();
                     }
                 }
-                mAppStandby.dumpUser(idpw, userId, pkgs);
                 idpw.decreaseIndent();
             }
 
-            if (CollectionUtils.isEmpty(pkgs)) {
-                pw.println();
-                mAppStandby.dumpState(args, pw);
-            }
-
             idpw.println();
             idpw.printPair("Usage Source", UsageStatsManager.usageSourceToString(mUsageSource));
             idpw.println();
 
             mAppTimeLimit.dump(null, pw);
         }
+
+        mAppStandby.dumpUsers(idpw, userIds, pkgs);
+
+        if (CollectionUtils.isEmpty(pkgs)) {
+            pw.println();
+            mAppStandby.dumpState(args, pw);
+        }
     }
 
     private int parseUserIdFromArgs(String[] args, int index, IndentingPrintWriter ipw) {
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 7b5afc5..fc9c9b3 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2983,4 +2983,29 @@
             Log.e(TAG, "setPremiumSmsPermission() RemoteException", e);
         }
     }
+
+    /**
+     * Reset all cell broadcast ranges. Previously enabled ranges will become invalid after this.
+     *
+     * @return {@code true} if succeeded, otherwise {@code false}.
+     *
+     * // TODO: Unhide the API in S.
+     * @hide
+     */
+    public boolean resetAllCellBroadcastRanges() {
+        boolean success = false;
+
+        try {
+            ISms iSms = getISmsService();
+            if (iSms != null) {
+                // If getSubscriptionId() returns INVALID or an inactive subscription, we will use
+                // the default phone internally.
+                success = iSms.resetAllCellBroadcastRanges(getSubscriptionId());
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return success;
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 89f811e..9ec3c67 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -563,4 +563,14 @@
      * @return capacity of ICC
      */
     int getSmsCapacityOnIccForSubscriber(int subId);
+
+    /**
+     * Reset all cell broadcast ranges. Previously enabled ranges will become invalid after this.
+     *
+     * @param subId Subscription index
+     * @return {@code true} if succeeded, otherwise {@code false}.
+     *
+     * @hide
+     */
+    boolean resetAllCellBroadcastRanges(int subId);
 }
diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
index f1182f7..c361d5b 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -212,4 +212,9 @@
     public int getSmsCapacityOnIccForSubscriber(int subId) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public boolean resetAllCellBroadcastRanges(int subId) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/tests/net/common/java/android/net/DhcpInfoTest.java b/tests/net/common/java/android/net/DhcpInfoTest.java
index bd5533f..4d45ad7 100644
--- a/tests/net/common/java/android/net/DhcpInfoTest.java
+++ b/tests/net/common/java/android/net/DhcpInfoTest.java
@@ -16,8 +16,7 @@
 
 package android.net;
 
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTL;
-
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTL;
 import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
 import static com.android.testutils.ParcelUtilsKt.parcelingRoundTrip;
 
diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt
index f0555e6..e253ae2 100644
--- a/wifi/jarjar-rules.txt
+++ b/wifi/jarjar-rules.txt
@@ -114,7 +114,6 @@
 ## used by both framework-wifi and service-wifi ##
 rule android.content.pm.BaseParceledListSlice* com.android.wifi.x.@0
 rule android.content.pm.ParceledListSlice* com.android.wifi.x.@0
-rule android.net.shared.Inet4AddressUtils* com.android.wifi.x.@0
 rule android.net.util.MacAddressUtils* com.android.wifi.x.@0
 rule android.net.util.nsd.DnsSdTxtRecord* com.android.wifi.x.@0
 rule android.os.HandlerExecutor* com.android.wifi.x.@0
@@ -123,3 +122,5 @@
 rule com.android.internal.util.AsyncService* com.android.wifi.x.@0
 rule com.android.internal.util.Preconditions* com.android.wifi.x.@0
 rule com.android.internal.util.Protocol* com.android.wifi.x.@0
+
+rule com.android.net.module.util.** com.android.wifi.x.@0
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 4b07944..2d89bbd 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -783,6 +783,10 @@
      * certificate when the config is saved and removing the certificate when
      * the config is removed.
      *
+     * Note: If no certificate is set for an Enterprise configuration, either by not calling this
+     * API (or the {@link #setCaCertificates(X509Certificate[])}, or by calling it with null, then
+     * the server certificate validation is skipped - which means that the connection is not secure.
+     *
      * @param cert X.509 CA certificate
      * @throws IllegalArgumentException if not a CA certificate
      */
@@ -822,6 +826,11 @@
      * certificates when the config is saved and removing the certificates when
      * the config is removed.
      *
+     * Note: If no certificates are set for an Enterprise configuration, either by not calling this
+     * API (or the {@link #setCaCertificate(X509Certificate)}, or by calling it with null, then the
+     * server certificate validation is skipped - which means that the
+     * connection is not secure.
+     *
      * @param certs X.509 CA certificates
      * @throws IllegalArgumentException if any of the provided certificates is
      *     not a CA certificate
@@ -873,6 +882,13 @@
      * like /etc/ssl/certs. If configured, these certificates are added to the
      * list of trusted CAs. ca_cert may also be included in that case, but it is
      * not required.
+     *
+     * Note: If no certificate path is set for an Enterprise configuration, either by not calling
+     * this API, or by calling it with null, and no certificate is set by
+     * {@link #setCaCertificate(X509Certificate)} or {@link #setCaCertificates(X509Certificate[])},
+     * then the server certificate validation is skipped - which means that the connection is not
+     * secure.
+     *
      * @param path The path for CA certificate files, or empty string to clear.
      * @hide
      */
@@ -882,7 +898,7 @@
     }
 
     /**
-     * Get the domain_suffix_match value. See setDomSuffixMatch.
+     * Get the ca_path directive from wpa_supplicant.
      * @return The path for CA certificate files, or an empty string if unset.
      * @hide
      */
@@ -1075,6 +1091,12 @@
     /**
      * Set alternate subject match. This is the substring to be matched against the
      * alternate subject of the authentication server certificate.
+     *
+     * Note: If no alternate subject is set for an Enterprise configuration, either by not calling
+     * this API, or by calling it with null, or not setting domain suffix match using the
+     * {@link #setDomainSuffixMatch(String)}, then the server certificate validation is incomplete -
+     * which means that the connection is not secure.
+     *
      * @param altSubjectMatch substring to be matched, for example
      *                     DNS:server.example.com;EMAIL:server@example.com
      */
@@ -1109,6 +1131,12 @@
      * ORed ogether.
      * <p>For example, domain_suffix_match=example.com would match test.example.com but would not
      * match test-example.com.
+     *
+     * Note: If no domain suffix is set for an Enterprise configuration, either by not calling this
+     * API, or by calling it with null, or not setting alternate subject match using the
+     * {@link #setAltSubjectMatch(String)}, then the server certificate
+     * validation is incomplete - which means that the connection is not secure.
+     *
      * @param domain The domain value
      */
     public void setDomainSuffixMatch(String domain) {
@@ -1411,10 +1439,19 @@
         if (mEapMethod != Eap.PEAP && mEapMethod != Eap.TLS && mEapMethod != Eap.TTLS) {
             return false;
         }
-        if (!mIsAppInstalledCaCert && TextUtils.isEmpty(getCaPath())) {
+        if (TextUtils.isEmpty(getAltSubjectMatch())
+                && TextUtils.isEmpty(getDomainSuffixMatch())) {
+            // Both subject and domain match are not set, it's insecure.
             return true;
         }
-        return TextUtils.isEmpty(getAltSubjectMatch()) && TextUtils.isEmpty(
-                getDomainSuffixMatch());
+        if (mIsAppInstalledCaCert) {
+            // CA certificate is installed by App, it's secure.
+            return false;
+        }
+        if (getCaCertificateAliases() != null) {
+            // CA certificate alias from keyStore is set, it's secure.
+            return false;
+        }
+        return TextUtils.isEmpty(getCaPath());
     }
 }
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 6412e4c..5209c42 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -22,12 +22,13 @@
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.net.NetworkInfo.DetailedState;
-import android.net.shared.Inet4AddressUtils;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.net.module.util.Inet4AddressUtils;
+
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
diff --git a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index 268645c..62485ec 100644
--- a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -565,6 +565,13 @@
         secureConfig.setCaCertificate(FakeKeys.CA_CERT0);
         secureConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
         assertFalse(secureConfig.isInsecure());
+
+        WifiEnterpriseConfig secureConfigWithCaAlias = new WifiEnterpriseConfig();
+        secureConfigWithCaAlias.setEapMethod(Eap.PEAP);
+        secureConfigWithCaAlias.setPhase2Method(Phase2.MSCHAPV2);
+        secureConfigWithCaAlias.setCaCertificateAliases(new String[]{"alias1", "alisa2"});
+        secureConfigWithCaAlias.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+        assertFalse(secureConfigWithCaAlias.isInsecure());
     }
 
 }