Merge "Fix content capture session id collision bug" into rvc-dev
diff --git a/apex/Android.bp b/apex/Android.bp
index c1715a00..371bd7f 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -63,9 +63,9 @@
     "--hide-annotation android.annotation.Hide " +
     "--hide InternalClasses " // com.android.* classes are okay in this interface
 
-// Defaults for mainline module provided java_sdk_library instances.
+// Defaults common to all mainline module java_sdk_library instances.
 java_defaults {
-    name: "framework-module-defaults",
+    name: "framework-module-common-defaults",
 
     // Additional annotations used for compiling both the implementation and the
     // stubs libraries.
@@ -88,14 +88,6 @@
         enabled: true,
         sdk_version: "module_current",
     },
-    system: {
-        enabled: true,
-        sdk_version: "module_current",
-    },
-    module_lib: {
-        enabled: true,
-        sdk_version: "module_current",
-    },
 
     // Configure framework module specific metalava options.
     droiddoc_options: [mainline_stubs_args],
@@ -127,6 +119,32 @@
     sdk_version: "module_current",
 }
 
+// Defaults for mainline module provided java_sdk_library instances.
+java_defaults {
+    name: "framework-module-defaults",
+    defaults: ["framework-module-common-defaults"],
+
+    system: {
+        enabled: true,
+        sdk_version: "module_current",
+    },
+    module_lib: {
+        enabled: true,
+        sdk_version: "module_current",
+    },
+}
+
+// Defaults for mainline module system server provided java_sdk_library instances.
+java_defaults {
+    name: "framework-system-server-module-defaults",
+    defaults: ["framework-module-common-defaults"],
+
+    system_server: {
+        enabled: true,
+        sdk_version: "module_current",
+    },
+}
+
 stubs_defaults {
     name: "framework-module-stubs-defaults-publicapi",
     args: mainline_framework_stubs_args,
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
index bcef8ce..ecc78ce 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
@@ -219,7 +219,7 @@
     public void dump(IndentingPrintWriter fout, boolean dumpFull) {
         if (dumpFull) {
             fout.println("algo: " + algorithm);
-            fout.println("digest: " + (dumpFull ? encodeDigest() : safeDigest()));
+            fout.println("digest: " + (dumpFull ? encodeDigest(digest) : safeDigest(digest)));
             fout.println("label: " + label);
             fout.println("expiryMs: " + expiryTimeMillis);
             fout.println("tag: " + tag);
@@ -243,19 +243,20 @@
     public String toString() {
         return "BlobHandle {"
                 + "algo:" + algorithm + ","
-                + "digest:" + safeDigest() + ","
+                + "digest:" + safeDigest(digest) + ","
                 + "label:" + label + ","
                 + "expiryMs:" + expiryTimeMillis + ","
                 + "tag:" + tag
                 + "}";
     }
 
-    private String safeDigest() {
-        final String digestStr = encodeDigest();
+    /** @hide */
+    public static String safeDigest(@NonNull byte[] digest) {
+        final String digestStr = encodeDigest(digest);
         return digestStr.substring(0, 2) + ".." + digestStr.substring(digestStr.length() - 2);
     }
 
-    private String encodeDigest() {
+    private static String encodeDigest(@NonNull byte[] digest) {
         return Base64.encodeToString(digest, Base64.NO_WRAP);
     }
 
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 0a5ada2..68c4bb6 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -478,7 +478,9 @@
                             ? Resources.ID_NULL
                             : getDescriptionResourceId(resourcesGetter.apply(leasee.packageName),
                                     leasee.descriptionResEntryName, leasee.packageName);
-                    leaseInfos.add(new LeaseInfo(leasee.packageName, leasee.expiryTimeMillis,
+                    final long expiryTimeMs = leasee.expiryTimeMillis == 0
+                            ? blobHandle.getExpiryTimeMillis() : leasee.expiryTimeMillis;
+                    leaseInfos.add(new LeaseInfo(leasee.packageName, expiryTimeMs,
                             descriptionResId, leasee.description));
                 });
                 blobInfos.add(new BlobInfo(blobMetadata.getBlobId(),
@@ -592,6 +594,7 @@
                         } else {
                             blob.addOrReplaceCommitter(existingCommitter);
                         }
+                        Slog.d(TAG, "Error committing the blob", e);
                         session.sendCommitCallbackResult(COMMIT_RESULT_ERROR);
                     }
                     getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
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 2b04583..22d5d11 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
@@ -28,7 +28,6 @@
 import static android.system.OsConstants.O_RDWR;
 import static android.system.OsConstants.SEEK_SET;
 
-import static com.android.server.blob.BlobStoreConfig.LOGV;
 import static com.android.server.blob.BlobStoreConfig.TAG;
 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_SESSION_CREATION_TIME;
 import static com.android.server.blob.BlobStoreConfig.hasSessionExpired;
@@ -423,9 +422,10 @@
                 mState = STATE_VERIFIED_VALID;
                 // Commit callback will be sent once the data is persisted.
             } else {
-                if (LOGV) {
-                    Slog.v(TAG, "Digest of the data didn't match the given BlobHandle.digest");
-                }
+                Slog.d(TAG, "Digest of the data ("
+                        + (mDataDigest == null ? "null" : BlobHandle.safeDigest(mDataDigest))
+                        + ") didn't match the given BlobHandle.digest ("
+                        + BlobHandle.safeDigest(mBlobHandle.digest) + ")");
                 mState = STATE_VERIFIED_INVALID;
                 sendCommitCallbackResult(COMMIT_RESULT_ERROR);
             }
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 7cbb98e..19f578e 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -203,6 +203,15 @@
         /** Returned by {@link #getDurationMicros()} when the duration is unknown. */
         public static final int UNKNOWN_DURATION = Integer.MIN_VALUE;
 
+        /**
+         * For each {@link #getSeekPoints} call, returns a single {@link SeekPoint} whose {@link
+         * SeekPoint#timeMicros} matches the requested timestamp, and whose {@link
+         * SeekPoint#position} is 0.
+         *
+         * @hide
+         */
+        public static final SeekMap DUMMY = new SeekMap(new DummyExoPlayerSeekMap());
+
         private final com.google.android.exoplayer2.extractor.SeekMap mExoPlayerSeekMap;
 
         private SeekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) {
@@ -795,6 +804,18 @@
      */
     public static final String PARAMETER_EAGERLY_EXPOSE_TRACKTYPE =
             "android.media.mediaparser.eagerlyExposeTrackType";
+    /**
+     * Sets whether a dummy {@link SeekMap} should be exposed before starting extraction. {@code
+     * boolean} expected. Default value is {@code false}.
+     *
+     * <p>For each {@link SeekMap#getSeekPoints} call, the dummy {@link SeekMap} returns a single
+     * {@link SeekPoint} whose {@link SeekPoint#timeMicros} matches the requested timestamp, and
+     * whose {@link SeekPoint#position} is 0.
+     *
+     * @hide
+     */
+    public static final String PARAMETER_EXPOSE_DUMMY_SEEKMAP =
+            "android.media.mediaparser.exposeDummySeekMap";
 
     // Private constants.
 
@@ -958,6 +979,7 @@
     private boolean mIncludeSupplementalData;
     private boolean mIgnoreTimestampOffset;
     private boolean mEagerlyExposeTrackType;
+    private boolean mExposeDummySeekMap;
     private String mParserName;
     private Extractor mExtractor;
     private ExtractorInput mExtractorInput;
@@ -1017,6 +1039,9 @@
         if (PARAMETER_EAGERLY_EXPOSE_TRACKTYPE.equals(parameterName)) {
             mEagerlyExposeTrackType = (boolean) value;
         }
+        if (PARAMETER_EXPOSE_DUMMY_SEEKMAP.equals(parameterName)) {
+            mExposeDummySeekMap = (boolean) value;
+        }
         mParserParameters.put(parameterName, value);
         return this;
     }
@@ -1078,11 +1103,10 @@
         }
         mExoDataReader.mInputReader = seekableInputReader;
 
-        // TODO: Apply parameters when creating extractor instances.
         if (mExtractor == null) {
+            mPendingExtractorInit = true;
             if (!mParserName.equals(PARSER_NAME_UNKNOWN)) {
                 mExtractor = createExtractor(mParserName);
-                mExtractor.init(new ExtractorOutputAdapter());
             } else {
                 for (String parserName : mParserNamesPool) {
                     Extractor extractor = createExtractor(parserName);
@@ -1107,9 +1131,18 @@
         }
 
         if (mPendingExtractorInit) {
+            if (mExposeDummySeekMap) {
+                // We propagate the dummy seek map before initializing the extractor, in case the
+                // extractor initialization outputs a seek map.
+                mOutputConsumer.onSeekMapFound(SeekMap.DUMMY);
+            }
             mExtractor.init(new ExtractorOutputAdapter());
             mPendingExtractorInit = false;
+            // We return after initialization to allow clients use any output information before
+            // starting actual extraction.
+            return true;
         }
+
         if (isPendingSeek()) {
             mExtractor.seek(mPendingSeekPosition, mPendingSeekTimeMicros);
             removePendingSeek();
@@ -1683,6 +1716,28 @@
         }
     }
 
+    private static final class DummyExoPlayerSeekMap
+            implements com.google.android.exoplayer2.extractor.SeekMap {
+
+        @Override
+        public boolean isSeekable() {
+            return true;
+        }
+
+        @Override
+        public long getDurationUs() {
+            return C.TIME_UNSET;
+        }
+
+        @Override
+        public SeekPoints getSeekPoints(long timeUs) {
+            com.google.android.exoplayer2.extractor.SeekPoint seekPoint =
+                    new com.google.android.exoplayer2.extractor.SeekPoint(
+                            timeUs, /* position= */ 0);
+            return new SeekPoints(seekPoint, seekPoint);
+        }
+    }
+
     /** Creates extractor instances. */
     private interface ExtractorFactory {
 
@@ -1923,6 +1978,7 @@
         expectedTypeByParameterName.put(PARAMETER_INCLUDE_SUPPLEMENTAL_DATA, Boolean.class);
         expectedTypeByParameterName.put(PARAMETER_IGNORE_TIMESTAMP_OFFSET, Boolean.class);
         expectedTypeByParameterName.put(PARAMETER_EAGERLY_EXPOSE_TRACKTYPE, Boolean.class);
+        expectedTypeByParameterName.put(PARAMETER_EXPOSE_DUMMY_SEEKMAP, Boolean.class);
         EXPECTED_TYPE_BY_PARAMETER_NAME = Collections.unmodifiableMap(expectedTypeByParameterName);
     }
 }
diff --git a/apex/permission/service/Android.bp b/apex/permission/service/Android.bp
index 6144976..7f31879 100644
--- a/apex/permission/service/Android.bp
+++ b/apex/permission/service/Android.bp
@@ -20,14 +20,26 @@
     path: "java",
 }
 
-java_library {
+java_sdk_library {
     name: "service-permission",
+    defaults: ["framework-system-server-module-defaults"],
+    visibility: [
+        "//frameworks/base/services/core",
+        "//frameworks/base/apex/permission",
+        "//frameworks/base/apex/permission/testing",
+        "//frameworks/base/apex/permission/tests",
+        "//frameworks/base/services/tests/mockingservicestests",
+    ],
+    impl_library_visibility: [
+        "//visibility:override",
+        "//frameworks/base/apex/permission/tests",
+        "//frameworks/base/services/tests/mockingservicestests",
+        "//frameworks/base/services/tests/servicestests",
+    ],
     srcs: [
         ":service-permission-sources",
     ],
-    sdk_version: "module_current",
     libs: [
-        "framework-annotations-lib",
         "framework-permission",
     ],
     apex_available: [
@@ -36,28 +48,3 @@
     ],
     installable: true,
 }
-
-droidstubs {
-    name: "service-permission-stubs-srcs",
-    srcs: [ ":service-permission-sources" ],
-    defaults: ["service-module-stubs-srcs-defaults"],
-    check_api: {
-        last_released: {
-            api_file: ":service-permission.api.system-server.latest",
-            removed_api_file: ":service-permission-removed.api.system-server.latest",
-        },
-        api_lint: {
-            new_since: ":service-permission.api.system-server.latest",
-        },
-    },
-    visibility: ["//visibility:private"],
-    dist: { dest: "service-permission.txt" },
-}
-
-java_library {
-    name: "service-permission-stubs",
-    srcs: [":service-permission-stubs-srcs"],
-    defaults: ["service-module-stubs-defaults"],
-    visibility: ["//frameworks/base/services/core"],
-    dist: { dest: "service-permission.jar" },
-}
diff --git a/apex/permission/service/api/current.txt b/apex/permission/service/api/current.txt
index c76cc32..d802177 100644
--- a/apex/permission/service/api/current.txt
+++ b/apex/permission/service/api/current.txt
@@ -1,46 +1 @@
 // Signature format: 2.0
-package com.android.permission.persistence {
-
-  public interface RuntimePermissionsPersistence {
-    method @NonNull public static com.android.permission.persistence.RuntimePermissionsPersistence createInstance();
-    method public void deleteForUser(@NonNull android.os.UserHandle);
-    method @Nullable public com.android.permission.persistence.RuntimePermissionsState readForUser(@NonNull android.os.UserHandle);
-    method public void writeForUser(@NonNull com.android.permission.persistence.RuntimePermissionsState, @NonNull android.os.UserHandle);
-  }
-
-  public final class RuntimePermissionsState {
-    ctor public RuntimePermissionsState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>);
-    method @Nullable public String getFingerprint();
-    method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getPackagePermissions();
-    method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getSharedUserPermissions();
-    method public int getVersion();
-    field public static final int NO_VERSION = -1; // 0xffffffff
-  }
-
-  public static final class RuntimePermissionsState.PermissionState {
-    ctor public RuntimePermissionsState.PermissionState(@NonNull String, boolean, int);
-    method public int getFlags();
-    method @NonNull public String getName();
-    method public boolean isGranted();
-  }
-
-}
-
-package com.android.role.persistence {
-
-  public interface RolesPersistence {
-    method @NonNull public static com.android.role.persistence.RolesPersistence createInstance();
-    method public void deleteForUser(@NonNull android.os.UserHandle);
-    method @Nullable public com.android.role.persistence.RolesState readForUser(@NonNull android.os.UserHandle);
-    method public void writeForUser(@NonNull com.android.role.persistence.RolesState, @NonNull android.os.UserHandle);
-  }
-
-  public final class RolesState {
-    ctor public RolesState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>>);
-    method @Nullable public String getPackagesHash();
-    method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getRoles();
-    method public int getVersion();
-  }
-
-}
-
diff --git a/apex/permission/service/api/system-server-current.txt b/apex/permission/service/api/system-server-current.txt
new file mode 100644
index 0000000..c76cc32
--- /dev/null
+++ b/apex/permission/service/api/system-server-current.txt
@@ -0,0 +1,46 @@
+// Signature format: 2.0
+package com.android.permission.persistence {
+
+  public interface RuntimePermissionsPersistence {
+    method @NonNull public static com.android.permission.persistence.RuntimePermissionsPersistence createInstance();
+    method public void deleteForUser(@NonNull android.os.UserHandle);
+    method @Nullable public com.android.permission.persistence.RuntimePermissionsState readForUser(@NonNull android.os.UserHandle);
+    method public void writeForUser(@NonNull com.android.permission.persistence.RuntimePermissionsState, @NonNull android.os.UserHandle);
+  }
+
+  public final class RuntimePermissionsState {
+    ctor public RuntimePermissionsState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>);
+    method @Nullable public String getFingerprint();
+    method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getPackagePermissions();
+    method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getSharedUserPermissions();
+    method public int getVersion();
+    field public static final int NO_VERSION = -1; // 0xffffffff
+  }
+
+  public static final class RuntimePermissionsState.PermissionState {
+    ctor public RuntimePermissionsState.PermissionState(@NonNull String, boolean, int);
+    method public int getFlags();
+    method @NonNull public String getName();
+    method public boolean isGranted();
+  }
+
+}
+
+package com.android.role.persistence {
+
+  public interface RolesPersistence {
+    method @NonNull public static com.android.role.persistence.RolesPersistence createInstance();
+    method public void deleteForUser(@NonNull android.os.UserHandle);
+    method @Nullable public com.android.role.persistence.RolesState readForUser(@NonNull android.os.UserHandle);
+    method public void writeForUser(@NonNull com.android.role.persistence.RolesState, @NonNull android.os.UserHandle);
+  }
+
+  public final class RolesState {
+    ctor public RolesState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>>);
+    method @Nullable public String getPackagesHash();
+    method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getRoles();
+    method public int getVersion();
+  }
+
+}
+
diff --git a/apex/permission/service/api/system-server-removed.txt b/apex/permission/service/api/system-server-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/apex/permission/service/api/system-server-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apex/permission/tests/Android.bp b/apex/permission/tests/Android.bp
index a1f7a54..271e328 100644
--- a/apex/permission/tests/Android.bp
+++ b/apex/permission/tests/Android.bp
@@ -19,7 +19,7 @@
         "java/**/*.kt",
     ],
     static_libs: [
-        "service-permission",
+        "service-permission.impl",
         "androidx.test.rules",
         "androidx.test.ext.junit",
         "androidx.test.ext.truth",
diff --git a/cmds/idmap2/idmap2/CommandUtils.cpp b/cmds/idmap2/idmap2/CommandUtils.cpp
index e058cd6e..8f5845b 100644
--- a/cmds/idmap2/idmap2/CommandUtils.cpp
+++ b/cmds/idmap2/idmap2/CommandUtils.cpp
@@ -29,7 +29,7 @@
 using android::idmap2::Unit;
 
 Result<Unit> Verify(const std::string& idmap_path, const std::string& target_path,
-                    const std::string& overlay_path, uint32_t fulfilled_policies,
+                    const std::string& overlay_path, PolicyBitmask fulfilled_policies,
                     bool enforce_overlayable) {
   SYSTRACE << "Verify " << idmap_path;
   std::ifstream fin(idmap_path);
diff --git a/cmds/idmap2/idmap2/CommandUtils.h b/cmds/idmap2/idmap2/CommandUtils.h
index 99605de..e717e04 100644
--- a/cmds/idmap2/idmap2/CommandUtils.h
+++ b/cmds/idmap2/idmap2/CommandUtils.h
@@ -17,12 +17,13 @@
 #ifndef IDMAP2_IDMAP2_COMMAND_UTILS_H_
 #define IDMAP2_IDMAP2_COMMAND_UTILS_H_
 
+#include "idmap2/PolicyUtils.h"
 #include "idmap2/Result.h"
 
 android::idmap2::Result<android::idmap2::Unit> Verify(const std::string& idmap_path,
                                                       const std::string& target_path,
                                                       const std::string& overlay_path,
-                                                      uint32_t fulfilled_policies,
+                                                      PolicyBitmask fulfilled_policies,
                                                       bool enforce_overlayable);
 
 #endif  // IDMAP2_IDMAP2_COMMAND_UTILS_H_
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 908d966..f95b73f 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -70,12 +70,12 @@
 }
 
 Status GetCrc(const std::string& apk_path, uint32_t* out_crc) {
-  const auto overlay_zip = ZipFile::Open(apk_path);
-  if (!overlay_zip) {
+  const auto zip = ZipFile::Open(apk_path);
+  if (!zip) {
     return error(StringPrintf("failed to open apk %s", apk_path.c_str()));
   }
 
-  const auto crc = GetPackageCrc(*overlay_zip);
+  const auto crc = GetPackageCrc(*zip);
   if (!crc) {
     return error(crc.GetErrorMessage());
   }
@@ -121,6 +121,7 @@
                                   bool* _aidl_return) {
   SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_apk_path;
   assert(_aidl_return);
+
   const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
   std::ifstream fin(idmap_path);
   const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin);
@@ -156,13 +157,10 @@
 
   auto up_to_date =
       header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), target_crc, overlay_crc,
-                         fulfilled_policies, enforce_overlayable);
-  if (!up_to_date) {
-    *_aidl_return = false;
-    return error(up_to_date.GetErrorMessage());
-  }
+                         ConvertAidlArgToPolicyBitmask(fulfilled_policies), enforce_overlayable);
 
-  return ok();
+  *_aidl_return = static_cast<bool>(up_to_date);
+  return *_aidl_return ? ok() : error(up_to_date.GetErrorMessage());
 }
 
 Status Idmap2Service::createIdmap(const std::string& target_apk_path,
diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h
index 8f25b8d..0f05592 100644
--- a/cmds/idmap2/include/idmap2/Idmap.h
+++ b/cmds/idmap2/include/idmap2/Idmap.h
@@ -141,9 +141,9 @@
   // field *must* be incremented. Because of this, we know that if the idmap
   // header is up-to-date the entire file is up-to-date.
   Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path,
-                          uint32_t fulfilled_policies, bool enforce_overlayable) const;
+                          PolicyBitmask fulfilled_policies, bool enforce_overlayable) const;
   Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path, uint32_t target_crc,
-                          uint32_t overlay_crc, uint32_t fulfilled_policies,
+                          uint32_t overlay_crc, PolicyBitmask fulfilled_policies,
                           bool enforce_overlayable) const;
 
   void accept(Visitor* v) const;
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 0bea217..23c25a7 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -115,8 +115,7 @@
   uint8_t enforce_overlayable;
   if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_) ||
       !Read32(stream, &idmap_header->target_crc_) || !Read32(stream, &idmap_header->overlay_crc_) ||
-      !Read32(stream, &idmap_header->fulfilled_policies_) ||
-      !Read8(stream, &enforce_overlayable) ||
+      !Read32(stream, &idmap_header->fulfilled_policies_) || !Read8(stream, &enforce_overlayable) ||
       !ReadString256(stream, idmap_header->target_path_) ||
       !ReadString256(stream, idmap_header->overlay_path_)) {
     return nullptr;
@@ -134,7 +133,8 @@
 }
 
 Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path,
-                                     uint32_t fulfilled_policies, bool enforce_overlayable) const {
+                                     PolicyBitmask fulfilled_policies,
+                                     bool enforce_overlayable) const {
   const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path);
   if (!target_zip) {
     return Error("failed to open target %s", target_path);
@@ -161,7 +161,8 @@
 
 Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path,
                                      uint32_t target_crc, uint32_t overlay_crc,
-                                     uint32_t fulfilled_policies, bool enforce_overlayable) const {
+                                     PolicyBitmask fulfilled_policies,
+                                     bool enforce_overlayable) const {
   if (magic_ != kIdmapMagic) {
     return Error("bad magic: actual 0x%08x, expected 0x%08x", magic_, kIdmapMagic);
   }
@@ -187,8 +188,7 @@
 
   if (enforce_overlayable != enforce_overlayable_) {
     return Error("bad enforce overlayable: idmap version %s, file system version %s",
-                 enforce_overlayable ? "true" : "false",
-                 enforce_overlayable_ ? "true" : "false");
+                 enforce_overlayable ? "true" : "false", enforce_overlayable_ ? "true" : "false");
   }
 
   if (strcmp(target_path, target_path_) != 0) {
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 095dd1e..e7b32c5 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -529,7 +529,9 @@
         VLOG("StatsdConfig valid");
     } else {
         // If there is any error in the config, don't use it.
+        // Remove any existing config with the same key.
         ALOGE("StatsdConfig NOT valid");
+        mMetricsManagers.erase(key);
     }
 }
 
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 7090bd4..23f2584 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -284,6 +284,7 @@
     FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize);
     FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast);
     FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
+    FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved);
     FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 052358b..1823bad 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -193,7 +193,7 @@
         BiometricAcquired biometric_acquired = 87 [(module) = "framework"];
         BiometricAuthenticated biometric_authenticated = 88 [(module) = "framework"];
         BiometricErrorOccurred biometric_error_occurred = 89 [(module) = "framework"];
-        UiEventReported ui_event_reported = 90 [(module) = "framework"];
+        UiEventReported ui_event_reported = 90 [(module) = "framework", (module) = "sysui"];
         BatteryHealthSnapshot battery_health_snapshot = 91;
         SlowIo slow_io = 92;
         BatteryCausedShutdown battery_caused_shutdown = 93;
@@ -419,7 +419,7 @@
         DisplayJankReported display_jank_reported = 257;
         AppStandbyBucketChanged app_standby_bucket_changed = 258 [(module) = "framework"];
         SharesheetStarted sharesheet_started = 259 [(module) = "framework"];
-        RankingSelected ranking_selected = 260 [(module) = "framework"];
+        RankingSelected ranking_selected = 260 [(module) = "framework", (module) = "sysui"];
         TvSettingsUIInteracted tvsettings_ui_interacted = 261 [(module) = "tv_settings"];
         LauncherStaticLayout launcher_snapshot = 262 [(module) = "sysui"];
         PackageInstallerV2Reported package_installer_v2_reported = 263 [(module) = "framework"];
@@ -566,6 +566,7 @@
         DataUsageBytesTransfer data_usage_bytes_transfer = 10082 [(module) = "framework"];
         BytesTransferByTagAndMetered bytes_transfer_by_tag_and_metered =
                 10083 [(module) = "framework"];
+        DNDModeProto dnd_mode_rule = 10084 [(module) = "framework"];
     }
 
     // DO NOT USE field numbers above 100,000 in AOSP.
@@ -6083,6 +6084,76 @@
 }
 
 /**
+ * Atom that represents an item in the list of Do Not Disturb rules, pulled from
+ * NotificationManagerService.java.
+ */
+message DNDModeProto {
+    enum Mode {
+        ROOT_CONFIG = -1;  // Used to distinguish the config (one per user) from the rules.
+        ZEN_MODE_OFF = 0;
+        ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1;
+        ZEN_MODE_NO_INTERRUPTIONS = 2;
+        ZEN_MODE_ALARMS = 3;
+    }
+    optional int32 user = 1;  // Android user ID (0, 1, 10, ...)
+    optional bool enabled = 2;  // true for ROOT_CONFIG if a manualRule is enabled
+    optional bool channels_bypassing = 3; // only valid for ROOT_CONFIG
+    optional Mode zen_mode = 4;
+    // id is one of the system default rule IDs, or empty
+    // May also be "MANUAL_RULE" to indicate app-activation of the manual rule.
+    optional string id = 5;
+    optional int32 uid = 6 [(is_uid) = true]; // currently only SYSTEM_UID or 0 for other
+    optional DNDPolicyProto policy = 7;
+}
+
+/**
+ * Atom that represents a Do Not Disturb policy, an optional detail proto for DNDModeProto.
+ */
+message DNDPolicyProto {
+    enum State {
+        STATE_UNSET = 0;
+        STATE_ALLOW = 1;
+        STATE_DISALLOW = 2;
+    }
+    optional State calls = 1;
+    optional State repeat_callers = 2;
+    optional State messages = 3;
+    optional State conversations = 4;
+    optional State reminders = 5;
+    optional State events = 6;
+    optional State alarms = 7;
+    optional State media = 8;
+    optional State system = 9;
+    optional State fullscreen = 10;
+    optional State lights = 11;
+    optional State peek = 12;
+    optional State status_bar = 13;
+    optional State badge = 14;
+    optional State ambient = 15;
+    optional State notification_list = 16;
+
+    enum PeopleType {
+        PEOPLE_UNSET = 0;
+        PEOPLE_ANYONE = 1;
+        PEOPLE_CONTACTS = 2;
+        PEOPLE_STARRED = 3;
+        PEOPLE_NONE = 4;
+    }
+
+    optional PeopleType allow_calls_from = 17;
+    optional PeopleType allow_messages_from = 18;
+
+    enum ConversationType {
+        CONV_UNSET = 0;
+        CONV_ANYONE = 1;
+        CONV_IMPORTANT = 2;
+        CONV_NONE = 3;
+    }
+
+    optional ConversationType allow_conversations_from = 19;
+}
+
+/**
  * Atom that contains a list of a package's channel group preferences, pulled from
  * NotificationManagerService.java.
  */
@@ -6333,6 +6404,16 @@
         SET_WHITELIST = 3;
         SET_DISABLED = 4;
         ON_USER_DATA_REMOVED = 5;
+        ON_DATA_SHARE_REQUEST = 6;
+        ACCEPT_DATA_SHARE_REQUEST = 7;
+        REJECT_DATA_SHARE_REQUEST = 8;
+        DATA_SHARE_WRITE_FINISHED = 9;
+        DATA_SHARE_ERROR_IOEXCEPTION = 10;
+        DATA_SHARE_ERROR_EMPTY_DATA = 11;
+        DATA_SHARE_ERROR_CLIENT_PIPE_FAIL = 12;
+        DATA_SHARE_ERROR_SERVICE_PIPE_FAIL = 13;
+        DATA_SHARE_ERROR_CONCURRENT_REQUEST = 14;
+        DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED = 15;
     }
     optional Event event = 1;
     // component/package of content capture service.
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 8587e145..7cac026 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -660,6 +660,8 @@
     FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats);
     FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit);
     FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats);
+
+    FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index e8c575a..7e825ef 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -361,8 +361,12 @@
         protoOutput->end(token);
     }
 
-    mLastReportTimeNs = dumpTimeStampNs;
-    mLastReportWallClockNs = getWallClockNs();
+    // Do not update the timestamps when data is not cleared to avoid timestamps from being
+    // misaligned.
+    if (erase_data) {
+        mLastReportTimeNs = dumpTimeStampNs;
+        mLastReportWallClockNs = getWallClockNs();
+    }
     VLOG("=========================Metric Reports End==========================");
 }
 
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 72decf2..acdffd3 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -198,6 +198,9 @@
   optional int64 condition = 3;
 
   repeated MetricConditionLink links = 4;
+
+  reserved 100;
+  reserved 101;
 }
 
 message CountMetric {
@@ -218,6 +221,9 @@
   repeated MetricStateLink state_link = 9;
 
   optional FieldMatcher dimensions_in_condition = 7 [deprecated = true];
+
+  reserved 100;
+  reserved 101;
 }
 
 message DurationMetric {
@@ -245,6 +251,9 @@
   optional TimeUnit bucket = 7;
 
   optional FieldMatcher dimensions_in_condition = 8 [deprecated = true];
+
+  reserved 100;
+  reserved 101;
 }
 
 message GaugeMetric {
@@ -281,6 +290,9 @@
   optional int32 max_pull_delay_sec = 13 [default = 30];
 
   optional bool split_bucket_for_app_upgrade = 14 [default = true];
+
+  reserved 100;
+  reserved 101;
 }
 
 message ValueMetric {
@@ -333,6 +345,9 @@
   optional bool split_bucket_for_app_upgrade = 17 [default = true];
 
   optional FieldMatcher dimensions_in_condition = 9 [deprecated = true];
+
+  reserved 100;
+  reserved 101;
 }
 
 message Alert {
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 076f327..13d977f 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -324,6 +324,41 @@
     EXPECT_EQ(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end());
 }
 
+TEST(StatsLogProcessorTest, InvalidConfigRemoved) {
+    // Setup simple config key corresponding to empty config.
+    StatsdStats::getInstance().reset();
+    sp<UidMap> m = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")},
+                 {String16("p1"), String16("p2")}, {String16(""), String16("")});
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> subscriberAlarmMonitor;
+    StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+                        [](const ConfigKey& key) { return true; },
+                        [](const int&, const vector<int64_t>&) {return true;});
+    ConfigKey key(3, 4);
+    StatsdConfig config = MakeConfig(true);
+    p.OnConfigUpdated(0, key, config);
+    EXPECT_EQ(1, p.mMetricsManagers.size());
+    EXPECT_NE(p.mMetricsManagers.find(key), p.mMetricsManagers.end());
+    // Cannot assert the size of mConfigStats since it is static and does not get cleared on reset.
+    EXPECT_NE(StatsdStats::getInstance().mConfigStats.end(),
+              StatsdStats::getInstance().mConfigStats.find(key));
+    EXPECT_EQ(0, StatsdStats::getInstance().mIceBox.size());
+
+    StatsdConfig invalidConfig = MakeConfig(true);
+    invalidConfig.clear_allowed_log_source();
+    p.OnConfigUpdated(0, key, invalidConfig);
+    EXPECT_EQ(0, p.mMetricsManagers.size());
+    // The current configs should not contain the invalid config.
+    EXPECT_EQ(StatsdStats::getInstance().mConfigStats.end(),
+              StatsdStats::getInstance().mConfigStats.find(key));
+    // Both "config" and "invalidConfig" should be in the icebox.
+    EXPECT_EQ(2, StatsdStats::getInstance().mIceBox.size());
+
+}
+
+
 TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) {
     int uid = 1111;
 
@@ -1796,6 +1831,48 @@
     EXPECT_EQ(field2, actualFieldValues->at(5).mValue.int_value);
 }
 
+TEST(StatsLogProcessorTest, TestDumpReportWithoutErasingDataDoesNotUpdateTimestamp) {
+    int hostUid = 20;
+    int isolatedUid = 30;
+    sp<MockUidMap> mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid});
+    ConfigKey key(3, 4);
+    StatsdConfig config = MakeConfig(false);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(1, 1, config, key, nullptr, 0, mockUidMap);
+    vector<uint8_t> bytes;
+
+    int64_t dumpTime1Ns = 1 * NS_PER_SEC;
+    processor->onDumpReport(key, dumpTime1Ns, false /* include_current_bucket */,
+            true /* erase_data */, ADB_DUMP, FAST, &bytes);
+
+    ConfigMetricsReportList output;
+    output.ParseFromArray(bytes.data(), bytes.size());
+    EXPECT_EQ(output.reports_size(), 1);
+    EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime1Ns);
+
+    int64_t dumpTime2Ns = 5 * NS_PER_SEC;
+    processor->onDumpReport(key, dumpTime2Ns, false /* include_current_bucket */,
+            false /* erase_data */, ADB_DUMP, FAST, &bytes);
+
+    // Check that the dump report without clearing data is successful.
+    output.ParseFromArray(bytes.data(), bytes.size());
+    EXPECT_EQ(output.reports_size(), 1);
+    EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime2Ns);
+    EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns);
+
+    int64_t dumpTime3Ns = 10 * NS_PER_SEC;
+    processor->onDumpReport(key, dumpTime3Ns, false /* include_current_bucket */,
+            true /* erase_data */, ADB_DUMP, FAST, &bytes);
+
+    // Check that the previous dump report that didn't clear data did not overwrite the first dump's
+    // timestamps.
+    output.ParseFromArray(bytes.data(), bytes.size());
+    EXPECT_EQ(output.reports_size(), 1);
+    EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime3Ns);
+    EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns);
+
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 97b704c..b608a34 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -601,20 +601,6 @@
     @TestApi
     public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2;
 
-    // TODO: remove this when development is done.
-    // These are debug flags used between OomAdjuster and AppOpsService to detect and report absence
-    // of the real flags.
-    /** @hide */
-    public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q = 1 << 27;
-    /** @hide */
-    public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q = 1 << 28;
-    /** @hide */
-    public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 29;
-    /** @hide */
-    public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 30;
-    /** @hide */
-    public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31;
-
     /** @hide all capabilities, the ORing of all flags in {@link ProcessCapability}*/
     @TestApi
     public static final int PROCESS_CAPABILITY_ALL = PROCESS_CAPABILITY_FOREGROUND_LOCATION
@@ -653,29 +639,9 @@
      */
     public static void printCapabilitiesFull(PrintWriter pw, @ProcessCapability int caps) {
         printCapabilitiesSummary(pw, caps);
-        if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) {
-            pw.print(" !L");
-        }
-        if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
-            pw.print(" !C");
-        }
-        if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q) != 0) {
-            pw.print(" !Cq");
-        }
-        if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) {
-            pw.print(" !M");
-        }
-        if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q) != 0) {
-            pw.print(" !Mq");
-        }
         final int remain = caps & ~(PROCESS_CAPABILITY_FOREGROUND_LOCATION
                 | PROCESS_CAPABILITY_FOREGROUND_CAMERA
-                | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
-                | DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION
-                | DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA
-                | DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q
-                | DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
-                | DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q);
+                | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE);
         if (remain != 0) {
             pw.print('+');
             pw.print(remain);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 2ce5521..a5965bc 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -398,13 +398,6 @@
      */
     public abstract boolean isUidCurrentlyInstrumented(int uid);
 
-    /**
-     * Show a debug toast, asking user to file a bugreport.
-     */
-    // TODO: remove this toast after feature development is done
-    public abstract void showWhileInUseDebugToast(int uid, int op, int mode);
-
-
     /** Is this a device owner app? */
     public abstract boolean isDeviceOwner(int uid);
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 108b9ee..812ca4a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -123,6 +123,7 @@
 import android.os.TelephonyServiceManager;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.permission.IPermissionManager;
 import android.provider.BlockedNumberContract;
 import android.provider.CalendarContract;
@@ -6816,7 +6817,11 @@
             throw ex.rethrowFromSystemServer();
         }
         if (holder == null) {
-            Slog.e(TAG, "Failed to find provider info for " + auth);
+            if (UserManager.get(c).isUserUnlocked(userId)) {
+                Slog.e(TAG, "Failed to find provider info for " + auth);
+            } else {
+                Slog.w(TAG, "Failed to find provider info for " + auth + " (user not unlocked)");
+            }
             return null;
         }
 
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index f881616..0e3f35e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7495,6 +7495,7 @@
             mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
             mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
             mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
+            mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON);
         }
 
         /**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c650643..1f90e40 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5868,12 +5868,22 @@
      * returned by {@link #getParentProfileInstance(ComponentName)}, where the caller must be
      * the profile owner of an organization-owned managed profile.
      * <p>
-     * If the caller is device owner or called on the parent instance, then the
-     * restriction will be applied to all users.
+     * If the caller is device owner, then the restriction will be applied to all users. If
+     * called on the parent instance, then the restriction will be applied on the personal profile.
      * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_DISABLE_CAMERA} to be able to call this method; if it has
      * not, a security exception will be thrown.
+     * <p>
+     * <b>Note</b>, this policy type is deprecated for legacy device admins since
+     * {@link android.os.Build.VERSION_CODES#Q}. On Android
+     * {@link android.os.Build.VERSION_CODES#Q} devices, legacy device admins targeting SDK
+     * version {@link android.os.Build.VERSION_CODES#P} or below can still call this API to
+     * disable camera, while legacy device admins targeting SDK version
+     * {@link android.os.Build.VERSION_CODES#Q} will receive a SecurityException. Starting
+     * from Android {@link android.os.Build.VERSION_CODES#R}, requests to disable camera from
+     * legacy device admins targeting SDK version {@link android.os.Build.VERSION_CODES#P} or
+     * below will be silently ignored.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param disabled Whether or not the camera should be disabled.
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 0f999ad..3522b1b 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -133,6 +133,7 @@
         /**
          * An event type denoting that a component was in the foreground when the stats
          * rolled-over. This is effectively treated as a {@link #ACTIVITY_PAUSED}.
+         * This event has a non-null packageName, and a null className.
          * {@hide}
          */
         public static final int END_OF_DAY = 3;
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index bd1ee27..1a694b3 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1243,14 +1243,7 @@
 
     private ParcelFileDescriptor getUriShortcutIconFd(@NonNull String packageName,
             @NonNull String shortcutId, int userId) {
-        String uri = null;
-        try {
-            uri = mService.getShortcutIconUri(mContext.getPackageName(), packageName, shortcutId,
-                    userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-
+        String uri = getShortcutIconUri(packageName, shortcutId, userId);
         if (uri == null) {
             return null;
         }
@@ -1262,6 +1255,18 @@
         }
     }
 
+    private String getShortcutIconUri(@NonNull String packageName,
+            @NonNull String shortcutId, int userId) {
+        String uri = null;
+        try {
+            uri = mService.getShortcutIconUri(mContext.getPackageName(), packageName, shortcutId,
+                    userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return uri;
+    }
+
     /**
      * Returns the icon for this shortcut, without any badging for the profile.
      *
@@ -1357,6 +1362,17 @@
                 } catch (IOException ignore) {
                 }
             }
+        } else if (shortcut.hasIconUri()) {
+            String uri = getShortcutIconUri(shortcut.getPackage(), shortcut.getId(),
+                    shortcut.getUserId());
+            if (uri == null) {
+                return null;
+            }
+            if (shortcut.hasAdaptiveBitmap()) {
+                return Icon.createWithAdaptiveBitmapContentUri(uri);
+            } else {
+                return Icon.createWithContentUri(uri);
+            }
         } else if (shortcut.hasIconResource()) {
             return Icon.createWithResource(shortcut.getPackage(), shortcut.getIconResourceId());
         } else {
diff --git a/core/java/android/content/pm/PackagePartitions.java b/core/java/android/content/pm/PackagePartitions.java
index 653b9ec..98a20f7 100644
--- a/core/java/android/content/pm/PackagePartitions.java
+++ b/core/java/android/content/pm/PackagePartitions.java
@@ -183,17 +183,20 @@
 
         /** Returns whether the partition contains the specified file in its priv-app folder. */
         public boolean containsPrivApp(@NonNull File scanFile) {
-            return FileUtils.contains(mPrivAppFolder.getFile(), canonicalize(scanFile));
+            return mPrivAppFolder != null
+                    && FileUtils.contains(mPrivAppFolder.getFile(), canonicalize(scanFile));
         }
 
         /** Returns whether the partition contains the specified file in its app folder. */
         public boolean containsApp(@NonNull File scanFile) {
-            return FileUtils.contains(mAppFolder.getFile(), canonicalize(scanFile));
+            return mAppFolder != null
+                    && FileUtils.contains(mAppFolder.getFile(), canonicalize(scanFile));
         }
 
         /** Returns whether the partition contains the specified file in its overlay folder. */
         public boolean containsOverlay(@NonNull File scanFile) {
-            return FileUtils.contains(mOverlayFolder.getFile(), canonicalize(scanFile));
+            return mOverlayFolder != null
+                    && FileUtils.contains(mOverlayFolder.getFile(), canonicalize(scanFile));
         }
     }
 
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 6bbc37a..7f834af 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -586,13 +586,27 @@
      * priority when accessing the camera, and this method will succeed even if the camera device is
      * in use by another camera API client. Any lower-priority application that loses control of the
      * camera in this way will receive an
-     * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback.</p>
+     * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback.
+     * Opening the same camera ID twice in the same application will similarly cause the
+     * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback
+     * being fired for the {@link CameraDevice} from the first open call and all ongoing tasks
+     * being droppped.</p>
      *
      * <p>Once the camera is successfully opened, {@link CameraDevice.StateCallback#onOpened} will
      * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up
      * for operation by calling {@link CameraDevice#createCaptureSession} and
      * {@link CameraDevice#createCaptureRequest}</p>
      *
+     * <p>Before API level 30, when the application tries to open multiple {@link CameraDevice} of
+     * different IDs and the device does not support opening such combination, either the
+     * {@link #openCamera} will fail and throw a {@link CameraAccessException} or one or more of
+     * already opened {@link CameraDevice} will be disconnected and receive
+     * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback. Which
+     * behavior will happen depends on the device implementation and can vary on different devices.
+     * Starting in API level 30, if the device does not support the combination of cameras being
+     * opened, it is guaranteed the {@link #openCamera} call will fail and none of existing
+     * {@link CameraDevice} will be disconnected.</p>
+     *
      * <!--
      * <p>Since the camera device will be opened asynchronously, any asynchronous operations done
      * on the returned CameraDevice instance will be queued up until the device startup has
@@ -618,7 +632,8 @@
      *             {@code null} to use the current thread's {@link android.os.Looper looper}.
      *
      * @throws CameraAccessException if the camera is disabled by device policy,
-     * has been disconnected, or is being used by a higher-priority camera API client.
+     * has been disconnected, is being used by a higher-priority camera API client, or the device
+     * has reached its maximal resource and cannot open this camera device.
      *
      * @throws IllegalArgumentException if cameraId or the callback was null,
      * or the cameraId does not match any currently or previously available
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 23c8602..6d49add 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1072,7 +1072,7 @@
      * @param lastFrameNumber last frame number returned from binder.
      * @param repeatingRequestTypes the repeating requests' types.
      */
-    private void checkEarlyTriggerSequenceCompleteLocked(
+    private void checkEarlyTriggerSequenceComplete(
             final int requestId, final long lastFrameNumber,
             final int[] repeatingRequestTypes) {
         // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request
@@ -1212,7 +1212,7 @@
 
             if (repeating) {
                 if (mRepeatingRequestId != REQUEST_ID_NONE) {
-                    checkEarlyTriggerSequenceCompleteLocked(mRepeatingRequestId,
+                    checkEarlyTriggerSequenceComplete(mRepeatingRequestId,
                             requestInfo.getLastFrameNumber(),
                             mRepeatingRequestTypes);
                 }
@@ -1269,7 +1269,7 @@
                     return;
                 }
 
-                checkEarlyTriggerSequenceCompleteLocked(requestId, lastFrameNumber, requestTypes);
+                checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber, requestTypes);
             }
         }
     }
@@ -1302,7 +1302,7 @@
 
             long lastFrameNumber = mRemoteDevice.flush();
             if (mRepeatingRequestId != REQUEST_ID_NONE) {
-                checkEarlyTriggerSequenceCompleteLocked(mRepeatingRequestId, lastFrameNumber,
+                checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber,
                         mRepeatingRequestTypes);
                 mRepeatingRequestId = REQUEST_ID_NONE;
                 mRepeatingRequestTypes = null;
@@ -1442,135 +1442,78 @@
         long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
         long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber();
         long completedZslStillFrameNumber = mFrameNumberTracker.getCompletedZslStillFrameNumber();
-
+        boolean isReprocess = false;
         Iterator<RequestLastFrameNumbersHolder> iter = mRequestLastFrameNumbersList.iterator();
         while (iter.hasNext()) {
             final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next();
+            boolean sequenceCompleted = false;
             final int requestId = requestLastFrameNumbers.getRequestId();
             final CaptureCallbackHolder holder;
-            if (mRemoteDevice == null) {
-                Log.w(TAG, "Camera closed while checking sequences");
-                return;
-            }
-            if (!requestLastFrameNumbers.isSequenceCompleted()) {
-                long lastRegularFrameNumber =
-                        requestLastFrameNumbers.getLastRegularFrameNumber();
-                long lastReprocessFrameNumber =
-                        requestLastFrameNumbers.getLastReprocessFrameNumber();
-                long lastZslStillFrameNumber =
-                        requestLastFrameNumbers.getLastZslStillFrameNumber();
-                if (lastRegularFrameNumber <= completedFrameNumber
-                        && lastReprocessFrameNumber <= completedReprocessFrameNumber
-                        && lastZslStillFrameNumber <= completedZslStillFrameNumber) {
-                    Log.v(TAG, String.format(
-                            "Mark requestId %d as completed, because lastRegularFrame %d "
-                            + "is <= %d, lastReprocessFrame %d is <= %d, "
-                            + "lastZslStillFrame %d is <= %d", requestId,
-                            lastRegularFrameNumber, completedFrameNumber,
-                            lastReprocessFrameNumber, completedReprocessFrameNumber,
-                            lastZslStillFrameNumber, completedZslStillFrameNumber));
-                    requestLastFrameNumbers.markSequenceCompleted();
+            synchronized(mInterfaceLock) {
+                if (mRemoteDevice == null) {
+                    Log.w(TAG, "Camera closed while checking sequences");
+                    return;
                 }
 
-                // Call onCaptureSequenceCompleted
                 int index = mCaptureCallbackMap.indexOfKey(requestId);
                 holder = (index >= 0) ?
                         mCaptureCallbackMap.valueAt(index) : null;
-                if (holder != null && requestLastFrameNumbers.isSequenceCompleted()) {
-                    Runnable resultDispatch = new Runnable() {
-                        @Override
-                        public void run() {
-                            if (!CameraDeviceImpl.this.isClosed()){
-                                if (DEBUG) {
-                                    Log.d(TAG, String.format(
-                                            "fire sequence complete for request %d",
-                                            requestId));
-                                }
-
-                                holder.getCallback().onCaptureSequenceCompleted(
-                                    CameraDeviceImpl.this,
-                                    requestId,
-                                    requestLastFrameNumbers.getLastFrameNumber());
-                            }
+                if (holder != null) {
+                    long lastRegularFrameNumber =
+                            requestLastFrameNumbers.getLastRegularFrameNumber();
+                    long lastReprocessFrameNumber =
+                            requestLastFrameNumbers.getLastReprocessFrameNumber();
+                    long lastZslStillFrameNumber =
+                            requestLastFrameNumbers.getLastZslStillFrameNumber();
+                    // check if it's okay to remove request from mCaptureCallbackMap
+                    if (lastRegularFrameNumber <= completedFrameNumber
+                            && lastReprocessFrameNumber <= completedReprocessFrameNumber
+                            && lastZslStillFrameNumber <= completedZslStillFrameNumber) {
+                        sequenceCompleted = true;
+                        mCaptureCallbackMap.removeAt(index);
+                        if (DEBUG) {
+                            Log.v(TAG, String.format(
+                                    "Remove holder for requestId %d, because lastRegularFrame %d "
+                                    + "is <= %d, lastReprocessFrame %d is <= %d, "
+                                    + "lastZslStillFrame %d is <= %d", requestId,
+                                    lastRegularFrameNumber, completedFrameNumber,
+                                    lastReprocessFrameNumber, completedReprocessFrameNumber,
+                                    lastZslStillFrameNumber, completedZslStillFrameNumber));
                         }
-                    };
-                    final long ident = Binder.clearCallingIdentity();
-                    try {
-                        holder.getExecutor().execute(resultDispatch);
-                    } finally {
-                        Binder.restoreCallingIdentity(ident);
                     }
                 }
             }
 
-            if (requestLastFrameNumbers.isSequenceCompleted() &&
-                    requestLastFrameNumbers.isInflightCompleted()) {
-                int index = mCaptureCallbackMap.indexOfKey(requestId);
-                if (index >= 0) {
-                    mCaptureCallbackMap.removeAt(index);
-                }
-                if (DEBUG) {
-                    Log.v(TAG, String.format(
-                            "Remove holder for requestId %d", requestId));
-                }
+            // If no callback is registered for this requestId or sequence completed, remove it
+            // from the frame number->request pair because it's not needed anymore.
+            if (holder == null || sequenceCompleted) {
                 iter.remove();
             }
-        }
-    }
 
-    private void removeCompletedCallbackHolderLocked(long lastCompletedRegularFrameNumber,
-            long lastCompletedReprocessFrameNumber, long lastCompletedZslStillFrameNumber) {
-        if (DEBUG) {
-            Log.v(TAG, String.format("remove completed callback holders for "
-                    + "lastCompletedRegularFrameNumber %d, "
-                    + "lastCompletedReprocessFrameNumber %d, "
-                    + "lastCompletedZslStillFrameNumber %d",
-                    lastCompletedRegularFrameNumber,
-                    lastCompletedReprocessFrameNumber,
-                    lastCompletedZslStillFrameNumber));
-        }
+            // Call onCaptureSequenceCompleted
+            if (sequenceCompleted) {
+                Runnable resultDispatch = new Runnable() {
+                    @Override
+                    public void run() {
+                        if (!CameraDeviceImpl.this.isClosed()){
+                            if (DEBUG) {
+                                Log.d(TAG, String.format(
+                                        "fire sequence complete for request %d",
+                                        requestId));
+                            }
 
-        Iterator<RequestLastFrameNumbersHolder> iter = mRequestLastFrameNumbersList.iterator();
-        while (iter.hasNext()) {
-            final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next();
-            final int requestId = requestLastFrameNumbers.getRequestId();
-            final CaptureCallbackHolder holder;
-            if (mRemoteDevice == null) {
-                Log.w(TAG, "Camera closed while removing completed callback holders");
-                return;
-            }
-
-            long lastRegularFrameNumber =
-                    requestLastFrameNumbers.getLastRegularFrameNumber();
-            long lastReprocessFrameNumber =
-                    requestLastFrameNumbers.getLastReprocessFrameNumber();
-            long lastZslStillFrameNumber =
-                    requestLastFrameNumbers.getLastZslStillFrameNumber();
-
-            if (lastRegularFrameNumber <= lastCompletedRegularFrameNumber
-                        && lastReprocessFrameNumber <= lastCompletedReprocessFrameNumber
-                        && lastZslStillFrameNumber <= lastCompletedZslStillFrameNumber) {
-
-                if (requestLastFrameNumbers.isSequenceCompleted()) {
-                    int index = mCaptureCallbackMap.indexOfKey(requestId);
-                    if (index >= 0) {
-                        mCaptureCallbackMap.removeAt(index);
+                            holder.getCallback().onCaptureSequenceCompleted(
+                                CameraDeviceImpl.this,
+                                requestId,
+                                requestLastFrameNumbers.getLastFrameNumber());
+                        }
                     }
-                    if (DEBUG) {
-                        Log.v(TAG, String.format(
-                                "Remove holder for requestId %d, because lastRegularFrame %d "
-                                + "is <= %d, lastReprocessFrame %d is <= %d, "
-                                + "lastZslStillFrame %d is <= %d", requestId,
-                                lastRegularFrameNumber, lastCompletedRegularFrameNumber,
-                                lastReprocessFrameNumber, lastCompletedReprocessFrameNumber,
-                                lastZslStillFrameNumber, lastCompletedZslStillFrameNumber));
-                    }
-                    iter.remove();
-                } else {
-                    if (DEBUG) {
-                        Log.v(TAG, "Sequence not yet completed for request id " + requestId);
-                    }
-                    requestLastFrameNumbers.markInflightCompleted();
+                };
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    holder.getExecutor().execute(resultDispatch);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
                 }
             }
         }
@@ -1759,12 +1702,6 @@
                 return;
             }
 
-            // Remove all capture callbacks now that device has gone to IDLE state.
-            removeCompletedCallbackHolderLocked(
-                    Long.MAX_VALUE, /*lastCompletedRegularFrameNumber*/
-                    Long.MAX_VALUE, /*lastCompletedReprocessFrameNumber*/
-                    Long.MAX_VALUE /*lastCompletedZslStillFrameNumber*/);
-
             if (!CameraDeviceImpl.this.mIdle) {
                 final long ident = Binder.clearCallingIdentity();
                 try {
@@ -1810,7 +1747,7 @@
                     return;
                 }
 
-                checkEarlyTriggerSequenceCompleteLocked(mRepeatingRequestId, lastFrameNumber,
+                checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber,
                         mRepeatingRequestTypes);
                 // Check if there is already a new repeating request
                 if (mRepeatingRequestId == repeatingRequestId) {
@@ -1829,18 +1766,9 @@
         public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
             int requestId = resultExtras.getRequestId();
             final long frameNumber = resultExtras.getFrameNumber();
-            final long lastCompletedRegularFrameNumber =
-                    resultExtras.getLastCompletedRegularFrameNumber();
-            final long lastCompletedReprocessFrameNumber =
-                    resultExtras.getLastCompletedReprocessFrameNumber();
-            final long lastCompletedZslFrameNumber =
-                    resultExtras.getLastCompletedZslFrameNumber();
 
             if (DEBUG) {
-                Log.d(TAG, "Capture started for id " + requestId + " frame number " + frameNumber
-                        + ": completedRegularFrameNumber " + lastCompletedRegularFrameNumber
-                        + ", completedReprocessFrameNUmber " + lastCompletedReprocessFrameNumber
-                        + ", completedZslFrameNumber " + lastCompletedZslFrameNumber);
+                Log.d(TAG, "Capture started for id " + requestId + " frame number " + frameNumber);
             }
             final CaptureCallbackHolder holder;
 
@@ -1856,12 +1784,6 @@
                     return;
                 }
 
-                // Check if it's okay to remove completed callbacks from mCaptureCallbackMap.
-                // A callback is completed if the corresponding inflight request has been removed
-                // from the inflight queue in cameraservice.
-                removeCompletedCallbackHolderLocked(lastCompletedRegularFrameNumber,
-                        lastCompletedReprocessFrameNumber, lastCompletedZslFrameNumber);
-
                 // Get the callback for this frame ID, if there is one
                 holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
 
diff --git a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
index 413caf5..1d9d644 100644
--- a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
@@ -182,12 +182,6 @@
                     return;
                 }
 
-                // Remove all capture callbacks now that device has gone to IDLE state.
-                removeCompletedCallbackHolderLocked(
-                        Long.MAX_VALUE, /*lastCompletedRegularFrameNumber*/
-                        Long.MAX_VALUE, /*lastCompletedReprocessFrameNumber*/
-                        Long.MAX_VALUE /*lastCompletedZslStillFrameNumber*/);
-
                 Runnable idleDispatch = new Runnable() {
                     @Override
                     public void run() {
@@ -210,22 +204,10 @@
         public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
             int requestId = resultExtras.getRequestId();
             final long frameNumber = resultExtras.getFrameNumber();
-            final long lastCompletedRegularFrameNumber =
-                    resultExtras.getLastCompletedRegularFrameNumber();
-            final long lastCompletedReprocessFrameNumber =
-                    resultExtras.getLastCompletedReprocessFrameNumber();
-            final long lastCompletedZslFrameNumber =
-                    resultExtras.getLastCompletedZslFrameNumber();
 
             final CaptureCallbackHolder holder;
 
             synchronized(mInterfaceLock) {
-                // Check if it's okay to remove completed callbacks from mCaptureCallbackMap.
-                // A callback is completed if the corresponding inflight request has been removed
-                // from the inflight queue in cameraservice.
-                removeCompletedCallbackHolderLocked(lastCompletedRegularFrameNumber,
-                        lastCompletedReprocessFrameNumber, lastCompletedZslFrameNumber);
-
                 // Get the callback for this frame ID, if there is one
                 holder = CameraOfflineSessionImpl.this.mCaptureCallbackMap.get(requestId);
 
@@ -619,61 +601,6 @@
         }
     }
 
-    private void removeCompletedCallbackHolderLocked(long lastCompletedRegularFrameNumber,
-            long lastCompletedReprocessFrameNumber, long lastCompletedZslStillFrameNumber) {
-        if (DEBUG) {
-            Log.v(TAG, String.format("remove completed callback holders for "
-                    + "lastCompletedRegularFrameNumber %d, "
-                    + "lastCompletedReprocessFrameNumber %d, "
-                    + "lastCompletedZslStillFrameNumber %d",
-                    lastCompletedRegularFrameNumber,
-                    lastCompletedReprocessFrameNumber,
-                    lastCompletedZslStillFrameNumber));
-        }
-
-        boolean isReprocess = false;
-        Iterator<RequestLastFrameNumbersHolder> iter =
-                mOfflineRequestLastFrameNumbersList.iterator();
-        while (iter.hasNext()) {
-            final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next();
-            final int requestId = requestLastFrameNumbers.getRequestId();
-            final CaptureCallbackHolder holder;
-
-            int index = mCaptureCallbackMap.indexOfKey(requestId);
-            holder = (index >= 0) ?
-                    mCaptureCallbackMap.valueAt(index) : null;
-            if (holder != null) {
-                long lastRegularFrameNumber =
-                        requestLastFrameNumbers.getLastRegularFrameNumber();
-                long lastReprocessFrameNumber =
-                        requestLastFrameNumbers.getLastReprocessFrameNumber();
-                long lastZslStillFrameNumber =
-                        requestLastFrameNumbers.getLastZslStillFrameNumber();
-                if (lastRegularFrameNumber <= lastCompletedRegularFrameNumber
-                        && lastReprocessFrameNumber <= lastCompletedReprocessFrameNumber
-                        && lastZslStillFrameNumber <= lastCompletedZslStillFrameNumber) {
-                    if (requestLastFrameNumbers.isSequenceCompleted()) {
-                        mCaptureCallbackMap.removeAt(index);
-                        if (DEBUG) {
-                            Log.v(TAG, String.format(
-                                    "Remove holder for requestId %d, because lastRegularFrame %d "
-                                    + "is <= %d, lastReprocessFrame %d is <= %d, "
-                                    + "lastZslStillFrame %d is <= %d", requestId,
-                                    lastRegularFrameNumber, lastCompletedRegularFrameNumber,
-                                    lastReprocessFrameNumber, lastCompletedReprocessFrameNumber,
-                                    lastZslStillFrameNumber, lastCompletedZslStillFrameNumber));
-                        }
-
-                        iter.remove();
-                    } else {
-                        Log.e(TAG, "Sequence not yet completed for request id " + requestId);
-                        continue;
-                    }
-                }
-            }
-        }
-    }
-
     public void notifyFailedSwitch() {
         synchronized(mInterfaceLock) {
             Runnable switchFailDispatch = new Runnable() {
diff --git a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java
index 5d9da73..1ff5bd5 100644
--- a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java
+++ b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java
@@ -30,9 +30,6 @@
     private int partialResultCount;
     private int errorStreamId;
     private String errorPhysicalCameraId;
-    private long lastCompletedRegularFrameNumber;
-    private long lastCompletedReprocessFrameNumber;
-    private long lastCompletedZslFrameNumber;
 
     public static final @android.annotation.NonNull Parcelable.Creator<CaptureResultExtras> CREATOR =
             new Parcelable.Creator<CaptureResultExtras>() {
@@ -54,9 +51,7 @@
     public CaptureResultExtras(int requestId, int subsequenceId, int afTriggerId,
                                int precaptureTriggerId, long frameNumber,
                                int partialResultCount, int errorStreamId,
-                               String errorPhysicalCameraId, long lastCompletedRegularFrameNumber,
-                               long lastCompletedReprocessFrameNumber,
-                               long lastCompletedZslFrameNumber) {
+                               String errorPhysicalCameraId) {
         this.requestId = requestId;
         this.subsequenceId = subsequenceId;
         this.afTriggerId = afTriggerId;
@@ -65,9 +60,6 @@
         this.partialResultCount = partialResultCount;
         this.errorStreamId = errorStreamId;
         this.errorPhysicalCameraId = errorPhysicalCameraId;
-        this.lastCompletedRegularFrameNumber = lastCompletedRegularFrameNumber;
-        this.lastCompletedReprocessFrameNumber = lastCompletedReprocessFrameNumber;
-        this.lastCompletedZslFrameNumber = lastCompletedZslFrameNumber;
     }
 
     @Override
@@ -90,9 +82,6 @@
         } else {
             dest.writeBoolean(false);
         }
-        dest.writeLong(lastCompletedRegularFrameNumber);
-        dest.writeLong(lastCompletedReprocessFrameNumber);
-        dest.writeLong(lastCompletedZslFrameNumber);
     }
 
     public void readFromParcel(Parcel in) {
@@ -107,9 +96,6 @@
         if (errorPhysicalCameraIdPresent) {
             errorPhysicalCameraId = in.readString();
         }
-        lastCompletedRegularFrameNumber = in.readLong();
-        lastCompletedReprocessFrameNumber = in.readLong();
-        lastCompletedZslFrameNumber = in.readLong();
     }
 
     public String getErrorPhysicalCameraId() {
@@ -143,16 +129,4 @@
     public int getErrorStreamId() {
         return errorStreamId;
     }
-
-    public long getLastCompletedRegularFrameNumber() {
-        return lastCompletedRegularFrameNumber;
-    }
-
-    public long getLastCompletedReprocessFrameNumber() {
-        return lastCompletedReprocessFrameNumber;
-    }
-
-    public long getLastCompletedZslFrameNumber() {
-        return lastCompletedZslFrameNumber;
-    }
 }
diff --git a/core/java/android/hardware/camera2/impl/RequestLastFrameNumbersHolder.java b/core/java/android/hardware/camera2/impl/RequestLastFrameNumbersHolder.java
index 0ee4ebc..bd1df9e 100644
--- a/core/java/android/hardware/camera2/impl/RequestLastFrameNumbersHolder.java
+++ b/core/java/android/hardware/camera2/impl/RequestLastFrameNumbersHolder.java
@@ -38,10 +38,6 @@
     // The last ZSL still capture frame number for this request ID. It's
     // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no zsl request.
     private final long mLastZslStillFrameNumber;
-    // Whether the sequence is completed. (only consider capture result)
-    private boolean mSequenceCompleted;
-    // Whether the inflight request is completed. (consider result, buffers, and notifies)
-    private boolean mInflightCompleted;
 
     /**
      * Create a request-last-frame-numbers holder with a list of requests, request ID, and
@@ -93,8 +89,6 @@
         mLastReprocessFrameNumber = lastReprocessFrameNumber;
         mLastZslStillFrameNumber = lastZslStillFrameNumber;
         mRequestId = requestInfo.getRequestId();
-        mSequenceCompleted = false;
-        mInflightCompleted = false;
     }
 
     /**
@@ -143,8 +137,6 @@
         mLastZslStillFrameNumber = lastZslStillFrameNumber;
         mLastReprocessFrameNumber = CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED;
         mRequestId = requestId;
-        mSequenceCompleted = false;
-        mInflightCompleted = false;
     }
 
     /**
@@ -185,34 +177,5 @@
     public int getRequestId() {
         return mRequestId;
     }
-
-    /**
-     * Return whether the capture sequence is completed.
-     */
-    public boolean isSequenceCompleted() {
-        return mSequenceCompleted;
-    }
-
-    /**
-     * Mark the capture sequence as completed.
-     */
-    public void markSequenceCompleted() {
-        mSequenceCompleted = true;
-    }
-
-    /**
-     * Return whether the inflight capture is completed.
-     */
-    public boolean isInflightCompleted() {
-        return mInflightCompleted;
-    }
-
-    /**
-     * Mark the inflight capture as completed.
-     */
-    public void markInflightCompleted() {
-        mInflightCompleted = true;
-    }
-
 }
 
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index fdd578c..fbc9ac32 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -109,12 +109,11 @@
         }
         if (holder == null) {
             return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE,
-                    ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, null,
-                    ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE);
+                    ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, null);
         }
         return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(),
                 /*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber(),
-                /*partialResultCount*/1, errorStreamId, null, holder.getFrameNumber(), -1, -1);
+                /*partialResultCount*/1, errorStreamId, null);
     }
 
     /**
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 425218a..c4d123c 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -195,11 +195,14 @@
 
     public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent(
             int modelHandle, RecognitionEvent aidlEvent) {
+        // The API recognition event doesn't allow for a null audio format, even though it doesn't
+        // always make sense. We thus replace it with a default.
+        AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(aidlEvent.audioConfig);
         return new SoundTrigger.GenericRecognitionEvent(
                 aidlEvent.status,
                 modelHandle, aidlEvent.captureAvailable, aidlEvent.captureSession,
                 aidlEvent.captureDelayMs, aidlEvent.capturePreambleMs, aidlEvent.triggerInData,
-                aidl2apiAudioFormat(aidlEvent.audioConfig), aidlEvent.data);
+                audioFormat, aidlEvent.data);
     }
 
     public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent(
@@ -210,11 +213,14 @@
         for (int i = 0; i < aidlEvent.phraseExtras.length; ++i) {
             apiExtras[i] = aidl2apiPhraseRecognitionExtra(aidlEvent.phraseExtras[i]);
         }
+        // The API recognition event doesn't allow for a null audio format, even though it doesn't
+        // always make sense. We thus replace it with a default.
+        AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(aidlEvent.common.audioConfig);
         return new SoundTrigger.KeyphraseRecognitionEvent(aidlEvent.common.status, modelHandle,
                 aidlEvent.common.captureAvailable,
                 aidlEvent.common.captureSession, aidlEvent.common.captureDelayMs,
                 aidlEvent.common.capturePreambleMs, aidlEvent.common.triggerInData,
-                aidl2apiAudioFormat(aidlEvent.common.audioConfig), aidlEvent.common.data,
+                audioFormat, aidlEvent.common.data,
                 apiExtras);
     }
 
@@ -226,6 +232,14 @@
         return apiBuilder.build();
     }
 
+    // Same as above, but in case of a null input returns a non-null valid output.
+    public static AudioFormat aidl2apiAudioFormatWithDefault(@Nullable AudioConfig audioConfig) {
+        if (audioConfig != null) {
+            return aidl2apiAudioFormat(audioConfig);
+        }
+        return new AudioFormat.Builder().build();
+    }
+
     public static int aidl2apiEncoding(int aidlFormat) {
         switch (aidlFormat) {
             case android.media.audio.common.AudioFormat.PCM
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index c1df5b6..a2a15b3 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -303,7 +303,7 @@
                                     (SoundTrigger.RecognitionEvent) msg.obj);
                             break;
                         case EVENT_SERVICE_STATE_CHANGE:
-                            listener.onServiceStateChange(msg.arg1);
+                            listener.onServiceStateChange((int) msg.obj);
                             break;
                         case EVENT_SERVICE_DIED:
                             listener.onServiceDied();
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 36ffe50..ba34899 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2246,26 +2246,6 @@
                 .getPackageNameForUid(context, uid), true /* throwException */);
     }
 
-    /** {@hide} */
-    public static final void enforceTetherChangePermission(Context context, String callingPkg) {
-        Preconditions.checkNotNull(context, "Context cannot be null");
-        Preconditions.checkNotNull(callingPkg, "callingPkg cannot be null");
-
-        if (context.getResources().getStringArray(
-                com.android.internal.R.array.config_mobile_hotspot_provision_app).length == 2) {
-            // Have a provisioning app - must only let system apps (which check this app)
-            // turn on tethering
-            context.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.TETHER_PRIVILEGED, "ConnectivityService");
-        } else {
-            int uid = Binder.getCallingUid();
-            // If callingPkg's uid is not same as Binder.getCallingUid(),
-            // AppOpsService throws SecurityException.
-            Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPkg,
-                    true /* throwException */);
-        }
-    }
-
     /**
      * @deprecated - use getSystemService. This is a kludge to support static access in certain
      *               situations where a Context pointer is unavailable.
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 65e772cb..482d2d2 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -357,6 +357,7 @@
         final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacyType,
                 config.legacyTypeName, "");
         ni.setIsAvailable(true);
+        ni.setExtraInfo(config.getLegacyExtraInfo());
         return ni;
     }
 
diff --git a/core/java/android/net/NetworkAgentConfig.java b/core/java/android/net/NetworkAgentConfig.java
index fee868a..fe1268d 100644
--- a/core/java/android/net/NetworkAgentConfig.java
+++ b/core/java/android/net/NetworkAgentConfig.java
@@ -185,6 +185,26 @@
         return legacyTypeName;
     }
 
+    /**
+     * The legacy extra info of the agent. The extra info should only be :
+     * <ul>
+     *   <li>For cellular agents, the APN name.</li>
+     *   <li>For ethernet agents, the interface name.</li>
+     * </ul>
+     * @hide
+     */
+    @NonNull
+    private String mLegacyExtraInfo = "";
+
+    /**
+     * The legacy extra info of the agent.
+     * @hide
+     */
+    @NonNull
+    public String getLegacyExtraInfo() {
+        return mLegacyExtraInfo;
+    }
+
     /** @hide */
     public NetworkAgentConfig() {
     }
@@ -201,6 +221,7 @@
             skip464xlat = nac.skip464xlat;
             legacyType = nac.legacyType;
             legacyTypeName = nac.legacyTypeName;
+            mLegacyExtraInfo = nac.mLegacyExtraInfo;
         }
     }
 
@@ -309,6 +330,18 @@
         }
 
         /**
+         * Sets the legacy extra info of the agent.
+         * @param legacyExtraInfo the legacy extra info.
+         * @return this builder, to facilitate chaining.
+         * @hide
+         */
+        @NonNull
+        public Builder setLegacyExtraInfo(@NonNull String legacyExtraInfo) {
+            mConfig.mLegacyExtraInfo = legacyExtraInfo;
+            return this;
+        }
+
+        /**
          * Returns the constructed {@link NetworkAgentConfig} object.
          */
         @NonNull
@@ -330,14 +363,15 @@
                 && skip464xlat == that.skip464xlat
                 && legacyType == that.legacyType
                 && Objects.equals(subscriberId, that.subscriberId)
-                && Objects.equals(legacyTypeName, that.legacyTypeName);
+                && Objects.equals(legacyTypeName, that.legacyTypeName)
+                && Objects.equals(mLegacyExtraInfo, that.mLegacyExtraInfo);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated,
                 acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId,
-                skip464xlat, legacyType, legacyTypeName);
+                skip464xlat, legacyType, legacyTypeName, mLegacyExtraInfo);
     }
 
     @Override
@@ -353,6 +387,7 @@
                 + ", legacyType = " + legacyType
                 + ", hasShownBroken = " + hasShownBroken
                 + ", legacyTypeName = '" + legacyTypeName + '\''
+                + ", legacyExtraInfo = '" + mLegacyExtraInfo + '\''
                 + "}";
     }
 
@@ -372,6 +407,7 @@
         out.writeInt(skip464xlat ? 1 : 0);
         out.writeInt(legacyType);
         out.writeString(legacyTypeName);
+        out.writeString(mLegacyExtraInfo);
     }
 
     public static final @NonNull Creator<NetworkAgentConfig> CREATOR =
@@ -388,6 +424,7 @@
             networkAgentConfig.skip464xlat = in.readInt() != 0;
             networkAgentConfig.legacyType = in.readInt();
             networkAgentConfig.legacyTypeName = in.readString();
+            networkAgentConfig.mLegacyExtraInfo = in.readString();
             return networkAgentConfig;
         }
 
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index fd7cdda..0625457 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -221,15 +221,16 @@
      *
      * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
      * each pair, the value in the amplitude array determines the strength of the vibration and the
-     * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
-     * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+     * value in the timing array determines how long it vibrates for, in milliseconds. Amplitude
+     * values must be between 0 and 255, and an amplitude of 0 implies no vibration (i.e. off). Any
+     * pairs with a timing value of 0 will be ignored.
      * </p><p>
      * To cause the pattern to repeat, pass the index into the timings array at which to start the
      * repetition, or -1 to disable repeating.
      * </p>
      *
-     * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
-     *                will cause the pair to be ignored.
+     * @param timings The timing values, in milliseconds, of the timing / amplitude pairs. Timing
+     *                values of 0 will cause the pair to be ignored.
      * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
      *                   must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
      *                   amplitude value of 0 implies the motor is off.
diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java
index 21738d80..8ba5c17 100644
--- a/core/java/android/service/autofill/augmented/FillCallback.java
+++ b/core/java/android/service/autofill/augmented/FillCallback.java
@@ -62,9 +62,10 @@
 
         List<Dataset> inlineSuggestions = response.getInlineSuggestions();
         Bundle clientState = response.getClientState();
+        // We need to report result regardless of whether inline suggestions are returned or not.
+        mProxy.reportResult(inlineSuggestions, clientState);
         if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) {
             mProxy.logEvent(AutofillProxy.REPORT_EVENT_INLINE_RESPONSE);
-            mProxy.reportResult(inlineSuggestions, clientState);
             return;
         }
 
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 1f6555c..0827fef 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -78,6 +78,7 @@
     private static final int DEFAULT_SOURCE = SOURCE_CONTACT;
     private static final int DEFAULT_CALLS_SOURCE = SOURCE_STAR;
 
+    public static final String MANUAL_RULE_ID = "MANUAL_RULE";
     public static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE";
     public static final String EVERY_NIGHT_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE";
     public static final List<String> DEFAULT_RULE_IDS = Arrays.asList(EVERY_NIGHT_DEFAULT_RULE_ID,
@@ -959,6 +960,48 @@
     };
 
     /**
+     * Converts a ZenModeConfig to a ZenPolicy
+     */
+    public ZenPolicy toZenPolicy() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder()
+                .allowCalls(allowCalls
+                        ? ZenModeConfig.getZenPolicySenders(allowCallsFrom)
+                        : ZenPolicy.PEOPLE_TYPE_NONE)
+                .allowRepeatCallers(allowRepeatCallers)
+                .allowMessages(allowMessages
+                        ? ZenModeConfig.getZenPolicySenders(allowMessagesFrom)
+                        : ZenPolicy.PEOPLE_TYPE_NONE)
+                .allowReminders(allowReminders)
+                .allowEvents(allowEvents)
+                .allowAlarms(allowAlarms)
+                .allowMedia(allowMedia)
+                .allowSystem(allowSystem)
+                .allowConversations(allowConversations
+                        ? ZenModeConfig.getZenPolicySenders(allowConversationsFrom)
+                        : ZenPolicy.PEOPLE_TYPE_NONE);
+        if (suppressedVisualEffects == 0) {
+            builder.showAllVisualEffects();
+        } else {
+            // configs don't have an unset state: wither true or false.
+            builder.showFullScreenIntent(
+                    (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) == 0);
+            builder.showLights(
+                    (suppressedVisualEffects & SUPPRESSED_EFFECT_LIGHTS) == 0);
+            builder.showPeeking(
+                    (suppressedVisualEffects & SUPPRESSED_EFFECT_PEEK) == 0);
+            builder.showStatusBarIcons(
+                    (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_STATUS_BAR) == 0);
+            builder.showBadges(
+                    (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_BADGE) == 0);
+            builder.showInAmbientDisplay(
+                    (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_AMBIENT) == 0);
+            builder.showInNotificationList(
+                    (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) == 0);
+        }
+        return builder.build();
+    }
+
+    /**
      * Converts a zenPolicy to a notificationPolicy using this ZenModeConfig's values as its
      * defaults for all unset values in zenPolicy
      */
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 87295e1..6d0bcff 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -24,6 +24,7 @@
 import android.os.Parcelable;
 import android.util.proto.ProtoOutputStream;
 
+import java.io.ByteArrayOutputStream;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -1111,6 +1112,40 @@
     }
 
     /**
+     * Converts a policy to a statsd proto.
+     * @hides
+     */
+    public byte[] toProto() {
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        ProtoOutputStream proto = new ProtoOutputStream(bytes);
+
+        proto.write(DNDPolicyProto.CALLS, getPriorityCategoryCalls());
+        proto.write(DNDPolicyProto.REPEAT_CALLERS, getPriorityCategoryRepeatCallers());
+        proto.write(DNDPolicyProto.MESSAGES, getPriorityCategoryMessages());
+        proto.write(DNDPolicyProto.CONVERSATIONS, getPriorityCategoryConversations());
+        proto.write(DNDPolicyProto.REMINDERS, getPriorityCategoryReminders());
+        proto.write(DNDPolicyProto.EVENTS, getPriorityCategoryEvents());
+        proto.write(DNDPolicyProto.ALARMS, getPriorityCategoryAlarms());
+        proto.write(DNDPolicyProto.MEDIA, getPriorityCategoryMedia());
+        proto.write(DNDPolicyProto.SYSTEM, getPriorityCategorySystem());
+
+        proto.write(DNDPolicyProto.FULLSCREEN, getVisualEffectFullScreenIntent());
+        proto.write(DNDPolicyProto.LIGHTS, getVisualEffectLights());
+        proto.write(DNDPolicyProto.PEEK, getVisualEffectPeek());
+        proto.write(DNDPolicyProto.STATUS_BAR, getVisualEffectStatusBar());
+        proto.write(DNDPolicyProto.BADGE, getVisualEffectBadge());
+        proto.write(DNDPolicyProto.AMBIENT, getVisualEffectAmbient());
+        proto.write(DNDPolicyProto.NOTIFICATION_LIST, getVisualEffectNotificationList());
+
+        proto.write(DNDPolicyProto.ALLOW_CALLS_FROM, getPriorityCallSenders());
+        proto.write(DNDPolicyProto.ALLOW_MESSAGES_FROM, getPriorityMessageSenders());
+        proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM, getPriorityConversationSenders());
+
+        proto.flush();
+        return bytes.toByteArray();
+    }
+
+    /**
      * Makes deep copy of this ZenPolicy.
      * @hide
      */
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index e4d53c6..9a9396c 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -575,21 +575,23 @@
 
     @VisibleForTesting
     public boolean onStateChanged(InsetsState state) {
-        boolean localStateChanged = !mState.equals(state, true /* excludingCaptionInsets */)
+        boolean stateChanged = !mState.equals(state, true /* excludingCaptionInsets */,
+                        false /* excludeInvisibleIme */)
                 || !captionInsetsUnchanged();
-        if (!localStateChanged && mLastDispatchedState.equals(state)) {
+        if (!stateChanged && mLastDispatchedState.equals(state)) {
             return false;
         }
         if (DEBUG) Log.d(TAG, "onStateChanged: " + state);
         updateState(state);
+
+        boolean localStateChanged = !mState.equals(mLastDispatchedState,
+                true /* excludingCaptionInsets */, true /* excludeInvisibleIme */);
         mLastDispatchedState.set(state, true /* copySources */);
+
         applyLocalVisibilityOverride();
         if (localStateChanged) {
-            if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged");
+            if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged, send state to WM: " + mState);
             mHost.notifyInsetsChanged();
-        }
-        if (!mState.equals(mLastDispatchedState, true /* excludingCaptionInsets */)) {
-            if (DEBUG) Log.d(TAG, "onStateChanged, send state to WM: " + mState);
             updateRequestedState();
         }
         return true;
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index b015846..15b9a93 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -191,6 +191,14 @@
 
     @Override
     public boolean equals(Object o) {
+        return equals(o, false);
+    }
+
+    /**
+     * @param excludeInvisibleImeFrames If {@link InsetsState#ITYPE_IME} frames should be ignored
+     *                                  when IME is not visible.
+     */
+    public boolean equals(Object o, boolean excludeInvisibleImeFrames) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
@@ -198,6 +206,7 @@
 
         if (mType != that.mType) return false;
         if (mVisible != that.mVisible) return false;
+        if (excludeInvisibleImeFrames && !mVisible && mType == ITYPE_IME) return true;
         if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
         return mFrame.equals(that.mFrame);
     }
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index ae70a49..3aa2464 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -18,8 +18,8 @@
 
 import static android.view.InsetsController.ANIMATION_TYPE_NONE;
 import static android.view.InsetsController.AnimationType;
-import static android.view.InsetsState.getDefaultVisibility;
 import static android.view.InsetsController.DEBUG;
+import static android.view.InsetsState.getDefaultVisibility;
 import static android.view.InsetsState.toPublicType;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
@@ -221,9 +221,10 @@
         final boolean hasControl = mSourceControl != null;
 
         // We still need to let the legacy app know the visibility change even if we don't have the
-        // control.
+        // control. If we don't have the source, we don't change the requested visibility for making
+        // the callback behavior compatible.
         mController.updateCompatSysUiVisibility(
-                mType, hasControl ? mRequestedVisible : isVisible, hasControl);
+                mType, (hasControl || source == null) ? mRequestedVisible : isVisible, hasControl);
 
         // If we don't have control, we are not able to change the visibility.
         if (!hasControl) {
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index f0bca26..397b04e 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -555,7 +555,7 @@
 
     @Override
     public boolean equals(Object o) {
-        return equals(o, false);
+        return equals(o, false, false);
     }
 
     /**
@@ -564,10 +564,13 @@
      * excluded.
      * @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but
      *                                           ignore the caption insets source value.
+     * @param excludeInvisibleImeFrames If {@link #ITYPE_IME} frames should be ignored when IME is
+     *                                  not visible.
      * @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise.
      */
     @VisibleForTesting
-    public boolean equals(Object o, boolean excludingCaptionInsets) {
+    public boolean equals(Object o, boolean excludingCaptionInsets,
+            boolean excludeInvisibleImeFrames) {
         if (this == o) { return true; }
         if (o == null || getClass() != o.getClass()) { return false; }
 
@@ -598,7 +601,7 @@
             if (otherSource == null) {
                 return false;
             }
-            if (!otherSource.equals(source)) {
+            if (!otherSource.equals(source, excludeInvisibleImeFrames)) {
                 return false;
             }
         }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 57f91ed..c098fae 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -893,12 +893,15 @@
             }
             return;
         }
-        ViewRootImpl viewRoot = getViewRootImpl();
-        if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
-            if (DEBUG) {
-                Log.d(TAG, System.identityHashCode(this)
-                        + " updateSurface: no valid surface");
-            }
+        final ViewRootImpl viewRoot = getViewRootImpl();
+
+        if (viewRoot == null) {
+            return;
+        }
+
+        if (viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
+            notifySurfaceDestroyed();
+            releaseSurfaces();
             return;
         }
 
@@ -1109,28 +1112,7 @@
                     final boolean surfaceChanged = creating;
                     if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
                         mSurfaceCreated = false;
-                        if (mSurface.isValid()) {
-                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                                    + "visibleChanged -- surfaceDestroyed");
-                            callbacks = getSurfaceCallbacks();
-                            for (SurfaceHolder.Callback c : callbacks) {
-                                c.surfaceDestroyed(mSurfaceHolder);
-                            }
-                            // Since Android N the same surface may be reused and given to us
-                            // again by the system server at a later point. However
-                            // as we didn't do this in previous releases, clients weren't
-                            // necessarily required to clean up properly in
-                            // surfaceDestroyed. This leads to problems for example when
-                            // clients don't destroy their EGL context, and try
-                            // and create a new one on the same surface following reuse.
-                            // Since there is no valid use of the surface in-between
-                            // surfaceDestroyed and surfaceCreated, we force a disconnect,
-                            // so the next connect will always work if we end up reusing
-                            // the surface.
-                            if (mSurface.isValid()) {
-                                mSurface.forceScopedDisconnect();
-                            }
-                        }
+                        notifySurfaceDestroyed();
                     }
 
                     if (creating) {
@@ -1786,6 +1768,31 @@
         }
     }
 
+    private void notifySurfaceDestroyed() {
+        if (mSurface.isValid()) {
+            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                    + "surfaceDestroyed");
+            SurfaceHolder.Callback[] callbacks = getSurfaceCallbacks();
+            for (SurfaceHolder.Callback c : callbacks) {
+                c.surfaceDestroyed(mSurfaceHolder);
+            }
+            // Since Android N the same surface may be reused and given to us
+            // again by the system server at a later point. However
+            // as we didn't do this in previous releases, clients weren't
+            // necessarily required to clean up properly in
+            // surfaceDestroyed. This leads to problems for example when
+            // clients don't destroy their EGL context, and try
+            // and create a new one on the same surface following reuse.
+            // Since there is no valid use of the surface in-between
+            // surfaceDestroyed and surfaceCreated, we force a disconnect,
+            // so the next connect will always work if we end up reusing
+            // the surface.
+            if (mSurface.isValid()) {
+                mSurface.forceScopedDisconnect();
+            }
+        }
+    }
+
     /**
      * Wrapper of accessibility embedded connection for embedded view hierarchy.
      */
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index e07181a..843700c 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -971,7 +971,8 @@
             return new TextClassifierEvent.LanguageDetectionEvent.Builder(eventType)
                     .setEventContext(classificationContext)
                     .setResultId(classification.getId())
-                    .setEntityTypes(language)
+                    // b/158481016: Disable language logging.
+                    //.setEntityTypes(language)
                     .setScores(score)
                     .setActionIndices(classification.getActions().indexOf(translateAction))
                     .setModelName(model)
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 2d4d957..0a1e3a0 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2816,8 +2816,7 @@
         }
 
         // no need to query direct share for work profile when its turned off
-        UserManager userManager = getSystemService(UserManager.class);
-        if (userManager.isQuietModeEnabled(chooserListAdapter.getUserHandle())) {
+        if (isQuietModeEnabled(chooserListAdapter.getUserHandle())) {
             getChooserActivityLogger().logSharesheetAppLoadComplete();
             return;
         }
@@ -2841,6 +2840,12 @@
         getChooserActivityLogger().logSharesheetAppLoadComplete();
     }
 
+    @VisibleForTesting
+    protected boolean isQuietModeEnabled(UserHandle userHandle) {
+        UserManager userManager = getSystemService(UserManager.class);
+        return userManager.isQuietModeEnabled(userHandle);
+    }
+
     private void setupScrollListener() {
         if (mResolverDrawerLayout == null) {
             return;
@@ -3095,7 +3100,7 @@
                 setVerticalScrollEnabled(false);
             }
         } else if (state == ViewPager.SCROLL_STATE_IDLE) {
-            if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) {
+            if (mScrollStatus == SCROLL_STATUS_SCROLLING_HORIZONTAL) {
                 mScrollStatus = SCROLL_STATUS_IDLE;
                 setVerticalScrollEnabled(true);
             }
@@ -3592,10 +3597,9 @@
          * Only expand direct share area if there is a minimum number of targets.
          */
         private boolean canExpandDirectShare() {
-            int orientation = getResources().getConfiguration().orientation;
-            return mChooserListAdapter.getNumServiceTargetsForExpand() > getMaxTargetsPerRow()
-                    && orientation == Configuration.ORIENTATION_PORTRAIT
-                    && !isInMultiWindowMode();
+            // Do not enable until we have confirmed more apps are using sharing shortcuts
+            // Check git history for enablement logic
+            return false;
         }
 
         public ChooserListAdapter getListAdapter() {
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index f4fb993..d6ff7b1 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -181,6 +181,7 @@
                     ri.icon = 0;
                 }
                 mCallerTargets.add(new DisplayResolveInfo(ii, ri, ii, makePresentationGetter(ri)));
+                if (mCallerTargets.size() == MAX_SUGGESTED_APP_TARGETS) break;
             }
         }
     }
@@ -320,7 +321,7 @@
 
 
     public int getCallerTargetCount() {
-        return Math.min(mCallerTargets.size(), MAX_SUGGESTED_APP_TARGETS);
+        return mCallerTargets.size();
     }
 
     /**
@@ -346,8 +347,9 @@
     }
 
     int getAlphaTargetCount() {
-        int standardCount = mSortedList.size();
-        return standardCount > mChooserListCommunicator.getMaxRankedTargets() ? standardCount : 0;
+        int groupedCount = mSortedList.size();
+        int ungroupedCount = mCallerTargets.size() + mDisplayList.size();
+        return ungroupedCount > mChooserListCommunicator.getMaxRankedTargets() ? groupedCount : 0;
     }
 
     /**
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index 157e0a7..2a7eae6 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -17,29 +17,32 @@
 package com.android.internal.app;
 
 import android.animation.ObjectAnimator;
-import android.animation.TimeAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActionBar;
 import android.app.Activity;
 import android.content.ActivityNotFoundException;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.Intent;
-import android.content.res.ColorStateList;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.ColorFilter;
-import android.graphics.Matrix;
+import android.graphics.LinearGradient;
 import android.graphics.Paint;
-import android.graphics.Path;
 import android.graphics.PixelFormat;
+import android.graphics.Rect;
 import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.provider.Settings;
+import android.util.AttributeSet;
 import android.util.Log;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
+import android.view.animation.PathInterpolator;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import com.android.internal.R;
@@ -50,23 +53,12 @@
  * @hide
  */
 public class PlatLogoActivity extends Activity {
-    ImageView mZeroView, mOneView;
-    BackslashDrawable mBackslash;
-    int mClicks;
+    private static final boolean WRITE_SETTINGS = true;
 
-    static final Paint sPaint = new Paint();
-    static {
-        sPaint.setStyle(Paint.Style.STROKE);
-        sPaint.setStrokeWidth(4f);
-        sPaint.setStrokeCap(Paint.Cap.SQUARE);
-    }
+    BigDialView mDialView;
 
     @Override
     protected void onPause() {
-        if (mBackslash != null) {
-            mBackslash.stopAnimating();
-        }
-        mClicks = 0;
         super.onPause();
     }
 
@@ -80,101 +72,33 @@
         getWindow().setNavigationBarColor(0);
         getWindow().setStatusBarColor(0);
 
-        getActionBar().hide();
+        final ActionBar ab = getActionBar();
+        if (ab != null) ab.hide();
 
-        setContentView(R.layout.platlogo_layout);
-
-        mBackslash = new BackslashDrawable((int) (50 * dp));
-
-        mOneView = findViewById(R.id.one);
-        mOneView.setImageDrawable(new OneDrawable());
-        mZeroView = findViewById(R.id.zero);
-        mZeroView.setImageDrawable(new ZeroDrawable());
-
-        final ViewGroup root = (ViewGroup) mOneView.getParent();
-        root.setClipChildren(false);
-        root.setBackground(mBackslash);
-        root.getBackground().setAlpha(0x20);
-
-        View.OnTouchListener tl = new View.OnTouchListener() {
-            float mOffsetX, mOffsetY;
-            long mClickTime;
-            ObjectAnimator mRotAnim;
-            @Override
-            public boolean onTouch(View v, MotionEvent event) {
-                measureTouchPressure(event);
-                switch (event.getActionMasked()) {
-                    case MotionEvent.ACTION_DOWN:
-                        v.animate().scaleX(1.1f).scaleY(1.1f);
-                        v.getParent().bringChildToFront(v);
-                        mOffsetX = event.getRawX() - v.getX();
-                        mOffsetY = event.getRawY() - v.getY();
-                        long now = System.currentTimeMillis();
-                        if (now - mClickTime < 350) {
-                            mRotAnim = ObjectAnimator.ofFloat(v, View.ROTATION,
-                                    v.getRotation(), v.getRotation() + 3600);
-                            mRotAnim.setDuration(10000);
-                            mRotAnim.start();
-                            mClickTime = 0;
-                        } else {
-                            mClickTime = now;
-                        }
-                        break;
-                    case MotionEvent.ACTION_MOVE:
-                        v.setX(event.getRawX() - mOffsetX);
-                        v.setY(event.getRawY() - mOffsetY);
-                        v.performHapticFeedback(HapticFeedbackConstants.TEXT_HANDLE_MOVE);
-                        break;
-                    case MotionEvent.ACTION_UP:
-                        v.performClick();
-                        // fall through
-                    case MotionEvent.ACTION_CANCEL:
-                        v.animate().scaleX(1f).scaleY(1f);
-                        if (mRotAnim != null) mRotAnim.cancel();
-                        testOverlap();
-                        break;
-                }
-                return true;
-            }
-        };
-
-        findViewById(R.id.one).setOnTouchListener(tl);
-        findViewById(R.id.zero).setOnTouchListener(tl);
-        findViewById(R.id.text).setOnTouchListener(tl);
-    }
-
-    private void testOverlap() {
-        final float width = mZeroView.getWidth();
-        final float targetX = mZeroView.getX() + width * .2f;
-        final float targetY = mZeroView.getY() + width * .3f;
-        if (Math.hypot(targetX - mOneView.getX(), targetY - mOneView.getY()) < width * .2f
-                && Math.abs(mOneView.getRotation() % 360 - 315) < 15) {
-            mOneView.animate().x(mZeroView.getX() + width * .2f);
-            mOneView.animate().y(mZeroView.getY() + width * .3f);
-            mOneView.setRotation(mOneView.getRotation() % 360);
-            mOneView.animate().rotation(315);
-            mOneView.performHapticFeedback(HapticFeedbackConstants.CONFIRM);
-
-            mBackslash.startAnimating();
-
-            mClicks++;
-            if (mClicks >= 7) {
-                launchNextStage();
-            }
-        } else {
-            mBackslash.stopAnimating();
+        mDialView = new BigDialView(this, null);
+        if (Settings.System.getLong(getContentResolver(),
+                "egg_mode" /* Settings.System.EGG_MODE */, 0) == 0) {
+            mDialView.setUnlockTries(3);
         }
+
+        final FrameLayout layout = new FrameLayout(this);
+        layout.setBackgroundColor(0xFFFF0000);
+        layout.addView(mDialView, FrameLayout.LayoutParams.MATCH_PARENT,
+                FrameLayout.LayoutParams.MATCH_PARENT);
+        setContentView(layout);
     }
 
-    private void launchNextStage() {
+    private void launchNextStage(boolean locked) {
         final ContentResolver cr = getContentResolver();
 
         if (Settings.System.getLong(cr, "egg_mode" /* Settings.System.EGG_MODE */, 0) == 0) {
             // For posterity: the moment this user unlocked the easter egg
             try {
-                Settings.System.putLong(cr,
-                        "egg_mode", // Settings.System.EGG_MODE,
-                        System.currentTimeMillis());
+                if (WRITE_SETTINGS) {
+                    Settings.System.putLong(cr,
+                            "egg_mode", // Settings.System.EGG_MODE,
+                            locked ? 0 : System.currentTimeMillis());
+                }
             } catch (RuntimeException e) {
                 Log.e("com.android.internal.app.PlatLogoActivity", "Can't write settings", e);
             }
@@ -182,12 +106,12 @@
         try {
             startActivity(new Intent(Intent.ACTION_MAIN)
                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                        | Intent.FLAG_ACTIVITY_CLEAR_TASK)
+                            | Intent.FLAG_ACTIVITY_CLEAR_TASK)
                     .addCategory("com.android.internal.category.PLATLOGO"));
         } catch (ActivityNotFoundException ex) {
             Log.e("com.android.internal.app.PlatLogoActivity", "No more eggs.");
         }
-        finish();
+        //finish(); // no longer finish upon unlock; it's fun to frob the dial
     }
 
     static final String TOUCH_STATS = "touch.stats";
@@ -223,7 +147,10 @@
             if (mPressureMax >= 0) {
                 touchData.put("min", mPressureMin);
                 touchData.put("max", mPressureMax);
-                Settings.System.putString(getContentResolver(), TOUCH_STATS, touchData.toString());
+                if (WRITE_SETTINGS) {
+                    Settings.System.putString(getContentResolver(), TOUCH_STATS,
+                            touchData.toString());
+                }
             }
         } catch (Exception e) {
             Log.e("com.android.internal.app.PlatLogoActivity", "Can't write touch settings", e);
@@ -242,149 +169,272 @@
         super.onStop();
     }
 
-    static class ZeroDrawable extends Drawable {
-        int mTintColor;
+    class BigDialView extends ImageView {
+        private static final int COLOR_GREEN = 0xff3ddc84;
+        private static final int COLOR_BLUE = 0xff4285f4;
+        private static final int COLOR_NAVY = 0xff073042;
+        private static final int COLOR_ORANGE = 0xfff86734;
+        private static final int COLOR_CHARTREUSE = 0xffeff7cf;
+        private static final int COLOR_LIGHTBLUE = 0xffd7effe;
 
-        @Override
-        public void draw(Canvas canvas) {
-            sPaint.setColor(mTintColor | 0xFF000000);
+        private static final int STEPS = 11;
+        private static final float VALUE_CHANGE_MAX = 1f / STEPS;
 
-            canvas.save();
-            canvas.scale(canvas.getWidth() / 24f, canvas.getHeight() / 24f);
+        private BigDialDrawable mDialDrawable;
+        private boolean mWasLocked;
 
-            canvas.drawCircle(12f, 12f, 10f, sPaint);
-            canvas.restore();
+        BigDialView(Context context, @Nullable AttributeSet attrs) {
+            super(context, attrs);
+            init();
+        }
+
+        BigDialView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+            init();
+        }
+
+        BigDialView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+                int defStyleRes) {
+            super(context, attrs, defStyleAttr, defStyleRes);
+            init();
+        }
+
+        private void init() {
+            mDialDrawable = new BigDialDrawable();
+            setImageDrawable(mDialDrawable);
         }
 
         @Override
-        public void setAlpha(int alpha) { }
+        public void onDraw(Canvas c) {
+            super.onDraw(c);
+        }
 
-        @Override
-        public void setColorFilter(ColorFilter colorFilter) { }
-
-        @Override
-        public void setTintList(ColorStateList tint) {
-            mTintColor = tint.getDefaultColor();
+        double toPositiveDegrees(double rad) {
+            return (Math.toDegrees(rad) + 360 - 90) % 360;
         }
 
         @Override
-        public int getOpacity() {
-            return PixelFormat.TRANSLUCENT;
-        }
-    }
-
-    static class OneDrawable extends Drawable {
-        int mTintColor;
-
-        @Override
-        public void draw(Canvas canvas) {
-            sPaint.setColor(mTintColor | 0xFF000000);
-
-            canvas.save();
-            canvas.scale(canvas.getWidth() / 24f, canvas.getHeight() / 24f);
-
-            final Path p = new Path();
-            p.moveTo(12f, 21.83f);
-            p.rLineTo(0f, -19.67f);
-            p.rLineTo(-5f, 0f);
-            canvas.drawPath(p, sPaint);
-            canvas.restore();
-        }
-
-        @Override
-        public void setAlpha(int alpha) { }
-
-        @Override
-        public void setColorFilter(ColorFilter colorFilter) { }
-
-        @Override
-        public void setTintList(ColorStateList tint) {
-            mTintColor = tint.getDefaultColor();
-        }
-
-        @Override
-        public int getOpacity() {
-            return PixelFormat.TRANSLUCENT;
-        }
-    }
-
-    private static class BackslashDrawable extends Drawable implements TimeAnimator.TimeListener {
-        Bitmap mTile;
-        Paint mPaint = new Paint();
-        BitmapShader mShader;
-        TimeAnimator mAnimator = new TimeAnimator();
-        Matrix mMatrix = new Matrix();
-
-        public void draw(Canvas canvas) {
-            canvas.drawPaint(mPaint);
-        }
-
-        BackslashDrawable(int width) {
-            mTile = Bitmap.createBitmap(width, width, Bitmap.Config.ALPHA_8);
-            mAnimator.setTimeListener(this);
-
-            final Canvas tileCanvas = new Canvas(mTile);
-            final float w = tileCanvas.getWidth();
-            final float h = tileCanvas.getHeight();
-
-            final Path path = new Path();
-            path.moveTo(0, 0);
-            path.lineTo(w / 2, 0);
-            path.lineTo(w, h / 2);
-            path.lineTo(w, h);
-            path.close();
-
-            path.moveTo(0, h / 2);
-            path.lineTo(w / 2, h);
-            path.lineTo(0, h);
-            path.close();
-
-            final Paint slashPaint = new Paint();
-            slashPaint.setAntiAlias(true);
-            slashPaint.setStyle(Paint.Style.FILL);
-            slashPaint.setColor(0xFF000000);
-            tileCanvas.drawPath(path, slashPaint);
-
-            //mPaint.setColor(0xFF0000FF);
-            mShader = new BitmapShader(mTile, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
-            mPaint.setShader(mShader);
-        }
-
-        public void startAnimating() {
-            if (!mAnimator.isStarted()) {
-                mAnimator.start();
+        public boolean onTouchEvent(MotionEvent ev) {
+            switch (ev.getActionMasked()) {
+                case MotionEvent.ACTION_DOWN:
+                    mWasLocked = mDialDrawable.isLocked();
+                    // pass through
+                case MotionEvent.ACTION_MOVE:
+                    float x = ev.getX();
+                    float y = ev.getY();
+                    float cx = (getLeft() + getRight()) / 2f;
+                    float cy = (getTop() + getBottom()) / 2f;
+                    float angle = (float) toPositiveDegrees(Math.atan2(x - cx, y - cy));
+                    final int oldLevel = mDialDrawable.getUserLevel();
+                    mDialDrawable.touchAngle(angle);
+                    final int newLevel = mDialDrawable.getUserLevel();
+                    if (oldLevel != newLevel) {
+                        performHapticFeedback(newLevel == STEPS
+                                ? HapticFeedbackConstants.CONFIRM
+                                : HapticFeedbackConstants.CLOCK_TICK);
+                    }
+                    return true;
+                case MotionEvent.ACTION_UP:
+                    if (mWasLocked && !mDialDrawable.isLocked()) {
+                        launchNextStage(false);
+                    }
+                    return true;
             }
+            return false;
         }
 
-        public void stopAnimating() {
-            if (mAnimator.isStarted()) {
-                mAnimator.cancel();
+        @Override
+        public boolean performClick() {
+            if (mDialDrawable.getUserLevel() < STEPS - 1) {
+                mDialDrawable.setUserLevel(mDialDrawable.getUserLevel() + 1);
+                performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
             }
+            return true;
         }
 
-        @Override
-        public void setAlpha(int alpha) {
-            mPaint.setAlpha(alpha);
+        void setUnlockTries(int tries) {
+            mDialDrawable.setUnlockTries(tries);
         }
 
-        @Override
-        public void setColorFilter(ColorFilter colorFilter) {
-            mPaint.setColorFilter(colorFilter);
-        }
+        private class BigDialDrawable extends Drawable {
+            public final int STEPS = 10;
+            private int mUnlockTries = 0;
+            final Paint mPaint = new Paint();
+            final Drawable mEleven;
+            private boolean mNightMode;
+            private float mValue = 0f;
+            float mElevenAnim = 0f;
+            ObjectAnimator mElevenShowAnimator = ObjectAnimator.ofFloat(this, "elevenAnim", 0f,
+                    1f).setDuration(300);
+            ObjectAnimator mElevenHideAnimator = ObjectAnimator.ofFloat(this, "elevenAnim", 1f,
+                    0f).setDuration(500);
 
-        @Override
-        public int getOpacity() {
-            return PixelFormat.TRANSLUCENT;
-        }
+            BigDialDrawable() {
+                mNightMode = getContext().getResources().getConfiguration().isNightModeActive();
+                mEleven = getContext().getDrawable(R.drawable.ic_number11);
+                mElevenShowAnimator.setInterpolator(new PathInterpolator(0.4f, 0f, 0.2f, 1f));
+                mElevenHideAnimator.setInterpolator(new PathInterpolator(0.8f, 0.2f, 0.6f, 1f));
+            }
 
-        @Override
-        public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
-            if (mShader != null) {
-                mMatrix.postTranslate(deltaTime / 4f, 0);
-                mShader.setLocalMatrix(mMatrix);
+            public void setUnlockTries(int count) {
+                if (mUnlockTries != count) {
+                    mUnlockTries = count;
+                    setValue(getValue());
+                    invalidateSelf();
+                }
+            }
+
+            boolean isLocked() {
+                return mUnlockTries > 0;
+            }
+
+            public void setValue(float v) {
+                // until the dial is "unlocked", you can't turn it all the way to 11
+                final float max = isLocked() ? 1f - 1f / STEPS : 1f;
+                mValue = v < 0f ? 0f : v > max ? max : v;
                 invalidateSelf();
             }
+
+            public float getValue() {
+                return mValue;
+            }
+
+            public int getUserLevel() {
+                return Math.round(getValue() * STEPS - 0.25f);
+            }
+
+            public void setUserLevel(int i) {
+                setValue(getValue() + ((float) i) / STEPS);
+            }
+
+            public float getElevenAnim() {
+                return mElevenAnim;
+            }
+
+            public void setElevenAnim(float f) {
+                if (mElevenAnim != f) {
+                    mElevenAnim = f;
+                    invalidateSelf();
+                }
+            }
+
+            @Override
+            public void draw(@NonNull Canvas canvas) {
+                final Rect bounds = getBounds();
+                final int w = bounds.width();
+                final int h = bounds.height();
+                final float w2 = w / 2f;
+                final float h2 = h / 2f;
+                final float radius = w / 4f;
+
+                canvas.drawColor(mNightMode ? COLOR_NAVY : COLOR_LIGHTBLUE);
+
+                canvas.save();
+                canvas.rotate(45, w2, h2);
+                canvas.clipRect(w2, h2 - radius, Math.min(w, h), h2 + radius);
+                final int gradientColor = mNightMode ? 0x60000020 : (0x10FFFFFF & COLOR_NAVY);
+                mPaint.setShader(
+                        new LinearGradient(w2, h2, Math.min(w, h), h2, gradientColor,
+                                0x00FFFFFF & gradientColor, Shader.TileMode.CLAMP));
+                mPaint.setColor(Color.BLACK);
+                canvas.drawPaint(mPaint);
+                mPaint.setShader(null);
+                canvas.restore();
+
+                mPaint.setStyle(Paint.Style.FILL);
+                mPaint.setColor(COLOR_GREEN);
+
+                canvas.drawCircle(w2, h2, radius, mPaint);
+
+                mPaint.setColor(mNightMode ? COLOR_LIGHTBLUE : COLOR_NAVY);
+                final float cx = w * 0.85f;
+                for (int i = 0; i < STEPS; i++) {
+                    final float f = (float) i / STEPS;
+                    canvas.save();
+                    final float angle = valueToAngle(f);
+                    canvas.rotate(-angle, w2, h2);
+                    canvas.drawCircle(cx, h2, (i <= getUserLevel()) ? 20 : 5, mPaint);
+                    canvas.restore();
+                }
+
+                if (mElevenAnim > 0f) {
+                    final int color = COLOR_ORANGE;
+                    final int size2 = (int) ((0.5 + 0.5f * mElevenAnim) * w / 14);
+                    final float cx11 = cx + size2 / 4f;
+                    mEleven.setBounds((int) cx11 - size2, (int) h2 - size2,
+                            (int) cx11 + size2, (int) h2 + size2);
+                    final int alpha = 0xFFFFFF | ((int) clamp(0xFF * 2 * mElevenAnim, 0, 0xFF)
+                            << 24);
+                    mEleven.setTint(alpha & color);
+                    mEleven.draw(canvas);
+                }
+
+                // don't want to use the rounded value here since the quantization will be visible
+                final float angle = valueToAngle(mValue);
+
+                // it's easier to draw at far-right and rotate backwards
+                canvas.rotate(-angle, w2, h2);
+                mPaint.setColor(Color.WHITE);
+                final float dimple = w2 / 12f;
+                canvas.drawCircle(w - radius - dimple * 2, h2, dimple, mPaint);
+            }
+
+            float clamp(float x, float a, float b) {
+                return x < a ? a : x > b ? b : x;
+            }
+
+            float angleToValue(float a) {
+                return 1f - clamp(a / (360 - 45), 0f, 1f);
+            }
+
+            // rotation: min is at 4:30, max is at 3:00
+            float valueToAngle(float v) {
+                return (1f - v) * (360 - 45);
+            }
+
+            public void touchAngle(float a) {
+                final int oldUserLevel = getUserLevel();
+                final float newValue = angleToValue(a);
+                // this is how we prevent the knob from snapping from max back to min, or from
+                // jumping around wherever the user presses. The new value must be pretty close
+                // to the
+                // previous one.
+                if (Math.abs(newValue - getValue()) < VALUE_CHANGE_MAX) {
+                    setValue(newValue);
+
+                    if (isLocked() && oldUserLevel != STEPS - 1 && getUserLevel() == STEPS - 1) {
+                        mUnlockTries--;
+                    }
+
+                    if (!isLocked()) {
+                        if (getUserLevel() == STEPS && mElevenAnim != 1f
+                                && !mElevenShowAnimator.isRunning()) {
+                            mElevenHideAnimator.cancel();
+                            mElevenShowAnimator.start();
+                        } else if (getUserLevel() != STEPS && mElevenAnim == 1f
+                                && !mElevenHideAnimator.isRunning()) {
+                            mElevenShowAnimator.cancel();
+                            mElevenHideAnimator.start();
+                        }
+                    }
+                }
+            }
+
+            @Override
+            public void setAlpha(int i) {
+            }
+
+            @Override
+            public void setColorFilter(@Nullable ColorFilter colorFilter) {
+            }
+
+            @Override
+            public int getOpacity() {
+                return PixelFormat.TRANSLUCENT;
+            }
         }
     }
 }
 
+
+
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index b1e8ed1..d63ebda 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -617,7 +617,8 @@
         }
     }
 
-    UserHandle getUserHandle() {
+    @VisibleForTesting
+    public UserHandle getUserHandle() {
         return mResolverListController.getUserHandle();
     }
 
diff --git a/core/java/com/android/internal/app/SimpleIconFactory.java b/core/java/com/android/internal/app/SimpleIconFactory.java
index ffe2dbe..2d91e64 100644
--- a/core/java/com/android/internal/app/SimpleIconFactory.java
+++ b/core/java/com/android/internal/app/SimpleIconFactory.java
@@ -19,6 +19,7 @@
 import static android.content.Context.ACTIVITY_SERVICE;
 import static android.graphics.Paint.DITHER_FLAG;
 import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
 
 import android.annotation.AttrRes;
 import android.annotation.NonNull;
@@ -54,6 +55,7 @@
 import org.xmlpull.v1.XmlPullParser;
 
 import java.nio.ByteBuffer;
+import java.util.Optional;
 
 
 /**
@@ -64,6 +66,7 @@
 @Deprecated
 public class SimpleIconFactory {
 
+
     private static final SynchronizedPool<SimpleIconFactory> sPool =
             new SynchronizedPool<>(Runtime.getRuntime().availableProcessors());
 
@@ -251,7 +254,7 @@
         } else if (w > h && h > 0) {
             scale = (float) w / h;
         }
-        Bitmap bitmap = createIconBitmap(icon, scale);
+        Bitmap bitmap = createIconBitmapNoInsetOrMask(icon, scale);
         bitmap = maskBitmapToCircle(bitmap);
         icon = new BitmapDrawable(mContext.getResources(), bitmap);
 
@@ -281,15 +284,19 @@
         final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
                 bitmap.getHeight(), Bitmap.Config.ARGB_8888);
         final Canvas canvas = new Canvas(output);
-        final Paint paint = new Paint();
-        paint.setAntiAlias(true);
+        final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG
+                | Paint.FILTER_BITMAP_FLAG);
+
+        // Apply an offset to enable shadow to be drawn
+        final int size = bitmap.getWidth();
+        int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size), 1);
 
         // Draw mask
         paint.setColor(0xffffffff);
         canvas.drawARGB(0, 0, 0, 0);
         canvas.drawCircle(bitmap.getWidth() / 2f,
                 bitmap.getHeight() / 2f,
-                bitmap.getWidth() / 2f - 1 /* -1 to avoid circles with flat sides */,
+                bitmap.getWidth() / 2f - offset,
                 paint);
 
         // Draw masked bitmap
@@ -306,24 +313,61 @@
     }
 
     private Bitmap createIconBitmap(Drawable icon, float scale) {
-        return createIconBitmap(icon, scale, mIconBitmapSize);
+        return createIconBitmap(icon, scale, mIconBitmapSize, true, false);
+    }
+
+    private Bitmap createIconBitmapNoInsetOrMask(Drawable icon, float scale) {
+        return createIconBitmap(icon, scale, mIconBitmapSize, false, true);
     }
 
     /**
      * @param icon drawable that should be flattened to a bitmap
      * @param scale the scale to apply before drawing {@param icon} on the canvas
+     * @param insetAdiForShadow when rendering AdaptiveIconDrawables inset to make room for a shadow
+     * @param ignoreAdiMask when rendering AdaptiveIconDrawables ignore the current system mask
      */
-    private Bitmap createIconBitmap(Drawable icon, float scale, int size) {
+    private Bitmap createIconBitmap(Drawable icon, float scale, int size, boolean insetAdiForShadow,
+            boolean ignoreAdiMask) {
         Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
 
         mCanvas.setBitmap(bitmap);
         mOldBounds.set(icon.getBounds());
 
         if (icon instanceof AdaptiveIconDrawable) {
-            int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),
-                    Math.round(size * (1 - scale) / 2));
-            icon.setBounds(offset, offset, size - offset, size - offset);
-            icon.draw(mCanvas);
+            final AdaptiveIconDrawable adi = (AdaptiveIconDrawable) icon;
+
+            // By default assumes the output bitmap will have a shadow directly applied and makes
+            // room for it by insetting. If there are intermediate steps before applying the shadow
+            // insetting is disableable.
+            int offset = Math.round(size * (1 - scale) / 2);
+            if (insetAdiForShadow) {
+                offset = Math.max((int) Math.ceil(BLUR_FACTOR * size), offset);
+            }
+            Rect bounds = new Rect(offset, offset, size - offset, size - offset);
+
+            // AdaptiveIconDrawables are by default masked by the user's icon shape selection.
+            // If further masking is to be done, directly render to avoid the system masking.
+            if (ignoreAdiMask) {
+                final int cX = bounds.width() / 2;
+                final int cY = bounds.height() / 2;
+                final float portScale = 1f / (1 + 2 * getExtraInsetFraction());
+                final int insetWidth = (int) (bounds.width() / (portScale * 2));
+                final int insetHeight = (int) (bounds.height() / (portScale * 2));
+
+                Rect childRect = new Rect(cX - insetWidth, cY - insetHeight, cX + insetWidth,
+                        cY + insetHeight);
+                Optional.ofNullable(adi.getBackground()).ifPresent(drawable -> {
+                    drawable.setBounds(childRect);
+                    drawable.draw(mCanvas);
+                });
+                Optional.ofNullable(adi.getForeground()).ifPresent(drawable -> {
+                    drawable.setBounds(childRect);
+                    drawable.draw(mCanvas);
+                });
+            } else {
+                adi.setBounds(bounds);
+                adi.draw(mCanvas);
+            }
         } else {
             if (icon instanceof BitmapDrawable) {
                 BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index 86a9af3..fe0e7d0 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -40,8 +40,10 @@
  * resolve it to an activity.
  */
 public class DisplayResolveInfo implements TargetInfo {
-    // Temporary flag for new chooser delegate behavior.
-    private static final boolean ENABLE_CHOOSER_DELEGATE = true;
+    // Temporary flag for new chooser delegate behavior. There are occassional token
+    // permission errors from bouncing through the delegate. Watch out before reenabling:
+    // b/157272342 is one example but this issue has been reported many times
+    private static final boolean ENABLE_CHOOSER_DELEGATE = false;
 
     private final ResolveInfo mResolveInfo;
     private CharSequence mDisplayLabel;
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index b7cdead..b64923f 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -72,6 +72,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.regex.Pattern;
 
@@ -85,7 +86,7 @@
 
     private static final float COLOR_SHIFT_AMOUNT = 60;
     /**
-     *  Pattren for filter some ingonable characters.
+     *  Pattern for filter some ignorable characters.
      *  p{Z} for any kind of whitespace or invisible separator.
      *  p{C} for any kind of punctuation character.
      */
@@ -761,9 +762,13 @@
                 // group
                 : mExpandedGroupMessagePadding;
 
+        int iconPadding = mIsOneToOne || mIsCollapsed
+                ? mConversationIconTopPadding
+                : mConversationIconTopPaddingExpandedGroup;
+
         mConversationIconContainer.setPaddingRelative(
                 mConversationIconContainer.getPaddingStart(),
-                mConversationIconTopPadding,
+                iconPadding,
                 mConversationIconContainer.getPaddingEnd(),
                 mConversationIconContainer.getPaddingBottom());
 
@@ -780,8 +785,8 @@
     }
 
     @RemotableViewMethod
-    public void setShortcutIcon(Icon conversationIcon) {
-        mConversationIcon = conversationIcon;
+    public void setShortcutIcon(Icon shortcutIcon) {
+        mShortcutIcon = shortcutIcon;
     }
 
     /**
@@ -791,7 +796,8 @@
      */
     @RemotableViewMethod
     public void setConversationTitle(CharSequence conversationTitle) {
-        mConversationTitle = conversationTitle;
+        // Remove formatting from the title.
+        mConversationTitle = conversationTitle != null ? conversationTitle.toString() : null;
     }
 
     public CharSequence getConversationTitle() {
@@ -1048,6 +1054,9 @@
                 groups.add(currentGroup);
                 if (sender == null) {
                     sender = mUser;
+                } else {
+                    // Remove all formatting from the sender name
+                    sender = sender.toBuilder().setName(Objects.toString(sender.getName())).build();
                 }
                 senders.add(sender);
                 currentSenderKey = key;
@@ -1245,7 +1254,6 @@
     }
 
     private void updateContentEndPaddings() {
-
         // Let's make sure the conversation header can't run into the expand button when we're
         // collapsed and update the paddings of the content
         int headerPaddingEnd;
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 03a7b3d..93690cd 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1430,6 +1430,32 @@
                 == StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
     }
 
+    private static class WrappedCallback extends ICheckCredentialProgressCallback.Stub {
+
+        private Handler mHandler;
+        private CheckCredentialProgressCallback mCallback;
+
+        WrappedCallback(Handler handler, CheckCredentialProgressCallback callback) {
+            mHandler = handler;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onCredentialVerified() throws RemoteException {
+            if (mHandler == null) {
+                Log.e(TAG, "Handler is null during callback");
+            }
+            // Kill reference immediately to allow early GC at client side independent of
+            // when system_server decides to lose its reference to the
+            // ICheckCredentialProgressCallback binder object.
+            mHandler.post(() -> {
+                mCallback.onEarlyMatched();
+                mCallback = null;
+            });
+            mHandler = null;
+        }
+    }
+
     private ICheckCredentialProgressCallback wrapCallback(
             final CheckCredentialProgressCallback callback) {
         if (callback == null) {
@@ -1439,13 +1465,7 @@
                 throw new IllegalStateException("Must construct LockPatternUtils on a looper thread"
                         + " to use progress callbacks.");
             }
-            return new ICheckCredentialProgressCallback.Stub() {
-
-                @Override
-                public void onCredentialVerified() throws RemoteException {
-                    mHandler.post(callback::onEarlyMatched);
-                }
-            };
+            return new WrappedCallback(mHandler, callback);
         }
     }
 
diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto
index ecb4193..8e4006a 100644
--- a/core/proto/android/service/notification.proto
+++ b/core/proto/android/service/notification.proto
@@ -274,4 +274,74 @@
 // Next Tag: 2
 message NotificationRemoteViewsProto {
     repeated PackageRemoteViewInfoProto package_remote_view_info = 1;
-}
\ No newline at end of file
+}
+
+/**
+ * Atom that represents an item in the list of Do Not Disturb rules, pulled from
+ * NotificationManagerService.java.
+ */
+message DNDModeProto {
+    enum Mode {
+        ROOT_CONFIG = -1;  // Used to distinguish the config (one per user) from the rules.
+        ZEN_MODE_OFF = 0;
+        ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1;
+        ZEN_MODE_NO_INTERRUPTIONS = 2;
+        ZEN_MODE_ALARMS = 3;
+    }
+    optional int32 user = 1;  // Android user ID (0, 1, 10, ...)
+    optional bool enabled = 2;  // true for ROOT_CONFIG if a manualRule is enabled
+    optional bool channels_bypassing = 3; // only valid for ROOT_CONFIG
+    optional Mode zen_mode = 4;
+    // id is one of the system default rule IDs, or empty
+    // May also be "MANUAL_RULE" to indicate app-activation of the manual rule.
+    optional string id = 5;
+    optional int32 uid = 6; // currently only SYSTEM_UID or 0 for other
+    optional DNDPolicyProto policy = 7;
+}
+
+/**
+ * Atom that represents a Do Not Disturb policy, an optional detail proto for DNDModeProto.
+ */
+message DNDPolicyProto {
+    enum State {
+        STATE_UNSET = 0;
+        STATE_ALLOW = 1;
+        STATE_DISALLOW = 2;
+    }
+    optional State calls = 1;
+    optional State repeat_callers = 2;
+    optional State messages = 3;
+    optional State conversations = 4;
+    optional State reminders = 5;
+    optional State events = 6;
+    optional State alarms = 7;
+    optional State media = 8;
+    optional State system = 9;
+    optional State fullscreen = 10;
+    optional State lights = 11;
+    optional State peek = 12;
+    optional State status_bar = 13;
+    optional State badge = 14;
+    optional State ambient = 15;
+    optional State notification_list = 16;
+
+    enum PeopleType {
+        PEOPLE_UNSET = 0;
+        PEOPLE_ANYONE = 1;
+        PEOPLE_CONTACTS = 2;
+        PEOPLE_STARRED = 3;
+        PEOPLE_NONE = 4;
+    }
+
+    optional PeopleType allow_calls_from = 17;
+    optional PeopleType allow_messages_from = 18;
+
+    enum ConversationType {
+        CONV_UNSET = 0;
+        CONV_ANYONE = 1;
+        CONV_IMPORTANT = 2;
+        CONV_NONE = 3;
+    }
+
+    optional ConversationType allow_conversations_from = 19;
+}
diff --git a/core/res/res/drawable-nodpi/ic_number11.xml b/core/res/res/drawable-nodpi/ic_number11.xml
new file mode 100644
index 0000000..daad611
--- /dev/null
+++ b/core/res/res/drawable-nodpi/ic_number11.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M5.14,5H1.59a0.88,0.88 0,0 1,-0.88 -0.89V0.88A0.87,0.87 0,0 1,1.59 0H9.36a0.87,0.87 0,0 1,0.88 0.88V23.12a0.88,0.88 0,0 1,-0.88 0.88H6a0.88,0.88 0,0 1,-0.88 -0.88Z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M18.19,5H14.64a0.89,0.89 0,0 1,-0.88 -0.89V0.88A0.88,0.88 0,0 1,14.64 0h7.78a0.87,0.87 0,0 1,0.87 0.88V23.12a0.88,0.88 0,0 1,-0.87 0.88H19.08a0.89,0.89 0,0 1,-0.89 -0.88Z"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/core/res/res/layout-car/car_alert_dialog.xml b/core/res/res/layout-car/car_alert_dialog.xml
new file mode 100644
index 0000000..569e594
--- /dev/null
+++ b/core/res/res/layout-car/car_alert_dialog.xml
@@ -0,0 +1,85 @@
+<?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.
+  -->
+
+<com.android.internal.widget.AlertDialogLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/parentPanel"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="start|top"
+    android:orientation="vertical">
+
+    <include layout="@layout/car_alert_dialog_title" />
+
+    <FrameLayout
+        android:id="@+id/contentPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="48dp">
+
+        <ScrollView
+            android:id="@+id/scrollView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:clipToPadding="false">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <Space
+                    android:id="@+id/textSpacerNoTitle"
+                    android:visibility="gone"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/dialog_no_title_padding_top" />
+
+                <TextView
+                    android:id="@+id/message"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="@dimen/text_view_start_margin"
+                    android:layout_marginEnd="@dimen/text_view_end_margin"
+                    style="@style/CarBody2"/>
+
+                <!-- we don't need this spacer, but the id needs to be here for compatibility -->
+                <Space
+                    android:id="@+id/textSpacerNoButtons"
+                    android:visibility="gone"
+                    android:layout_width="match_parent"
+                    android:layout_height="0dp" />
+            </LinearLayout>
+        </ScrollView>
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/customPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="48dp">
+
+        <FrameLayout
+            android:id="@+id/custom"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </FrameLayout>
+
+    <include
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        layout="@layout/car_alert_dialog_button_bar" />
+</com.android.internal.widget.AlertDialogLayout>
diff --git a/core/res/res/layout-car/car_alert_dialog_button_bar.xml b/core/res/res/layout-car/car_alert_dialog_button_bar.xml
new file mode 100644
index 0000000..277b0dc
--- /dev/null
+++ b/core/res/res/layout-car/car_alert_dialog_button_bar.xml
@@ -0,0 +1,64 @@
+<?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.
+  -->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/buttonPanel"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollbarAlwaysDrawVerticalTrack="true"
+            android:scrollIndicators="top|bottom"
+            android:fillViewport="true"
+            style="?attr/buttonBarStyle">
+    <com.android.internal.widget.ButtonBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="@dimen/button_bar_layout_start_padding"
+        android:paddingEnd="@dimen/button_bar_layout_end_padding"
+        android:paddingTop="@dimen/button_bar_layout_top_padding"
+        android:layoutDirection="locale"
+        android:orientation="horizontal"
+        android:gravity="left|center_vertical">
+
+        <Button
+            android:id="@+id/button3"
+            style="@style/CarAction1"
+            android:background="@drawable/car_dialog_button_background"
+            android:layout_marginRight="@dimen/button_end_margin"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/button_layout_height" />
+
+        <Button
+            android:id="@+id/button2"
+            style="@style/CarAction1"
+            android:background="@drawable/car_dialog_button_background"
+            android:layout_marginRight="@dimen/button_end_margin"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/button_layout_height" />
+
+        <Button
+            android:id="@+id/button1"
+            style="@style/CarAction1"
+            android:background="@drawable/car_dialog_button_background"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/button_layout_height" />
+        <Space
+            android:id="@+id/spacer"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:visibility="invisible" />
+    </com.android.internal.widget.ButtonBarLayout>
+</ScrollView>
diff --git a/core/res/res/layout-car/car_alert_dialog_title.xml b/core/res/res/layout-car/car_alert_dialog_title.xml
new file mode 100644
index 0000000..ba735a6
--- /dev/null
+++ b/core/res/res/layout-car/car_alert_dialog_title.xml
@@ -0,0 +1,62 @@
+<?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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/topPanel"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:gravity="center_vertical"
+              android:orientation="vertical">
+
+    <!-- If the client uses a customTitle, it will be added here. -->
+
+    <RelativeLayout
+        android:id="@+id/title_template"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/car_card_header_height"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="@dimen/image_size"
+            android:layout_height="@dimen/image_size"
+            android:layout_marginStart="@dimen/image_margin_start"
+            android:layout_alignParentStart="true"
+            android:layout_centerVertical="true"
+            android:scaleType="fitCenter"
+            android:src="@null" />
+
+        <com.android.internal.widget.DialogTitle
+            android:id="@+id/alertTitle"
+            android:maxLines="1"
+            android:ellipsize="none"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_toEndOf="@+id/icon"
+            android:textAlignment="viewStart"
+            android:layout_centerVertical="true"
+            android:layout_marginStart="@dimen/text_view_start_margin"
+            android:layout_marginEnd="@dimen/text_view_end_margin"
+            style="?attr/windowTitleStyle" />
+    </RelativeLayout>
+
+    <Space
+        android:id="@+id/titleDividerNoCustom"
+        android:visibility="gone"
+        android:layout_width="match_parent"
+        android:layout_height="0dp" />
+</LinearLayout>
diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml
index 0411f55..139185f 100644
--- a/core/res/res/layout/notification_template_material_conversation.xml
+++ b/core/res/res/layout/notification_template_material_conversation.xml
@@ -141,21 +141,20 @@
                         android:id="@+id/conversation_text"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
-                        android:layout_marginEnd="@dimen/notification_header_separating_margin"
+                        android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
                         android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
                         android:textSize="16sp"
                         android:singleLine="true"
                         android:layout_weight="1"
                         />
 
-
                     <TextView
                         android:id="@+id/app_name_divider"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:textAppearance="?attr/notificationHeaderTextAppearance"
-                        android:layout_marginStart="@dimen/notification_header_separating_margin"
-                        android:layout_marginEnd="@dimen/notification_header_separating_margin"
+                        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+                        android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
                         android:text="@string/notification_header_divider_symbol"
                         android:layout_gravity="center"
                         android:paddingTop="1sp"
@@ -170,9 +169,11 @@
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:layout_gravity="center"
-                        android:layout_marginStart="@dimen/notification_header_separating_margin"
+                        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+                        android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
                         android:paddingTop="1sp"
                         android:singleLine="true"
+                        android:visibility="gone"
                     />
 
                     <TextView
@@ -180,8 +181,8 @@
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:textAppearance="?attr/notificationHeaderTextAppearance"
-                        android:layout_marginStart="@dimen/notification_header_separating_margin"
-                        android:layout_marginEnd="@dimen/notification_header_separating_margin"
+                        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+                        android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
                         android:text="@string/notification_header_divider_symbol"
                         android:layout_gravity="center"
                         android:paddingTop="1sp"
@@ -191,11 +192,11 @@
 
                     <DateTimeView
                         android:id="@+id/time"
-                        android:textAppearance="@style/TextAppearance.Material.Notification.Time"
+                        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:layout_gravity="center"
-                        android:layout_marginStart="@dimen/notification_header_separating_margin"
+                        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
                         android:paddingTop="1sp"
                         android:showRelative="true"
                         android:singleLine="true"
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 1a8c129..8272fed 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1786,7 +1786,7 @@
     <string name="managed_profile_label_badge" msgid="6762559569999499495">"<xliff:g id="LABEL">%1$s</xliff:g> do traballo"</string>
     <string name="managed_profile_label_badge_2" msgid="5673187309555352550">"2.º <xliff:g id="LABEL">%1$s</xliff:g> do traballo"</string>
     <string name="managed_profile_label_badge_3" msgid="6882151970556391957">"3.º <xliff:g id="LABEL">%1$s</xliff:g> do traballo"</string>
-    <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Solicitar PIN para soltar fixación"</string>
+    <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Solicitar PIN para deixar de fixar"</string>
     <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Solicitar un padrón de desbloqueo antes de deixar de fixar a pantalla"</string>
     <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Solicitar un contrasinal para deixar de fixar a pantalla"</string>
     <string name="package_installed_device_owner" msgid="7035926868974878525">"Instalado polo teu administrador"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 2877e80..11683ae 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -1786,7 +1786,7 @@
     <string name="managed_profile_label_badge" msgid="6762559569999499495">"Ажлын <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_2" msgid="5673187309555352550">"2 дахь ажил <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_3" msgid="6882151970556391957">"3 дахь ажил <xliff:g id="LABEL">%1$s</xliff:g>"</string>
-    <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Тогтоосныг суллахаас өмнө PIN асуух"</string>
+    <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Бэхэлснийг болиулахаасаа өмнө PIN асуух"</string>
     <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Тогтоосныг суллахаас өмнө түгжээ тайлах хээ асуух"</string>
     <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Тогтоосныг суллахаас өмнө нууц үг асуух"</string>
     <string name="package_installed_device_owner" msgid="7035926868974878525">"Таны админ суулгасан"</string>
@@ -1841,7 +1841,7 @@
     <string name="zen_mode_default_weeknights_name" msgid="7902108149994062847">"Ажлын өдрийн шөнө"</string>
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Амралтын өдөр"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Үйл явдал"</string>
-    <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Идэвхгүй"</string>
+    <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Унтлагын цаг"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> зарим дууны дууг хааж байна"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Таны төхөөрөмжид дотоод алдаа байна.Та төхөөрөмжөө үйлдвэрээс гарсан төлөвт шилжүүлэх хүртэл таны төхөөрөмж чинь тогтворгүй байж болох юм."</string>
     <string name="system_error_manufacturer" msgid="703545241070116315">"Таны төхөөрөмжид дотоод алдаа байна. Дэлгэрэнгүй мэдээлэл авахыг хүсвэл үйлдвэрлэгчтэйгээ холбоо барина уу."</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 1f72b8b..d910fc0 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1551,7 +1551,7 @@
     <string name="activity_resolver_use_once" msgid="948462794469672658">"တစ်ခါတည်း"</string>
     <string name="activity_resolver_work_profiles_support" msgid="4071345609235361269">"%1$s က အလုပ်ပရိုဖိုင်ကို မပံ့ပိုးပါ။"</string>
     <string name="default_audio_route_name" product="tablet" msgid="367936735632195517">"တက်ဘလက်"</string>
-    <string name="default_audio_route_name" product="tv" msgid="4908971385068087367">"တီဗွီ"</string>
+    <string name="default_audio_route_name" product="tv" msgid="4908971385068087367">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="9213546147739983977">"ဖုန်း"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="1551166029093995289">"အထိုင်ရှိသော စပီကာများ"</string>
     <string name="default_audio_route_name_hdmi" msgid="5474470558160717850">"HDMI"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 1739453..b1c4fbb 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -951,11 +951,11 @@
     <string name="autofill_area" msgid="8289022370678448983">"Gebied"</string>
     <string name="autofill_emirate" msgid="2544082046790551168">"Emiraat"</string>
     <string name="permlab_readHistoryBookmarks" msgid="9102293913842539697">"je webbladwijzers en -geschiedenis lezen"</string>
-    <string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"Hiermee kan de app de geschiedenis lezen van alle URL\'s die in de systeemeigen browser zijn bezocht, en alle bladwijzers in de systeemeigen browser. Let op: deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden."</string>
+    <string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"Hiermee kan de app de geschiedenis lezen van alle URL\'s die in de systeemeigen browser zijn bezocht, en alle bookmarks in de systeemeigen browser. Let op: deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"webbladwijzers en -geschiedenis schrijven"</string>
-    <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"Hiermee kan de app de webgeschiedenis wijzigen in de systeemeigen browser en de bladwijzers die zijn opgeslagen op je tablet. Deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden.."</string>
-    <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Hiermee kan de app de browsergeschiedenis of opgeslagen bladwijzers bewerken op je Android TV-apparaat. Hierdoor kan de app mogelijk browsergegevens wissen of aanpassen. Opmerking: Dit recht kan niet worden afgedwongen door andere browsers of andere apps met internetmogelijkheden."</string>
-    <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"Hiermee kan de app de webgeschiedenis wijzigen in de systeemeigen browser en de bladwijzers die zijn opgeslagen op je telefoon. Deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden."</string>
+    <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"Hiermee kan de app de webgeschiedenis wijzigen in de systeemeigen browser en de bookmarks die zijn opgeslagen op je tablet. Deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden.."</string>
+    <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Hiermee kan de app de browsergeschiedenis of opgeslagen bookmarks bewerken op je Android TV-apparaat. Hierdoor kan de app mogelijk browsergegevens wissen of aanpassen. Opmerking: Dit recht kan niet worden afgedwongen door andere browsers of andere apps met internetmogelijkheden."</string>
+    <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"Hiermee kan de app de webgeschiedenis wijzigen in de systeemeigen browser en de bookmarks die zijn opgeslagen op je telefoon. Deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden."</string>
     <string name="permlab_setAlarm" msgid="1158001610254173567">"een wekker instellen"</string>
     <string name="permdesc_setAlarm" msgid="2185033720060109640">"Hiermee kan de app een wekker instellen in een geïnstalleerde wekker-app. Deze functie wordt door sommige wekker-apps niet geïmplementeerd."</string>
     <string name="permlab_addVoicemail" msgid="4770245808840814471">"voicemail toevoegen"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 4224523..5986e76 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -787,7 +787,7 @@
     <string name="imProtocolYahoo" msgid="5373338758093392231">"Yahoo"</string>
     <string name="imProtocolSkype" msgid="1486297589164830043">"Skype"</string>
     <string name="imProtocolQq" msgid="7254708777029006592">"QQ"</string>
-    <string name="imProtocolGoogleTalk" msgid="9194016024343166782">"ହ୍ୟାଙ୍ଗଆଉଟ୍ସ"</string>
+    <string name="imProtocolGoogleTalk" msgid="9194016024343166782">"Hangouts"</string>
     <string name="imProtocolIcq" msgid="2410325380427389521">"ICQ"</string>
     <string name="imProtocolJabber" msgid="7919269388889582015">"Jabber"</string>
     <string name="imProtocolNetMeeting" msgid="4985002408136148256">"NetMeeting"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b79c9e8..0e11d49 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4313,10 +4313,6 @@
     notifications until they target R -->
     <string-array name="config_notificationMsgPkgsAllowedAsConvos" translatable="false"/>
 
-    <!-- Contains a blacklist of apps that should not get pre-installed carrier app permission
-         grants, even if the UICC claims that the app should be privileged. See b/138150105 -->
-    <string-array name="config_restrictedPreinstalledCarrierApps" translatable="false"/>
-
     <!-- Sharesheet: define a max number of targets per application for new shortcuts-based direct share introduced in Q -->
     <integer name="config_maxShortcutTargetsPerApp">3</integer>
 
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index a771904..ebaf85c 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -273,6 +273,9 @@
     <!-- The margin before and after each of the items in the notification header. -->
     <dimen name="notification_header_separating_margin">2dp</dimen>
 
+    <!-- The margin before and after each of the items in the conversation header. -->
+    <dimen name="notification_conversation_header_separating_margin">4dp</dimen>
+
     <!-- The absolute size of the notification expand icon. -2 for wrap_content. -->
     <dimen name="notification_header_expand_icon_size">-2px</dimen>
 
@@ -719,7 +722,7 @@
     <!-- The width of the protection of the face pile layout when expanded-->
     <dimen name="conversation_face_pile_protection_width_expanded">1dp</dimen>
     <!-- The padding of the expanded message container-->
-    <dimen name="expanded_group_conversation_message_padding">14dp</dimen>
+    <dimen name="expanded_group_conversation_message_padding">17dp</dimen>
     <!-- The stroke width of the ring used to visually mark a conversation as important -->
     <dimen name="importance_ring_stroke_width">2dp</dimen>
     <!-- The maximum stroke width used for the animation shown when a conversation is marked as important -->
@@ -728,10 +731,10 @@
     <dimen name="importance_ring_size">20dp</dimen>
 
     <!-- The top padding of the conversation icon container in the regular state-->
-    <dimen name="conversation_icon_container_top_padding">9dp</dimen>
+    <dimen name="conversation_icon_container_top_padding">12dp</dimen>
 
     <!-- The top padding of the conversation icon container when the avatar is small-->
-    <dimen name="conversation_icon_container_top_padding_small_avatar">17.5dp</dimen>
+    <dimen name="conversation_icon_container_top_padding_small_avatar">9dp</dimen>
 
     <!-- The padding of the conversation header when expanded. This is calculated from the expand button size + notification_content_margin_end -->
     <dimen name="conversation_header_expanded_padding_end">38dp</dimen>
diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml
index e9ac679..ef019ba 100644
--- a/core/res/res/values/styles_device_defaults.xml
+++ b/core/res/res/values/styles_device_defaults.xml
@@ -296,9 +296,12 @@
     <style name="TextAppearance.DeviceDefault.Notification.Info" parent="TextAppearance.Material.Notification.Info">
         <item name="fontFamily">@string/config_bodyFontFamily</item>
     </style>
+    <style name="TextAppearance.DeviceDefault.Notification.Time" parent="TextAppearance.Material.Notification.Time">
+        <item name="fontFamily">@string/config_bodyFontFamily</item>
+    </style>
     <style name="TextAppearance.DeviceDefault.Notification.Conversation.AppName"
-           parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title">
-        <item name="android:textSize">16sp</item>
+           parent="TextAppearance.Material.Notification.Conversation.AppName">
+        <item name="fontFamily">@string/config_headlineFontFamilyMedium</item>
     </style>
     <style name="TextAppearance.DeviceDefault.Widget" parent="TextAppearance.Material.Widget">
         <item name="fontFamily">@string/config_bodyFontFamily</item>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 2415837..67536fd 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -491,6 +491,10 @@
         <item name="textColor">#66000000</item>
     </style>
 
+    <style name="TextAppearance.Material.Notification.Conversation.AppName" parent="TextAppearance.Material.Notification.Title">
+        <item name="android:textSize">16sp</item>
+    </style>
+
     <style name="TextAppearance.Material.ListItem" parent="TextAppearance.Material.Subhead" />
     <style name="TextAppearance.Material.ListItemSecondary" parent="TextAppearance.Material.Body1" />
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 23ae1e7..8e1dc89 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3827,10 +3827,10 @@
 
   <java-symbol type="string" name="config_defaultSupervisionProfileOwnerComponent" />
   <java-symbol type="bool" name="config_inflateSignalStrength" />
-  <java-symbol type="array" name="config_restrictedPreinstalledCarrierApps" />
 
   <java-symbol type="drawable" name="android_logotype" />
   <java-symbol type="layout" name="platlogo_layout" />
+  <java-symbol type="drawable" name="ic_number11" />
 
   <java-symbol type="integer" name="config_notificationWarnRemoteViewSizeBytes" />
   <java-symbol type="integer" name="config_notificationStripRemoteViewSizeBytes" />
@@ -3956,6 +3956,13 @@
   <java-symbol type="id" name="conversation_unread_count" />
   <java-symbol type="string" name="unread_convo_overflow" />
   <java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Conversation.AppName" />
+  <java-symbol type="drawable" name="conversation_badge_background" />
+  <java-symbol type="drawable" name="conversation_badge_ring" />
+  <java-symbol type="color" name="conversation_important_highlight" />
+  <java-symbol type="dimen" name="importance_ring_stroke_width" />
+  <java-symbol type="dimen" name="importance_ring_anim_max_stroke_width" />
+  <java-symbol type="dimen" name="importance_ring_size" />
+  <java-symbol type="dimen" name="conversation_icon_size_badged" />
 
   <!-- Intent resolver and share sheet -->
   <java-symbol type="string" name="resolver_personal_tab" />
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 70917e7..d5733e3 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -39,7 +39,7 @@
     <!-- Albania: 5 digits, known short codes listed -->
     <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" />
 
-    <!-- Argentia: 5 digits, known short codes listed -->
+    <!-- Argentina: 5 digits, known short codes listed -->
     <shortcode country="ar" pattern="\\d{5}" free="11711|28291" />
 
     <!-- Armenia: 3-4 digits, emergency numbers 10[123] -->
@@ -70,7 +70,7 @@
     <shortcode country="by" pattern="\\d{4}" premium="3336|4161|444[4689]|501[34]|7781" />
 
     <!-- Canada: 5-6 digits -->
-    <shortcode country="ca" pattern="\\d{5,6}" premium="60999|88188|43030" standard="244444" />
+    <shortcode country="ca" pattern="\\d{5,6}" premium="60999|88188|43030" standard="244444" free="455677" />
 
     <!-- Switzerland: 3-5 digits: http://www.swisscom.ch/fxres/kmu/thirdpartybusiness_code_of_conduct_en.pdf -->
     <shortcode country="ch" pattern="[2-9]\\d{2,4}" premium="543|83111|30118" free="98765|30075|30047" />
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index d4c2569..964ae21 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -678,7 +678,8 @@
             final InsetsState currentState = new InsetsState(mController.getState());
             // The caption bar source should be synced with the info in mAttachInfo.
             assertEquals(captionFrame, currentState.peekSource(ITYPE_CAPTION_BAR).getFrame());
-            assertTrue(currentState.equals(state, true /* excludingCaptionInsets*/));
+            assertTrue(currentState.equals(state, true /* excludingCaptionInsets*/,
+                    true /* excludeInvisibleIme */));
             mController.setCaptionInsetsHeight(0);
             mController.onStateChanged(state);
             // The caption bar source should not be there at all, because we don't add empty
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index cd93eeb..7115acf 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -276,6 +276,15 @@
     }
 
     @Test
+    public void testEquals_excludeInvisibleIme() {
+        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
+        mState.getSource(ITYPE_IME).setVisible(false);
+        mState2.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 200));
+        mState2.getSource(ITYPE_IME).setVisible(false);
+        assertTrue(mState2.equals(mState, true, true /* excludeInvisibleIme */));
+    }
+
+    @Test
     public void testParcelUnparcel() {
         mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
         mState.getSource(ITYPE_IME).setVisibleFrame(new Rect(0, 0, 50, 10));
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 5471768..49de7c8 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -21,6 +21,7 @@
 import static androidx.test.espresso.action.ViewActions.swipeUp;
 import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.hasSibling;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
@@ -299,9 +300,8 @@
     public void fourOptionsStackedIntoOneTarget() throws InterruptedException {
         Intent sendIntent = createSendTextIntent();
 
-        // create 12 unique app targets to ensure the app ranking row can be filled, otherwise
-        // targets will not stack
-        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(12);
+        // create just enough targets to ensure the a-z list should be shown
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(1);
 
         // next create 4 targets in a single app that should be stacked into a single target
         String packageName = "xxx.yyy";
@@ -328,8 +328,8 @@
                 .launchActivity(Intent.createChooser(sendIntent, null));
         waitForIdle();
 
-        // expect 12 unique targets + 1 group + 4 ranked app targets
-        assertThat(activity.getAdapter().getCount(), is(17));
+        // expect 1 unique targets + 1 group + 4 ranked app targets
+        assertThat(activity.getAdapter().getCount(), is(6));
 
         ResolveInfo[] chosen = new ResolveInfo[1];
         sOverrides.onSafelyStartCallback = targetInfo -> {
@@ -337,7 +337,7 @@
             return true;
         };
 
-        onView(withText(appName)).perform(click());
+        onView(allOf(withText(appName), hasSibling(withText("")))).perform(click());
         waitForIdle();
 
         // clicking will launch a dialog to choose the activity within the app
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 44a5263..0f6b51f 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -222,6 +222,11 @@
         super.queryTargetServices(adapter);
     }
 
+    @Override
+    protected boolean isQuietModeEnabled(UserHandle userHandle) {
+        return sOverrides.isQuietModeEnabled;
+    }
+
     /**
      * We cannot directly mock the activity created since instrumentation creates it.
      * <p>
diff --git a/core/tests/overlaytests/remount/Android.bp b/core/tests/overlaytests/remount/Android.bp
index 4e79a45..939334c 100644
--- a/core/tests/overlaytests/remount/Android.bp
+++ b/core/tests/overlaytests/remount/Android.bp
@@ -19,6 +19,9 @@
         "tradefed",
         "junit",
     ],
+    static_libs: [
+        "frameworks-base-hostutils",
+    ],
     test_suites: ["general-tests"],
     java_resources: [
         ":com.android.overlaytest.overlaid",
diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java
index 14b5bf6..1a39e20 100644
--- a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java
+++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.fail;
 
+import com.android.internal.util.test.SystemPreparer;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java
deleted file mode 100644
index bb72d0e..0000000
--- a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.overlaytest.remounted;
-
-import static org.junit.Assert.assertTrue;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-
-import org.junit.Assert;
-import org.junit.rules.ExternalResource;
-import org.junit.rules.TemporaryFolder;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-
-class SystemPreparer extends ExternalResource {
-    private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000;
-
-    // The paths of the files pushed onto the device through this rule.
-    private ArrayList<String> mPushedFiles = new ArrayList<>();
-
-    // The package names of packages installed through this rule.
-    private ArrayList<String> mInstalledPackages = new ArrayList<>();
-
-    private final TemporaryFolder mHostTempFolder;
-    private final DeviceProvider mDeviceProvider;
-
-    SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) {
-        mHostTempFolder = hostTempFolder;
-        mDeviceProvider = deviceProvider;
-    }
-
-    /** Copies a file within the host test jar to a path on device. */
-    SystemPreparer pushResourceFile(String resourcePath,
-            String outputPath) throws DeviceNotAvailableException, IOException {
-        final ITestDevice device = mDeviceProvider.getDevice();
-        remount();
-        assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath));
-        mPushedFiles.add(outputPath);
-        return this;
-    }
-
-    /** Installs an APK within the host test jar onto the device. */
-    SystemPreparer installResourceApk(String resourcePath, String packageName)
-            throws DeviceNotAvailableException, IOException {
-        final ITestDevice device = mDeviceProvider.getDevice();
-        final File tmpFile = copyResourceToTemp(resourcePath);
-        final String result = device.installPackage(tmpFile, true /* reinstall */);
-        Assert.assertNull(result);
-        mInstalledPackages.add(packageName);
-        return this;
-    }
-
-    /** Sets the enable state of an overlay package. */
-    SystemPreparer setOverlayEnabled(String packageName, boolean enabled)
-            throws DeviceNotAvailableException {
-        final ITestDevice device = mDeviceProvider.getDevice();
-        final String enable = enabled ? "enable" : "disable";
-
-        // Wait for the overlay to change its enabled state.
-        final long endMillis = System.currentTimeMillis() + OVERLAY_ENABLE_TIMEOUT_MS;
-        String result;
-        while (System.currentTimeMillis() <= endMillis) {
-            device.executeShellCommand(String.format("cmd overlay %s %s", enable, packageName));
-            result = device.executeShellCommand("cmd overlay dump isenabled "
-                    + packageName);
-            if (((enabled) ? "true\n" : "false\n").equals(result)) {
-                return this;
-            }
-
-            try {
-                Thread.sleep(200);
-            } catch (InterruptedException ignore) {
-            }
-        }
-
-        throw new IllegalStateException(String.format("Failed to %s overlay %s:\n%s", enable,
-                packageName, device.executeShellCommand("cmd overlay list")));
-    }
-
-    /** Restarts the device and waits until after boot is completed. */
-    SystemPreparer reboot() throws DeviceNotAvailableException {
-        final ITestDevice device = mDeviceProvider.getDevice();
-        device.reboot();
-        return this;
-    }
-
-    SystemPreparer remount() throws DeviceNotAvailableException {
-        mDeviceProvider.getDevice().executeAdbCommand("remount");
-        return this;
-    }
-
-    /** Copies a file within the host test jar to a temporary file on the host machine. */
-    private File copyResourceToTemp(String resourcePath) throws IOException {
-        final File tempFile = mHostTempFolder.newFile(resourcePath);
-        final ClassLoader classLoader = getClass().getClassLoader();
-        try (InputStream assetIs = classLoader.getResource(resourcePath).openStream();
-             FileOutputStream assetOs = new FileOutputStream(tempFile)) {
-            if (assetIs == null) {
-                throw new IllegalStateException("Failed to find resource " + resourcePath);
-            }
-
-            int b;
-            while ((b = assetIs.read()) >= 0) {
-                assetOs.write(b);
-            }
-        }
-
-        return tempFile;
-    }
-
-    /** Removes installed packages and files that were pushed to the device. */
-    @Override
-    protected void after() {
-        final ITestDevice device = mDeviceProvider.getDevice();
-        try {
-            remount();
-            for (final String file : mPushedFiles) {
-                device.deleteFile(file);
-            }
-            for (final String packageName : mInstalledPackages) {
-                device.uninstallPackage(packageName);
-            }
-            device.reboot();
-        } catch (DeviceNotAvailableException e) {
-            Assert.fail(e.toString());
-        }
-    }
-
-    interface DeviceProvider {
-        ITestDevice getDevice();
-    }
-}
diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml
index 17e1f2e..6255584 100644
--- a/data/etc/preinstalled-packages-platform.xml
+++ b/data/etc/preinstalled-packages-platform.xml
@@ -17,7 +17,9 @@
 <!--
 This XML file declares which system packages should be initially installed for new users based on
 their user type. All system packages on the device should ideally have an entry in an xml file
-(keyed by its manifest name).
+(keyed by its manifest name), except auto-generated rro packages. Auto-generated RRO packages 
+(package name ends with ".auto_generated_rro_product__" or ".auto_generated_rro_vendor__")
+will be installed for new users according to corresponding overlay target packages.
 
 Base user-types (every user will be at least one of these types) are:
   SYSTEM    (user 0)
@@ -51,7 +53,7 @@
 
 
 2. For a system package to be pre-installed on all human users (e.g. a web browser), i.e. to be
-installed on any user of type type FULL or PROFILE (since this covers all human users):
+installed on any user of type FULL or PROFILE (since this covers all human users):
 
    <install-in-user-type package="com.android.example">
        <install-in user-type="FULL" />
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index a112bdd0..7d15bbd 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -2583,9 +2583,15 @@
         }
 
         public void cancel() {
+            remove();
+        }
+
+        private Consumer<Location> remove() {
+            Consumer<Location> consumer;
             ICancellationSignal cancellationSignal;
             synchronized (this) {
                 mExecutor = null;
+                consumer = mConsumer;
                 mConsumer = null;
 
                 if (mAlarmManager != null) {
@@ -2605,6 +2611,8 @@
                     // ignore
                 }
             }
+
+            return consumer;
         }
 
         public void fail() {
@@ -2663,16 +2671,10 @@
         }
 
         private void acceptResult(Location location) {
-            Consumer<Location> consumer;
-            synchronized (this) {
-                if (mConsumer == null) {
-                    return;
-                }
-                consumer = mConsumer;
-                cancel();
+            Consumer<Location> consumer = remove();
+            if (consumer != null) {
+                consumer.accept(location);
             }
-
-            consumer.accept(location);
         }
     }
 
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index 9846436..67a040d 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -18,7 +18,6 @@
 
 import android.app.Notification;
 import android.app.NotificationManager;
-import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -402,13 +401,9 @@
             mNiNotificationBuilder.setDefaults(0);
         }
 
-        // if not to popup dialog immediately, pending intent will open the dialog
-        Intent intent = !mPopupImmediately ? getDlgIntent(notif) : new Intent();
-        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
         mNiNotificationBuilder.setTicker(getNotifTicker(notif, mContext))
                 .setContentTitle(title)
-                .setContentText(message)
-                .setContentIntent(pi);
+                .setContentText(message);
 
         notificationManager.notifyAsUser(null, notif.notificationId, mNiNotificationBuilder.build(),
                 UserHandle.ALL);
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index e5ad569..54c0bc9 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -317,9 +317,10 @@
     @ConnectionState
     final int mConnectionState;
     final String mClientPackageName;
-    final int mVolume;
-    final int mVolumeMax;
     final int mVolumeHandling;
+    final int mVolumeMax;
+    final int mVolume;
+    final String mAddress;
     final Bundle mExtras;
     final String mProviderId;
 
@@ -336,6 +337,7 @@
         mVolumeHandling = builder.mVolumeHandling;
         mVolumeMax = builder.mVolumeMax;
         mVolume = builder.mVolume;
+        mAddress = builder.mAddress;
         mExtras = builder.mExtras;
         mProviderId = builder.mProviderId;
     }
@@ -353,6 +355,7 @@
         mVolumeHandling = in.readInt();
         mVolumeMax = in.readInt();
         mVolume = in.readInt();
+        mAddress = in.readString();
         mExtras = in.readBundle();
         mProviderId = in.readString();
     }
@@ -483,6 +486,15 @@
         return mVolume;
     }
 
+    /**
+     * Gets the hardware address of the route if available.
+     * @hide
+     */
+    @Nullable
+    public String getAddress() {
+        return mAddress;
+    }
+
     @Nullable
     public Bundle getExtras() {
         return mExtras == null ? null : new Bundle(mExtras);
@@ -564,6 +576,7 @@
                 && (mVolumeHandling == other.mVolumeHandling)
                 && (mVolumeMax == other.mVolumeMax)
                 && (mVolume == other.mVolume)
+                && Objects.equals(mAddress, other.mAddress)
                 && Objects.equals(mProviderId, other.mProviderId);
     }
 
@@ -572,7 +585,7 @@
         // Note: mExtras is not included.
         return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription,
                 mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume,
-                mProviderId);
+                mAddress, mProviderId);
     }
 
     @Override
@@ -614,6 +627,7 @@
         dest.writeInt(mVolumeHandling);
         dest.writeInt(mVolumeMax);
         dest.writeInt(mVolume);
+        dest.writeString(mAddress);
         dest.writeBundle(mExtras);
         dest.writeString(mProviderId);
     }
@@ -637,6 +651,7 @@
         int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
         int mVolumeMax;
         int mVolume;
+        String mAddress;
         Bundle mExtras;
         String mProviderId;
 
@@ -669,24 +684,7 @@
          * @param routeInfo the existing instance to copy data from.
          */
         public Builder(@NonNull MediaRoute2Info routeInfo) {
-            Objects.requireNonNull(routeInfo, "routeInfo must not be null");
-
-            mId = routeInfo.mId;
-            mName = routeInfo.mName;
-            mFeatures = new ArrayList<>(routeInfo.mFeatures);
-            mType = routeInfo.mType;
-            mIsSystem = routeInfo.mIsSystem;
-            mIconUri = routeInfo.mIconUri;
-            mDescription = routeInfo.mDescription;
-            mConnectionState = routeInfo.mConnectionState;
-            mClientPackageName = routeInfo.mClientPackageName;
-            mVolumeHandling = routeInfo.mVolumeHandling;
-            mVolumeMax = routeInfo.mVolumeMax;
-            mVolume = routeInfo.mVolume;
-            if (routeInfo.mExtras != null) {
-                mExtras = new Bundle(routeInfo.mExtras);
-            }
-            mProviderId = routeInfo.mProviderId;
+            this(routeInfo.mId, routeInfo);
         }
 
         /**
@@ -715,6 +713,7 @@
             mVolumeHandling = routeInfo.mVolumeHandling;
             mVolumeMax = routeInfo.mVolumeMax;
             mVolume = routeInfo.mVolume;
+            mAddress = routeInfo.mAddress;
             if (routeInfo.mExtras != null) {
                 mExtras = new Bundle(routeInfo.mExtras);
             }
@@ -865,6 +864,16 @@
         }
 
         /**
+         * Sets the hardware address of the route.
+         * @hide
+         */
+        @NonNull
+        public Builder setAddress(String address) {
+            mAddress = address;
+            return this;
+        }
+
+        /**
          * Sets a bundle of extras for the route.
          * <p>
          * Note: The extras will not affect the result of {@link MediaRoute2Info#equals(Object)}.
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 05c6e3a..908fd82 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -40,8 +40,10 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Deque;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -132,15 +134,21 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface Reason {}
 
+    private static final int MAX_REQUEST_IDS_SIZE = 500;
+
     private final Handler mHandler;
     private final Object mSessionLock = new Object();
+    private final Object mRequestIdsLock = new Object();
     private final AtomicBoolean mStatePublishScheduled = new AtomicBoolean(false);
     private MediaRoute2ProviderServiceStub mStub;
     private IMediaRoute2ProviderServiceCallback mRemoteCallback;
     private volatile MediaRoute2ProviderInfo mProviderInfo;
 
+    @GuardedBy("mRequestIdsLock")
+    private final Deque<Long> mRequestIds = new ArrayDeque<>(MAX_REQUEST_IDS_SIZE);
+
     @GuardedBy("mSessionLock")
-    private ArrayMap<String, RoutingSessionInfo> mSessionInfo = new ArrayMap<>();
+    private final ArrayMap<String, RoutingSessionInfo> mSessionInfo = new ArrayMap<>();
 
     public MediaRoute2ProviderService() {
         mHandler = new Handler(Looper.getMainLooper());
@@ -230,6 +238,11 @@
             @NonNull RoutingSessionInfo sessionInfo) {
         Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
 
+        if (requestId != REQUEST_ID_NONE && !removeRequestId(requestId)) {
+            Log.w(TAG, "notifySessionCreated: The requestId doesn't exist. requestId=" + requestId);
+            return;
+        }
+
         String sessionId = sessionInfo.getId();
         synchronized (mSessionLock) {
             if (mSessionInfo.containsKey(sessionId)) {
@@ -322,6 +335,13 @@
         if (mRemoteCallback == null) {
             return;
         }
+
+        if (!removeRequestId(requestId)) {
+            Log.w(TAG, "notifyRequestFailed: The requestId doesn't exist. requestId="
+                    + requestId);
+            return;
+        }
+
         try {
             mRemoteCallback.notifyRequestFailed(requestId, reason);
         } catch (RemoteException ex) {
@@ -469,6 +489,36 @@
         }
     }
 
+    /**
+     * Adds a requestId in the request ID list whose max size is {@link #MAX_REQUEST_IDS_SIZE}.
+     * When the max size is reached, the first element is removed (FIFO).
+     */
+    private void addRequestId(long requestId) {
+        synchronized (mRequestIdsLock) {
+            if (mRequestIds.size() >= MAX_REQUEST_IDS_SIZE) {
+                mRequestIds.removeFirst();
+            }
+            mRequestIds.addLast(requestId);
+        }
+    }
+
+    /**
+     * Removes the given {@code requestId} from received request ID list.
+     * <p>
+     * Returns whether the list contains the {@code requestId}. These are the cases when the list
+     * doesn't contain the given {@code requestId}:
+     * <ul>
+     *     <li>This service has never received a request with the requestId. </li>
+     *     <li>{@link #notifyRequestFailed} or {@link #notifySessionCreated} already has been called
+     *         for the requestId. </li>
+     * </ul>
+     */
+    private boolean removeRequestId(long requestId) {
+        synchronized (mRequestIdsLock) {
+            return mRequestIds.removeFirstOccurrence(requestId);
+        }
+    }
+
     final class MediaRoute2ProviderServiceStub extends IMediaRoute2ProviderService.Stub {
         MediaRoute2ProviderServiceStub() { }
 
@@ -529,6 +579,7 @@
             if (!checkRouteIdIsValid(routeId, "setRouteVolume")) {
                 return;
             }
+            addRequestId(requestId);
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetRouteVolume,
                     MediaRoute2ProviderService.this, requestId, routeId, volume));
         }
@@ -542,6 +593,7 @@
             if (!checkRouteIdIsValid(routeId, "requestCreateSession")) {
                 return;
             }
+            addRequestId(requestId);
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession,
                     MediaRoute2ProviderService.this, requestId, packageName, routeId,
                     requestCreateSession));
@@ -556,6 +608,7 @@
                     || !checkRouteIdIsValid(routeId, "selectRoute")) {
                 return;
             }
+            addRequestId(requestId);
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
                     MediaRoute2ProviderService.this, requestId, sessionId, routeId));
         }
@@ -569,6 +622,7 @@
                     || !checkRouteIdIsValid(routeId, "deselectRoute")) {
                 return;
             }
+            addRequestId(requestId);
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute,
                     MediaRoute2ProviderService.this, requestId, sessionId, routeId));
         }
@@ -582,6 +636,7 @@
                     || !checkRouteIdIsValid(routeId, "transferToRoute")) {
                 return;
             }
+            addRequestId(requestId);
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute,
                     MediaRoute2ProviderService.this, requestId, sessionId, routeId));
         }
@@ -594,6 +649,7 @@
             if (!checkSessionIdIsValid(sessionId, "setSessionVolume")) {
                 return;
             }
+            addRequestId(requestId);
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetSessionVolume,
                     MediaRoute2ProviderService.this, requestId, sessionId, volume));
         }
@@ -606,6 +662,7 @@
             if (!checkSessionIdIsValid(sessionId, "releaseSession")) {
                 return;
             }
+            addRequestId(requestId);
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession,
                     MediaRoute2ProviderService.this, requestId, sessionId));
         }
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl
index de4d060..a237ec1 100644
--- a/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl
@@ -43,9 +43,9 @@
     boolean triggerInData;
     /**
      * Audio format of either the trigger in event data or to use for capture of the rest of the
-     * utterance.
+     * utterance. May be null when no audio is available for this event type.
      */
-    AudioConfig audioConfig;
+    @nullable AudioConfig audioConfig;
     /** Additional data. */
     byte[] data;
 }
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index a03b24c..f970b59 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -233,10 +233,12 @@
 }
 
 void JMediaCodec::releaseAsync() {
-    if (mCodec != NULL) {
-        mCodec->releaseAsync();
-    }
-    mInitStatus = NO_INIT;
+    std::call_once(mAsyncReleaseFlag, [this] {
+        if (mCodec != NULL) {
+            mCodec->releaseAsync(new AMessage(kWhatAsyncReleaseComplete, this));
+        }
+        mInitStatus = NO_INIT;
+    });
 }
 
 JMediaCodec::~JMediaCodec() {
@@ -1084,6 +1086,12 @@
             handleFrameRenderedNotification(msg);
             break;
         }
+        case kWhatAsyncReleaseComplete:
+        {
+            mCodec.clear();
+            mLooper->stop();
+            break;
+        }
         default:
             TRESPASS();
     }
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index 5c34341..a58f9a7 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -173,6 +173,7 @@
     enum {
         kWhatCallbackNotify,
         kWhatFrameRendered,
+        kWhatAsyncReleaseComplete,
     };
 
     jclass mClass;
@@ -185,6 +186,7 @@
     bool mGraphicOutput{false};
     bool mHasCryptoOrDescrambler{false};
     std::once_flag mReleaseFlag;
+    std::once_flag mAsyncReleaseFlag;
 
     sp<AMessage> mCallbackNotification;
     sp<AMessage> mOnFrameRenderedNotification;
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 9575581..638a842 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -593,11 +593,16 @@
 
         final int failureReason = REASON_REJECTED;
         final CountDownLatch onRequestFailedLatch = new CountDownLatch(1);
+        final CountDownLatch onRequestFailedSecondCallLatch = new CountDownLatch(1);
         addManagerCallback(new MediaRouter2Manager.Callback() {
             @Override
             public void onRequestFailed(int reason) {
                 if (reason == failureReason) {
-                    onRequestFailedLatch.countDown();
+                    if (onRequestFailedLatch.getCount() > 0) {
+                        onRequestFailedLatch.countDown();
+                    } else {
+                        onRequestFailedSecondCallLatch.countDown();
+                    }
                 }
             }
         });
@@ -609,6 +614,11 @@
         final long validRequestId = requestIds.get(0);
         instance.notifyRequestFailed(validRequestId, failureReason);
         assertTrue(onRequestFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        // Test calling notifyRequestFailed() multiple times with the same valid requestId.
+        // onRequestFailed() shouldn't be called since the requestId has been already handled.
+        instance.notifyRequestFailed(validRequestId, failureReason);
+        assertFalse(onRequestFailedSecondCallLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
     @Test
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
index d20ab49..ab94265 100644
--- a/packages/CarSystemUI/res/values/colors.xml
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -15,7 +15,6 @@
   ~ limitations under the License
   -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <color name="nav_bar_ripple_background_color">#40ffffff</color>
     <!-- colors for user switcher -->
     <color name="car_user_switcher_background_color">#000000</color>
     <color name="car_user_switcher_name_text_color">@*android:color/car_body1_light</color>
diff --git a/packages/CarSystemUI/res/values/styles.xml b/packages/CarSystemUI/res/values/styles.xml
index 7fc69e6..e76373d 100644
--- a/packages/CarSystemUI/res/values/styles.xml
+++ b/packages/CarSystemUI/res/values/styles.xml
@@ -37,13 +37,9 @@
         <item name="android:textColor">@*android:color/car_grey_50</item>
     </style>
 
-    <style name="CarNavigationBarButtonTheme">
-        <item name="android:colorControlHighlight">@color/nav_bar_ripple_background_color</item>
-    </style>
-
     <style name="NavigationBarButton">
         <item name="android:layout_height">96dp</item>
         <item name="android:layout_width">96dp</item>
-        <item name="android:background">@*android:drawable/item_background_material</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
index 2dad5f8..69766cc 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
@@ -103,6 +103,7 @@
     private KeyguardBouncer mBouncer;
     private OnKeyguardCancelClickedListener mKeyguardCancelClickedListener;
     private boolean mShowing;
+    private boolean mIsOccluded;
 
     @Inject
     public CarKeyguardViewController(
@@ -220,6 +221,7 @@
 
     @Override
     public void setOccluded(boolean occluded, boolean animate) {
+        mIsOccluded = occluded;
         getOverlayViewGlobalStateController().setOccluded(occluded);
         if (!occluded) {
             reset(/* hideBouncerWhenShowing= */ false);
@@ -244,6 +246,12 @@
 
     @Override
     public void dismissAndCollapse() {
+        // If dismissing and collapsing Keyguard is requested (e.g. by a Keyguard-dismissing
+        // Activity) while Keyguard is occluded, unocclude Keyguard so the user can authenticate to
+        // dismiss Keyguard.
+        if (mIsOccluded) {
+            setOccluded(/* occluded= */ false, /* animate= */ false);
+        }
         if (!mBouncer.isSecure()) {
             hide(/* startTime= */ 0, /* fadeoutDuration= */ 0);
         }
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java
index 6d140ca..7d353f5a 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.car.notification;
 
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.car.window.OverlayPanelViewController;
@@ -37,6 +38,7 @@
             NotificationPanelViewController notificationPanelViewController,
 
             PowerManagerHelper powerManagerHelper,
+            BroadcastDispatcher broadcastDispatcher,
 
             CarDeviceProvisionedController carDeviceProvisionedController,
             ConfigurationController configurationController
@@ -44,6 +46,7 @@
         super(carNavigationBarController,
                 notificationPanelViewController,
                 powerManagerHelper,
+                broadcastDispatcher,
                 carDeviceProvisionedController,
                 configurationController);
         notificationPanelViewController.setOverlayDirection(
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
index 41349b2..0c185ba 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
@@ -17,10 +17,17 @@
 package com.android.systemui.car.notification;
 
 import android.car.hardware.power.CarPowerManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.Configuration;
+import android.os.UserHandle;
+import android.util.Log;
 
 import androidx.annotation.CallSuper;
 
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.car.window.OverlayViewMediator;
@@ -37,18 +44,36 @@
 public class NotificationPanelViewMediator implements OverlayViewMediator,
         ConfigurationController.ConfigurationListener {
 
+    private static final boolean DEBUG = false;
+    private static final String TAG = "NotificationPanelVM";
+
     private final CarNavigationBarController mCarNavigationBarController;
     private final NotificationPanelViewController mNotificationPanelViewController;
     private final PowerManagerHelper mPowerManagerHelper;
+    private final BroadcastDispatcher mBroadcastDispatcher;
     private final CarDeviceProvisionedController mCarDeviceProvisionedController;
     private final ConfigurationController mConfigurationController;
 
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) Log.v(TAG, "onReceive: " + intent);
+            String action = intent.getAction();
+            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
+                if (mNotificationPanelViewController.isPanelExpanded()) {
+                    mNotificationPanelViewController.toggle();
+                }
+            }
+        }
+    };
+
     @Inject
     public NotificationPanelViewMediator(
             CarNavigationBarController carNavigationBarController,
             NotificationPanelViewController notificationPanelViewController,
 
             PowerManagerHelper powerManagerHelper,
+            BroadcastDispatcher broadcastDispatcher,
 
             CarDeviceProvisionedController carDeviceProvisionedController,
             ConfigurationController configurationController
@@ -56,6 +81,7 @@
         mCarNavigationBarController = carNavigationBarController;
         mNotificationPanelViewController = notificationPanelViewController;
         mPowerManagerHelper = powerManagerHelper;
+        mBroadcastDispatcher = broadcastDispatcher;
         mCarDeviceProvisionedController = carDeviceProvisionedController;
         mConfigurationController = configurationController;
     }
@@ -84,6 +110,9 @@
                         return mNotificationPanelViewController.isPanelExpanded();
                     }
                 });
+
+        mBroadcastDispatcher.registerReceiver(mBroadcastReceiver,
+                new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null, UserHandle.ALL);
     }
 
     @Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java
index 8d3eb4c..89c9931 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.car.notification;
 
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.car.window.OverlayPanelViewController;
@@ -37,6 +38,7 @@
             NotificationPanelViewController notificationPanelViewController,
 
             PowerManagerHelper powerManagerHelper,
+            BroadcastDispatcher broadcastDispatcher,
 
             CarDeviceProvisionedController carDeviceProvisionedController,
             ConfigurationController configurationController
@@ -44,6 +46,7 @@
         super(carNavigationBarController,
                 notificationPanelViewController,
                 powerManagerHelper,
+                broadcastDispatcher,
                 carDeviceProvisionedController,
                 configurationController);
         notificationPanelViewController.setOverlayDirection(
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 8f919c3..f42bf19 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -95,7 +95,8 @@
         public String docId;
         public File visiblePath;
         public File path;
-        public boolean reportAvailableBytes = true;
+        // TODO (b/157033915): Make getFreeBytes() faster
+        public boolean reportAvailableBytes = false;
     }
 
     private static final String ROOT_ID_PRIMARY_EMULATED =
@@ -520,9 +521,11 @@
         final RootInfo root = resolvedDocId.first;
         File child = resolvedDocId.second;
 
+        final File rootFile = root.visiblePath != null ? root.visiblePath
+                : root.path;
         final File parent = TextUtils.isEmpty(parentDocId)
-                        ? root.path
-                        : getFileForDocId(parentDocId);
+                ? rootFile
+                : getFileForDocId(parentDocId);
 
         return new Path(parentDocId == null ? root.rootId : null, findDocumentPath(parent, child));
     }
diff --git a/packages/OsuLogin/Android.bp b/packages/OsuLogin/Android.bp
index d7e36b1..445c81b 100644
--- a/packages/OsuLogin/Android.bp
+++ b/packages/OsuLogin/Android.bp
@@ -1,5 +1,6 @@
 android_app {
     name: "OsuLogin",
+    defaults: ["wifi-module-sdk-version-defaults"],
     static_libs: ["androidx.legacy_legacy-support-v4"],
     resource_dirs: ["res"],
     srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index a28ba85..d7bd6a4 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -3,6 +3,7 @@
 edgarwang@google.com
 emilychuang@google.com
 evanlaird@google.com
+juliacr@google.com
 leifhendrik@google.com
 rafftsai@google.com
 tmfang@google.com
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 2b51b54..69f7fb0 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -557,5 +557,5 @@
     <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"غير مفعّل"</string>
     <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"مفعّل"</string>
     <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"يجب إعادة تشغيل جهازك ليتم تطبيق هذا التغيير. يمكنك إعادة التشغيل الآن أو إلغاء التغيير."</string>
-    <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"سمّاعة رأس سلكية"</string>
+    <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"سمّاعة سلكية"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 0ef8e06..0def17e 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -553,5 +553,5 @@
     <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string>
     <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
     <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
-    <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
+    <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphones"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 0ef8e06..0def17e 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -553,5 +553,5 @@
     <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string>
     <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
     <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
-    <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
+    <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphones"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 0ef8e06..0def17e 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -553,5 +553,5 @@
     <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string>
     <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
     <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
-    <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
+    <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphones"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 0ef8e06..0def17e 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -553,5 +553,5 @@
     <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string>
     <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
     <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
-    <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
+    <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphones"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index efc186a..24711ff 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -64,10 +64,10 @@
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Scaduta"</string>
     <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="bluetooth_disconnected" msgid="7739366554710388701">"Disconnesso"</string>
-    <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Disconnessione..."</string>
-    <string name="bluetooth_connecting" msgid="5871702668260192755">"Connessione..."</string>
+    <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Disconnessione…"</string>
+    <string name="bluetooth_connecting" msgid="5871702668260192755">"Connessione…"</string>
     <string name="bluetooth_connected" msgid="8065345572198502293">"<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> connesso"</string>
-    <string name="bluetooth_pairing" msgid="4269046942588193600">"Accoppiamento..."</string>
+    <string name="bluetooth_pairing" msgid="4269046942588193600">"Accoppiamento…"</string>
     <string name="bluetooth_connected_no_headset" msgid="2224101138659967604">"<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> connesso (telefono escluso)"</string>
     <string name="bluetooth_connected_no_a2dp" msgid="8566874395813947092">"<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> connesso (contenuti multimediali esclusi)"</string>
     <string name="bluetooth_connected_no_map" msgid="3381860077002724689">"<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> connesso (nessun accesso ai messaggi)"</string>
@@ -406,11 +406,11 @@
     <string name="select_webview_provider_dialog_title" msgid="2444261109877277714">"Imposta l\'implementazione di WebView"</string>
     <string name="select_webview_provider_toast_text" msgid="8512254949169359848">"La selezione non è più valida. Riprova."</string>
     <string name="convert_to_file_encryption" msgid="2828976934129751818">"Converti in crittografia basata su file"</string>
-    <string name="convert_to_file_encryption_enabled" msgid="840757431284311754">"Converti..."</string>
+    <string name="convert_to_file_encryption_enabled" msgid="840757431284311754">"Converti…"</string>
     <string name="convert_to_file_encryption_done" msgid="8965831011811180627">"Crittografia su base file già eseguita"</string>
     <string name="title_convert_fbe" msgid="5780013350366495149">"Conversione in crittografia basata su file"</string>
-    <string name="convert_to_fbe_warning" msgid="34294381569282109">"Converti la partizione di dati in crittografia basata su file.\n Attenzione. Questa operazione eliminerà tutti i tuoi dati.\n Questa funzione è in versione alpha, pertanto potrebbe non funzionare correttamente.\n Premi \"Cancella e converti...\" per continuare."</string>
-    <string name="button_convert_fbe" msgid="1159861795137727671">"Cancella e converti..."</string>
+    <string name="convert_to_fbe_warning" msgid="34294381569282109">"Converti la partizione di dati in crittografia basata su file.\n Attenzione. Questa operazione eliminerà tutti i tuoi dati.\n Questa funzione è in versione alpha, pertanto potrebbe non funzionare correttamente.\n Premi \"Cancella e converti…\" per continuare."</string>
+    <string name="button_convert_fbe" msgid="1159861795137727671">"Cancella e converti…"</string>
     <string name="picture_color_mode" msgid="1013807330552931903">"Modalità colori immagini"</string>
     <string name="picture_color_mode_desc" msgid="151780973768136200">"Use sRGB"</string>
     <string name="daltonizer_mode_disabled" msgid="403424372812399228">"Disattivato"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index b501c65..7e1b476 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -195,7 +195,7 @@
   </string-array>
     <string name="choose_profile" msgid="343803890897657450">"Chọn hồ sơ"</string>
     <string name="category_personal" msgid="6236798763159385225">"Cá nhân"</string>
-    <string name="category_work" msgid="4014193632325996115">"Cơ quan"</string>
+    <string name="category_work" msgid="4014193632325996115">"Công việc"</string>
     <string name="development_settings_title" msgid="140296922921597393">"Tùy chọn cho nhà phát triển"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"Bật tùy chọn nhà phát triển"</string>
     <string name="development_settings_summary" msgid="8718917813868735095">"Đặt tùy chọn cho phát triển ứng dụng"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index b83a9c4..6c7e03f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -56,7 +56,7 @@
 public class InfoMediaManager extends MediaManager {
 
     private static final String TAG = "InfoMediaManager";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);;
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     @VisibleForTesting
     final RouterManagerCallback mMediaRouterCallback = new RouterManagerCallback();
     @VisibleForTesting
@@ -430,7 +430,7 @@
             case TYPE_HEARING_AID:
             case TYPE_BLUETOOTH_A2DP:
                 final BluetoothDevice device =
-                        BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getOriginalId());
+                        BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getAddress());
                 final CachedBluetoothDevice cachedDevice =
                         mBluetoothManager.getCachedDeviceManager().findDevice(device);
                 if (cachedDevice != null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index f381cde..9d06c84 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -20,7 +20,6 @@
 import android.app.Notification;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.media.RoutingSessionInfo;
 import android.text.TextUtils;
@@ -409,7 +408,8 @@
         synchronized (mMediaDevicesLock) {
             for (MediaDevice device : mMediaDevices) {
                 if (device instanceof BluetoothMediaDevice) {
-                    if (isActiveDevice(((BluetoothMediaDevice) device).getCachedDevice())) {
+                    if (isActiveDevice(((BluetoothMediaDevice) device).getCachedDevice())
+                            && device.isConnected()) {
                         return device;
                     }
                 } else if (device instanceof PhoneMediaDevice) {
@@ -422,8 +422,22 @@
     }
 
     private boolean isActiveDevice(CachedBluetoothDevice device) {
-        return device.isActiveDevice(BluetoothProfile.A2DP)
-                || device.isActiveDevice(BluetoothProfile.HEARING_AID);
+        boolean isActiveDeviceA2dp = false;
+        boolean isActiveDeviceHearingAid = false;
+        final A2dpProfile a2dpProfile = mLocalBluetoothManager.getProfileManager().getA2dpProfile();
+        if (a2dpProfile != null) {
+            isActiveDeviceA2dp = device.getDevice().equals(a2dpProfile.getActiveDevice());
+        }
+        if (!isActiveDeviceA2dp) {
+            final HearingAidProfile hearingAidProfile = mLocalBluetoothManager.getProfileManager()
+                    .getHearingAidProfile();
+            if (hearingAidProfile != null) {
+                isActiveDeviceHearingAid =
+                        hearingAidProfile.getActiveDevices().contains(device.getDevice());
+            }
+        }
+
+        return isActiveDeviceA2dp || isActiveDeviceHearingAid;
     }
 
     private Collection<DeviceCallback> getCallbacks() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
index d5f1ece..549bc8a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
@@ -86,7 +86,7 @@
     /**
      * Returns the conversation info drawable
      */
-    private Drawable getBaseIconDrawable(ShortcutInfo shortcutInfo) {
+    public Drawable getBaseIconDrawable(ShortcutInfo shortcutInfo) {
         return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
     }
 
@@ -94,7 +94,7 @@
      * Get the {@link Drawable} that represents the app icon, badged with the work profile icon
      * if appropriate.
      */
-    private Drawable getAppBadge(String packageName, int userId) {
+    public Drawable getAppBadge(String packageName, int userId) {
         Drawable badge = null;
         try {
             final ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index d1cd043..3015397 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -900,9 +900,7 @@
                 updateNetworkInfo(info);
                 fetchScansAndConfigsAndUpdateAccessPoints();
             } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
-                NetworkInfo info =
-                        mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
-                updateNetworkInfo(info);
+                updateNetworkInfo(/* networkInfo= */ null);
             }
         }
     };
@@ -948,7 +946,7 @@
                 // We don't send a NetworkInfo object along with this message, because even if we
                 // fetch one from ConnectivityManager, it might be older than the most recent
                 // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
-                updateNetworkInfo(null);
+                updateNetworkInfo(/* networkInfo= */ null);
             }
         }
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 248eb5b..94d95f0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -681,7 +681,7 @@
         assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof PhoneMediaDevice).isTrue();
 
         when(route2Info.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
-        when(route2Info.getOriginalId()).thenReturn("00:00:00:00:00:00");
+        when(route2Info.getAddress()).thenReturn("00:00:00:00:00:00");
         when(mLocalBluetoothManager.getCachedDeviceManager())
                 .thenReturn(cachedBluetoothDeviceManager);
         when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class)))
@@ -703,7 +703,7 @@
                 mock(CachedBluetoothDeviceManager.class);
 
         when(route2Info.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
-        when(route2Info.getOriginalId()).thenReturn("00:00:00:00:00:00");
+        when(route2Info.getAddress()).thenReturn("00:00:00:00:00:00");
         when(mLocalBluetoothManager.getCachedDeviceManager())
                 .thenReturn(cachedBluetoothDeviceManager);
         when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class)))
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 368245f..a654fd4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -578,6 +578,9 @@
         when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
         when(cachedDevice.isConnected()).thenReturn(false);
         when(cachedDevice.getConnectableProfiles()).thenReturn(profiles);
+        when(cachedDevice.getDevice()).thenReturn(bluetoothDevice);
+        when(mA2dpProfile.getActiveDevice()).thenReturn(bluetoothDevice);
+        when(mHapProfile.getActiveDevices()).thenReturn(new ArrayList<>());
 
         when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
         when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
@@ -734,11 +737,19 @@
         final PhoneMediaDevice phoneDevice = mock(PhoneMediaDevice.class);
         final CachedBluetoothDevice cachedDevice1 = mock(CachedBluetoothDevice.class);
         final CachedBluetoothDevice cachedDevice2 = mock(CachedBluetoothDevice.class);
+        final BluetoothDevice bluetoothDevice1 = mock(BluetoothDevice.class);
+        final BluetoothDevice bluetoothDevice2 = mock(BluetoothDevice.class);
 
+        when(mA2dpProfile.getActiveDevice()).thenReturn(bluetoothDevice2);
+        when(mHapProfile.getActiveDevices()).thenReturn(new ArrayList<>());
         when(device1.getCachedDevice()).thenReturn(cachedDevice1);
         when(device2.getCachedDevice()).thenReturn(cachedDevice2);
+        when(cachedDevice1.getDevice()).thenReturn(bluetoothDevice1);
+        when(cachedDevice2.getDevice()).thenReturn(bluetoothDevice2);
         when(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(false);
         when(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(true);
+        when(device1.isConnected()).thenReturn(true);
+        when(device2.isConnected()).thenReturn(true);
 
         mLocalMediaManager.mMediaDevices.add(device1);
         mLocalMediaManager.mMediaDevices.add(phoneDevice);
@@ -754,11 +765,19 @@
         final PhoneMediaDevice phoneDevice = mock(PhoneMediaDevice.class);
         final CachedBluetoothDevice cachedDevice1 = mock(CachedBluetoothDevice.class);
         final CachedBluetoothDevice cachedDevice2 = mock(CachedBluetoothDevice.class);
+        final BluetoothDevice bluetoothDevice1 = mock(BluetoothDevice.class);
+        final BluetoothDevice bluetoothDevice2 = mock(BluetoothDevice.class);
 
+        when(mA2dpProfile.getActiveDevice()).thenReturn(null);
+        when(mHapProfile.getActiveDevices()).thenReturn(new ArrayList<>());
         when(device1.getCachedDevice()).thenReturn(cachedDevice1);
         when(device2.getCachedDevice()).thenReturn(cachedDevice2);
+        when(cachedDevice1.getDevice()).thenReturn(bluetoothDevice1);
+        when(cachedDevice2.getDevice()).thenReturn(bluetoothDevice2);
         when(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(false);
         when(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(false);
+        when(device1.isConnected()).thenReturn(true);
+        when(device2.isConnected()).thenReturn(true);
 
         mLocalMediaManager.mMediaDevices.add(device1);
         mLocalMediaManager.mMediaDevices.add(phoneDevice);
diff --git a/packages/SystemUI/docs/broadcasts.md b/packages/SystemUI/docs/broadcasts.md
index 6c8488b..8ec20f5 100644
--- a/packages/SystemUI/docs/broadcasts.md
+++ b/packages/SystemUI/docs/broadcasts.md
@@ -62,7 +62,7 @@
  * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an
  *                 executor in the main thread (default).
  * @param user A user handle to determine which broadcast should be dispatched to this receiver.
- *             By default, it is the current user.
+ *             By default, it is the user of the context (system user in SystemUI).
  * @throws IllegalArgumentException if the filter has other constraints that are not actions or
  *                                  categories or the filter has no actions.
  */
diff --git a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml
index 0cabc32..7313d54 100644
--- a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml
+++ b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml
@@ -17,30 +17,72 @@
 <com.android.systemui.bubbles.BubbleManageEducationView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
+    android:layout_height="match_parent"
+    >
     <LinearLayout
-        android:id="@+id/manage_education_view"
-        android:orientation="vertical"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_width="@dimen/bubbles_manage_education_width">
+        android:id="@+id/manage_education_view"
+        android:clickable="true"
+        android:paddingTop="28dp"
+        android:paddingBottom="16dp"
+        android:paddingStart="@dimen/bubble_expanded_view_padding"
+        android:paddingEnd="48dp"
+        android:layout_marginEnd="24dp"
+        android:orientation="vertical"
+        android:background="@drawable/bubble_stack_user_education_bg"
+        android:alpha="0.9"
+        >
+
+        <TextView
+            android:id="@+id/user_education_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingStart="16dp"
+            android:paddingBottom="16dp"
+            android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+            android:maxLines="1"
+            android:text="@string/bubbles_user_education_manage_title"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"/>
 
         <TextView
             android:id="@+id/user_education_description"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:padding="8dp"
+            android:paddingStart="16dp"
+            android:paddingBottom="24dp"
             android:text="@string/bubbles_user_education_manage"
             android:fontFamily="@*android:string/config_bodyFontFamily"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
-            android:background="@drawable/bubble_manage_user_education_bg"
-        />
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/>
 
-        <View
-            android:id="@+id/user_education_pointer"
-            android:layout_width="@dimen/bubble_pointer_width"
-            android:layout_height="@dimen/bubble_pointer_height"
-        />
+        <LinearLayout
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:orientation="horizontal" >
 
+            <com.android.systemui.statusbar.AlphaOptimizedButton
+                style="@android:style/Widget.Material.Button.Borderless"
+                android:id="@+id/manage"
+                android:layout_gravity="start"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:focusable="true"
+                android:clickable="false"
+                android:text="@string/manage_bubbles_text"
+                android:textColor="?attr/wallpaperTextColor"
+                android:alpha="0.89"
+                />
+
+            <com.android.systemui.statusbar.AlphaOptimizedButton
+                style="@android:style/Widget.Material.Button.Borderless"
+                android:id="@+id/got_it"
+                android:layout_gravity="start"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:focusable="true"
+                android:text="@string/bubbles_user_education_got_it"
+                android:textColor="?attr/wallpaperTextColor"
+                />
+        </LinearLayout>
     </LinearLayout>
 </com.android.systemui.bubbles.BubbleManageEducationView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index fce4610..ef7325e 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -30,11 +30,10 @@
         android:id="@+id/global_screenshot_animated_view"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_gravity="center"
+        android:layout_gravity="top|start"
         android:visibility="gone"
         android:elevation="@dimen/screenshot_preview_elevation"
-        android:background="@drawable/screenshot_rounded_corners"
-        android:adjustViewBounds="true"/>
+        android:background="@drawable/screenshot_rounded_corners" />
     <ImageView
         android:id="@+id/global_screenshot_flash"
         android:layout_width="match_parent"
@@ -49,4 +48,18 @@
         android:visibility="gone"
         android:pointerIcon="crosshair"/>
     <include layout="@layout/global_screenshot_static"/>
+    <FrameLayout
+        android:id="@+id/global_screenshot_dismiss_button"
+        android:layout_width="@dimen/screenshot_dismiss_button_tappable_size"
+        android:layout_height="@dimen/screenshot_dismiss_button_tappable_size"
+        android:elevation="7dp"
+        android:visibility="gone"
+        android:contentDescription="@string/screenshot_dismiss_ui_description">
+        <ImageView
+            android:id="@+id/global_screenshot_dismiss_image"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_margin="@dimen/screenshot_dismiss_button_margin"
+            android:src="@drawable/screenshot_cancel"/>
+    </FrameLayout>
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/global_screenshot_static.xml b/packages/SystemUI/res/layout/global_screenshot_static.xml
index da5277c..9ec2f20 100644
--- a/packages/SystemUI/res/layout/global_screenshot_static.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_static.xml
@@ -54,22 +54,4 @@
             android:layout_height="wrap_content"/>
     </HorizontalScrollView>
     <include layout="@layout/global_screenshot_preview"/>
-    <FrameLayout
-        android:id="@+id/global_screenshot_dismiss_button"
-        android:layout_width="@dimen/screenshot_dismiss_button_tappable_size"
-        android:layout_height="@dimen/screenshot_dismiss_button_tappable_size"
-        android:elevation="7dp"
-        android:visibility="gone"
-        android:contentDescription="@string/screenshot_dismiss_ui_description"
-        app:layout_constraintStart_toEndOf="@+id/global_screenshot_preview"
-        app:layout_constraintEnd_toEndOf="@+id/global_screenshot_preview"
-        app:layout_constraintTop_toTopOf="@+id/global_screenshot_preview"
-        app:layout_constraintBottom_toTopOf="@+id/global_screenshot_preview">
-        <ImageView
-            android:id="@+id/global_screenshot_dismiss_image"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_margin="@dimen/screenshot_dismiss_button_margin"
-            android:src="@drawable/screenshot_cancel"/>
-    </FrameLayout>
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml
index 21a671c..214c44a 100644
--- a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml
+++ b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml
@@ -20,24 +20,26 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:gravity="center_vertical|start"
+    android:paddingTop="1dp"
+    android:paddingBottom="1dp"
     android:paddingEnd="12dp">
 
     <FrameLayout
         android:layout_width="@*android:dimen/conversation_content_start"
-        android:layout_height="36dp"
+        android:layout_height="25dp"
     >
         <ImageView
             android:id="@*android:id/conversation_icon"
-            android:layout_width="24dp"
-            android:layout_height="24dp"
+            android:layout_width="20dp"
+            android:layout_height="20dp"
             android:layout_gravity="center"
         />
 
         <ViewStub
             android:id="@*android:id/conversation_face_pile"
             android:layout="@*android:layout/conversation_face_pile_layout"
-            android:layout_width="36dp"
-            android:layout_height="36dp"
+            android:layout_width="25dp"
+            android:layout_height="25dp"
             android:layout_gravity="center"
         />
     </FrameLayout>
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index 49d525f..2c4b937 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -34,7 +34,7 @@
         android:layout_height="wrap_content"
         android:gravity="center_vertical"
         android:clipChildren="false"
-        android:paddingTop="8dp"
+        android:paddingTop="11dp"
         android:clipToPadding="true">
         <ImageView
             android:id="@+id/conversation_icon"
@@ -85,14 +85,6 @@
                     style="@style/TextAppearance.NotificationImportanceChannel"/>
             </LinearLayout>
             <TextView
-                android:id="@+id/pkg_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                style="@style/TextAppearance.NotificationImportanceChannelGroup"
-                android:ellipsize="end"
-                android:textDirection="locale"
-                android:maxLines="1"/>
-            <TextView
                 android:id="@+id/group_name"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
@@ -100,6 +92,14 @@
                 android:textDirection="locale"
                 style="@style/TextAppearance.NotificationImportanceChannelGroup"/>
             <TextView
+                android:id="@+id/pkg_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/TextAppearance.NotificationImportanceApp"
+                android:ellipsize="end"
+                android:textDirection="locale"
+                android:maxLines="1"/>
+            <TextView
                 android:id="@+id/delegate_name"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 870deac..1c7c226 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -59,14 +59,6 @@
                 android:textDirection="locale"
                 style="@style/TextAppearance.NotificationImportanceChannel"/>
             <TextView
-                android:id="@+id/pkg_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                style="@style/TextAppearance.NotificationImportanceChannelGroup"
-                android:ellipsize="end"
-                android:textDirection="locale"
-                android:maxLines="1"/>
-            <TextView
                 android:id="@+id/group_name"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
@@ -74,6 +66,14 @@
                 android:ellipsize="end"
                 style="@style/TextAppearance.NotificationImportanceChannelGroup"/>
             <TextView
+                android:id="@+id/pkg_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/TextAppearance.NotificationImportanceApp"
+                android:ellipsize="end"
+                android:textDirection="locale"
+                android:maxLines="1"/>
+            <TextView
                 android:id="@+id/delegate_name"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml
index 803b0c6..0795170 100644
--- a/packages/SystemUI/res/layout/partial_conversation_info.xml
+++ b/packages/SystemUI/res/layout/partial_conversation_info.xml
@@ -35,7 +35,7 @@
         android:clipChildren="false"
         android:clipToPadding="false">
         <ImageView
-            android:id="@+id/conversation_icon"
+            android:id="@+id/icon"
             android:layout_width="@dimen/notification_guts_conversation_icon_size"
             android:layout_height="@dimen/notification_guts_conversation_icon_size"
             android:layout_centerVertical="true"
@@ -60,20 +60,6 @@
                 android:textDirection="locale"
                 style="@style/TextAppearance.NotificationImportanceChannel"/>
             <TextView
-                android:id="@+id/parent_channel_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:ellipsize="end"
-                android:textDirection="locale"
-                style="@style/TextAppearance.NotificationImportanceChannel"/>
-            <TextView
-                android:id="@+id/group_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:ellipsize="end"
-                android:textDirection="locale"
-                style="@style/TextAppearance.NotificationImportanceChannelGroup"/>
-            <TextView
                 android:id="@+id/delegate_name"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
@@ -127,18 +113,21 @@
                 android:layout_height="wrap_content"
                 android:clipChildren="false"
                 android:clipToPadding="false"
+                android:layout_centerVertical="true"
                 android:orientation="horizontal">
                 <ImageView
-                    android:layout_height="wrap_content"
+                    android:layout_height="match_parent"
                     android:layout_width="wrap_content"
                     android:contentDescription="@null"
                     android:src="@drawable/ic_info"
+                    android:layout_centerVertical="true"
                     android:tint="?android:attr/textColorPrimary"
                     android:layout_marginEnd="8dp"/>
                 <TextView
                     android:id="@+id/non_configurable_text"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
                     style="@style/TextAppearance.NotificationImportanceChannelGroup" />
             </LinearLayout>
         </com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
diff --git a/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml b/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml
index bf2eac3..3f0e514 100644
--- a/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml
+++ b/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml
@@ -38,11 +38,61 @@
         android:background="@drawable/rounded_bg_full"
         >
 
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:padding="12dp"
+        android:layout_gravity="center_horizontal"
+    >
+
+        <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right -->
         <ImageView
             android:id="@+id/conversation_icon"
-            android:layout_width="@dimen/notification_guts_conversation_icon_size"
-            android:layout_height="@dimen/notification_guts_conversation_icon_size"
-            android:layout_gravity="center_horizontal" />
+            android:layout_width="@*android:dimen/conversation_avatar_size"
+            android:layout_height="@*android:dimen/conversation_avatar_size"
+            android:scaleType="centerCrop"
+            android:importantForAccessibility="no"
+        />
+
+        <FrameLayout
+            android:id="@+id/conversation_icon_badge"
+            android:layout_width="@*android:dimen/conversation_icon_size_badged"
+            android:layout_height="@*android:dimen/conversation_icon_size_badged"
+            android:layout_marginLeft="@*android:dimen/conversation_badge_side_margin"
+            android:layout_marginTop="@*android:dimen/conversation_badge_side_margin"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+        >
+            <ImageView
+                android:id="@+id/conversation_icon_badge_bg"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_gravity="center"
+                android:src="@*android:drawable/conversation_badge_background"
+                android:forceHasOverlappingRendering="false"
+            />
+            <ImageView
+                android:id="@+id/icon"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_margin="4dp"
+                android:layout_gravity="center"
+                android:forceHasOverlappingRendering="false"
+            />
+            <ImageView
+                android:id="@+id/conversation_icon_badge_ring"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:src="@*android:drawable/conversation_badge_ring"
+                android:forceHasOverlappingRendering="false"
+                android:clipToPadding="false"
+                android:scaleType="center"
+            />
+        </FrameLayout>
+    </FrameLayout>
 
         <TextView
             android:id="@+id/title"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 837627c..4fc904b 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -202,6 +202,10 @@
     <color name="global_screenshot_dismiss_foreground">@color/GM2_grey_500</color>
     <color name="global_screenshot_background_protection_start">#40000000</color> <!-- 25% black -->
 
+    <!-- Bubbles -->
+    <color name="bubbles_pointer_light">#FFFFFF</color>
+    <color name="bubbles_pointer_dark">@color/GM2_grey_800</color>
+
     <!-- GM2 colors -->
     <color name="GM2_grey_50">#F8F9FA</color>
     <color name="GM2_grey_100">#F1F3F4</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 73d8e9a..44d3b02 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -631,10 +631,10 @@
     <dimen name="notification_section_divider_height">@dimen/notification_side_paddings</dimen>
 
     <!-- Size of the face pile shown on one-line (children of a group) conversation notifications -->
-    <dimen name="conversation_single_line_face_pile_size">36dp</dimen>
+    <dimen name="conversation_single_line_face_pile_size">25dp</dimen>
 
     <!-- Size of an avatar shown on one-line (children of a group) conversation notifications -->
-    <dimen name="conversation_single_line_avatar_size">24dp</dimen>
+    <dimen name="conversation_single_line_avatar_size">20dp</dimen>
 
     <!-- Border width for avatars in the face pile shown on one-line (children of a group) conversation notifications -->
     <dimen name="conversation_single_line_face_pile_protection_width">1dp</dimen>
@@ -1222,10 +1222,6 @@
     <dimen name="bubble_dismiss_slop">16dp</dimen>
     <!-- Height of button allowing users to adjust settings for bubbles. -->
     <dimen name="bubble_manage_button_height">48dp</dimen>
-    <!-- How far, horizontally, to animate the expanded view over when animating in/out. -->
-    <dimen name="bubble_expanded_animate_x_distance">100dp</dimen>
-    <!-- How far, vertically, to animate the expanded view over when animating in/out. -->
-    <dimen name="bubble_expanded_animate_y_distance">500dp</dimen>
     <!-- Max width of the message bubble-->
     <dimen name="bubble_message_max_width">144dp</dimen>
     <!-- Min width of the message bubble -->
@@ -1252,7 +1248,7 @@
     <!-- Bubbles user education views -->
     <dimen name="bubbles_manage_education_width">160dp</dimen>
     <!-- The inset from the top bound of the manage button to place the user education. -->
-    <dimen name="bubbles_manage_education_top_inset">10dp</dimen>
+    <dimen name="bubbles_manage_education_top_inset">65dp</dimen>
     <!-- Size of padding for the user education cling, this should at minimum be larger than
         individual_bubble_size + some padding. -->
     <dimen name="bubble_stack_user_education_side_inset">72dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 820615a6..48ff5c6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1256,7 +1256,7 @@
     <string name="manage_notifications_history_text">History</string>
 
     <!-- Section title for notifications that have recently appeared. [CHAR LIMIT=40] -->
-    <string name="notification_section_header_incoming">Incoming</string>
+    <string name="notification_section_header_incoming">New</string>
 
     <!-- Section title for notifications that do not vibrate or make noise. [CHAR LIMIT=40] -->
     <string name="notification_section_header_gentle">Silent</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 39f78bf..8097c01 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -549,6 +549,13 @@
     <style name="TextAppearance.NotificationImportanceChannelGroup">
         <item name="android:textSize">@dimen/notification_importance_channel_group_text</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:textColor">@color/notification_guts_header_text_color</item>
+        <item name="android:textSize">@dimen/notification_importance_channel_group_text</item>
+    </style>
+
+    <style name="TextAppearance.NotificationImportanceApp">
+        <item name="android:textSize">@dimen/notification_importance_channel_group_text</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:textColor">@color/notification_guts_sub_text_color</item>
         <item name="android:textSize">@dimen/notification_importance_channel_group_text</item>
     </style>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java
index ebed1fc..27e4c85 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java
@@ -63,8 +63,9 @@
      */
     private final class InputEventReceiver extends BatchedInputEventReceiver {
 
-        public InputEventReceiver(InputChannel inputChannel, Looper looper) {
-            super(inputChannel, looper, Choreographer.getInstance());
+        InputEventReceiver(InputChannel inputChannel, Looper looper,
+                Choreographer choreographer) {
+            super(inputChannel, looper, choreographer);
         }
 
         @Override
@@ -143,6 +144,14 @@
      * Registers the input consumer.
      */
     public void registerInputConsumer() {
+        registerInputConsumer(false);
+    }
+
+    /**
+     * Registers the input consumer.
+     * @param withSfVsync the flag set using sf vsync signal or no
+     */
+    public void registerInputConsumer(boolean withSfVsync) {
         if (mInputEventReceiver == null) {
             final InputChannel inputChannel = new InputChannel();
             try {
@@ -152,7 +161,8 @@
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to create input consumer", e);
             }
-            mInputEventReceiver = new InputEventReceiver(inputChannel, Looper.myLooper());
+            mInputEventReceiver = new InputEventReceiver(inputChannel, Looper.myLooper(),
+                    withSfVsync ? Choreographer.getSfInstance() : Choreographer.getInstance());
             if (mRegistrationListener != null) {
                 mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
             }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 9d8c545..f639c88 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -141,14 +141,6 @@
         mLayoutTransition.setAnimateParentHierarchy(false);
     }
 
-    // Temporary workaround to allow KeyguardStatusView to inflate a copy for Universal Smartspace.
-    // Eventually the existing copy will be reparented instead, and we won't need this.
-    public KeyguardSliceView(Context context, AttributeSet attributeSet) {
-        this(context, attributeSet, Dependency.get(ActivityStarter.class),
-                Dependency.get(ConfigurationController.class), Dependency.get(TunerService.class),
-                context.getResources());
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index a181ce4..5a1c997 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -18,10 +18,7 @@
 
 import android.app.ActivityManager;
 import android.app.IActivityManager;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.os.Handler;
@@ -43,8 +40,6 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.shared.system.SurfaceViewRequestReceiver;
-import com.android.systemui.shared.system.UniversalSmartspaceUtils;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.FileDescriptor;
@@ -127,21 +122,6 @@
         }
     };
 
-    private final BroadcastReceiver mUniversalSmartspaceBroadcastReceiver =
-            new BroadcastReceiver() {
-        private final SurfaceViewRequestReceiver mReceiver = new SurfaceViewRequestReceiver();
-
-        @Override
-        public void onReceive(Context context, Intent i) {
-            // TODO(b/148159743): Restrict to Pixel Launcher.
-            if (UniversalSmartspaceUtils.ACTION_REQUEST_SMARTSPACE_VIEW.equals(i.getAction())) {
-                mReceiver.onReceive(context,
-                        i.getBundleExtra(UniversalSmartspaceUtils.INTENT_BUNDLE_KEY),
-                        inflate(mContext, R.layout.keyguard_status_area, null));
-            }
-        }
-    };
-
     public KeyguardStatusView(Context context) {
         this(context, null, 0);
     }
@@ -336,8 +316,6 @@
         super.onAttachedToWindow();
         Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback);
         Dependency.get(ConfigurationController.class).addCallback(this);
-        getContext().registerReceiver(mUniversalSmartspaceBroadcastReceiver,
-                new IntentFilter(UniversalSmartspaceUtils.ACTION_REQUEST_SMARTSPACE_VIEW));
     }
 
     @Override
@@ -345,7 +323,6 @@
         super.onDetachedFromWindow();
         Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback);
         Dependency.get(ConfigurationController.class).removeCallback(this);
-        getContext().unregisterReceiver(mUniversalSmartspaceBroadcastReceiver);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index ee31706..34c8587 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1903,6 +1903,12 @@
     private boolean shouldListenForFingerprint() {
         final boolean allowedOnBouncer =
                 !(mFingerprintLockedOut && mBouncer && mCredentialAttempted);
+        final int user = getCurrentUser();
+        final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
+        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);
 
         // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
@@ -1911,7 +1917,7 @@
                 shouldListenForFingerprintAssistant() || (mKeyguardOccluded && mIsDreaming))
                 && !mSwitchingUser && !isFingerprintDisabled(getCurrentUser())
                 && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser
-                && allowedOnBouncer;
+                && allowedOnBouncer && !isLockDown && !isEncrypted;
         return shouldListen;
     }
 
@@ -1928,9 +1934,10 @@
         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);
+        final boolean isEncrypted =
+                containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT);
+        final boolean isTimedOut =
+                containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
 
         boolean canBypass = mKeyguardBypassController != null
                 && mKeyguardBypassController.canBypass();
@@ -1939,10 +1946,9 @@
         // TrustAgents or biometrics are keeping the device unlocked.
         boolean becauseCannotSkipBouncer = !getUserCanSkipBouncer(user) || canBypass;
 
-        // Scan even when encrypted or timeout to show a preemptive bouncer when bypassing.
+        // Scan even when timeout to show a preemptive bouncer when bypassing.
         // Lock-down mode shouldn't scan, since it is more explicit.
-        boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass && !mBouncer)
-                && !isLockDown;
+        boolean strongAuthAllowsScanning = (!isTimedOut || canBypass && !mBouncer);
 
         // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
@@ -1952,7 +1958,7 @@
                 && !mSwitchingUser && !isFaceDisabled(user) && becauseCannotSkipBouncer
                 && !mKeyguardGoingAway && mFaceSettingEnabledForUser.get(user) && !mLockIconPressed
                 && strongAuthAllowsScanning && mIsPrimaryUser
-                && !mSecureCameraLaunched;
+                && !mSecureCameraLaunched && !isLockDown && !isEncrypted;
 
         // Aggregate relevant fields for debug logging.
         if (DEBUG_FACE || DEBUG_SPEW) {
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index ccb506d..0218cd2 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -124,7 +124,7 @@
         String HAS_SEEN_BUBBLES_MANAGE_EDUCATION = "HasSeenBubblesManageOnboarding";
         String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount";
         /** Tracks whether the user has seen the onboarding screen for priority conversations */
-        String HAS_SEEN_PRIORITY_ONBOARDING = "HaveShownPriorityOnboarding";
+        String HAS_SEEN_PRIORITY_ONBOARDING = "HasUserSeenPriorityOnboarding";
     }
 
     public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 4269605..67c0c62 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.broadcast
 
+import android.app.ActivityManager
 import android.content.BroadcastReceiver
 import android.content.Context
+import android.content.Intent
 import android.content.IntentFilter
 import android.os.Handler
 import android.os.HandlerExecutor
@@ -28,15 +30,11 @@
 import android.util.SparseArray
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.Dumpable
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
 import com.android.systemui.dump.DumpManager
 import java.io.FileDescriptor
 import java.io.PrintWriter
-import java.lang.IllegalStateException
 import java.util.concurrent.Executor
-import javax.inject.Inject
-import javax.inject.Singleton
 
 data class ReceiverData(
     val receiver: BroadcastReceiver,
@@ -48,6 +46,8 @@
 private const val MSG_ADD_RECEIVER = 0
 private const val MSG_REMOVE_RECEIVER = 1
 private const val MSG_REMOVE_RECEIVER_FOR_USER = 2
+private const val MSG_USER_SWITCH = 3
+private const val MSG_SET_STARTING_USER = 99
 private const val TAG = "BroadcastDispatcher"
 private const val DEBUG = true
 
@@ -62,20 +62,27 @@
  * permissions, schemes, data types, data authorities or priority different than 0.
  * Cannot be used for getting sticky broadcasts (either as return of registering or as re-delivery).
  */
-@Singleton
-open class BroadcastDispatcher @Inject constructor (
+open class BroadcastDispatcher constructor (
     private val context: Context,
-    @Main private val mainHandler: Handler,
-    @Background private val bgLooper: Looper,
-    dumpManager: DumpManager
-) : Dumpable {
+    private val bgLooper: Looper,
+    private val dumpManager: DumpManager,
+    private val logger: BroadcastDispatcherLogger
+) : Dumpable, BroadcastReceiver() {
 
     // Only modify in BG thread
     private val receiversByUser = SparseArray<UserBroadcastDispatcher>(20)
 
-    init {
-        // TODO: Don't do this in the constructor
+    fun initialize() {
         dumpManager.registerDumpable(javaClass.name, this)
+        handler.sendEmptyMessage(MSG_SET_STARTING_USER)
+        registerReceiver(this, IntentFilter(Intent.ACTION_USER_SWITCHED), null, UserHandle.ALL)
+    }
+
+    override fun onReceive(context: Context, intent: Intent) {
+        if (intent.action == Intent.ACTION_USER_SWITCHED) {
+            val user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL)
+            handler.obtainMessage(MSG_USER_SWITCH, user, 0).sendToTarget()
+        }
     }
 
     /**
@@ -87,7 +94,7 @@
      *               have at least one action.
      * @param handler A handler to dispatch [BroadcastReceiver.onReceive].
      * @param user A user handle to determine which broadcast should be dispatched to this receiver.
-     *             By default, it is the current user.
+     *             By default, it is the user of the context (system user in SystemUI).
      * @throws IllegalArgumentException if the filter has other constraints that are not actions or
      *                                  categories or the filter has no actions.
      */
@@ -113,7 +120,7 @@
      * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an
      *                 executor in the main thread (default).
      * @param user A user handle to determine which broadcast should be dispatched to this receiver.
-     *             By default, it is the current user.
+     *             By default, it is the user of the context (system user in SystemUI).
      * @throws IllegalArgumentException if the filter has other constraints that are not actions or
      *                                  categories or the filter has no actions.
      */
@@ -156,7 +163,7 @@
     /**
      * Unregister receiver for a particular user.
      *
-     * @param receiver The receiver to unregister. It will be unregistered for all users.
+     * @param receiver The receiver to unregister.
      * @param user The user associated to the registered [receiver]. It can be [UserHandle.ALL].
      */
     open fun unregisterReceiverForUser(receiver: BroadcastReceiver, user: UserHandle) {
@@ -166,10 +173,11 @@
 
     @VisibleForTesting
     protected open fun createUBRForUser(userId: Int) =
-            UserBroadcastDispatcher(context, userId, bgLooper)
+            UserBroadcastDispatcher(context, userId, bgLooper, logger)
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
         pw.println("Broadcast dispatcher:")
+        pw.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)
@@ -177,6 +185,8 @@
     }
 
     private val handler = object : Handler(bgLooper) {
+        var currentUser = UserHandle.USER_SYSTEM
+
         override fun handleMessage(msg: Message) {
             when (msg.what) {
                 MSG_ADD_RECEIVER -> {
@@ -184,7 +194,7 @@
                     // If the receiver asked to be registered under the current user, we register
                     // under the actual current user.
                     val userId = if (data.user.identifier == UserHandle.USER_CURRENT) {
-                        context.userId
+                        currentUser
                     } else {
                         data.user.identifier
                     }
@@ -207,6 +217,13 @@
                     receiversByUser.get(msg.arg1)?.unregisterReceiver(msg.obj as BroadcastReceiver)
                 }
 
+                MSG_USER_SWITCH -> {
+                    currentUser = msg.arg1
+                }
+                MSG_SET_STARTING_USER -> {
+                    currentUser = ActivityManager.getCurrentUser()
+                }
+
                 else -> super.handleMessage(msg)
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index 3272fb7..96f5a1f 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -30,6 +30,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.internal.util.Preconditions
 import com.android.systemui.Dumpable
+import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.lang.IllegalArgumentException
@@ -54,7 +55,8 @@
 class UserBroadcastDispatcher(
     private val context: Context,
     private val userId: Int,
-    private val bgLooper: Looper
+    private val bgLooper: Looper,
+    private val logger: BroadcastDispatcherLogger
 ) : BroadcastReceiver(), Dumpable {
 
     companion object {
@@ -109,10 +111,12 @@
     }
 
     override fun onReceive(context: Context, intent: Intent) {
-        val id = if (DEBUG) index.getAndIncrement() else 0
+        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))
+                HandleBroadcastRunnable(
+                        actionsToReceivers, context, intent, pendingResult, id, logger))
     }
 
     /**
@@ -143,6 +147,7 @@
                 ArraySet()
             }.add(receiverData)
         }
+        logger.logReceiverRegistered(userId, receiverData.receiver)
         if (changed) {
             createFilterAndRegisterReceiverBG()
         }
@@ -163,6 +168,7 @@
                 actionsToReceivers.remove(action)
             }
         }
+        logger.logReceiverUnregistered(userId, receiver)
         if (changed) {
             createFilterAndRegisterReceiverBG()
         }
@@ -187,7 +193,8 @@
         val context: Context,
         val intent: Intent,
         val pendingResult: PendingResult,
-        val index: Int
+        val index: Int,
+        val logger: BroadcastDispatcherLogger
     ) : Runnable {
         override fun run() {
             if (DEBUG) Log.w(TAG, "[$index] Dispatching $intent")
@@ -199,6 +206,7 @@
                         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)
                         }
@@ -215,6 +223,7 @@
             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))
@@ -230,6 +239,7 @@
                         null,
                         bgHandler)
                 registered.set(true)
+                logger.logContextReceiverRegistered(userId, intentFilter)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
new file mode 100644
index 0000000..123a8ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.logging
+
+import android.content.BroadcastReceiver
+import android.content.Intent
+import android.content.IntentFilter
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogMessage
+import com.android.systemui.log.dagger.BroadcastDispatcherLog
+import javax.inject.Inject
+
+private const val TAG = "BroadcastDispatcherLog"
+
+class BroadcastDispatcherLogger @Inject constructor(
+    @BroadcastDispatcherLog private val buffer: LogBuffer
+) {
+
+    fun logBroadcastReceived(broadcastId: Int, user: Int, intent: Intent) {
+        val intentString = intent.toString()
+        log(INFO, {
+            int1 = broadcastId
+            int2 = user
+            str1 = intentString
+        }, {
+            "[$int1] Broadcast received for user $int2: $str1"
+        })
+    }
+
+    fun logBroadcastDispatched(broadcastId: Int, action: String?, receiver: BroadcastReceiver) {
+        val receiverString = receiver.toString()
+        log(DEBUG, {
+            int1 = broadcastId
+            str1 = action
+            str2 = receiverString
+        }, {
+            "Broadcast $int1 ($str1) dispatched to $str2"
+        })
+    }
+
+    fun logReceiverRegistered(user: Int, receiver: BroadcastReceiver) {
+        val receiverString = receiver.toString()
+        log(INFO, {
+            int1 = user
+            str1 = receiverString
+        }, {
+            "Receiver $str1 registered for user $int1"
+        })
+    }
+
+    fun logReceiverUnregistered(user: Int, receiver: BroadcastReceiver) {
+        val receiverString = receiver.toString()
+        log(INFO, {
+            int1 = user
+            str1 = receiverString
+        }, {
+            "Receiver $str1 unregistered for user $int1"
+        })
+    }
+
+    fun logContextReceiverRegistered(user: Int, filter: IntentFilter) {
+        val actions = filter.actionsIterator().asSequence()
+                .joinToString(separator = ",", prefix = "Actions(", postfix = ")")
+        val categories = if (filter.countCategories() != 0) {
+            filter.categoriesIterator().asSequence()
+                    .joinToString(separator = ",", prefix = "Categories(", postfix = ")")
+        } else {
+            ""
+        }
+        log(INFO, {
+            int1 = user
+            str1 = if (categories != "") {
+                "${actions}\n$categories"
+            } else {
+                actions
+            }
+        }, {
+            """
+                Receiver registered with Context for user $int1.
+                $str1
+            """.trimIndent()
+        })
+    }
+
+    fun logContextReceiverUnregistered(user: Int) {
+        log(INFO, {
+            int1 = user
+        }, {
+            "Receiver unregistered with Context for user $int1."
+        })
+    }
+
+    private inline fun log(
+        logLevel: LogLevel,
+        initializer: LogMessage.() -> Unit,
+        noinline printer: LogMessage.() -> String
+    ) {
+        buffer.log(TAG, logLevel, initializer, printer)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 278f3e5..c4c5da4 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -55,7 +55,6 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
-import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -180,6 +179,9 @@
     // Callback that updates BubbleOverflowActivity on data change.
     @Nullable private Runnable mOverflowCallback = null;
 
+    // Only load overflow data from disk once
+    private boolean mOverflowDataLoaded = false;
+
     private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private IStatusBarService mBarService;
     private WindowManager mWindowManager;
@@ -193,9 +195,6 @@
     /** Whether or not the BubbleStackView has been added to the WindowManager. */
     private boolean mAddedToWindowManager = false;
 
-    // Used for determining view rect for touch interaction
-    private Rect mTempRect = new Rect();
-
     // Listens to user switch so bubbles can be saved and restored.
     private final NotificationLockscreenUserManager mNotifUserManager;
 
@@ -419,6 +418,16 @@
         mCallbacks.add(callback);
     }
 
+    /**
+     * Dispatches a back press into the expanded Bubble's ActivityView if its IME is visible,
+     * causing it to hide.
+     */
+    public void hideImeFromExpandedBubble() {
+        if (mStackView != null) {
+            mStackView.hideImeFromExpandedBubble();
+        }
+    }
+
     private void setupNEM() {
         mNotificationEntryManager.addNotificationEntryListener(
                 new NotificationEntryListener() {
@@ -621,7 +630,7 @@
         if (mStackView == null) {
             mStackView = new BubbleStackView(
                     mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
-                    mSysUiState, mNotificationShadeWindowController, this::onAllBubblesAnimatedOut,
+                    mSysUiState, this::onAllBubblesAnimatedOut,
                     this::onImeVisibilityChanged);
             mStackView.addView(mBubbleScrim);
             if (mExpandListener != null) {
@@ -705,6 +714,9 @@
      * the new params if the stack has been added.
      */
     private void updateWmFlags() {
+        if (mStackView == null) {
+            return;
+        }
         if (isStackExpanded() && !mImeVisible) {
             // If we're expanded, and the IME isn't visible, we want to be focusable. This ensures
             // that any taps within Bubbles (including on the ActivityView) results in Bubbles
@@ -716,7 +728,7 @@
             mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
         }
 
-        if (mStackView != null && mAddedToWindowManager) {
+        if (mAddedToWindowManager) {
             try {
                 mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
             } catch (IllegalArgumentException e) {
@@ -901,7 +913,7 @@
         mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK);
         bubble.setInflateSynchronously(mInflateSynchronously);
         bubble.setShouldAutoExpand(true);
-        bubble.markUpdatedAt(System.currentTimeMillis());
+        bubble.markAsAccessedAt(System.currentTimeMillis());
         setIsBubble(bubble, true /* isBubble */);
     }
 
@@ -952,13 +964,14 @@
      * Fills the overflow bubbles by loading them from disk.
      */
     void loadOverflowBubblesFromDisk() {
-        if (!mBubbleData.getOverflowBubbles().isEmpty()) {
+        if (!mBubbleData.getOverflowBubbles().isEmpty() || mOverflowDataLoaded) {
             // we don't need to load overflow bubbles from disk if it is already in memory
             return;
         }
+        mOverflowDataLoaded = true;
         mDataRepository.loadBubbles((bubbles) -> {
             bubbles.forEach(bubble -> {
-                if (mBubbleData.getBubbles().contains(bubble)) {
+                if (mBubbleData.hasAnyBubbleWithKey(bubble.getKey())) {
                     // if the bubble is already active, there's no need to push it to overflow
                     return;
                 }
@@ -1156,7 +1169,7 @@
         if (b.getEntry() != null) {
             // Updating the entry to be a bubble will trigger our normal update flow
             setIsBubble(b.getEntry(), isBubble, b.shouldAutoExpand());
-        } else {
+        } else if (isBubble) {
             // If we have no entry to update, it's a persisted bubble so
             // we need to add it to the stack ourselves
             Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */);
@@ -1214,7 +1227,7 @@
                             }
                         }
                     } else {
-                        if (bubble.isBubble() && bubble.showInShade()) {
+                        if (bubble.isBubble()) {
                             setIsBubble(bubble, false /* isBubble */);
                         }
                         if (bubble.getEntry() != null && bubble.getEntry().getRow() != null) {
@@ -1235,24 +1248,23 @@
             }
             mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository);
 
-            if (update.addedBubble != null) {
+            if (update.addedBubble != null && mStackView != null) {
                 mDataRepository.addBubble(mCurrentUserId, update.addedBubble);
                 mStackView.addBubble(update.addedBubble);
-
             }
 
-            if (update.updatedBubble != null) {
+            if (update.updatedBubble != null && mStackView != null) {
                 mStackView.updateBubble(update.updatedBubble);
             }
 
             // At this point, the correct bubbles are inflated in the stack.
             // Make sure the order in bubble data is reflected in bubble row.
-            if (update.orderChanged) {
+            if (update.orderChanged && mStackView != null) {
                 mDataRepository.addBubbles(mCurrentUserId, update.bubbles);
                 mStackView.updateBubbleOrder(update.bubbles);
             }
 
-            if (update.selectionChanged) {
+            if (update.selectionChanged && mStackView != null) {
                 mStackView.setSelectedBubble(update.selectedBubble);
                 if (update.selectedBubble != null && update.selectedBubble.getEntry() != null) {
                     mNotificationGroupManager.updateSuppression(
@@ -1262,7 +1274,9 @@
 
             // Expanding? Apply this last.
             if (update.expandedChanged && update.expanded) {
-                mStackView.setExpanded(true);
+                if (mStackView != null) {
+                    mStackView.setExpanded(true);
+                }
             }
 
             for (NotifCallback cb : mCallbacks) {
@@ -1368,7 +1382,6 @@
     }
 
     /**
-     * Lets any listeners know if bubble state has changed.
      * Updates the visibility of the bubbles based on current state.
      * Does not un-bubble, just hides or un-hides.
      * Updates stack description for TalkBack focus.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 24d44d5..20a9a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -417,7 +417,8 @@
         if (mBubbles.size() == 1) {
             // Going to become empty, handle specially.
             setExpandedInternal(false);
-            setSelectedBubbleInternal(null);
+            // Don't use setSelectedBubbleInternal because we don't want to trigger an applyUpdate
+            mSelectedBubble = null;
         }
         if (indexToRemove < mBubbles.size() - 1) {
             // Removing anything but the last bubble means positions will change.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index db79802..47e6645 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -22,6 +22,7 @@
 import static android.graphics.PixelFormat.TRANSPARENT;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.InsetsState.ITYPE_IME;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.ViewRootImpl.sNewInsetsMode;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
@@ -33,7 +34,6 @@
 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
@@ -42,10 +42,12 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.Insets;
+import android.graphics.Outline;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.ShapeDrawable;
@@ -55,12 +57,19 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Gravity;
+import android.view.SurfaceControl;
+import android.view.SurfaceView;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -88,6 +97,7 @@
     // The triangle pointing to the expanded view
     private View mPointerView;
     private int mPointerMargin;
+    @Nullable private int[] mExpandedViewContainerLocation;
 
     private AlphaOptimizedButton mSettingsIcon;
 
@@ -121,6 +131,16 @@
     private View mVirtualImeView;
     private WindowManager mVirtualDisplayWindowManager;
     private boolean mImeShowing = false;
+    private float mCornerRadius = 0f;
+
+    /**
+     * Container for the ActivityView that has a solid, round-rect background that shows if the
+     * ActivityView hasn't loaded.
+     */
+    private FrameLayout mActivityViewContainer = new FrameLayout(getContext());
+
+    /** The SurfaceView that the ActivityView draws to. */
+    @Nullable private SurfaceView mActivitySurface;
 
     private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
         @Override
@@ -255,8 +275,6 @@
 
         mPointerDrawable = new ShapeDrawable(TriangleShape.create(
                 mPointerWidth, mPointerHeight, true /* pointUp */));
-        mPointerDrawable.setTint(Color.WHITE);
-        mPointerView.setBackground(mPointerDrawable);
         mPointerView.setVisibility(INVISIBLE);
 
         mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
@@ -269,7 +287,28 @@
 
         // Set ActivityView's alpha value as zero, since there is no view content to be shown.
         setContentVisibility(false);
-        addView(mActivityView);
+
+        mActivityViewContainer.setBackgroundColor(Color.WHITE);
+        mActivityViewContainer.setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
+            }
+        });
+        mActivityViewContainer.setClipToOutline(true);
+        mActivityViewContainer.addView(mActivityView);
+        mActivityViewContainer.setLayoutParams(
+                new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+        addView(mActivityViewContainer);
+
+        if (mActivityView != null
+                && mActivityView.getChildCount() > 0
+                && mActivityView.getChildAt(0) instanceof SurfaceView) {
+            // Retrieve the surface from the ActivityView so we can screenshot it and change its
+            // z-ordering. This should always be possible, since ActivityView's constructor adds the
+            // SurfaceView as its first child.
+            mActivitySurface = (SurfaceView) mActivityView.getChildAt(0);
+        }
 
         // Expanded stack layout, top to bottom:
         // Expanded view container
@@ -311,7 +350,10 @@
             // ActivityView's vertical bounds. These events are part of a back gesture, and so they
             // should not collapse the stack (which all other touches on areas around the AV would
             // do).
-            if (motionEvent.getRawY() >= avBounds.top && motionEvent.getRawY() <= avBounds.bottom) {
+            if (motionEvent.getRawY() >= avBounds.top
+                            && motionEvent.getRawY() <= avBounds.bottom
+                            && (motionEvent.getRawX() < avBounds.left
+                                || motionEvent.getRawX() > avBounds.right)) {
                 return true;
             }
 
@@ -327,6 +369,39 @@
         return mBubble != null ? mBubble.getKey() : "null";
     }
 
+    /**
+     * Asks the ActivityView's surface to draw on top of all other views in the window. This is
+     * useful for ordering surfaces during animations, but should otherwise be set to false so that
+     * bubbles and menus can draw over the ActivityView.
+     */
+    void setSurfaceZOrderedOnTop(boolean onTop) {
+        if (mActivitySurface == null) {
+            return;
+        }
+
+        mActivitySurface.setZOrderedOnTop(onTop, true);
+    }
+
+    /** Return a GraphicBuffer with the contents of the ActivityView's underlying surface. */
+    @Nullable SurfaceControl.ScreenshotGraphicBuffer snapshotActivitySurface() {
+        if (mActivitySurface == null) {
+            return null;
+        }
+
+        return SurfaceControl.captureLayers(
+                mActivitySurface.getSurfaceControl(),
+                new Rect(0, 0, mActivityView.getWidth(), mActivityView.getHeight()),
+                1 /* scale */);
+    }
+
+    int[] getActivityViewLocationOnScreen() {
+        if (mActivityView != null) {
+            return mActivityView.getLocationOnScreen();
+        } else {
+            return new int[]{0, 0};
+        }
+    }
+
     void setManageClickListener(OnClickListener manageClickListener) {
         findViewById(R.id.settings_button).setOnClickListener(manageClickListener);
     }
@@ -345,13 +420,25 @@
     void applyThemeAttrs() {
         final TypedArray ta = mContext.obtainStyledAttributes(
                 new int[] {android.R.attr.dialogCornerRadius});
-        float cornerRadius = ta.getDimensionPixelSize(0, 0);
+        mCornerRadius = ta.getDimensionPixelSize(0, 0);
         ta.recycle();
 
         if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
                 mContext.getResources())) {
-            mActivityView.setCornerRadius(cornerRadius);
+            mActivityView.setCornerRadius(mCornerRadius);
         }
+
+        final int mode =
+                getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        switch (mode) {
+            case Configuration.UI_MODE_NIGHT_NO:
+                mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_pointer_light));
+                break;
+            case Configuration.UI_MODE_NIGHT_YES:
+                mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_pointer_dark));
+                break;
+        }
+        mPointerView.setBackground(mPointerDrawable);
     }
 
     /**
@@ -398,6 +485,7 @@
         mPointerView.setAlpha(alpha);
         if (mActivityView != null) {
             mActivityView.setAlpha(alpha);
+            mActivityView.bringToFront();
         }
     }
 
@@ -557,6 +645,11 @@
         if (DEBUG_BUBBLE_EXPANDED_VIEW) {
             Log.d(TAG, "updateHeight: bubble=" + getBubbleKey());
         }
+
+        if (mExpandedViewContainerLocation == null) {
+            return;
+        }
+
         if (usingActivityView()) {
             float desiredHeight = mOverflowHeight;
             if (!mIsOverflow) {
@@ -564,7 +657,7 @@
             }
             float height = Math.min(desiredHeight, getMaxExpandedHeight());
             height = Math.max(height, mIsOverflow? mOverflowHeight : mMinHeight);
-            LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
+            ViewGroup.LayoutParams lp = mActivityView.getLayoutParams();
             mNeedsNewHeight = lp.height != height;
             if (!mKeyboardVisible) {
                 // If the keyboard is visible... don't adjust the height because that will cause
@@ -574,7 +667,8 @@
                 mNeedsNewHeight = false;
             }
             if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-                Log.d(TAG, "updateHeight: bubble=" + getBubbleKey() + " height=" + height
+                Log.d(TAG, "updateHeight: bubble=" + getBubbleKey()
+                        + " height=" + height
                         + " mNeedsNewHeight=" + mNeedsNewHeight);
             }
         }
@@ -582,28 +676,40 @@
 
     private int getMaxExpandedHeight() {
         mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
-        int[] windowLocation = mActivityView.getLocationOnScreen();
         int bottomInset = getRootWindowInsets() != null
                 ? getRootWindowInsets().getStableInsetBottom()
                 : 0;
-        return mDisplaySize.y - windowLocation[1] - mSettingsIconHeight - mPointerHeight
+
+        return mDisplaySize.y
+                - mExpandedViewContainerLocation[1]
+                - getPaddingTop()
+                - getPaddingBottom()
+                - mSettingsIconHeight
+                - mPointerHeight
                 - mPointerMargin - bottomInset;
     }
 
     /**
      * Update appearance of the expanded view being displayed.
+     *
+     * @param containerLocationOnScreen The location on-screen of the container the expanded view is
+     *                                  added to. This allows us to calculate max height without
+     *                                  waiting for layout.
      */
-    public void updateView() {
+    public void updateView(int[] containerLocationOnScreen) {
         if (DEBUG_BUBBLE_EXPANDED_VIEW) {
             Log.d(TAG, "updateView: bubble="
                     + getBubbleKey());
         }
+
+        mExpandedViewContainerLocation = containerLocationOnScreen;
+
         if (usingActivityView()
                 && mActivityView.getVisibility() == VISIBLE
                 && mActivityView.isAttachedToWindow()) {
             mActivityView.onLocationChanged();
+            updateHeight();
         }
-        updateHeight();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java
index f4d6432..4712012 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java
@@ -17,10 +17,8 @@
 package com.android.systemui.bubbles;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
-import android.graphics.drawable.ShapeDrawable;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.LinearLayout;
@@ -28,7 +26,6 @@
 
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.R;
-import com.android.systemui.recents.TriangleShape;
 
 /**
  * Educational view to highlight the manage button that allows a user to configure the settings
@@ -36,7 +33,6 @@
  */
 public class BubbleManageEducationView extends LinearLayout {
 
-    private View mPointerView;
     private View mManageView;
 
     public BubbleManageEducationView(Context context) {
@@ -70,26 +66,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);
-
-        final Resources res = getResources();
-        final int pointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
-        final int pointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
-
-        ShapeDrawable triangleShape =
-                new ShapeDrawable(TriangleShape.create(
-                        pointerWidth, pointerHeight, false /* isPointingUp */));
-        triangleShape.setTint(bgColor);
-
-        mPointerView = findViewById(R.id.user_education_pointer);
-        mPointerView.setBackground(triangleShape);
-    }
-
-    /**
-     * Specifies the x value this pointer should point to.
-     */
-    public void setPointerPosition(int x) {
-        mPointerView.setTranslationX(x - (mPointerView.getWidth() / 2));
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
index b77e226..0c62e9f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
@@ -62,13 +62,12 @@
 
     void setUpOverflow(ViewGroup parentViewGroup, BubbleStackView stackView) {
         updateDimensions();
-
         mExpandedView = (BubbleExpandedView) mInflater.inflate(
                 R.layout.bubble_expanded_view, parentViewGroup /* root */,
                 false /* attachToRoot */);
         mExpandedView.setOverflow(true);
         mExpandedView.setStackView(stackView);
-
+        mExpandedView.applyThemeAttrs();
         updateIcon(mContext, parentViewGroup);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 978ae63..088747b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -56,6 +56,8 @@
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.SurfaceView;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
@@ -82,6 +84,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
+import com.android.systemui.bubbles.animation.AnimatableScaleMatrix;
 import com.android.systemui.bubbles.animation.ExpandedAnimationController;
 import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
 import com.android.systemui.bubbles.animation.StackAnimationController;
@@ -90,7 +93,6 @@
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.util.DismissCircleView;
 import com.android.systemui.util.FloatingContentCoordinator;
 import com.android.systemui.util.RelativeTouchListener;
@@ -147,6 +149,16 @@
                     StackAnimationController.IME_ANIMATION_STIFFNESS,
                     StackAnimationController.DEFAULT_BOUNCINESS);
 
+    private final PhysicsAnimator.SpringConfig mScaleInSpringConfig =
+            new PhysicsAnimator.SpringConfig(300f, 0.9f);
+
+    private final PhysicsAnimator.SpringConfig mScaleOutSpringConfig =
+            new PhysicsAnimator.SpringConfig(900f, 1f);
+
+    private final PhysicsAnimator.SpringConfig mTranslateSpringConfig =
+            new PhysicsAnimator.SpringConfig(
+                    SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY);
+
     /**
      * Interface to synchronize {@link View} state and the screen.
      *
@@ -186,8 +198,6 @@
 
     private Point mDisplaySize;
 
-    private final SpringAnimation mExpandedViewXAnim;
-    private final SpringAnimation mExpandedViewYAnim;
     private final BubbleData mBubbleData;
 
     private final ValueAnimator mDesaturateAndDarkenAnimator;
@@ -199,6 +209,24 @@
 
     private FrameLayout mExpandedViewContainer;
 
+    /** Matrix used to scale the expanded view container with a given pivot point. */
+    private final AnimatableScaleMatrix mExpandedViewContainerMatrix = new AnimatableScaleMatrix();
+
+    /**
+     * SurfaceView that we draw screenshots of animating-out bubbles into. This allows us to animate
+     * between bubble activities without needing both to be alive at the same time.
+     */
+    private SurfaceView mAnimatingOutSurfaceView;
+
+    /** Container for the animating-out SurfaceView. */
+    private FrameLayout mAnimatingOutSurfaceContainer;
+
+    /**
+     * Buffer containing a screenshot of the animating-out bubble. This is drawn into the
+     * SurfaceView during animations.
+     */
+    private SurfaceControl.ScreenshotGraphicBuffer mAnimatingOutBubbleBuffer;
+
     private BubbleFlyoutView mFlyout;
     /** Runnable that fades out the flyout and then sets it to GONE. */
     private Runnable mHideFlyout = () -> animateFlyoutCollapsed(true, 0 /* velX */);
@@ -230,8 +258,7 @@
     private int mBubblePaddingTop;
     private int mBubbleTouchPadding;
     private int mExpandedViewPadding;
-    private int mExpandedAnimateXDistance;
-    private int mExpandedAnimateYDistance;
+    private int mCornerRadius;
     private int mPointerHeight;
     private int mStatusBarHeight;
     private int mImeOffset;
@@ -333,8 +360,6 @@
     @NonNull
     private final SurfaceSynchronizer mSurfaceSynchronizer;
 
-    private final NotificationShadeWindowController mNotificationShadeWindowController;
-
     /**
      * Callback to run when the IME visibility changes - BubbleController uses this to update the
      * Bubbles window focusability flags with the WindowManager.
@@ -667,7 +692,6 @@
             @Nullable SurfaceSynchronizer synchronizer,
             FloatingContentCoordinator floatingContentCoordinator,
             SysUiState sysUiState,
-            NotificationShadeWindowController notificationShadeWindowController,
             Runnable allBubblesAnimatedOutAction,
             Consumer<Boolean> onImeVisibilityChanged) {
         super(context);
@@ -676,7 +700,6 @@
         mInflater = LayoutInflater.from(context);
 
         mSysUiState = sysUiState;
-        mNotificationShadeWindowController = notificationShadeWindowController;
 
         Resources res = getResources();
         mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
@@ -684,10 +707,6 @@
         mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
         mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
-        mExpandedAnimateXDistance =
-                res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance);
-        mExpandedAnimateYDistance =
-                res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_y_distance);
         mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
 
         mStatusBarHeight =
@@ -702,6 +721,11 @@
         mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
         int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
 
+        final TypedArray ta = mContext.obtainStyledAttributes(
+                new int[] {android.R.attr.dialogCornerRadius});
+        mCornerRadius = ta.getDimensionPixelSize(0, 0);
+        ta.recycle();
+
         final Runnable onBubbleAnimatedOut = () -> {
             if (getBubbleCount() == 0) {
                 allBubblesAnimatedOutAction.run();
@@ -735,6 +759,24 @@
         mExpandedViewContainer.setClipChildren(false);
         addView(mExpandedViewContainer);
 
+        mAnimatingOutSurfaceContainer = new FrameLayout(getContext());
+        mAnimatingOutSurfaceContainer.setLayoutParams(
+                new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+        addView(mAnimatingOutSurfaceContainer);
+
+        mAnimatingOutSurfaceView = new SurfaceView(getContext());
+        mAnimatingOutSurfaceView.setUseAlpha();
+        mAnimatingOutSurfaceView.setZOrderOnTop(true);
+        mAnimatingOutSurfaceView.setCornerRadius(mCornerRadius);
+        mAnimatingOutSurfaceView.setLayoutParams(new ViewGroup.LayoutParams(0, 0));
+        mAnimatingOutSurfaceContainer.addView(mAnimatingOutSurfaceView);
+
+        mAnimatingOutSurfaceContainer.setPadding(
+                mExpandedViewPadding,
+                mExpandedViewPadding,
+                mExpandedViewPadding,
+                mExpandedViewPadding);
+
         setUpManageMenu();
 
         setUpFlyout();
@@ -780,26 +822,6 @@
         // MagnetizedObjects.
         mMagneticTarget = new MagnetizedObject.MagneticTarget(mDismissTargetCircle, dismissRadius);
 
-        mExpandedViewXAnim =
-                new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
-        mExpandedViewXAnim.setSpring(
-                new SpringForce()
-                        .setStiffness(SpringForce.STIFFNESS_LOW)
-                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
-
-        mExpandedViewYAnim =
-                new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_Y);
-        mExpandedViewYAnim.setSpring(
-                new SpringForce()
-                        .setStiffness(SpringForce.STIFFNESS_LOW)
-                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
-        mExpandedViewYAnim.addEndListener((anim, cancelled, value, velocity) -> {
-            if (mIsExpanded && mExpandedBubble != null
-                    && mExpandedBubble.getExpandedView() != null) {
-                mExpandedBubble.getExpandedView().updateView();
-            }
-        });
-
         setClipChildren(false);
         setFocusable(true);
         mBubbleContainer.bringToFront();
@@ -834,7 +856,7 @@
                     if (mIsExpanded) {
                         mExpandedViewContainer.setTranslationY(getExpandedViewY());
                         if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-                            mExpandedBubble.getExpandedView().updateView();
+                            mExpandedBubble.getExpandedView().updateView(getLocationOnScreen());
                         }
                     }
 
@@ -958,15 +980,10 @@
 
         PhysicsAnimator.getInstance(mManageMenu).setDefaultSpringConfig(mManageSpringConfig);
 
-        final TypedArray ta = mContext.obtainStyledAttributes(
-                new int[] {android.R.attr.dialogCornerRadius});
-        final int menuCornerRadius = ta.getDimensionPixelSize(0, 0);
-        ta.recycle();
-
         mManageMenu.setOutlineProvider(new ViewOutlineProvider() {
             @Override
             public void getOutline(View view, Outline outline) {
-                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), menuCornerRadius);
+                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
             }
         });
         mManageMenu.setClipToOutline(true);
@@ -1082,13 +1099,11 @@
             mBubbleOverflow.setUpOverflow(mBubbleContainer, this);
         } else {
             mBubbleContainer.removeView(mBubbleOverflow.getBtn());
-            mBubbleOverflow.updateDimensions();
-            mBubbleOverflow.updateIcon(mContext,this);
+            mBubbleOverflow.setUpOverflow(mBubbleContainer, this);
             overflowBtnIndex = mBubbleContainer.getChildCount();
         }
         mBubbleContainer.addView(mBubbleOverflow.getBtn(), overflowBtnIndex,
                 new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
-
         mBubbleOverflow.getBtn().setOnClickListener(v -> setSelectedBubble(mBubbleOverflow));
     }
     /**
@@ -1099,6 +1114,7 @@
         setUpOverflow();
         setUpUserEducation();
         setUpManageMenu();
+        updateExpandedViewTheme();
     }
 
     /** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -1200,6 +1216,18 @@
         setupLocalMenu(info);
     }
 
+    void updateExpandedViewTheme() {
+        final List<Bubble> bubbles = mBubbleData.getBubbles();
+        if (bubbles.isEmpty()) {
+            return;
+        }
+        bubbles.forEach(bubble -> {
+            if (bubble.getExpandedView() != null) {
+                bubble.getExpandedView().applyThemeAttrs();
+            }
+        });
+    }
+
     void setupLocalMenu(AccessibilityNodeInfo info) {
         Resources res = mContext.getResources();
 
@@ -1451,6 +1479,31 @@
             mBubbleData.setShowingOverflow(true);
         }
 
+        // If we're expanded, screenshot the currently expanded bubble (before expanding the newly
+        // selected bubble) so we can animate it out.
+        if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+            if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+                // Before screenshotting, have the real ActivityView show on top of other surfaces
+                // so that the screenshot doesn't flicker on top of it.
+                mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true);
+            }
+
+            try {
+                screenshotAnimatingOutBubbleIntoSurface((success) -> {
+                    mAnimatingOutSurfaceContainer.setVisibility(
+                            success ? View.VISIBLE : View.INVISIBLE);
+                    showNewlySelectedBubble(bubbleToSelect);
+                });
+            } catch (Exception e) {
+                showNewlySelectedBubble(bubbleToSelect);
+                e.printStackTrace();
+            }
+        } else {
+            showNewlySelectedBubble(bubbleToSelect);
+        }
+    }
+
+    private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) {
         final BubbleViewProvider previouslySelected = mExpandedBubble;
         mExpandedBubble = bubbleToSelect;
         updatePointerPosition();
@@ -1461,7 +1514,9 @@
             // expanded view becomes visible on the screen. See b/126856255
             mExpandedViewContainer.setAlpha(0.0f);
             mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
-                previouslySelected.setContentVisibility(false);
+                if (previouslySelected != null) {
+                    previouslySelected.setContentVisibility(false);
+                }
                 updateExpandedBubble();
                 requestUpdate();
 
@@ -1578,12 +1633,19 @@
                 final int inset = getResources().getDimensionPixelSize(
                         R.dimen.bubbles_manage_education_top_inset);
                 mManageEducationView.bringToFront();
-                mManageEducationView.setManageViewPosition(mTempRect.left,
-                        mTempRect.top - viewHeight + inset);
-                mManageEducationView.setPointerPosition(mTempRect.centerX() - mTempRect.left);
+                mManageEducationView.setManageViewPosition(0, mTempRect.top - viewHeight + inset);
                 mManageEducationView.animate()
                         .setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION)
                         .setInterpolator(FAST_OUT_SLOW_IN).alpha(1);
+                mManageEducationView.findViewById(R.id.manage).setOnClickListener(view -> {
+                            mExpandedBubble.getExpandedView().findViewById(R.id.settings_button)
+                                    .performClick();
+                            maybeShowManageEducation(false);
+                        });
+                mManageEducationView.findViewById(R.id.got_it).setOnClickListener(view ->
+                        maybeShowManageEducation(false));
+                mManageEducationView.setOnClickListener(view ->
+                        maybeShowManageEducation(false));
             });
             Prefs.putBoolean(getContext(), HAS_SEEN_BUBBLES_MANAGE_EDUCATION, true);
         } else if (!show
@@ -1640,51 +1702,27 @@
         }
     }
 
+    void hideImeFromExpandedBubble() {
+        if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+            // Hide the currently expanded bubble's IME if it's visible before switching to a new
+            // bubble.
+            mExpandedBubble.getExpandedView().hideImeIfVisible();
+        }
+    }
+
     private void beforeExpandedViewAnimation() {
+        mIsExpansionAnimating = true;
         hideFlyoutImmediate();
         updateExpandedBubble();
         updateExpandedView();
-        mIsExpansionAnimating = true;
     }
 
     private void afterExpandedViewAnimation() {
-        updateExpandedView();
         mIsExpansionAnimating = false;
+        updateExpandedView();
         requestUpdate();
     }
 
-    private void animateCollapse() {
-        // Hide the menu if it's visible.
-        showManageMenu(false);
-
-        mIsExpanded = false;
-        final BubbleViewProvider previouslySelected = mExpandedBubble;
-        beforeExpandedViewAnimation();
-        maybeShowManageEducation(false);
-
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "animateCollapse");
-            Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
-                    mExpandedBubble));
-        }
-        updateOverflowVisibility();
-        mBubbleContainer.cancelAllAnimations();
-        mExpandedAnimationController.collapseBackToStack(
-                mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
-                /* collapseTo */,
-                () -> {
-                    mBubbleContainer.setActiveController(mStackAnimationController);
-                    afterExpandedViewAnimation();
-                    previouslySelected.setContentVisibility(false);
-                });
-
-        mExpandedViewXAnim.animateToFinalPosition(getCollapsedX());
-        mExpandedViewYAnim.animateToFinalPosition(getCollapsedY());
-        mExpandedViewContainer.animate()
-                .setDuration(100)
-                .alpha(0f);
-    }
-
     private void animateExpansion() {
         mIsExpanded = true;
         hideStackUserEducation(true /* fromExpansion */);
@@ -1692,32 +1730,198 @@
 
         mBubbleContainer.setActiveController(mExpandedAnimationController);
         updateOverflowVisibility();
+        updatePointerPosition();
         mExpandedAnimationController.expandFromStack(() -> {
-            updatePointerPosition();
             afterExpandedViewAnimation();
             maybeShowManageEducation(true);
         } /* after */);
 
-        mExpandedViewContainer.setTranslationX(getCollapsedX());
-        mExpandedViewContainer.setTranslationY(getCollapsedY());
-        mExpandedViewContainer.setAlpha(0f);
+        mExpandedViewContainer.setTranslationX(0);
+        mExpandedViewContainer.setTranslationY(getExpandedViewY());
+        mExpandedViewContainer.setAlpha(1f);
 
-        mExpandedViewXAnim.animateToFinalPosition(0f);
-        mExpandedViewYAnim.animateToFinalPosition(getExpandedViewY());
-        mExpandedViewContainer.animate()
-                .setDuration(100)
-                .alpha(1f);
+        // X-value of the bubble we're expanding, once it's settled in its row.
+        final float bubbleWillBeAtX =
+                mExpandedAnimationController.getBubbleLeft(
+                        mBubbleData.getBubbles().indexOf(mExpandedBubble));
+
+        // How far horizontally the bubble will be animating. We'll wait a bit longer for bubbles
+        // that are animating farther, so that the expanded view doesn't move as much.
+        final float horizontalDistanceAnimated =
+                Math.abs(bubbleWillBeAtX
+                        - mStackAnimationController.getStackPosition().x);
+
+        // Wait for the path animation target to reach its end, and add a small amount of extra time
+        // if the bubble is moving a lot horizontally.
+        long startDelay = 0L;
+
+        // Should not happen since we lay out before expanding, but just in case...
+        if (getWidth() > 0) {
+            startDelay = (long)
+                    (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION
+                            + (horizontalDistanceAnimated / getWidth()) * 30);
+        }
+
+        // Set the pivot point for the scale, so the expanded view animates out from the bubble.
+        mExpandedViewContainerMatrix.setScale(
+                0f, 0f,
+                bubbleWillBeAtX + mBubbleSize / 2f, getExpandedViewY());
+        mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
+
+        if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+            mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
+        }
+
+        postDelayed(() -> PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
+                .spring(AnimatableScaleMatrix.SCALE_X,
+                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+                        mScaleInSpringConfig)
+                .spring(AnimatableScaleMatrix.SCALE_Y,
+                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+                        mScaleInSpringConfig)
+                .addUpdateListener((target, values) -> {
+                    mExpandedViewContainerMatrix.postTranslate(
+                            mExpandedBubble.getIconView().getTranslationX()
+                                    - bubbleWillBeAtX,
+                            0);
+                    mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
+                })
+                .withEndActions(() -> {
+                    if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+                        mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
+                    }
+                })
+                .start(), startDelay);
+
     }
 
-    private float getCollapsedX() {
-        return mStackAnimationController.getStackPosition().x < getWidth() / 2
-                ? -mExpandedAnimateXDistance
-                : mExpandedAnimateXDistance;
+    private void animateCollapse() {
+        // Hide the menu if it's visible.
+        showManageMenu(false);
+
+        mIsExpanded = false;
+
+        mBubbleContainer.cancelAllAnimations();
+
+        // If we were in the middle of swapping, the animating-out surface would have been scaling
+        // to zero - finish it off.
+        PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
+        mAnimatingOutSurfaceContainer.setScaleX(0f);
+        mAnimatingOutSurfaceContainer.setScaleY(0f);
+
+        if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+            mExpandedBubble.getExpandedView().hideImeIfVisible();
+        }
+
+        final long startDelay =
+                (long) (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION * 0.6f);
+        postDelayed(() -> mExpandedAnimationController.collapseBackToStack(
+                mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
+                /* collapseTo */,
+                () -> {
+                    mBubbleContainer.setActiveController(mStackAnimationController);
+                }), startDelay);
+
+        // We want to visually collapse into this bubble during the animation.
+        final View expandingFromBubble = mExpandedBubble.getIconView();
+
+        // X-value the bubble is animating from (back into the stack).
+        final float expandingFromBubbleAtX =
+                mExpandedAnimationController.getBubbleLeft(
+                        mBubbleData.getBubbles().indexOf(mExpandedBubble));
+
+        // Set the pivot point.
+        mExpandedViewContainerMatrix.setScale(
+                1f, 1f,
+                expandingFromBubbleAtX + mBubbleSize / 2f,
+                getExpandedViewY());
+
+        PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
+        PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
+                .spring(AnimatableScaleMatrix.SCALE_X, 0f, mScaleOutSpringConfig)
+                .spring(AnimatableScaleMatrix.SCALE_Y, 0f, mScaleOutSpringConfig)
+                .addUpdateListener((target, values) -> {
+                    if (expandingFromBubble != null) {
+                        // Follow the bubble as it translates!
+                        mExpandedViewContainerMatrix.postTranslate(
+                                expandingFromBubble.getTranslationX()
+                                        - expandingFromBubbleAtX, 0f);
+                    }
+
+                    mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
+
+                    // Hide early so we don't have a tiny little expanded view still visible at the
+                    // end of the scale animation.
+                    if (mExpandedViewContainerMatrix.getScaleX() < 0.05f) {
+                        mExpandedViewContainer.setVisibility(View.INVISIBLE);
+                    }
+                })
+                .withEndActions(() -> {
+                    final BubbleViewProvider previouslySelected = mExpandedBubble;
+                    beforeExpandedViewAnimation();
+                    maybeShowManageEducation(false);
+
+                    if (DEBUG_BUBBLE_STACK_VIEW) {
+                        Log.d(TAG, "animateCollapse");
+                        Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
+                                mExpandedBubble));
+                    }
+                    updateOverflowVisibility();
+
+                    afterExpandedViewAnimation();
+                    previouslySelected.setContentVisibility(false);
+                })
+                .start();
     }
 
-    private float getCollapsedY() {
-        return Math.min(mStackAnimationController.getStackPosition().y,
-                mExpandedAnimateYDistance);
+    private void animateSwitchBubbles() {
+        // The surface contains a screenshot of the animating out bubble, so we just need to animate
+        // it out (and then release the GraphicBuffer).
+        PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
+        PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer)
+                .spring(DynamicAnimation.SCALE_X, 0f, mScaleOutSpringConfig)
+                .spring(DynamicAnimation.SCALE_Y, 0f, mScaleOutSpringConfig)
+                .spring(DynamicAnimation.TRANSLATION_Y,
+                        mAnimatingOutSurfaceContainer.getTranslationY() - mBubbleSize * 2,
+                        mTranslateSpringConfig)
+                .withEndActions(this::releaseAnimatingOutBubbleBuffer)
+                .start();
+
+        boolean isOverflow = mExpandedBubble != null
+                && mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
+        float expandingFromBubbleDestinationX =
+                mExpandedAnimationController.getBubbleLeft(isOverflow ? getBubbleCount()
+                        : mBubbleData.getBubbles().indexOf(mExpandedBubble));
+
+        mExpandedViewContainer.setAlpha(1f);
+        mExpandedViewContainer.setVisibility(View.VISIBLE);
+
+        mExpandedViewContainerMatrix.setScale(
+                0f, 0f, expandingFromBubbleDestinationX + mBubbleSize / 2f, getExpandedViewY());
+        mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
+
+        mExpandedViewContainer.postDelayed(() -> {
+            if (!mIsExpanded) {
+                return;
+            }
+
+            PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
+                    .spring(AnimatableScaleMatrix.SCALE_X,
+                            AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+                            mScaleInSpringConfig)
+                    .spring(AnimatableScaleMatrix.SCALE_Y,
+                            AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+                            mScaleInSpringConfig)
+                    .addUpdateListener((target, values) -> {
+                        mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
+                    })
+                    .withEndActions(() -> {
+                        if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+                            mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
+                        }
+                    })
+                    .start();
+        }, 25);
     }
 
     private void notifyExpansionChanged(BubbleViewProvider bubble, boolean expanded) {
@@ -1784,9 +1988,13 @@
      */
     @Override
     public void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
-        // If the notification shade is expanded, or the manage menu is open, we shouldn't let the
-        // ActivityView steal any touch events from any location.
-        if (mNotificationShadeWindowController.getPanelExpanded() || mShowingManage) {
+        // If the notification shade is expanded, or the manage menu is open, or we are showing
+        // manage bubbles user education, we shouldn't let the ActivityView steal any touch events
+        // from any location.
+        if (!mIsExpanded
+                || mShowingManage
+                || (mManageEducationView != null
+                    && mManageEducationView.getVisibility() == VISIBLE)) {
             touchableRegion.setEmpty();
         }
     }
@@ -2222,21 +2430,120 @@
             Log.d(TAG, "updateExpandedBubble()");
         }
 
-        if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-            // Hide the currently expanded bubble's IME if it's visible before switching to a new
-            // bubble.
-            mExpandedBubble.getExpandedView().hideImeIfVisible();
-        }
+        hideImeFromExpandedBubble();
 
         mExpandedViewContainer.removeAllViews();
         if (mIsExpanded && mExpandedBubble != null
                 && mExpandedBubble.getExpandedView() != null) {
             BubbleExpandedView bev = mExpandedBubble.getExpandedView();
+            bev.setContentVisibility(false);
+            mExpandedViewContainerMatrix.setScaleX(0f);
+            mExpandedViewContainerMatrix.setScaleY(0f);
+            mExpandedViewContainer.setVisibility(View.INVISIBLE);
+            mExpandedViewContainer.setAlpha(0f);
             mExpandedViewContainer.addView(bev);
             bev.setManageClickListener((view) -> showManageMenu(!mShowingManage));
             bev.populateExpandedView();
-            mExpandedViewContainer.setVisibility(VISIBLE);
-            mExpandedViewContainer.setAlpha(1.0f);
+
+            if (!mIsExpansionAnimating) {
+                mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
+                    post(this::animateSwitchBubbles);
+                });
+            }
+        }
+    }
+
+    /**
+     * Requests a snapshot from the currently expanded bubble's ActivityView and displays it in a
+     * SurfaceView. This allows us to load a newly expanded bubble's Activity into the ActivityView,
+     * while animating the (screenshot of the) previously selected bubble's content away.
+     *
+     * @param onComplete Callback to run once we're done here - called with 'false' if something
+     *                   went wrong, or 'true' if the SurfaceView is now showing a screenshot of the
+     *                   expanded bubble.
+     */
+    private void screenshotAnimatingOutBubbleIntoSurface(Consumer<Boolean> onComplete) {
+        if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
+            // You can't animate null.
+            onComplete.accept(false);
+            return;
+        }
+
+        final BubbleExpandedView animatingOutExpandedView = mExpandedBubble.getExpandedView();
+
+        // Release the previous screenshot if it hasn't been released already.
+        if (mAnimatingOutBubbleBuffer != null) {
+            releaseAnimatingOutBubbleBuffer();
+        }
+
+        try {
+            mAnimatingOutBubbleBuffer = animatingOutExpandedView.snapshotActivitySurface();
+        } catch (Exception e) {
+            // If we fail for any reason, print the stack trace and then notify the callback of our
+            // failure. This is not expected to occur, but it's not worth crashing over.
+            Log.wtf(TAG, e);
+            onComplete.accept(false);
+        }
+
+        if (mAnimatingOutBubbleBuffer == null
+                || mAnimatingOutBubbleBuffer.getGraphicBuffer() == null) {
+            // While no exception was thrown, we were unable to get a snapshot.
+            onComplete.accept(false);
+            return;
+        }
+
+        // Make sure the surface container's properties have been reset.
+        PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
+        mAnimatingOutSurfaceContainer.setScaleX(1f);
+        mAnimatingOutSurfaceContainer.setScaleY(1f);
+        mAnimatingOutSurfaceContainer.setTranslationX(0);
+        mAnimatingOutSurfaceContainer.setTranslationY(0);
+
+        final int[] activityViewLocation =
+                mExpandedBubble.getExpandedView().getActivityViewLocationOnScreen();
+        final int[] surfaceViewLocation = mAnimatingOutSurfaceView.getLocationOnScreen();
+
+        // Translate the surface to overlap the real ActivityView.
+        mAnimatingOutSurfaceContainer.setTranslationY(
+                activityViewLocation[1] - surfaceViewLocation[1]);
+
+        // Set the width/height of the SurfaceView to match the snapshot.
+        mAnimatingOutSurfaceView.getLayoutParams().width =
+                mAnimatingOutBubbleBuffer.getGraphicBuffer().getWidth();
+        mAnimatingOutSurfaceView.getLayoutParams().height =
+                mAnimatingOutBubbleBuffer.getGraphicBuffer().getHeight();
+        mAnimatingOutSurfaceView.requestLayout();
+
+        // Post to wait for layout.
+        post(() -> {
+            // The buffer might have been destroyed if the user is mashing on bubbles, that's okay.
+            if (mAnimatingOutBubbleBuffer.getGraphicBuffer().isDestroyed()) {
+                onComplete.accept(false);
+                return;
+            }
+
+            if (!mIsExpanded) {
+                onComplete.accept(false);
+                return;
+            }
+
+            // Attach the buffer! We're now displaying the snapshot.
+            mAnimatingOutSurfaceView.getHolder().getSurface().attachAndQueueBufferWithColorSpace(
+                    mAnimatingOutBubbleBuffer.getGraphicBuffer(),
+                    mAnimatingOutBubbleBuffer.getColorSpace());
+
+            mSurfaceSynchronizer.syncSurfaceAndRun(() -> post(() -> onComplete.accept(true)));
+        });
+    }
+
+    /**
+     * Releases the buffer containing the screenshot of the animating-out bubble, if it exists and
+     * isn't yet destroyed.
+     */
+    private void releaseAnimatingOutBubbleBuffer() {
+        if (mAnimatingOutBubbleBuffer != null
+                && !mAnimatingOutBubbleBuffer.getGraphicBuffer().isDestroyed()) {
+            mAnimatingOutBubbleBuffer.getGraphicBuffer().destroy();
         }
     }
 
@@ -2246,19 +2553,10 @@
         }
 
         mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
-        if (mIsExpanded) {
-            final float y = getExpandedViewY();
-            if (!mExpandedViewYAnim.isRunning()) {
-                // We're not animating so set the value
-                mExpandedViewContainer.setTranslationY(y);
-                if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-                    mExpandedBubble.getExpandedView().updateView();
-                }
-            } else {
-                // We are animating so update the value; there is an end listener on the animator
-                // that will ensure expandedeView.updateView gets called.
-                mExpandedViewYAnim.animateToFinalPosition(y);
-            }
+        if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+            mExpandedViewContainer.setTranslationY(getExpandedViewY());
+            mExpandedBubble.getExpandedView().updateView(
+                    mExpandedViewContainer.getLocationOnScreen());
         }
 
         mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java
new file mode 100644
index 0000000..ae78336
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java
@@ -0,0 +1,137 @@
+/*
+ * 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.bubbles.animation;
+
+import android.graphics.Matrix;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+
+/**
+ * Matrix whose scale properties can be animated using physics animations, via the {@link #SCALE_X}
+ * and {@link #SCALE_Y} FloatProperties.
+ *
+ * This is useful when you need to perform a scale animation with a pivot point, since pivot points
+ * are not supported by standard View scale operations but are supported by matrices.
+ *
+ * NOTE: DynamicAnimation assumes that all custom properties are denominated in pixels, and thus
+ * considers 1 to be the smallest user-visible change for custom properties. This means that if you
+ * animate {@link #SCALE_X} and {@link #SCALE_Y} to 3f, for example, the animation would have only
+ * three frames.
+ *
+ * To work around this, whenever animating to a desired scale value, animate to the value returned
+ * by {@link #getAnimatableValueForScaleFactor} instead. The SCALE_X and SCALE_Y properties will
+ * convert that (larger) value into the appropriate scale factor when scaling the matrix.
+ */
+public class AnimatableScaleMatrix extends Matrix {
+
+    /**
+     * The X value of the scale.
+     *
+     * NOTE: This must be set or animated to the value returned by
+     * {@link #getAnimatableValueForScaleFactor}, not the desired scale factor itself.
+     */
+    public static final FloatPropertyCompat<AnimatableScaleMatrix> SCALE_X =
+            new FloatPropertyCompat<AnimatableScaleMatrix>("matrixScaleX") {
+        @Override
+        public float getValue(AnimatableScaleMatrix object) {
+            return getAnimatableValueForScaleFactor(object.mScaleX);
+        }
+
+        @Override
+        public void setValue(AnimatableScaleMatrix object, float value) {
+            object.setScaleX(value * DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);
+        }
+    };
+
+    /**
+     * The Y value of the scale.
+     *
+     * NOTE: This must be set or animated to the value returned by
+     * {@link #getAnimatableValueForScaleFactor}, not the desired scale factor itself.
+     */
+    public static final FloatPropertyCompat<AnimatableScaleMatrix> SCALE_Y =
+            new FloatPropertyCompat<AnimatableScaleMatrix>("matrixScaleY") {
+                @Override
+                public float getValue(AnimatableScaleMatrix object) {
+                    return getAnimatableValueForScaleFactor(object.mScaleY);
+                }
+
+                @Override
+                public void setValue(AnimatableScaleMatrix object, float value) {
+                    object.setScaleY(value * DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);
+                }
+            };
+
+    private float mScaleX = 1f;
+    private float mScaleY = 1f;
+
+    private float mPivotX = 0f;
+    private float mPivotY = 0f;
+
+    /**
+     * Return the value to animate SCALE_X or SCALE_Y to in order to achieve the desired scale
+     * factor.
+     */
+    public static float getAnimatableValueForScaleFactor(float scale) {
+        return scale * (1f / DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);
+    }
+
+    @Override
+    public void setScale(float sx, float sy, float px, float py) {
+        mScaleX = sx;
+        mScaleY = sy;
+        mPivotX = px;
+        mPivotY = py;
+        super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
+    }
+
+    public void setScaleX(float scaleX) {
+        mScaleX = scaleX;
+        super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
+    }
+
+    public void setScaleY(float scaleY) {
+        mScaleY = scaleY;
+        super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
+    }
+
+    public void setPivotX(float pivotX) {
+        mPivotX = pivotX;
+        super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
+    }
+
+    public void setPivotY(float pivotY) {
+        mPivotY = pivotY;
+        super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
+    }
+
+    public float getScaleX() {
+        return mScaleX;
+    }
+
+    public float getScaleY() {
+        return mScaleY;
+    }
+
+    public float getPivotX() {
+        return mPivotX;
+    }
+
+    public float getPivotY() {
+        return mPivotY;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 76ff1af..86fe10d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -56,7 +56,7 @@
     private static final int ANIMATE_TRANSLATION_FACTOR = 4;
 
     /** Duration of the expand/collapse target path animation. */
-    private static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175;
+    public static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175;
 
     /** Stiffness for the expand/collapse path-following animation. */
     private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
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 fd1f879..3ef2044 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -1034,13 +1034,13 @@
                 mMagnetizedStack.getFlingToTargetMinVelocity() /* default */);
         final float maxVelocity = Settings.Secure.getFloat(contentResolver,
                 "bubble_dismiss_stick_max_velocity",
-                mMagnetizedStack.getStickToTargetMaxVelocity() /* default */);
+                mMagnetizedStack.getStickToTargetMaxXVelocity() /* default */);
         final float targetWidth = Settings.Secure.getFloat(contentResolver,
                 "bubble_dismiss_target_width_percent",
                 mMagnetizedStack.getFlingToTargetWidthPercent() /* default */);
 
         mMagnetizedStack.setFlingToTargetMinVelocity(minVelocity);
-        mMagnetizedStack.setStickToTargetMaxVelocity(maxVelocity);
+        mMagnetizedStack.setStickToTargetMaxXVelocity(maxVelocity);
         mMagnetizedStack.setFlingToTargetWidthPercent(targetWidth);
 
         return mMagnetizedStack;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 91f032d..28bcf3a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -24,6 +24,8 @@
 import com.android.systemui.screenrecord.ScreenRecordDialog;
 import com.android.systemui.settings.BrightnessDialog;
 import com.android.systemui.tuner.TunerActivity;
+import com.android.systemui.usb.UsbDebuggingActivity;
+import com.android.systemui.usb.UsbDebuggingSecondaryUserActivity;
 
 import dagger.Binds;
 import dagger.Module;
@@ -70,4 +72,17 @@
     @IntoMap
     @ClassKey(BubbleOverflowActivity.class)
     public abstract Activity bindBubbleOverflowActivity(BubbleOverflowActivity activity);
+
+    /** Inject into UsbDebuggingActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(UsbDebuggingActivity.class)
+    public abstract Activity bindUsbDebuggingActivity(UsbDebuggingActivity activity);
+
+    /** Inject into UsbDebuggingSecondaryUserActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(UsbDebuggingSecondaryUserActivity.class)
+    public abstract Activity bindUsbDebuggingSecondaryUserActivity(
+            UsbDebuggingSecondaryUserActivity activity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 8368b2c..1f30305 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -25,6 +25,7 @@
 import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.ServiceManager;
 import android.util.DisplayMetrics;
 import android.view.Choreographer;
@@ -39,9 +40,12 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.Prefs;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.PluginInitializerImpl;
 import com.android.systemui.shared.plugins.PluginManager;
@@ -178,6 +182,21 @@
         return ActivityManagerWrapper.getInstance();
     }
 
+    /** Provides and initializes the {#link BroadcastDispatcher} for SystemUI */
+    @Singleton
+    @Provides
+    public BroadcastDispatcher providesBroadcastDispatcher(
+            Context context,
+            @Background Looper backgroundLooper,
+            DumpManager dumpManager,
+            BroadcastDispatcherLogger logger
+    ) {
+        BroadcastDispatcher bD =
+                new BroadcastDispatcher(context, backgroundLooper, dumpManager, logger);
+        bD.initialize();
+        return bD;
+    }
+
     @Singleton
     @Provides
     public DevicePolicyManagerWrapper provideDevicePolicyManagerWrapper() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b26dc5f..53251ed43 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -654,7 +654,7 @@
         @Override
         public void onBouncerVisiblityChanged(boolean shown) {
             synchronized (KeyguardViewMediator.this) {
-                adjustStatusBarLocked(shown);
+                adjustStatusBarLocked(shown, false);
             }
         }
 
@@ -2003,10 +2003,12 @@
     }
 
     private void adjustStatusBarLocked() {
-        adjustStatusBarLocked(false /* forceHideHomeRecentsButtons */);
+        adjustStatusBarLocked(false /* forceHideHomeRecentsButtons */,
+                false /* forceClearFlags */);
     }
 
-    private void adjustStatusBarLocked(boolean forceHideHomeRecentsButtons) {
+    private void adjustStatusBarLocked(boolean forceHideHomeRecentsButtons,
+            boolean forceClearFlags) {
         if (mStatusBarManager == null) {
             mStatusBarManager = (StatusBarManager)
                     mContext.getSystemService(Context.STATUS_BAR_SERVICE);
@@ -2018,6 +2020,13 @@
             // Disable aspects of the system/status/navigation bars that must not be re-enabled by
             // windows that appear on top, ever
             int flags = StatusBarManager.DISABLE_NONE;
+
+            // TODO (b/155663717) After restart, status bar will not properly hide home button
+            //  unless disable is called to show un-hide it once first
+            if (forceClearFlags) {
+                mStatusBarManager.disable(flags);
+            }
+
             if (forceHideHomeRecentsButtons || isShowingAndNotOccluded()) {
                 if (!mShowHomeOverLockscreen || !mInGestureNavigationMode) {
                     flags |= StatusBarManager.DISABLE_HOME;
@@ -2141,6 +2150,7 @@
     public void onBootCompleted() {
         synchronized (this) {
             mBootCompleted = true;
+            adjustStatusBarLocked(false, true);
             if (mBootSendUserPresent) {
                 sendUserPresentBroadcast();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
new file mode 100644
index 0000000..7d1f1c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
@@ -0,0 +1,33 @@
+/*
+ * 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for BroadcastDispatcher-related messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface BroadcastDispatcherLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 9c89fee..a2086e8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -97,6 +97,18 @@
         return buffer;
     }
 
+    /** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */
+    @Provides
+    @Singleton
+    @BroadcastDispatcherLog
+    public static LogBuffer provideBroadcastDispatcherLogBuffer(
+            LogcatEchoTracker bufferFilter,
+            DumpManager dumpManager) {
+        LogBuffer buffer = new LogBuffer("BroadcastDispatcherLog", 500, 10, bufferFilter);
+        buffer.attach(dumpManager);
+        return buffer;
+    }
+
     /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
     @Provides
     @Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index 2f521ea..de0af16 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -34,7 +34,6 @@
     private val context: Context,
     private val localMediaManagerFactory: LocalMediaManagerFactory,
     private val mr2manager: MediaRouter2Manager,
-    private val featureFlag: MediaFeatureFlag,
     @Main private val fgExecutor: Executor,
     private val mediaDataManager: MediaDataManager
 ) : MediaDataManager.Listener {
@@ -56,20 +55,19 @@
     fun removeListener(listener: Listener) = listeners.remove(listener)
 
     override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
-        if (featureFlag.enabled) {
-            if (oldKey != null && oldKey != key) {
-                val oldToken = entries.remove(oldKey)
-                oldToken?.stop()
+        if (oldKey != null && oldKey != key) {
+            val oldEntry = entries.remove(oldKey)
+            oldEntry?.stop()
+        }
+        var entry = entries[key]
+        if (entry == null || entry?.token != data.token) {
+            entry?.stop()
+            val controller = data.token?.let {
+                MediaController(context, it)
             }
-            var tok = entries[key]
-            if (tok == null && data.token != null) {
-                val controller = MediaController(context, data.token!!)
-                tok = Token(key, controller, localMediaManagerFactory.create(data.packageName))
-                entries[key] = tok
-                tok.start()
-            }
-        } else {
-            onMediaDataRemoved(key)
+            entry = Token(key, controller, localMediaManagerFactory.create(data.packageName))
+            entries[key] = entry
+            entry.start()
         }
     }
 
@@ -100,9 +98,11 @@
 
     private inner class Token(
         val key: String,
-        val controller: MediaController,
+        val controller: MediaController?,
         val localMediaManager: LocalMediaManager
     ) : LocalMediaManager.DeviceCallback {
+        val token
+            get() = controller?.sessionToken
         private var started = false
         private var current: MediaDevice? = null
             set(value) {
@@ -132,10 +132,14 @@
         }
         private fun updateCurrent() {
             val device = localMediaManager.getCurrentConnectedDevice()
-            val route = mr2manager.getRoutingSessionForMediaController(controller)
-            // If we get a null route, then don't trust the device. Just set to null to disable the
-            // output switcher chip.
-            current = if (route != null) device else null
+            controller?.let {
+                val route = mr2manager.getRoutingSessionForMediaController(it)
+                // If we get a null route, then don't trust the device. Just set to null to disable the
+                // output switcher chip.
+                current = if (route != null) device else null
+            } ?: run {
+                current = device
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index 7f7e108..2980f11 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.pip;
 
+import android.animation.AnimationHandler;
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
@@ -26,6 +27,7 @@
 import android.view.animation.Interpolator;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -79,6 +81,13 @@
 
     private PipTransitionAnimator mCurrentAnimator;
 
+    private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
+            ThreadLocal.withInitial(() -> {
+                AnimationHandler handler = new AnimationHandler();
+                handler.setProvider(new SfVsyncFrameCallbackProvider());
+                return handler;
+            });
+
     @Inject
     PipAnimationController(Context context, PipSurfaceTransactionHelper helper) {
         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
@@ -135,6 +144,7 @@
         animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper);
         animator.setInterpolator(mFastOutSlowInInterpolator);
         animator.setFloatValues(FRACTION_START, FRACTION_END);
+        animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get());
         return animator;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index c6f144a..b93e07e 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -504,8 +504,18 @@
                 // this could happen if rotation finishes before the animation
                 mLastReportedBounds.set(destinationBoundsOut);
                 scheduleFinishResizePip(mLastReportedBounds);
-            } else if (!mLastReportedBounds.isEmpty()) {
-                destinationBoundsOut.set(mLastReportedBounds);
+            } else  {
+                // There could be an animation on-going. If there is one on-going, last-reported
+                // bounds isn't yet updated. We'll use the animator's bounds instead.
+                if (animator != null && animator.isRunning()) {
+                    if (!animator.getDestinationBounds().isEmpty()) {
+                        destinationBoundsOut.set(animator.getDestinationBounds());
+                    }
+                } else {
+                    if (!mLastReportedBounds.isEmpty()) {
+                        destinationBoundsOut.set(mLastReportedBounds);
+                    }
+                }
             }
             return;
         }
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 b714bff..e38bfb4 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -269,7 +269,7 @@
             if (stackInfo != null) {
                 // If SystemUI restart, and it already existed a pinned stack,
                 // register the pip input consumer to ensure touch can send to it.
-                mInputConsumerController.registerInputConsumer();
+                mInputConsumerController.registerInputConsumer(true /* withSfVsync */);
             }
         } catch (RemoteException | UnsupportedOperationException e) {
             e.printStackTrace();
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 a3185a2..6ab73fc 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -295,6 +295,14 @@
     }
 
     @Override
+    public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
+        super.onTopResumedActivityChanged(isTopResumedActivity);
+        if (!isTopResumedActivity && mMenuState != MENU_STATE_NONE) {
+            hideMenu();
+        }
+    }
+
+    @Override
     protected void onStop() {
         super.onStop();
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index 8b4d932..31d292f 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -197,7 +197,7 @@
     }
 
     public void onActivityPinned() {
-        mInputConsumerController.registerInputConsumer();
+        mInputConsumerController.registerInputConsumer(true /* withSfVsync */);
     }
 
     public void onActivityUnpinned() {
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 2e75bab..d077666 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -23,9 +23,11 @@
 import android.graphics.Rect;
 import android.os.Debug;
 import android.util.Log;
+import android.view.Choreographer;
 
 import androidx.dynamicanimation.animation.SpringForce;
 
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.pip.PipSnapAlgorithm;
 import com.android.systemui.pip.PipTaskOrganizer;
 import com.android.systemui.util.FloatingContentCoordinator;
@@ -68,6 +70,9 @@
     /** The region that all of PIP must stay within. */
     private final Rect mFloatingAllowedArea = new Rect();
 
+    private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider =
+            new SfVsyncFrameCallbackProvider();
+
     /**
      * Bounds that are animated using the physics animator.
      */
@@ -79,6 +84,10 @@
     /** Coordinator instance for resolving conflicts with other floating content. */
     private FloatingContentCoordinator mFloatingContentCoordinator;
 
+    /** Callback that re-sizes PIP to the animated bounds. */
+    private final Choreographer.FrameCallback mResizePipVsyncCallback =
+            l -> resizePipUnchecked(mAnimatedBounds);
+
     /**
      * PhysicsAnimator instance for animating {@link #mAnimatedBounds} using physics animations.
      */
@@ -89,7 +98,7 @@
      * Update listener that resizes the PIP to {@link #mAnimatedBounds}.
      */
     final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener =
-            (target, values) -> resizePipUnchecked(mAnimatedBounds);
+            (target, values) -> mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback);
 
     /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */
     private PhysicsAnimator.FlingConfig mFlingConfigX;
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 bafbd21..f6b212c 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
@@ -32,6 +32,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.DeviceConfig;
+import android.view.BatchedInputEventReceiver;
+import android.view.Choreographer;
 import android.view.InputChannel;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
@@ -323,9 +325,9 @@
         mMinSize.set(minX, minY);
     }
 
-    class SysUiInputEventReceiver extends InputEventReceiver {
+    class SysUiInputEventReceiver extends BatchedInputEventReceiver {
         SysUiInputEventReceiver(InputChannel channel, Looper looper) {
-            super(channel, looper);
+            super(channel, looper, Choreographer.getSfInstance());
         }
 
         public void onInputEvent(InputEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index a624479..7babe2f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -41,13 +41,19 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Outline;
 import android.graphics.PixelFormat;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.LayerDrawable;
 import android.media.MediaActionSound;
 import android.net.Uri;
 import android.os.Handler;
@@ -114,7 +120,6 @@
         public Consumer<Uri> finisher;
         public GlobalScreenshot.ActionsReadyListener mActionsReadyListener;
         public int errorMsgResId;
-        public boolean createDeleteAction;
 
         void clearImage() {
             image = null;
@@ -168,6 +173,8 @@
 
     private static final long SCREENSHOT_FLASH_IN_DURATION_MS = 133;
     private static final long SCREENSHOT_FLASH_OUT_DURATION_MS = 217;
+    // delay before starting to fade in dismiss button
+    private static final long SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS = 200;
     private static final long SCREENSHOT_TO_CORNER_X_DURATION_MS = 234;
     private static final long SCREENSHOT_TO_CORNER_Y_DURATION_MS = 500;
     private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
@@ -208,6 +215,7 @@
     private Animator mScreenshotAnimation;
     private Runnable mOnCompleteRunnable;
     private Animator mDismissAnimation;
+    private SavedImageData mImageData;
     private boolean mInDarkMode = false;
     private boolean mDirectionLTR = true;
     private boolean mOrientationPortrait = true;
@@ -226,6 +234,9 @@
             switch (msg.what) {
                 case MESSAGE_CORNER_TIMEOUT:
                     mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT);
+                    if (mImageData != null) {
+                        mNotificationsController.showSilentScreenshotNotification(mImageData);
+                    }
                     GlobalScreenshot.this.dismissScreenshot("timeout", false);
                     mOnCompleteRunnable.run();
                     break;
@@ -261,7 +272,8 @@
                         | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                         | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
+                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                        | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                 PixelFormat.TRANSLUCENT);
         mWindowLayoutParams.setTitle("ScreenshotAnimation");
         mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -396,6 +408,9 @@
         mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button);
         mDismissButton.setOnClickListener(view -> {
             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL);
+            if (mImageData != null) {
+                mNotificationsController.showSilentScreenshotNotification(mImageData);
+            }
             dismissScreenshot("dismiss_button", false);
             mOnCompleteRunnable.run();
         });
@@ -424,7 +439,6 @@
         data.image = mScreenBitmap;
         data.finisher = finisher;
         data.mActionsReadyListener = actionsReadyListener;
-        data.createDeleteAction = false;
 
         if (mSaveInBgTask != null) {
             // just log success/failure for the pre-existing screenshot
@@ -436,6 +450,10 @@
             });
         }
 
+        mImageData = null; // make sure we clear the current stored data
+        mNotificationsController.reset();
+        mNotificationsController.setImage(mScreenBitmap);
+
         mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data);
         mSaveInBgTask.execute();
     }
@@ -449,10 +467,12 @@
         int rot = mDisplay.getRotation();
         int width = crop.width();
         int height = crop.height();
-        takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect);
+        takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect,
+                Insets.NONE, true);
     }
 
-    private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect) {
+    private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
+            Insets screenInsets, boolean showFlash) {
         dismissScreenshot("new screenshot requested", true);
 
         mScreenBitmap = screenshot;
@@ -482,7 +502,7 @@
             mDismissAnimation.cancel();
         }
         // Start the post-screenshot animation
-        startAnimation(finisher, mScreenBitmap.getWidth(), mScreenBitmap.getHeight(), screenRect);
+        startAnimation(finisher, screenRect, screenInsets, showFlash);
     }
 
     void takeScreenshot(Consumer<Uri> finisher, Runnable onComplete) {
@@ -498,9 +518,15 @@
             Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
             Consumer<Uri> finisher, Runnable onComplete) {
         // TODO: use task Id, userId, topComponent for smart handler
-        // TODO: use visibleInsets for animation
+
         mOnCompleteRunnable = onComplete;
-        takeScreenshot(screenshot, finisher, screenshotScreenBounds);
+        if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
+            takeScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false);
+        } else {
+            takeScreenshot(screenshot, finisher,
+                    new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE,
+                    true);
+        }
     }
 
     /**
@@ -621,8 +647,9 @@
         }
 
         // Clear any references to the bitmap
-        mScreenshotPreview.setImageBitmap(null);
-        mScreenshotAnimatedView.setImageBitmap(null);
+        mScreenshotPreview.setImageDrawable(null);
+        mScreenshotAnimatedView.setImageDrawable(null);
+        mScreenshotAnimatedView.setVisibility(View.GONE);
         mActionsContainerBackground.setVisibility(View.GONE);
         mActionsContainer.setVisibility(View.GONE);
         mBackgroundProtection.setAlpha(0f);
@@ -643,6 +670,7 @@
      */
     private void showUiOnActionsReady(SavedImageData imageData) {
         logSuccessOnActionsReady(imageData);
+        mImageData = imageData;
 
         AccessibilityManager accessibilityManager = (AccessibilityManager)
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
@@ -688,8 +716,9 @@
     /**
      * Starts the animation after taking the screenshot
      */
-    private void startAnimation(
-            final Consumer<Uri> finisher, int bitmapWidth, int bitmapHeight, Rect screenRect) {
+    private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets,
+            boolean showFlash) {
+
         // If power save is on, show a toast so there is some visual indication that a
         // screenshot has been taken.
         PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -701,9 +730,13 @@
             if (!mScreenshotLayout.isAttachedToWindow()) {
                 mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
             }
-            mScreenshotAnimatedView.setImageBitmap(mScreenBitmap);
-            mScreenshotPreview.setImageBitmap(mScreenBitmap);
+            mScreenshotAnimatedView.setImageDrawable(
+                    createScreenDrawable(mScreenBitmap, screenInsets));
+            setAnimatedViewSize(screenRect.width(), screenRect.height());
+            // Show when the animation starts
+            mScreenshotAnimatedView.setVisibility(View.GONE);
 
+            mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets));
             // make static preview invisible (from gone) so we can query its location on screen
             mScreenshotPreview.setVisibility(View.INVISIBLE);
 
@@ -711,14 +744,14 @@
                 mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
 
                 mScreenshotAnimation =
-                        createScreenshotDropInAnimation(bitmapWidth, bitmapHeight, screenRect);
+                        createScreenshotDropInAnimation(screenRect, showFlash);
 
                 saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
-                            @Override
-                            void onActionsReady(SavedImageData imageData) {
-                                showUiOnActionsReady(imageData);
-                            }
-                        });
+                    @Override
+                    void onActionsReady(SavedImageData imageData) {
+                        showUiOnActionsReady(imageData);
+                    }
+                });
 
                 // Play the shutter sound to notify that we've taken a screenshot
                 mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
@@ -730,19 +763,19 @@
         });
     }
 
-    private AnimatorSet createScreenshotDropInAnimation(
-            int bitmapWidth, int bitmapHeight, Rect bounds) {
+    private AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) {
         Rect previewBounds = new Rect();
         mScreenshotPreview.getBoundsOnScreen(previewBounds);
 
-        float cornerScale = mCornerSizeX / (mOrientationPortrait ? bitmapWidth : bitmapHeight);
-        float currentScale = bounds.height() / (float) bitmapHeight;
+        float cornerScale =
+                mCornerSizeX / (mOrientationPortrait ? bounds.width() : bounds.height());
+        final float currentScale = 1f;
 
         mScreenshotAnimatedView.setScaleX(currentScale);
         mScreenshotAnimatedView.setScaleY(currentScale);
 
-        mScreenshotAnimatedView.setPivotX(0);
-        mScreenshotAnimatedView.setPivotY(0);
+        mDismissButton.setAlpha(0);
+        mDismissButton.setVisibility(View.VISIBLE);
 
         AnimatorSet dropInAnimation = new AnimatorSet();
         ValueAnimator flashInAnimator = ValueAnimator.ofFloat(0, 1);
@@ -765,6 +798,8 @@
         toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
         float xPositionPct =
                 SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
+        float dismissPct =
+                SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
         float scalePct =
                 SCREENSHOT_TO_CORNER_SCALE_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
         toCorner.addUpdateListener(animation -> {
@@ -785,13 +820,26 @@
             if (t < xPositionPct) {
                 float xCenter = MathUtils.lerp(startPos.x, finalPos.x,
                         mFastOutSlowIn.getInterpolation(t / xPositionPct));
-                mScreenshotAnimatedView.setX(xCenter - bitmapWidth * currentScaleX / 2f);
+                mScreenshotAnimatedView.setX(xCenter - bounds.width() * currentScaleX / 2f);
             } else {
-                mScreenshotAnimatedView.setX(finalPos.x - bitmapWidth * currentScaleX / 2f);
+                mScreenshotAnimatedView.setX(finalPos.x - bounds.width() * currentScaleX / 2f);
             }
             float yCenter = MathUtils.lerp(
                     startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t));
-            mScreenshotAnimatedView.setY(yCenter - bitmapHeight * currentScaleY / 2f);
+            mScreenshotAnimatedView.setY(yCenter - bounds.height() * currentScaleY / 2f);
+
+            if (t >= dismissPct) {
+                mDismissButton.setAlpha((t - dismissPct) / (1 - dismissPct));
+                float currentX = mScreenshotAnimatedView.getX();
+                float currentY = mScreenshotAnimatedView.getY();
+                mDismissButton.setY(currentY - mDismissButton.getHeight() / 2f);
+                if (mDirectionLTR) {
+                    mDismissButton.setX(currentX
+                            + bounds.width() * currentScaleX - mDismissButton.getWidth() / 2f);
+                } else {
+                    mDismissButton.setX(currentX - mDismissButton.getWidth() / 2f);
+                }
+            }
         });
 
         toCorner.addListener(new AnimatorListenerAdapter() {
@@ -805,20 +853,31 @@
         mScreenshotFlash.setAlpha(0f);
         mScreenshotFlash.setVisibility(View.VISIBLE);
 
-        dropInAnimation.play(flashOutAnimator).after(flashInAnimator);
-        dropInAnimation.play(flashOutAnimator).with(toCorner);
+        if (showFlash) {
+            dropInAnimation.play(flashOutAnimator).after(flashInAnimator);
+            dropInAnimation.play(flashOutAnimator).with(toCorner);
+        } else {
+            dropInAnimation.play(toCorner);
+        }
 
         dropInAnimation.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
+                mDismissButton.setAlpha(1);
+                float dismissOffset = mDismissButton.getWidth() / 2f;
+                float finalDismissX = mDirectionLTR
+                        ? finalPos.x - dismissOffset + bounds.width() * cornerScale / 2f
+                        : finalPos.x - dismissOffset - bounds.width() * cornerScale / 2f;
+                mDismissButton.setX(finalDismissX);
+                mDismissButton.setY(
+                        finalPos.y - dismissOffset - bounds.height() * cornerScale / 2f);
                 mScreenshotAnimatedView.setScaleX(1);
                 mScreenshotAnimatedView.setScaleY(1);
                 mScreenshotAnimatedView.setX(finalPos.x - bounds.width() * cornerScale / 2f);
                 mScreenshotAnimatedView.setY(finalPos.y - bounds.height() * cornerScale / 2f);
                 mScreenshotAnimatedView.setVisibility(View.GONE);
                 mScreenshotPreview.setVisibility(View.VISIBLE);
-                mDismissButton.setVisibility(View.VISIBLE);
                 mScreenshotLayout.forceLayout();
             }
         });
@@ -972,6 +1031,71 @@
         return animSet;
     }
 
+    private void setAnimatedViewSize(int width, int height) {
+        ViewGroup.LayoutParams layoutParams = mScreenshotAnimatedView.getLayoutParams();
+        layoutParams.width = width;
+        layoutParams.height = height;
+        mScreenshotAnimatedView.setLayoutParams(layoutParams);
+    }
+
+    /** Does the aspect ratio of the bitmap with insets removed match the bounds. */
+    private boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, Rect screenBounds) {
+        int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right;
+        int insettedHeight = bitmap.getHeight() - bitmapInsets.top - bitmapInsets.bottom;
+
+        if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0
+                || bitmap.getHeight() == 0) {
+            Log.e(TAG, String.format(
+                    "Provided bitmap and insets create degenerate region: %dx%d %s",
+                    bitmap.getWidth(), bitmap.getHeight(), bitmapInsets));
+            return false;
+        }
+
+        float insettedBitmapAspect = ((float) insettedWidth) / insettedHeight;
+        float boundsAspect = ((float) screenBounds.width()) / screenBounds.height();
+
+        boolean matchWithinTolerance = Math.abs(insettedBitmapAspect - boundsAspect) < 0.1f;
+        if (!matchWithinTolerance) {
+            Log.d(TAG, String.format("aspectRatiosMatch: don't match bitmap: %f, bounds: %f",
+                    insettedBitmapAspect, boundsAspect));
+        }
+
+        return matchWithinTolerance;
+    }
+
+    /**
+     * Create a drawable using the size of the bitmap and insets as the fractional inset parameters.
+     */
+    private Drawable createScreenDrawable(Bitmap bitmap, Insets insets) {
+        int insettedWidth = bitmap.getWidth() - insets.left - insets.right;
+        int insettedHeight = bitmap.getHeight() - insets.top - insets.bottom;
+
+        BitmapDrawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
+        if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0
+                || bitmap.getHeight() == 0) {
+            Log.e(TAG, String.format(
+                    "Can't create insetted drawable, using 0 insets "
+                            + "bitmap and insets create degenerate region: %dx%d %s",
+                    bitmap.getWidth(), bitmap.getHeight(), insets));
+            return bitmapDrawable;
+        }
+
+        InsetDrawable insetDrawable = new InsetDrawable(bitmapDrawable,
+                -1f * insets.left / insettedWidth,
+                -1f * insets.top / insettedHeight,
+                -1f * insets.right / insettedWidth,
+                -1f * insets.bottom / insettedHeight);
+
+        if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) {
+            // Are any of the insets negative, meaning the bitmap is smaller than the bounds so need
+            // to fill in the background of the drawable.
+            return new LayerDrawable(new Drawable[] {
+                    new ColorDrawable(Color.BLACK), insetDrawable});
+        } else {
+            return insetDrawable;
+        }
+    }
+
     /**
      * Receiver to proxy the share or edit intent, used to clean up the notification and send
      * appropriate signals to the system (ie. to dismiss the keyguard if necessary).
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
deleted file mode 100644
index 0017b1f..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
+++ /dev/null
@@ -1,567 +0,0 @@
-/*
- * Copyright (C) 2011 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.screenshot;
-
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Insets;
-import android.graphics.PixelFormat;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.media.MediaActionSound;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.PowerManager;
-import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.SurfaceControl;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.animation.Interpolator;
-import android.widget.ImageView;
-import android.widget.Toast;
-
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Class for handling device screen shots
- *
- * @deprecated will be removed when corner flow is complete and tested
- */
-@Singleton
-@Deprecated
-public class GlobalScreenshotLegacy {
-
-    // These strings are used for communicating the action invoked to
-    // ScreenshotNotificationSmartActionsProvider.
-    static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
-    static final String EXTRA_ID = "android:screenshot_id";
-    static final String ACTION_TYPE_DELETE = "Delete";
-    static final String ACTION_TYPE_SHARE = "Share";
-    static final String ACTION_TYPE_EDIT = "Edit";
-    static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
-    static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
-
-    static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
-    static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
-    static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
-
-    private static final String TAG = "GlobalScreenshot";
-
-    private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
-    private static final int SCREENSHOT_DROP_IN_DURATION = 430;
-    private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
-    private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
-    private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
-    private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320;
-    private static final float BACKGROUND_ALPHA = 0.5f;
-    private static final float SCREENSHOT_SCALE = 1f;
-    private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f;
-    private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f;
-    private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f;
-    private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;
-
-    private final ScreenshotNotificationsController mNotificationsController;
-
-    private Context mContext;
-    private WindowManager mWindowManager;
-    private WindowManager.LayoutParams mWindowLayoutParams;
-    private Display mDisplay;
-    private DisplayMetrics mDisplayMetrics;
-
-    private Bitmap mScreenBitmap;
-    private View mScreenshotLayout;
-    private ScreenshotSelectorView mScreenshotSelectorView;
-    private ImageView mBackgroundView;
-    private ImageView mScreenshotView;
-    private ImageView mScreenshotFlash;
-
-    private AnimatorSet mScreenshotAnimation;
-
-    private float mBgPadding;
-    private float mBgPaddingScale;
-
-    private AsyncTask<Void, Void, Void> mSaveInBgTask;
-
-    private MediaActionSound mCameraSound;
-
-    /**
-     * @param context everything needs a context :(
-     */
-    @Inject
-    public GlobalScreenshotLegacy(
-            Context context, @Main Resources resources, LayoutInflater layoutInflater,
-            ScreenshotNotificationsController screenshotNotificationsController) {
-        mContext = context;
-        mNotificationsController = screenshotNotificationsController;
-
-        // Inflate the screenshot layout
-        mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot_legacy, null);
-        mBackgroundView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_background);
-        mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy);
-
-        mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_flash);
-        mScreenshotSelectorView = mScreenshotLayout.findViewById(
-                R.id.global_screenshot_legacy_selector);
-        mScreenshotLayout.setFocusable(true);
-        mScreenshotSelectorView.setFocusable(true);
-        mScreenshotSelectorView.setFocusableInTouchMode(true);
-        mScreenshotLayout.setOnTouchListener((v, event) -> {
-            // Intercept and ignore all touch events
-            return true;
-        });
-
-        // Setup the window that we are going to use
-        mWindowLayoutParams = new WindowManager.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
-                WindowManager.LayoutParams.TYPE_SCREENSHOT,
-                WindowManager.LayoutParams.FLAG_FULLSCREEN
-                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
-                PixelFormat.TRANSLUCENT);
-        mWindowLayoutParams.setTitle("ScreenshotAnimation");
-        mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        mWindowLayoutParams.setFitInsetsTypes(0 /* types */);
-        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        mDisplay = mWindowManager.getDefaultDisplay();
-        mDisplayMetrics = new DisplayMetrics();
-        mDisplay.getRealMetrics(mDisplayMetrics);
-
-        // Scale has to account for both sides of the bg
-        mBgPadding = (float) resources.getDimensionPixelSize(
-                R.dimen.global_screenshot_legacy_bg_padding);
-        mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
-
-
-        // Setup the Camera shutter sound
-        mCameraSound = new MediaActionSound();
-        mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
-    }
-
-    /**
-     * Creates a new worker thread and saves the screenshot to the media store.
-     */
-    private void saveScreenshotInWorkerThread(
-            Consumer<Uri> finisher,
-            @Nullable GlobalScreenshot.ActionsReadyListener actionsReadyListener) {
-        GlobalScreenshot.SaveImageInBackgroundData data =
-                new GlobalScreenshot.SaveImageInBackgroundData();
-        data.image = mScreenBitmap;
-        data.finisher = finisher;
-        data.mActionsReadyListener = actionsReadyListener;
-        data.createDeleteAction = true;
-        if (mSaveInBgTask != null) {
-            mSaveInBgTask.cancel(false);
-        }
-
-        mNotificationsController.reset();
-        mNotificationsController.setImage(mScreenBitmap);
-        mNotificationsController.showSavingScreenshotNotification();
-
-        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
-    }
-
-    /**
-     * Takes a screenshot of the current display and shows an animation.
-     */
-    private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
-            boolean navBarVisible, Rect crop) {
-        int rot = mDisplay.getRotation();
-        int width = crop.width();
-        int height = crop.height();
-
-        takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher,
-                statusBarVisible, navBarVisible, null);
-    }
-
-    private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, boolean statusBarVisible,
-            boolean navBarVisible, Rect screenboundsOfBitmap) {
-        mScreenBitmap = screenshot;
-        if (mScreenBitmap == null) {
-            mNotificationsController.notifyScreenshotError(
-                    R.string.screenshot_failed_to_capture_text);
-            finisher.accept(null);
-            return;
-        }
-
-        // Optimizations
-        mScreenBitmap.setHasAlpha(false);
-        mScreenBitmap.prepareToDraw();
-
-        // Start the post-screenshot animation
-        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
-                statusBarVisible, navBarVisible, screenboundsOfBitmap);
-    }
-
-    void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
-        mDisplay.getRealMetrics(mDisplayMetrics);
-        takeScreenshot(finisher, statusBarVisible, navBarVisible,
-                new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
-    }
-
-    void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
-            Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
-            Consumer<Uri> finisher) {
-        // TODO: use task Id, userId, topComponent for smart handler
-        // TODO: use visibleInsets for animation
-        takeScreenshot(screenshot, finisher, false, false, screenshotScreenBounds);
-    }
-
-    /**
-     * Displays a screenshot selector
-     */
-    void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible,
-            final boolean navBarVisible) {
-        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
-        mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
-            @Override
-            public boolean onTouch(View v, MotionEvent event) {
-                ScreenshotSelectorView view = (ScreenshotSelectorView) v;
-                switch (event.getAction()) {
-                    case MotionEvent.ACTION_DOWN:
-                        view.startSelection((int) event.getX(), (int) event.getY());
-                        return true;
-                    case MotionEvent.ACTION_MOVE:
-                        view.updateSelection((int) event.getX(), (int) event.getY());
-                        return true;
-                    case MotionEvent.ACTION_UP:
-                        view.setVisibility(View.GONE);
-                        mWindowManager.removeView(mScreenshotLayout);
-                        final Rect rect = view.getSelectionRect();
-                        if (rect != null) {
-                            if (rect.width() != 0 && rect.height() != 0) {
-                                // Need mScreenshotLayout to handle it after the view disappears
-                                mScreenshotLayout.post(() -> takeScreenshot(
-                                        finisher, statusBarVisible, navBarVisible, rect));
-                            }
-                        }
-
-                        view.stopSelection();
-                        return true;
-                }
-
-                return false;
-            }
-        });
-        mScreenshotLayout.post(() -> {
-            mScreenshotSelectorView.setVisibility(View.VISIBLE);
-            mScreenshotSelectorView.requestFocus();
-        });
-    }
-
-    /**
-     * Cancels screenshot request
-     */
-    void stopScreenshot() {
-        // If the selector layer still presents on screen, we remove it and resets its state.
-        if (mScreenshotSelectorView.getSelectionRect() != null) {
-            mWindowManager.removeView(mScreenshotLayout);
-            mScreenshotSelectorView.stopSelection();
-        }
-    }
-
-    /**
-     * Clears current screenshot
-     */
-    private void clearScreenshot() {
-        if (mScreenshotLayout.isAttachedToWindow()) {
-            mWindowManager.removeView(mScreenshotLayout);
-        }
-
-        // Clear any references to the bitmap
-        mScreenBitmap = null;
-        mScreenshotView.setImageBitmap(null);
-        mBackgroundView.setVisibility(View.GONE);
-        mScreenshotView.setVisibility(View.GONE);
-        mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
-    }
-
-    /**
-     * Starts the animation after taking the screenshot
-     */
-    private void startAnimation(final Consumer<Uri> finisher, int w, int h,
-            boolean statusBarVisible, boolean navBarVisible, @Nullable Rect screenBoundsOfBitmap) {
-        // If power save is on, show a toast so there is some visual indication that a screenshot
-        // has been taken.
-        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        if (powerManager.isPowerSaveMode()) {
-            Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
-        }
-
-        // Add the view for the animation
-        mScreenshotView.setImageBitmap(mScreenBitmap);
-        mScreenshotLayout.requestFocus();
-
-        // Setup the animation with the screenshot just taken
-        if (mScreenshotAnimation != null) {
-            if (mScreenshotAnimation.isStarted()) {
-                mScreenshotAnimation.end();
-            }
-            mScreenshotAnimation.removeAllListeners();
-        }
-
-        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
-        ValueAnimator screenshotDropInAnim = screenBoundsOfBitmap != null
-                ? createRectAnimation(screenBoundsOfBitmap) : createScreenshotDropInAnimation();
-        ValueAnimator screenshotFadeOutAnim =
-                createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible);
-        mScreenshotAnimation = new AnimatorSet();
-        mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
-        mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                // Save the screenshot once we have a bit of time now
-                saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() {
-                    @Override
-                    void onActionsReady(GlobalScreenshot.SavedImageData actionData) {
-                        if (actionData.uri == null) {
-                            mNotificationsController.notifyScreenshotError(
-                                    R.string.screenshot_failed_to_capture_text);
-                        } else {
-                            mNotificationsController
-                                    .showScreenshotActionsNotification(actionData);
-                        }
-                    }
-                });
-                clearScreenshot();
-            }
-        });
-        mScreenshotLayout.post(() -> {
-            // Play the shutter sound to notify that we've taken a screenshot
-            mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
-
-            mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-            mScreenshotView.buildLayer();
-            mScreenshotAnimation.start();
-        });
-    }
-
-    private ValueAnimator createScreenshotDropInAnimation() {
-        final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
-                / SCREENSHOT_DROP_IN_DURATION);
-        final float flashDurationPct = 2f * flashPeakDurationPct;
-        final Interpolator flashAlphaInterpolator = new Interpolator() {
-            @Override
-            public float getInterpolation(float x) {
-                // Flash the flash view in and out quickly
-                if (x <= flashDurationPct) {
-                    return (float) Math.sin(Math.PI * (x / flashDurationPct));
-                }
-                return 0;
-            }
-        };
-        final Interpolator scaleInterpolator = new Interpolator() {
-            @Override
-            public float getInterpolation(float x) {
-                // We start scaling when the flash is at it's peak
-                if (x < flashPeakDurationPct) {
-                    return 0;
-                }
-                return (x - flashDurationPct) / (1f - flashDurationPct);
-            }
-        };
-
-        Resources r = mContext.getResources();
-        if ((r.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
-                == Configuration.UI_MODE_NIGHT_YES) {
-            mScreenshotView.getBackground().setTint(Color.BLACK);
-        } else {
-            mScreenshotView.getBackground().setTintList(null);
-        }
-
-        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
-        anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mBackgroundView.setAlpha(0f);
-                mBackgroundView.setVisibility(View.VISIBLE);
-                mScreenshotView.setAlpha(0f);
-                mScreenshotView.setTranslationX(0f);
-                mScreenshotView.setTranslationY(0f);
-                mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
-                mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
-                mScreenshotView.setVisibility(View.VISIBLE);
-                mScreenshotFlash.setAlpha(0f);
-                mScreenshotFlash.setVisibility(View.VISIBLE);
-            }
-
-            @Override
-            public void onAnimationEnd(android.animation.Animator animation) {
-                mScreenshotFlash.setVisibility(View.GONE);
-            }
-        });
-        anim.addUpdateListener(new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float t = (Float) animation.getAnimatedValue();
-                float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
-                        - scaleInterpolator.getInterpolation(t)
-                        * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
-                mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
-                mScreenshotView.setAlpha(t);
-                mScreenshotView.setScaleX(scaleT);
-                mScreenshotView.setScaleY(scaleT);
-                mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
-            }
-        });
-        return anim;
-    }
-
-    /**
-     * If a bitmap was supplied to be used as the screenshot, animated from where that bitmap was
-     * on screen, rather than using the whole screen.
-     */
-    private ValueAnimator createRectAnimation(Rect rect) {
-        mScreenshotView.setAdjustViewBounds(true);
-        mScreenshotView.setMaxHeight(rect.height());
-        mScreenshotView.setMaxWidth(rect.width());
-
-        final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
-                / SCREENSHOT_DROP_IN_DURATION);
-        final float flashDurationPct = 2f * flashPeakDurationPct;
-        final Interpolator scaleInterpolator = x -> {
-            // We start scaling when the flash is at it's peak
-            if (x < flashPeakDurationPct) {
-                return 0;
-            }
-            return (x - flashDurationPct) / (1f - flashDurationPct);
-        };
-
-        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
-        anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mBackgroundView.setAlpha(0f);
-                mBackgroundView.setVisibility(View.VISIBLE);
-                mScreenshotView.setAlpha(0f);
-                mScreenshotView.setElevation(0f);
-                mScreenshotView.setTranslationX(0f);
-                mScreenshotView.setTranslationY(0f);
-                mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
-                mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
-                mScreenshotView.setVisibility(View.VISIBLE);
-            }
-        });
-        anim.addUpdateListener(animation -> {
-            float t = (Float) animation.getAnimatedValue();
-            float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
-                    - scaleInterpolator.getInterpolation(t)
-                    * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
-            mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
-            mScreenshotView.setAlpha(t);
-        });
-        return anim;
-    }
-
-    private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
-            boolean navBarVisible) {
-        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
-        anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mBackgroundView.setVisibility(View.GONE);
-                mScreenshotView.setVisibility(View.GONE);
-                mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
-            }
-        });
-
-        if (!statusBarVisible || !navBarVisible) {
-            // There is no status bar/nav bar, so just fade the screenshot away in place
-            anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
-            anim.addUpdateListener(new AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    float t = (Float) animation.getAnimatedValue();
-                    float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
-                            - t * (SCREENSHOT_DROP_IN_MIN_SCALE
-                            - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
-                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
-                    mScreenshotView.setAlpha(1f - t);
-                    mScreenshotView.setScaleX(scaleT);
-                    mScreenshotView.setScaleY(scaleT);
-                }
-            });
-        } else {
-            // In the case where there is a status bar, animate to the origin of the bar (top-left)
-            final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
-                    / SCREENSHOT_DROP_OUT_DURATION;
-            final Interpolator scaleInterpolator = new Interpolator() {
-                @Override
-                public float getInterpolation(float x) {
-                    if (x < scaleDurationPct) {
-                        // Decelerate, and scale the input accordingly
-                        return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
-                    }
-                    return 1f;
-                }
-            };
-
-            // Determine the bounds of how to scale
-            float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
-            float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
-            final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
-            final PointF finalPos = new PointF(
-                    -halfScreenWidth
-                            + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
-                    -halfScreenHeight
-                            + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
-
-            // Animate the screenshot to the status bar
-            anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
-            anim.addUpdateListener(new AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    float t = (Float) animation.getAnimatedValue();
-                    float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
-                            - scaleInterpolator.getInterpolation(t)
-                            * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
-                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
-                    mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
-                    mScreenshotView.setScaleX(scaleT);
-                    mScreenshotView.setScaleY(scaleT);
-                    mScreenshotView.setTranslationX(t * finalPos.x);
-                    mScreenshotView.setTranslationY(t * finalPos.y);
-                }
-            });
-        }
-        return anim;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 221174f..10e6902 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -88,7 +88,6 @@
     private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
     private final String mScreenshotId;
     private final boolean mSmartActionsEnabled;
-    private final boolean mCreateDeleteAction;
     private final Random mRandom = new Random();
 
     SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) {
@@ -102,8 +101,6 @@
         mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
         mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, UUID.randomUUID());
 
-        mCreateDeleteAction = data.createDeleteAction;
-
         // Initialize screenshot notification smart actions provider.
         mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
index fbcd6ba..e4e253e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
@@ -148,43 +148,11 @@
     }
 
     /**
-     * Shows a notification to inform the user that a screenshot is currently being saved.
-     */
-    public void showSavingScreenshotNotification() {
-        final long now = System.currentTimeMillis();
-
-        mPublicNotificationBuilder
-                .setContentTitle(mResources.getString(R.string.screenshot_saving_title))
-                .setSmallIcon(R.drawable.stat_notify_image)
-                .setCategory(Notification.CATEGORY_PROGRESS)
-                .setWhen(now)
-                .setShowWhen(true)
-                .setColor(mResources.getColor(
-                        com.android.internal.R.color.system_notification_accent_color));
-        SystemUI.overrideNotificationAppName(mContext, mPublicNotificationBuilder, true);
-
-        mNotificationBuilder
-                .setContentTitle(mResources.getString(R.string.screenshot_saving_title))
-                .setSmallIcon(R.drawable.stat_notify_image)
-                .setWhen(now)
-                .setShowWhen(true)
-                .setColor(mResources.getColor(
-                        com.android.internal.R.color.system_notification_accent_color))
-                .setStyle(mNotificationStyle)
-                .setPublicVersion(mPublicNotificationBuilder.build());
-        mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true);
-        SystemUI.overrideNotificationAppName(mContext, mNotificationBuilder, true);
-
-        mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
-                mNotificationBuilder.build());
-    }
-
-    /**
-     * Shows a notification with the saved screenshot and actions that can be taken with it.
+     * Shows a silent notification with the saved screenshot and actions that can be taken with it.
      *
      * @param actionData SavedImageData struct with image URI and actions
      */
-    public void showScreenshotActionsNotification(
+    public void showSilentScreenshotNotification(
             GlobalScreenshot.SavedImageData actionData) {
         mNotificationBuilder.addAction(actionData.shareAction);
         mNotificationBuilder.addAction(actionData.editAction);
@@ -206,20 +174,34 @@
                 .setContentTitle(mResources.getString(R.string.screenshot_saved_title))
                 .setContentText(mResources.getString(R.string.screenshot_saved_text))
                 .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0))
+                .setSmallIcon(R.drawable.stat_notify_image)
+                .setCategory(Notification.CATEGORY_PROGRESS)
                 .setWhen(now)
+                .setShowWhen(true)
                 .setAutoCancel(true)
                 .setColor(mContext.getColor(
-                        com.android.internal.R.color.system_notification_accent_color));
+                        com.android.internal.R.color.system_notification_accent_color))
+                .setGroup("silent")
+                .setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY);
         mNotificationBuilder
                 .setContentTitle(mResources.getString(R.string.screenshot_saved_title))
                 .setContentText(mResources.getString(R.string.screenshot_saved_text))
                 .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0))
+                .setSmallIcon(R.drawable.stat_notify_image)
+                .setCategory(Notification.CATEGORY_PROGRESS)
                 .setWhen(now)
+                .setShowWhen(true)
                 .setAutoCancel(true)
                 .setColor(mContext.getColor(
                         com.android.internal.R.color.system_notification_accent_color))
                 .setPublicVersion(mPublicNotificationBuilder.build())
-                .setFlag(Notification.FLAG_NO_CLEAR, false);
+                .setStyle(mNotificationStyle)
+                .setFlag(Notification.FLAG_NO_CLEAR, false)
+                .setGroup("silent")
+                .setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY);
+
+        SystemUI.overrideNotificationAppName(mContext, mPublicNotificationBuilder, true);
+        SystemUI.overrideNotificationAppName(mContext, mNotificationBuilder, true);
 
         mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
                 mNotificationBuilder.build());
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 8322fe0..c05c823 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -48,7 +48,6 @@
     private static final String TAG = "TakeScreenshotService";
 
     private final GlobalScreenshot mScreenshot;
-    private final GlobalScreenshotLegacy mScreenshotLegacy;
     private final UserManager mUserManager;
     private final UiEventLogger mUiEventLogger;
 
@@ -81,9 +80,6 @@
                 return;
             }
 
-            // TODO: clean up once notifications flow is fully deprecated
-            boolean useCornerFlow = true;
-
             ScreenshotHelper.ScreenshotRequest screenshotRequest =
                     (ScreenshotHelper.ScreenshotRequest) msg.obj;
 
@@ -91,22 +87,10 @@
 
             switch (msg.what) {
                 case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
-                    if (useCornerFlow) {
-                        mScreenshot.takeScreenshot(uriConsumer, onComplete);
-                    } else {
-                        mScreenshotLegacy.takeScreenshot(
-                                uriConsumer, screenshotRequest.getHasStatusBar(),
-                                screenshotRequest.getHasNavBar());
-                    }
+                    mScreenshot.takeScreenshot(uriConsumer, onComplete);
                     break;
                 case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
-                    if (useCornerFlow) {
-                        mScreenshot.takeScreenshotPartial(uriConsumer, onComplete);
-                    } else {
-                        mScreenshotLegacy.takeScreenshotPartial(
-                                uriConsumer, screenshotRequest.getHasStatusBar(),
-                                screenshotRequest.getHasNavBar());
-                    }
+                    mScreenshot.takeScreenshotPartial(uriConsumer, onComplete);
                     break;
                 case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
                     Bitmap screenshot = BitmapUtil.bundleToHardwareBitmap(
@@ -116,13 +100,8 @@
                     int taskId = screenshotRequest.getTaskId();
                     int userId = screenshotRequest.getUserId();
                     ComponentName topComponent = screenshotRequest.getTopComponent();
-                    if (useCornerFlow) {
-                        mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
-                                taskId, userId, topComponent, uriConsumer, onComplete);
-                    } else {
-                        mScreenshotLegacy.handleImageAsScreenshot(screenshot, screenBounds, insets,
-                                taskId, userId, topComponent, uriConsumer);
-                    }
+                    mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
+                            taskId, userId, topComponent, uriConsumer, onComplete);
                     break;
                 default:
                     Log.d(TAG, "Invalid screenshot option: " + msg.what);
@@ -131,11 +110,9 @@
     };
 
     @Inject
-    public TakeScreenshotService(GlobalScreenshot globalScreenshot,
-            GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager,
+    public TakeScreenshotService(GlobalScreenshot globalScreenshot, UserManager userManager,
             UiEventLogger uiEventLogger) {
         mScreenshot = globalScreenshot;
-        mScreenshotLegacy = globalScreenshotLegacy;
         mUserManager = userManager;
         mUiEventLogger = uiEventLogger;
     }
@@ -148,8 +125,6 @@
     @Override
     public boolean onUnbind(Intent intent) {
         if (mScreenshot != null) mScreenshot.stopScreenshot();
-        // TODO remove once notifications flow is fully deprecated
-        if (mScreenshotLegacy != null) mScreenshotLegacy.stopScreenshot();
         return true;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 6aef6b4..6a33024 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -41,6 +41,8 @@
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
 
+import java.util.concurrent.Executor;
+
 /**
  * A class that allows activities to be launched in a seamless way where the notification
  * transforms nicely into the starting window.
@@ -59,6 +61,7 @@
     private final float mWindowCornerRadius;
     private final NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     private final NotificationShadeDepthController mDepthController;
+    private final Executor mMainExecutor;
     private Callback mCallback;
     private final Runnable mTimeoutRunnable = () -> {
         setAnimationPending(false);
@@ -73,12 +76,14 @@
             Callback callback,
             NotificationPanelViewController notificationPanel,
             NotificationShadeDepthController depthController,
-            NotificationListContainer container) {
+            NotificationListContainer container,
+            Executor mainExecutor) {
         mNotificationPanel = notificationPanel;
         mNotificationContainer = container;
         mDepthController = depthController;
         mNotificationShadeWindowViewController = notificationShadeWindowViewController;
         mCallback = callback;
+        mMainExecutor = mainExecutor;
         mWindowCornerRadius = ScreenDecorationsUtils
                 .getWindowCornerRadius(mNotificationShadeWindowViewController.getView()
                         .getResources());
@@ -155,7 +160,7 @@
                 RemoteAnimationTarget[] remoteAnimationWallpaperTargets,
                 IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback)
                     throws RemoteException {
-            mSourceNotification.post(() -> {
+            mMainExecutor.execute(() -> {
                 RemoteAnimationTarget primary = getPrimaryRemoteAnimationTarget(
                         remoteAnimationTargets);
                 if (primary == null) {
@@ -191,8 +196,9 @@
                     }
                 }
                 int targetWidth = primary.sourceContainerBounds.width();
-                int notificationHeight = mSourceNotification.getActualHeight()
-                        - mSourceNotification.getClipBottomAmount();
+                // If the notification panel is collapsed, the clip may be larger than the height.
+                int notificationHeight = Math.max(mSourceNotification.getActualHeight()
+                        - mSourceNotification.getClipBottomAmount(), 0);
                 int notificationWidth = mSourceNotification.getWidth();
                 anim.setDuration(ANIMATION_DURATION);
                 anim.setInterpolator(Interpolators.LINEAR);
@@ -292,7 +298,7 @@
 
         @Override
         public void onAnimationCancelled() throws RemoteException {
-            mSourceNotification.post(() -> {
+            mMainExecutor.execute(() -> {
                 setAnimationPending(false);
                 mCallback.onLaunchAnimationCancelled();
             });
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 f4afb91..bee2f70 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
@@ -44,6 +44,7 @@
 import android.content.pm.ShortcutManager;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
@@ -540,7 +541,9 @@
                 .setView(onboardingView)
                 .setIgnoresDnd(ignoreDnd)
                 .setShowsAsBubble(showAsBubble)
-                .setIcon(((ImageView) findViewById(R.id.conversation_icon)).getDrawable())
+                .setIcon(mIconFactory.getBaseIconDrawable(mShortcutInfo))
+                .setBadge(mIconFactory.getAppBadge(
+                        mPackageName, UserHandle.getUserId(mSbn.getUid())))
                 .setOnSettingsClick(mOnConversationSettingsClickListener)
                 .build();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
index f1fe54a..186ffa6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
@@ -148,8 +148,7 @@
     }
 
     private void bindHeader() {
-        bindConversationDetails();
-
+        bindPackage();
         // Delegate
         bindDelegate();
     }
@@ -180,51 +179,6 @@
         });
     }
 
-    private void bindConversationDetails() {
-        final TextView channelName = findViewById(R.id.parent_channel_name);
-        channelName.setText(mNotificationChannel.getName());
-
-        bindGroup();
-        bindName();
-        bindPackage();
-        bindIcon();
-    }
-
-    private void bindName() {
-        TextView name = findViewById(R.id.name);
-        Bundle extras = mSbn.getNotification().extras;
-        CharSequence nameString = extras.getCharSequence(Notification.EXTRA_CONVERSATION_TITLE, "");
-        if (TextUtils.isEmpty(nameString)) {
-            nameString = extras.getCharSequence(Notification.EXTRA_TITLE, "");
-        }
-        name.setText(nameString);
-    }
-
-    private void bindIcon() {
-        ImageView image = findViewById(R.id.conversation_icon);
-        if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) {
-            // TODO: maybe use a generic group icon, or a composite of recent senders
-            image.setImageDrawable(mPkgIcon);
-        } else {
-            final List<Notification.MessagingStyle.Message> messages =
-                    Notification.MessagingStyle.Message.getMessagesFromBundleArray(
-                            (Parcelable[]) mSbn.getNotification().extras.get(
-                                    Notification.EXTRA_MESSAGES));
-
-            final Notification.MessagingStyle.Message latestMessage =
-                    Notification.MessagingStyle.findLatestIncomingMessage(messages);
-            Icon personIcon = null;
-            if (latestMessage != null && latestMessage.getSenderPerson() != null) {
-                personIcon = latestMessage.getSenderPerson().getIcon();
-            }
-            if (personIcon != null) {
-                image.setImageIcon(latestMessage.getSenderPerson().getIcon());
-            } else {
-                image.setImageDrawable(mPkgIcon);
-            }
-        }
-    }
-
     private void bindPackage() {
         ApplicationInfo info;
         try {
@@ -241,6 +195,10 @@
         } catch (PackageManager.NameNotFoundException e) {
             mPkgIcon = mPm.getDefaultActivityIcon();
         }
+        TextView name = findViewById(R.id.name);
+        name.setText(mAppName);
+        ImageView image = findViewById(R.id.icon);
+        image.setImageDrawable(mPkgIcon);
     }
 
     private void bindDelegate() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt
index c88f0bd..fab367d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt
@@ -16,41 +16,57 @@
 
 package com.android.systemui.statusbar.notification.row
 
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
 import android.app.Dialog
 import android.content.Context
 import android.graphics.Color
 import android.graphics.PixelFormat
 import android.graphics.drawable.ColorDrawable
 import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
 import android.text.SpannableStringBuilder
 import android.text.style.BulletSpan
 import android.view.Gravity
 import android.view.View
+import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.view.Window
 import android.view.WindowInsets.Type.statusBars
 import android.view.WindowManager
+import android.view.animation.Interpolator
+import android.view.animation.PathInterpolator
 import android.widget.ImageView
 import android.widget.TextView
+import com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN
 import com.android.systemui.Prefs
 import com.android.systemui.R
 import com.android.systemui.statusbar.notification.row.NotificationConversationInfo.OnConversationSettingsClickListener
 import javax.inject.Inject
 
+
 /**
  * Controller to handle presenting the priority conversations onboarding dialog
  */
 class PriorityOnboardingDialogController @Inject constructor(
-    val view: View,
-    val context: Context,
-    val ignoresDnd: Boolean,
-    val showsAsBubble: Boolean,
-    val icon : Drawable,
-    val onConversationSettingsClickListener : OnConversationSettingsClickListener
+        val view: View,
+        val context: Context,
+        private val ignoresDnd: Boolean,
+        private val showsAsBubble: Boolean,
+        val icon : Drawable,
+        private val onConversationSettingsClickListener : OnConversationSettingsClickListener,
+        val badge : Drawable
 ) {
 
     private lateinit var dialog: Dialog
+    private val OVERSHOOT: Interpolator = PathInterpolator(0.4f, 0f, 0.2f, 1.4f)
+    private val IMPORTANCE_ANIM_DELAY = 150L
+    private val IMPORTANCE_ANIM_GROW_DURATION = 250L
+    private val IMPORTANCE_ANIM_SHRINK_DURATION = 200L
+    private val IMPORTANCE_ANIM_SHRINK_DELAY = 25L
 
     fun init() {
         initDialog()
@@ -81,6 +97,7 @@
         private lateinit var icon: Drawable
         private lateinit var onConversationSettingsClickListener
                 : OnConversationSettingsClickListener
+        private lateinit var badge : Drawable
 
         fun setView(v: View): Builder {
             view = v
@@ -106,6 +123,10 @@
             icon = draw
             return this
         }
+        fun setBadge(badge : Drawable) : Builder {
+            this.badge = badge
+            return this
+        }
 
         fun setOnSettingsClick(onClick : OnConversationSettingsClickListener) : Builder {
             onConversationSettingsClickListener = onClick
@@ -115,7 +136,7 @@
         fun build(): PriorityOnboardingDialogController {
             val controller = PriorityOnboardingDialogController(
                     view, context, ignoresDnd, showAsBubble, icon,
-                    onConversationSettingsClickListener)
+                    onConversationSettingsClickListener, badge)
             return controller
         }
     }
@@ -143,6 +164,65 @@
             }
 
             findViewById<ImageView>(R.id.conversation_icon)?.setImageDrawable(icon)
+            findViewById<ImageView>(R.id.icon)?.setImageDrawable(badge)
+            val mImportanceRingView = findViewById<ImageView>(R.id.conversation_icon_badge_ring)
+            val conversationIconBadgeBg = findViewById<ImageView>(R.id.conversation_icon_badge_bg)
+
+            val ring: GradientDrawable = mImportanceRingView.drawable as GradientDrawable
+            ring.mutate()
+            val bg = conversationIconBadgeBg.drawable as GradientDrawable
+            bg.mutate()
+            val ringColor = context.getResources()
+                    .getColor(com.android.internal.R.color.conversation_important_highlight)
+            val standardThickness = context.resources.getDimensionPixelSize(
+                    com.android.internal.R.dimen.importance_ring_stroke_width)
+            val largeThickness = context.resources.getDimensionPixelSize(
+                    com.android.internal.R.dimen.importance_ring_anim_max_stroke_width)
+            val standardSize = context.resources.getDimensionPixelSize(
+                    com.android.internal.R.dimen.importance_ring_size)
+            val baseSize = standardSize - standardThickness * 2
+            val largeSize = baseSize + largeThickness * 2
+            val bgSize = context.resources.getDimensionPixelSize(
+                    com.android.internal.R.dimen.conversation_icon_size_badged)
+
+            val animatorUpdateListener: ValueAnimator.AnimatorUpdateListener
+                    = ValueAnimator.AnimatorUpdateListener { animation ->
+                val strokeWidth = animation.animatedValue as Int
+                ring.setStroke(strokeWidth, ringColor)
+                val newSize = baseSize + strokeWidth * 2
+                ring.setSize(newSize, newSize)
+                mImportanceRingView.invalidate()
+            }
+
+            val growAnimation: ValueAnimator = ValueAnimator.ofInt(0, largeThickness)
+            growAnimation.interpolator = LINEAR_OUT_SLOW_IN
+            growAnimation.duration = IMPORTANCE_ANIM_GROW_DURATION
+            growAnimation.addUpdateListener(animatorUpdateListener)
+
+            val shrinkAnimation: ValueAnimator
+                    = ValueAnimator.ofInt(largeThickness, standardThickness)
+            shrinkAnimation.duration = IMPORTANCE_ANIM_SHRINK_DURATION
+            shrinkAnimation.startDelay = IMPORTANCE_ANIM_SHRINK_DELAY
+            shrinkAnimation.interpolator = OVERSHOOT
+            shrinkAnimation.addUpdateListener(animatorUpdateListener)
+            shrinkAnimation.addListener(object : AnimatorListenerAdapter() {
+                override fun onAnimationStart(animation: Animator?) {
+                    // Shrink the badge bg so that it doesn't peek behind the animation
+                    bg.setSize(baseSize, baseSize);
+                    conversationIconBadgeBg.invalidate();
+                }
+
+                override fun onAnimationEnd(animation: Animator?) {
+                    // Reset bg back to normal size
+                    bg.setSize(bgSize, bgSize);
+                    conversationIconBadgeBg.invalidate();
+
+                }
+            })
+
+            val anims = AnimatorSet()
+            anims.startDelay = IMPORTANCE_ANIM_DELAY
+            anims.playSequentially(growAnimation, shrinkAnimation)
 
             val gapWidth = dialog.context.getResources().getDimensionPixelSize(
                     R.dimen.conversation_onboarding_bullet_gap_width)
@@ -180,6 +260,7 @@
                     height = WRAP_CONTENT
                 }
             }
+            anims.start()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 325c8c0..684bf19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -146,6 +146,7 @@
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
@@ -6538,7 +6539,7 @@
             return row.canViewBeDismissed();
         }
         if (v instanceof PeopleHubView) {
-            return true;
+            return ((PeopleHubView) v).getCanSwipe();
         }
         return false;
     }
@@ -6633,6 +6634,7 @@
                         MetricsEvent.ACTION_LS_SHADE,
                         (int) (dragLengthY / mDisplayMetrics.density),
                         0 /* velocityDp - N/A */);
+                mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_PULL_SHADE_OPEN);
 
                 if (!mAmbientState.isDozing() || startingChild != null) {
                     // We have notifications, go to locked shade.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
index a1d898f..8f77a1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
@@ -72,7 +72,7 @@
         }
     }
 
-    var canSwipe: Boolean = true
+    var canSwipe: Boolean = false
         set(value) {
             if (field != value) {
                 if (field) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
index 8c3420a..83d398d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
@@ -20,6 +20,9 @@
 import android.util.ArrayMap;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
 import com.android.systemui.EventLogConstants;
@@ -34,6 +37,56 @@
  */
 @Singleton
 public class LockscreenGestureLogger {
+
+    /**
+     * Contains Lockscreen related Westworld UiEvent enums.
+     */
+    public enum LockscreenUiEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Lockscreen > Pull shade open")
+        LOCKSCREEN_PULL_SHADE_OPEN(539),
+
+        @UiEvent(doc = "Lockscreen > Tap on lock, locks phone")
+        LOCKSCREEN_LOCK_TAP(540),
+
+        @UiEvent(doc = "Lockscreen > Swipe down to open quick settings")
+        LOCKSCREEN_QUICK_SETTINGS_OPEN(541),
+
+        @UiEvent(doc = "Swipe down to open quick settings when unlocked")
+        LOCKSCREEN_UNLOCKED_QUICK_SETTINGS_OPEN(542),
+
+        @UiEvent(doc = "Lockscreen > Tap on lock, shows hint")
+        LOCKSCREEN_LOCK_SHOW_HINT(543),
+
+        @UiEvent(doc = "Notification shade > Tap to open quick settings")
+        LOCKSCREEN_NOTIFICATION_SHADE_QUICK_SETTINGS_OPEN(544),
+
+        @UiEvent(doc = "Lockscreen > Dialer")
+        LOCKSCREEN_DIALER(545),
+
+        @UiEvent(doc = "Lockscreen > Camera")
+        LOCKSCREEN_CAMERA(546),
+
+        @UiEvent(doc = "Lockscreen > Unlock gesture")
+        LOCKSCREEN_UNLOCK(547),
+
+        @UiEvent(doc = "Lockscreen > Tap on notification, false touch rejection")
+        LOCKSCREEN_NOTIFICATION_FALSE_TOUCH(548),
+
+        @UiEvent(doc = "Expand the notification panel while unlocked")
+        LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND(549);
+
+        private final int mId;
+
+        LockscreenUiEvent(int id) {
+            mId = id;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
+
     private ArrayMap<Integer, Integer> mLegacyMap;
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
@@ -55,6 +108,13 @@
     }
 
     /**
+     * Logs {@link LockscreenUiEvent}.
+     */
+    public void log(LockscreenUiEvent lockscreenUiEvent) {
+        new UiEventLoggerImpl().log(lockscreenUiEvent);
+    }
+
+    /**
      * Record the location of a swipe gesture, expressed as percentages of the whole screen
      * @param category the action
      * @param xPercent x-location / width * 100
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
index 8a5c8b0..5d3910b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator.WakeUpListener;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -436,6 +437,7 @@
     private boolean handleLongClick(View view) {
         mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_LS_LOCK,
                 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
+        mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_LOCK_TAP);
         mKeyguardIndicationController.showTransientIndication(
                 R.string.keyguard_indication_trust_disabled);
         mKeyguardUpdateMonitor.onLockIconPressed();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index bf53a2f..11022bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -96,6 +96,8 @@
 
 import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.view.AppearanceRegion;
@@ -225,6 +227,25 @@
     private int mCurrentRotation;
     private boolean mFixedRotationEnabled;
     private ViewTreeObserver.OnGlobalLayoutListener mOrientationHandleGlobalLayoutListener;
+    private UiEventLogger mUiEventLogger;
+
+    @com.android.internal.annotations.VisibleForTesting
+    public enum NavBarActionEvent implements UiEventLogger.UiEventEnum {
+
+        @UiEvent(doc = "Assistant invoked via home button long press.")
+        NAVBAR_ASSIST_LONGPRESS(550);
+
+        private final int mId;
+
+        NavBarActionEvent(int id) {
+            mId = id;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
 
     /** Only for default display */
     @Nullable
@@ -367,7 +388,8 @@
             ShadeController shadeController,
             NotificationRemoteInputManager notificationRemoteInputManager,
             SystemActions systemActions,
-            @Main Handler mainHandler) {
+            @Main Handler mainHandler,
+            UiEventLogger uiEventLogger) {
         mAccessibilityManagerWrapper = accessibilityManagerWrapper;
         mDeviceProvisionedController = deviceProvisionedController;
         mStatusBarStateController = statusBarStateController;
@@ -387,6 +409,7 @@
         mRecentsOptional = recentsOptional;
         mSystemActions = systemActions;
         mHandler = mainHandler;
+        mUiEventLogger = uiEventLogger;
     }
 
     // ----- Fragment Lifecycle Callbacks -----
@@ -605,6 +628,12 @@
             resetSecondaryHandle();
         } else {
             int deltaRotation = deltaRotation(mCurrentRotation, mStartingQuickSwitchRotation);
+            if (mStartingQuickSwitchRotation == -1 || deltaRotation == -1) {
+                // Curious if starting quickswitch can change between the if check and our delta
+                Log.d(TAG, "secondary nav delta rotation: " + deltaRotation
+                        + " current: " + mCurrentRotation
+                        + " starting: " + mStartingQuickSwitchRotation);
+            }
             int height = 0;
             int width = 0;
             Rect dispSize = mWindowManager.getCurrentWindowMetrics().getBounds();
@@ -668,6 +697,8 @@
             dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions());
         }
 
+        pw.print("  mStartingQuickSwitchRotation=" + mStartingQuickSwitchRotation);
+        pw.print("  mCurrentRotation=" + mCurrentRotation);
         pw.print("  mNavigationBarView=");
         if (mNavigationBarView == null) {
             pw.println("null");
@@ -1008,6 +1039,7 @@
             return false;
         }
         mMetricsLogger.action(MetricsEvent.ACTION_ASSIST_LONG_PRESS);
+        mUiEventLogger.log(NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS);
         Bundle args  = new Bundle();
         args.putInt(
                 AssistManager.INVOCATION_TYPE_KEY, AssistManager.INVOCATION_HOME_BUTTON_LONG_PRESS);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 8889510..d884bdd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -104,9 +104,9 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
-import com.android.systemui.statusbar.notification.stack.MediaHeaderView;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -2474,6 +2474,8 @@
                     } else {
                         mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT,
                                 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
+                        mLockscreenGestureLogger
+                            .log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT);
                         startUnlockHintAnimation();
                     }
                 }
@@ -3257,7 +3259,7 @@
             int velocityDp = Math.abs((int) (vel / displayDensity));
             if (start) {
                 mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_DIALER, lengthDp, velocityDp);
-
+                mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_DIALER);
                 mFalsingManager.onLeftAffordanceOn();
                 if (mFalsingManager.shouldEnforceBouncer()) {
                     mStatusBar.executeRunnableDismissingKeyguard(
@@ -3272,6 +3274,7 @@
                         mLastCameraLaunchSource)) {
                     mLockscreenGestureLogger.write(
                             MetricsEvent.ACTION_LS_CAMERA, lengthDp, velocityDp);
+                    mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_CAMERA);
                 }
                 mFalsingManager.onCameraOn();
                 if (mFalsingManager.shouldEnforceBouncer()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 57d36fc..a902e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.FileDescriptor;
@@ -319,6 +320,8 @@
 
         mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND,
                 (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot);
+        mLockscreenGestureLogger
+                .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND);
     }
 
     protected void maybeVibrateOnOpening() {
@@ -378,6 +381,7 @@
                 int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity);
                 int velocityDp = (int) Math.abs(vel / displayDensity);
                 mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
+                mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK);
             }
             fling(vel, expand, isFalseTouch(x, y));
             onTrackingStopped(expand);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 1bc42d1..e2714af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1275,7 +1275,7 @@
         mActivityLaunchAnimator = new ActivityLaunchAnimator(
                 mNotificationShadeWindowViewController, this, mNotificationPanelViewController,
                 mNotificationShadeDepthControllerLazy.get(),
-                (NotificationListContainer) mStackScroller);
+                (NotificationListContainer) mStackScroller, mContext.getMainExecutor());
 
         // TODO: inject this.
         mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 84da35b..45f0c49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -72,6 +72,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -374,6 +375,7 @@
         mLockscreenGestureLogger.write(
                 MetricsEvent.ACTION_LS_NOTE,
                 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
+        mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_NOTIFICATION_FALSE_TOUCH);
         mNotificationPanel.showTransientIndication(R.string.notification_tap_again);
         ActivatableNotificationView previousView = mNotificationPanel.getActivatedChild();
         if (previousView != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
index 0db76ec..c929243 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
@@ -28,6 +28,7 @@
 import android.graphics.RecordingCanvas;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
+import android.util.Log;
 import android.view.RenderNodeAnimator;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -220,7 +221,7 @@
 
     @Override
     public void jumpToCurrentState() {
-        cancelAnimations();
+        cancelAnimations("jumpToCurrentState");
     }
 
     @Override
@@ -234,6 +235,7 @@
     }
 
     public void setPressed(boolean pressed) {
+        Log.d("b/63783866", "KeyButtonRipple.setPressed: pressed=" + pressed);
         if (mDark != mLastDark && pressed) {
             mRipplePaint = null;
             mLastDark = mDark;
@@ -253,7 +255,8 @@
         mHandler.removeCallbacksAndMessages(null);
     }
 
-    private void cancelAnimations() {
+    private void cancelAnimations(String reason) {
+        Log.d("b/63783866", "KeyButtonRipple.cancelAnimations: reason=" + reason);
         mVisible = false;
         mTmpArray.addAll(mRunningAnimations);
         int size = mTmpArray.size();
@@ -284,7 +287,7 @@
     }
 
     private void enterSoftware() {
-        cancelAnimations();
+        cancelAnimations("enterSoftware");
         mVisible = true;
         mGlowAlpha = getMaxGlowAlpha();
         ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale",
@@ -370,7 +373,8 @@
     }
 
     private void enterHardware() {
-        cancelAnimations();
+        Log.d("b/63783866", "enterHardware");
+        cancelAnimations("enterHardware");
         mVisible = true;
         mDrawingHardwareGlow = true;
         setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2));
@@ -422,6 +426,7 @@
     }
 
     private void exitHardware() {
+        Log.d("b/63783866", "exitHardware");
         mPaintProp = CanvasProperty.createPaint(getRipplePaint());
         final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPaintProp,
                 RenderNodeAnimator.PAINT_ALPHA, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 58f5d2a..8d7ecd0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -87,7 +87,7 @@
     private boolean mHasOvalBg = false;
 
     @VisibleForTesting
-    public enum NavBarActionsEvent implements UiEventLogger.UiEventEnum {
+    public enum NavBarButtonEvent implements UiEventLogger.UiEventEnum {
 
         @UiEvent(doc = "The home button was pressed in the navigation bar.")
         NAVBAR_HOME_BUTTON_TAP(533),
@@ -111,7 +111,7 @@
 
         private final int mId;
 
-        NavBarActionsEvent(int id) {
+        NavBarButtonEvent(int id) {
             mId = id;
         }
 
@@ -368,7 +368,7 @@
 
     private void logSomePresses(int action, int flags) {
         boolean longPressSet = (flags & KeyEvent.FLAG_LONG_PRESS) != 0;
-        NavBarActionsEvent uiEvent = NavBarActionsEvent.NONE;
+        NavBarButtonEvent uiEvent = NavBarButtonEvent.NONE;
         if (action == MotionEvent.ACTION_UP && mLongClicked) {
             return;  // don't log the up after a long press
         }
@@ -382,21 +382,21 @@
         switch(mCode) {
             case KeyEvent.KEYCODE_BACK:
                 uiEvent = longPressSet
-                        ? NavBarActionsEvent.NAVBAR_BACK_BUTTON_LONGPRESS
-                        : NavBarActionsEvent.NAVBAR_BACK_BUTTON_TAP;
+                        ? NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS
+                        : NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
                 break;
             case KeyEvent.KEYCODE_HOME:
                 uiEvent = longPressSet
-                        ? NavBarActionsEvent.NAVBAR_HOME_BUTTON_LONGPRESS
-                        : NavBarActionsEvent.NAVBAR_HOME_BUTTON_TAP;
+                        ? NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS
+                        : NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
                 break;
             case KeyEvent.KEYCODE_APP_SWITCH:
                 uiEvent = longPressSet
-                        ? NavBarActionsEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS
-                        : NavBarActionsEvent.NAVBAR_OVERVIEW_BUTTON_TAP;
+                        ? NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS
+                        : NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_TAP;
                 break;
         }
-        if (uiEvent != NavBarActionsEvent.NONE) {
+        if (uiEvent != NavBarButtonEvent.NONE) {
             mUiEventLogger.log(uiEvent);
         }
     }
@@ -441,6 +441,7 @@
 
     @Override
     public void abortCurrentGesture() {
+        Log.d("b/63783866", "KeyButtonView.abortCurrentGesture");
         setPressed(false);
         mRipple.abortDelayedRipple();
         mGestureAborted = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 270f248..ce5bb05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -367,8 +367,14 @@
         int id;
         if (record.isGuest && record.info == null) {
             // No guest user. Create one.
-            UserInfo guest = mUserManager.createGuest(
-                    mContext, mContext.getString(com.android.settingslib.R.string.guest_nickname));
+            UserInfo guest;
+            try {
+                guest = mUserManager.createGuest(mContext,
+                        mContext.getString(com.android.settingslib.R.string.guest_nickname));
+            } catch (UserManager.UserOperationException e) {
+                Log.e(TAG, "Couldn't create guest user", e);
+                return;
+            }
             if (guest == null) {
                 // Couldn't create guest, most likely because there already exists one, we just
                 // haven't reloaded the user list yet.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
index 9148683..8b85a09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
@@ -29,6 +29,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.PixelFormat;
+import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 import android.view.Gravity;
@@ -63,6 +65,9 @@
     // CtsSystemUiHostTestCases:TvMicrophoneCaptureIndicatorTest
     private static final String LAYOUT_PARAMS_TITLE = "MicrophoneCaptureIndicator";
 
+    private static final String EXEMPT_PACKAGES_LIST = "sysui_mic_disclosure_exempt";
+    private static final String FORCED_PACKAGES_LIST = "sysui_mic_disclosure_forced";
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"STATE_"}, value = {
             STATE_NOT_SHOWN,
@@ -134,6 +139,8 @@
         mExemptPackages = new ArraySet<>(
                 Arrays.asList(mContext.getResources().getStringArray(
                         R.array.audio_recording_disclosure_exempt_apps)));
+        mExemptPackages.addAll(Arrays.asList(getGlobalStringArray(EXEMPT_PACKAGES_LIST)));
+        mExemptPackages.removeAll(Arrays.asList(getGlobalStringArray(FORCED_PACKAGES_LIST)));
 
         mAudioActivityObservers = new AudioActivityObserver[]{
                 new RecordAudioAppOpObserver(mContext, this),
@@ -141,6 +148,11 @@
         };
     }
 
+    private String[] getGlobalStringArray(String setting) {
+        String result = Settings.Global.getString(mContext.getContentResolver(), setting);
+        return TextUtils.isEmpty(result) ? new String[0] : result.split(",");
+    }
+
     @UiThread
     @Override
     public void onAudioActivityStateChange(boolean active, String packageName) {
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
index 2973e0a..bc2a55c 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
@@ -16,13 +16,19 @@
 
 package com.android.systemui.usb;
 
+import android.app.Activity;
 import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.debug.IAdbManager;
+import android.hardware.usb.UsbManager;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -33,13 +39,25 @@
 import com.android.internal.app.AlertActivity;
 import com.android.internal.app.AlertController;
 import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+
+import javax.inject.Inject;
 
 public class UsbDebuggingActivity extends AlertActivity
                                   implements DialogInterface.OnClickListener {
     private static final String TAG = "UsbDebuggingActivity";
 
     private CheckBox mAlwaysAllow;
+    private UsbDisconnectedReceiver mDisconnectedReceiver;
+    private final BroadcastDispatcher mBroadcastDispatcher;
     private String mKey;
+    private boolean mServiceNotified;
+
+    @Inject
+    public UsbDebuggingActivity(BroadcastDispatcher broadcastDispatcher) {
+        super();
+        mBroadcastDispatcher = broadcastDispatcher;
+    }
 
     @Override
     public void onCreate(Bundle icicle) {
@@ -50,6 +68,10 @@
 
         super.onCreate(icicle);
 
+        if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0) {
+            mDisconnectedReceiver = new UsbDisconnectedReceiver(this);
+        }
+
         Intent intent = getIntent();
         String fingerprints = intent.getStringExtra("fingerprints");
         mKey = intent.getStringExtra("key");
@@ -83,10 +105,77 @@
         super.onWindowAttributesChanged(params);
     }
 
+    private class UsbDisconnectedReceiver extends BroadcastReceiver {
+        private final Activity mActivity;
+        UsbDisconnectedReceiver(Activity activity) {
+            mActivity = activity;
+        }
+
+        @Override
+        public void onReceive(Context content, Intent intent) {
+            String action = intent.getAction();
+            if (!UsbManager.ACTION_USB_STATE.equals(action)) {
+                return;
+            }
+            boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
+            if (!connected) {
+                notifyService(false);
+                mActivity.finish();
+            }
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        if (mDisconnectedReceiver != null) {
+            IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE);
+            mBroadcastDispatcher.registerReceiver(mDisconnectedReceiver, filter);
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        if (mDisconnectedReceiver != null) {
+            mBroadcastDispatcher.unregisterReceiver(mDisconnectedReceiver);
+        }
+        // If the ADB service has not yet been notified due to this dialog being closed in some
+        // other way then notify the service to deny the connection to ensure system_server sends
+        // a response to adbd.
+        if (!mServiceNotified) {
+            notifyService(false);
+        }
+        super.onStop();
+    }
+
     @Override
     public void onClick(DialogInterface dialog, int which) {
         boolean allow = (which == AlertDialog.BUTTON_POSITIVE);
         boolean alwaysAllow = allow && mAlwaysAllow.isChecked();
+        notifyService(allow, alwaysAllow);
+        finish();
+    }
+
+    /**
+     * Notifies the ADB service as to whether the current ADB request should be allowed; if the
+     * request is allowed it is only allowed for this session, and the user should be prompted again
+     * on subsequent requests from this key.
+     *
+     * @param allow whether the connection should be allowed for this session
+     */
+    private void notifyService(boolean allow) {
+        notifyService(allow, false);
+    }
+
+    /**
+     * Notifies the ADB service as to whether the current ADB request should be allowed, and if
+     * subsequent requests from this key should be allowed without user consent.
+     *
+     * @param allow whether the connection should be allowed
+     * @param alwaysAllow whether subsequent requests from this key should be allowed without user
+     *                    consent
+     */
+    private void notifyService(boolean allow, boolean alwaysAllow) {
         try {
             IBinder b = ServiceManager.getService(ADB_SERVICE);
             IAdbManager service = IAdbManager.Stub.asInterface(b);
@@ -95,9 +184,9 @@
             } else {
                 service.denyDebugging();
             }
+            mServiceNotified = true;
         } catch (Exception e) {
             Log.e(TAG, "Unable to notify Usb service", e);
         }
-        finish();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
index 4214242..4850a02 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
@@ -16,19 +16,47 @@
 
 package com.android.systemui.usb;
 
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.debug.IAdbManager;
+import android.hardware.usb.UsbManager;
 import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Log;
 
 import com.android.internal.app.AlertActivity;
 import com.android.internal.app.AlertController;
 import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+
+import javax.inject.Inject;
 
 public class UsbDebuggingSecondaryUserActivity extends AlertActivity
         implements DialogInterface.OnClickListener {
+    private static final String TAG = "UsbDebuggingSecondaryUserActivity";
+    private UsbDisconnectedReceiver mDisconnectedReceiver;
+    private final BroadcastDispatcher mBroadcastDispatcher;
+
+    @Inject
+    public UsbDebuggingSecondaryUserActivity(BroadcastDispatcher broadcastDispatcher) {
+        mBroadcastDispatcher = broadcastDispatcher;
+    }
+
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
+        if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0) {
+            mDisconnectedReceiver = new UsbDisconnectedReceiver(this);
+        }
+
         final AlertController.AlertParams ap = mAlertParams;
         ap.mTitle = getString(R.string.usb_debugging_secondary_user_title);
         ap.mMessage = getString(R.string.usb_debugging_secondary_user_message);
@@ -38,6 +66,48 @@
         setupAlert();
     }
 
+    private class UsbDisconnectedReceiver extends BroadcastReceiver {
+        private final Activity mActivity;
+        UsbDisconnectedReceiver(Activity activity) {
+            mActivity = activity;
+        }
+
+        @Override
+        public void onReceive(Context content, Intent intent) {
+            String action = intent.getAction();
+            if (UsbManager.ACTION_USB_STATE.equals(action)) {
+                boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
+                if (!connected) {
+                    mActivity.finish();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        if (mDisconnectedReceiver != null) {
+            IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE);
+            mBroadcastDispatcher.registerReceiver(mDisconnectedReceiver, filter);
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        if (mDisconnectedReceiver != null) {
+            mBroadcastDispatcher.unregisterReceiver(mDisconnectedReceiver);
+        }
+        try {
+            IBinder b = ServiceManager.getService(ADB_SERVICE);
+            IAdbManager service = IAdbManager.Stub.asInterface(b);
+            service.denyDebugging();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to notify Usb service", e);
+        }
+        super.onStop();
+    }
+
     @Override
     public void onClick(DialogInterface dialog, int which) {
         finish();
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
index 318a6d7..016f4de 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -644,6 +644,12 @@
                 it.onInternalAnimationEnd(
                         property, canceled, value, velocity, anim is FlingAnimation)
             }
+            if (springAnimations[property] == anim) {
+                springAnimations.remove(property)
+            }
+            if (flingAnimations[property] == anim) {
+                flingAnimations.remove(property)
+            }
         }
         return anim
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
index da7953d..5a2b064 100644
--- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
@@ -32,6 +32,7 @@
 import androidx.dynamicanimation.animation.FloatPropertyCompat
 import androidx.dynamicanimation.animation.SpringForce
 import com.android.systemui.util.animation.PhysicsAnimator
+import kotlin.math.abs
 import kotlin.math.hypot
 
 /**
@@ -231,11 +232,11 @@
     var flingUnstuckFromTargetMinVelocity = 1000f
 
     /**
-     * Sets the maximum velocity above which the object will not stick to the target. Even if the
+     * Sets the maximum X velocity above which the object will not stick to the target. Even if the
      * object is dragged through the magnetic field, it will not stick to the target until the
-     * velocity is below this value.
+     * horizontal velocity is below this value.
      */
-    var stickToTargetMaxVelocity = 2000f
+    var stickToTargetMaxXVelocity = 2000f
 
     /**
      * Enable or disable haptic vibration effects when the object interacts with the magnetic field.
@@ -363,7 +364,7 @@
             // If the object is moving too quickly within the magnetic field, do not stick it. This
             // only applies to objects newly stuck to a target. If the object is moved into a new
             // target, it wasn't moving at all (since it was stuck to the previous one).
-            if (objectNewlyStuckToTarget && hypot(velX, velY) > stickToTargetMaxVelocity) {
+            if (objectNewlyStuckToTarget && abs(velX) > stickToTargetMaxXVelocity) {
                 return false
             }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 7bc453a..0238799 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -452,12 +452,6 @@
     }
 
     @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);
@@ -513,10 +507,20 @@
 
     @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(
-                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+        when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
 
         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 b2c3586..713aef9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -35,6 +35,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.broadcast.FakeBroadcastDispatcher;
+import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.FalsingManager;
@@ -72,8 +73,8 @@
     public void SysuiSetup() throws Exception {
         SystemUIFactory.createFromConfig(mContext);
         mDependency = new TestableDependency(mContext);
-        mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(mContext, mock(Handler.class),
-                mock(Looper.class), mock(DumpManager.class));
+        mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(mContext, mock(Looper.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/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 3357c58..4ed284e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.BroadcastReceiver
 import android.content.Context
+import android.content.Intent
 import android.content.IntentFilter
 import android.os.Handler
 import android.os.Looper
@@ -27,6 +28,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
@@ -76,6 +78,8 @@
     private lateinit var intentFilterOther: IntentFilter
     @Mock
     private lateinit var mockHandler: Handler
+    @Mock
+    private lateinit var logger: BroadcastDispatcherLogger
 
     private lateinit var executor: Executor
 
@@ -93,9 +97,9 @@
 
         broadcastDispatcher = TestBroadcastDispatcher(
                 mockContext,
-                Handler(testableLooper.looper),
                 testableLooper.looper,
                 mock(DumpManager::class.java),
+                logger,
                 mapOf(0 to mockUBRUser0, 1 to mockUBRUser1))
 
         // These should be valid filters
@@ -173,7 +177,12 @@
 
     @Test
     fun testRegisterCurrentAsActualUser() {
-        setUserMock(mockContext, user1)
+        val intent = Intent(Intent.ACTION_USER_SWITCHED).apply {
+            putExtra(Intent.EXTRA_USER_HANDLE, user1.identifier)
+        }
+        broadcastDispatcher.onReceive(mockContext, intent)
+        testableLooper.processAllMessages()
+
         broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
                 mockHandler, UserHandle.CURRENT)
 
@@ -236,11 +245,11 @@
 
     private class TestBroadcastDispatcher(
         context: Context,
-        mainHandler: Handler,
         bgLooper: Looper,
         dumpManager: DumpManager,
+        logger: BroadcastDispatcherLogger,
         var mockUBRMap: Map<Int, UserBroadcastDispatcher>
-    ) : BroadcastDispatcher(context, mainHandler, bgLooper, dumpManager) {
+    ) : BroadcastDispatcher(context, bgLooper, 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 9a5773a..09a0916 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -24,15 +24,16 @@
 import android.util.ArraySet
 import android.util.Log
 import com.android.systemui.SysuiTestableContext
+import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
 import com.android.systemui.dump.DumpManager
 import java.util.concurrent.Executor
 
 class FakeBroadcastDispatcher(
     context: SysuiTestableContext,
-    handler: Handler,
     looper: Looper,
-    dumpManager: DumpManager
-) : BroadcastDispatcher(context, handler, looper, dumpManager) {
+    dumpManager: DumpManager,
+    logger: BroadcastDispatcherLogger
+) : BroadcastDispatcher(context, looper, 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 847e442..4433576 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -26,6 +26,7 @@
 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.time.FakeSystemClock
 import junit.framework.Assert.assertEquals
@@ -40,6 +41,7 @@
 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
@@ -62,6 +64,8 @@
         private val USER_HANDLE = UserHandle.of(USER_ID)
 
         fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+        fun <T> any(): T = Mockito.any()
+        fun <T> eq(v: T) = Mockito.eq(v) ?: v
     }
 
     @Mock
@@ -72,6 +76,8 @@
     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>
@@ -91,7 +97,7 @@
         fakeExecutor = FakeExecutor(FakeSystemClock())
 
         userBroadcastDispatcher = UserBroadcastDispatcher(
-                mockContext, USER_ID, testableLooper.looper)
+                mockContext, USER_ID, testableLooper.looper, logger)
         userBroadcastDispatcher.pendingResult = mPendingResult
     }
 
@@ -106,6 +112,13 @@
     }
 
     @Test
+    fun testNotRegisteredOnStart_logging() {
+        testableLooper.processAllMessages()
+
+        verify(logger, never()).logContextReceiverRegistered(anyInt(), any())
+    }
+
+    @Test
     fun testSingleReceiverRegistered() {
         intentFilter = IntentFilter(ACTION_1)
 
@@ -126,6 +139,18 @@
     }
 
     @Test
+    fun testSingleReceiverRegistered_logging() {
+        intentFilter = IntentFilter(ACTION_1)
+
+        userBroadcastDispatcher.registerReceiver(
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
+        testableLooper.processAllMessages()
+
+        verify(logger).logReceiverRegistered(USER_HANDLE.identifier, broadcastReceiver)
+        verify(logger).logContextReceiverRegistered(eq(USER_HANDLE.identifier), any())
+    }
+
+    @Test
     fun testSingleReceiverUnregistered() {
         intentFilter = IntentFilter(ACTION_1)
 
@@ -145,6 +170,21 @@
     }
 
     @Test
+    fun testSingleReceiverUnregistered_logger() {
+        intentFilter = IntentFilter(ACTION_1)
+
+        userBroadcastDispatcher.registerReceiver(
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
+        testableLooper.processAllMessages()
+
+        userBroadcastDispatcher.unregisterReceiver(broadcastReceiver)
+        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 {
@@ -196,6 +236,30 @@
     }
 
     @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)
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 472b121..1e48b99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -425,7 +425,8 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
-        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
+        verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
+                true, mRow2.getEntry().getKey());
 
         assertTrue(mSysUiStateBubblesExpanded);
 
@@ -443,9 +444,11 @@
                 mRow.getEntry()));
 
         // collapse for previous bubble
-        verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
+        verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
+                false, mRow2.getEntry().getKey());
         // expand for selected bubble
-        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
+        verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
+                true, mRow.getEntry().getKey());
 
         // Collapse
         mBubbleController.collapseStack();
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 17022da..1ca2f02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -537,7 +537,7 @@
         // Verify the selection was cleared.
         verifyUpdateReceived();
         assertThat(mBubbleData.isExpanded()).isFalse();
-        assertSelectionCleared();
+        assertThat(mBubbleData.getSelectedBubble()).isNull();
     }
 
     // EXPANDED / ADD / UPDATE
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 47cd9bc..0be2472 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -382,7 +382,8 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
-        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
+        verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
+                true, mRow.getEntry().getKey());
 
         // Last added is the one that is expanded
         assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry());
@@ -397,9 +398,11 @@
                 mRow.getEntry()));
 
         // collapse for previous bubble
-        verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
+        verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
+                false, mRow2.getEntry().getKey());
         // expand for selected bubble
-        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
+        verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
+                true, mRow.getEntry().getKey());
 
         // Collapse
         mBubbleController.collapseStack();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index 3a3140f..6fcf6e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -51,6 +51,7 @@
 import org.mockito.junit.MockitoJUnit
 
 private const val KEY = "TEST_KEY"
+private const val KEY_OLD = "TEST_KEY_OLD"
 private const val PACKAGE = "PKG"
 private const val SESSION_KEY = "SESSION_KEY"
 private const val SESSION_ARTIST = "SESSION_ARTIST"
@@ -69,7 +70,6 @@
     @Mock private lateinit var lmmFactory: LocalMediaManagerFactory
     @Mock private lateinit var lmm: LocalMediaManager
     @Mock private lateinit var mr2: MediaRouter2Manager
-    @Mock private lateinit var featureFlag: MediaFeatureFlag
     private lateinit var fakeExecutor: FakeExecutor
     @Mock private lateinit var listener: MediaDeviceManager.Listener
     @Mock private lateinit var device: MediaDevice
@@ -85,8 +85,7 @@
     @Before
     fun setUp() {
         fakeExecutor = FakeExecutor(FakeSystemClock())
-        manager = MediaDeviceManager(context, lmmFactory, mr2, featureFlag, fakeExecutor,
-            mediaDataManager)
+        manager = MediaDeviceManager(context, lmmFactory, mr2, fakeExecutor, mediaDataManager)
         manager.addListener(listener)
 
         // Configure mocks.
@@ -95,7 +94,6 @@
         whenever(lmmFactory.create(PACKAGE)).thenReturn(lmm)
         whenever(lmm.getCurrentConnectedDevice()).thenReturn(device)
         whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(route)
-        whenever(featureFlag.enabled).thenReturn(true)
 
         // Create a media sesssion and notification for testing.
         metadataBuilder = MediaMetadata.Builder().apply {
@@ -132,26 +130,77 @@
     }
 
     @Test
-    fun addNotification() {
+    fun loadMediaData() {
         manager.onMediaDataLoaded(KEY, null, mediaData)
         verify(lmmFactory).create(PACKAGE)
     }
 
     @Test
-    fun featureDisabled() {
-        whenever(featureFlag.enabled).thenReturn(false)
-        manager.onMediaDataLoaded(KEY, null, mediaData)
-        verify(lmmFactory, never()).create(PACKAGE)
-    }
-
-    @Test
-    fun addAndRemoveNotification() {
+    fun loadAndRemoveMediaData() {
         manager.onMediaDataLoaded(KEY, null, mediaData)
         manager.onMediaDataRemoved(KEY)
         verify(lmm).unregisterCallback(any())
     }
 
     @Test
+    fun loadMediaDataWithNullToken() {
+        manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null))
+        fakeExecutor.runAllReady()
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isTrue()
+        assertThat(data.name).isEqualTo(DEVICE_NAME)
+    }
+
+    @Test
+    fun loadWithNewKey() {
+        // GIVEN that media data has been loaded with an old key
+        manager.onMediaDataLoaded(KEY_OLD, null, mediaData)
+        reset(listener)
+        // WHEN data is loaded with a new key
+        manager.onMediaDataLoaded(KEY, KEY_OLD, mediaData)
+        // THEN the listener for the old key should removed.
+        verify(lmm).unregisterCallback(any())
+        // AND a new device event emitted
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isTrue()
+        assertThat(data.name).isEqualTo(DEVICE_NAME)
+    }
+
+    @Test
+    fun newKeySameAsOldKey() {
+        // GIVEN that media data has been loaded
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        reset(listener)
+        // WHEN the new key is the same as the old key
+        manager.onMediaDataLoaded(KEY, KEY, mediaData)
+        // THEN no event should be emitted
+        verify(listener, never()).onMediaDeviceChanged(eq(KEY), any())
+    }
+
+    @Test
+    fun unknownOldKey() {
+        manager.onMediaDataLoaded(KEY, "unknown", mediaData)
+        verify(listener).onMediaDeviceChanged(eq(KEY), any())
+    }
+
+    @Test
+    fun updateToSessionTokenWithNullRoute() {
+        // GIVEN that media data has been loaded with a null token
+        manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null))
+        // WHEN media data is loaded with a different token
+        // AND that token results in a null route
+        reset(listener)
+        whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        // THEN the device should be disabled
+        fakeExecutor.runAllReady()
+        val data = captureDeviceData(KEY)
+        assertThat(data.enabled).isFalse()
+        assertThat(data.name).isNull()
+        assertThat(data.icon).isNull()
+    }
+
+    @Test
     fun deviceEventOnAddNotification() {
         // WHEN a notification is added
         manager.onMediaDataLoaded(KEY, null, mediaData)
@@ -164,6 +213,15 @@
     }
 
     @Test
+    fun removeListener() {
+        // WHEN a listener is removed
+        manager.removeListener(listener)
+        // THEN it doesn't receive device events
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        verify(listener, never()).onMediaDeviceChanged(eq(KEY), any())
+    }
+
+    @Test
     fun deviceListUpdate() {
         manager.onMediaDataLoaded(KEY, null, mediaData)
         val deviceCallback = captureCallback()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
index cdef49d..2fa6cf0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
@@ -36,6 +36,8 @@
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -51,6 +53,7 @@
 @TestableLooper.RunWithLooper
 public class ActivityLaunchAnimatorTest extends SysuiTestCase {
 
+    private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
     private ActivityLaunchAnimator mLaunchAnimator;
     @Mock
     private ActivityLaunchAnimator.Callback mCallback;
@@ -80,8 +83,8 @@
                 mCallback,
                 mNotificationPanelViewController,
                 mNotificationShadeDepthController,
-                mNotificationContainer);
-
+                mNotificationContainer,
+                mExecutor);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
index f327967..43aa8fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
@@ -25,6 +25,7 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
@@ -162,6 +163,7 @@
 
     @Test
     public void testBindNotification_SetsName() {
+        when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Package");
         mInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
@@ -174,15 +176,13 @@
                 true,
                 false);
         final TextView textView = mInfo.findViewById(R.id.name);
-        assertTrue(textView.getText().toString().contains("title"));
+        assertTrue(textView.getText().toString().equals("Package"));
     }
 
+
     @Test
-    public void testBindNotification_groupSetsPackageIcon() {
-        mEntry.getSbn().getNotification().extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, true);
-        final Drawable iconDrawable = mock(Drawable.class);
-        when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class)))
-                .thenReturn(iconDrawable);
+    public void testBindNotification_setsIcon() {
+        when(mMockPackageManager.getApplicationIcon((ApplicationInfo) any())).thenReturn(mDrawable);
         mInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
@@ -194,34 +194,7 @@
                 null,
                 true,
                 false);
-        final ImageView iconView = mInfo.findViewById(R.id.conversation_icon);
-        assertEquals(iconDrawable, iconView.getDrawable());
-    }
-
-    @Test
-    public void testBindNotification_notGroupSetsMessageIcon() {
-        Notification n = new Notification.Builder(mContext, TEST_CHANNEL_NAME)
-                .setStyle(new Notification.MessagingStyle(
-                        new Person.Builder().setName("me").build())
-                .addMessage(new Notification.MessagingStyle.Message("hello", 0,
-                        new Person.Builder().setName("friend").setIcon(mIcon).build())))
-                .build();
-        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
-                n, UserHandle.CURRENT, null, 0);
-        mEntry.setSbn(mSbn);
-        mEntry.getSbn().getNotification().extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, false);
-        mInfo.bindNotification(
-                mMockPackageManager,
-                mMockINotificationManager,
-                mChannelEditorDialogController,
-                TEST_PACKAGE_NAME,
-                mNotificationChannel,
-                mNotificationChannelSet,
-                mEntry,
-                null,
-                true,
-                false);
-        final ImageView iconView = mInfo.findViewById(R.id.conversation_icon);
+        final ImageView iconView = mInfo.findViewById(R.id.icon);
         assertEquals(mDrawable.hashCode() + "", mDrawable, iconView.getDrawable());
     }
 
@@ -270,64 +243,6 @@
     }
 
     @Test
-    public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception {
-        mInfo.bindNotification(
-                mMockPackageManager,
-                mMockINotificationManager,
-                mChannelEditorDialogController,
-                TEST_PACKAGE_NAME,
-                mNotificationChannel,
-                mNotificationChannelSet,
-                mEntry,
-                null,
-                true,
-                false);
-        final TextView groupNameView = mInfo.findViewById(R.id.group_name);
-        assertEquals(GONE, groupNameView.getVisibility());
-    }
-
-    @Test
-    public void testBindNotification_SetsGroupNameIfNonNull() throws Exception {
-        mNotificationChannel.setGroup("test_group_id");
-        final NotificationChannelGroup notificationChannelGroup =
-                new NotificationChannelGroup("test_group_id", "Test Group Name");
-        when(mMockINotificationManager.getNotificationChannelGroupForPackage(
-                eq("test_group_id"), eq(TEST_PACKAGE_NAME), eq(TEST_UID)))
-                .thenReturn(notificationChannelGroup);
-        mInfo.bindNotification(
-                mMockPackageManager,
-                mMockINotificationManager,
-                mChannelEditorDialogController,
-                TEST_PACKAGE_NAME,
-                mNotificationChannel,
-                mNotificationChannelSet,
-                mEntry,
-                null,
-                true,
-                false);
-        final TextView groupNameView = mInfo.findViewById(R.id.group_name);
-        assertEquals(View.VISIBLE, groupNameView.getVisibility());
-        assertEquals("Test Group Name", groupNameView.getText());
-    }
-
-    @Test
-    public void testBindNotification_SetsTextChannelName() {
-        mInfo.bindNotification(
-                mMockPackageManager,
-                mMockINotificationManager,
-                mChannelEditorDialogController,
-                TEST_PACKAGE_NAME,
-                mNotificationChannel,
-                mNotificationChannelSet,
-                mEntry,
-                null,
-                true,
-                false);
-        final TextView textView = mInfo.findViewById(R.id.parent_channel_name);
-        assertEquals(TEST_CHANNEL_NAME, textView.getText());
-    }
-
-    @Test
     public void testBindNotification_SetsOnClickListenerForSettings() {
         final CountDownLatch latch = new CountDownLatch(1);
         mInfo.bindNotification(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index a77f8c6..00cbddc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -21,6 +21,8 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 
+import static com.android.systemui.statusbar.phone.NavigationBarFragment.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -28,6 +30,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -59,6 +62,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.SysuiTestableContext;
@@ -105,6 +109,8 @@
     private Recents mRecents;
     @Mock
     private SystemActions mSystemActions;
+    @Mock
+    private UiEventLogger mUiEventLogger;
 
     private AccessibilityManagerWrapper mAccessibilityWrapper =
             new AccessibilityManagerWrapper(mContext) {
@@ -187,6 +193,8 @@
         mFragments.dispatchResume();
         processAllMessages();
         navigationBarFragment.onHomeLongClick(navigationBarFragment.getView());
+
+        verify(mUiEventLogger, times(1)).log(NAVBAR_ASSIST_LONGPRESS);
     }
 
     @Test
@@ -242,6 +250,7 @@
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         DeviceProvisionedController deviceProvisionedController =
                 mock(DeviceProvisionedController.class);
+        when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
         assertNotNull(mAccessibilityWrapper);
         return new NavigationBarFragment(
                 context.getDisplayId() == DEFAULT_DISPLAY ? mAccessibilityWrapper
@@ -261,7 +270,8 @@
                 mock(ShadeController.class),
                 mock(NotificationRemoteInputManager.class),
                 mock(SystemActions.class),
-                mHandler);
+                mHandler,
+                mUiEventLogger);
     }
 
     private class HostCallbacksForExternalDisplay extends
@@ -319,6 +329,7 @@
                     mock(LightBarTransitionsController.class));
             when(view.getRotationButtonController()).thenReturn(
                     mock(RotationButtonController.class));
+            when(view.isRecentsButtonVisible()).thenReturn(true);
             return view;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
index 5e9d592..d52d686 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
@@ -23,12 +23,12 @@
 import static android.view.KeyEvent.KEYCODE_BACK;
 import static android.view.KeyEvent.KEYCODE_HOME;
 
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarActionsEvent.NAVBAR_BACK_BUTTON_LONGPRESS;
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarActionsEvent.NAVBAR_BACK_BUTTON_TAP;
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarActionsEvent.NAVBAR_HOME_BUTTON_LONGPRESS;
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarActionsEvent.NAVBAR_HOME_BUTTON_TAP;
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarActionsEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS;
-import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarActionsEvent.NAVBAR_OVERVIEW_BUTTON_TAP;
+import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS;
+import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP;
+import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS;
+import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP;
+import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS;
+import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_TAP;
 
 import static junit.framework.Assert.assertEquals;
 
@@ -140,11 +140,11 @@
     }
 
     private void checkmetrics(int code, int action, int flag,
-            KeyButtonView.NavBarActionsEvent expected) {
+            KeyButtonView.NavBarButtonEvent expected) {
         mKeyButtonView.setCode(code);
         mKeyButtonView.sendEvent(action, flag);
         if (expected == null) {
-            verify(mUiEventLogger, never()).log(any(KeyButtonView.NavBarActionsEvent.class));
+            verify(mUiEventLogger, never()).log(any(KeyButtonView.NavBarButtonEvent.class));
         } else {
             verify(mUiEventLogger, times(1)).log(expected);
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
index 661fad2..bd59680 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
@@ -38,6 +38,7 @@
 @TestableLooper.RunWithLooper
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
+@Ignore("Blocking presubmits - investigating in b/158697054")
 class PhysicsAnimatorTest : SysuiTestCase() {
     private lateinit var viewGroup: ViewGroup
     private lateinit var testView: View
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index d07a70c..32e2b04 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -25,7 +25,7 @@
     ],
     static_libs: [
         "androidx.annotation_annotation",
-        "netd_aidl_interface-V3-java",
+        "netd_aidl_interface-java",
         "netlink-client",
         "networkstack-aidl-interfaces-java",
         "android.hardware.tetheroffload.config-V1.0-java",
diff --git a/packages/Tethering/TEST_MAPPING b/packages/Tethering/TEST_MAPPING
index 73254cd..5617b0c 100644
--- a/packages/Tethering/TEST_MAPPING
+++ b/packages/Tethering/TEST_MAPPING
@@ -1,7 +1,12 @@
 {
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "TetheringTests"
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "TetheringIntegrationTests"
+    }
   ]
 }
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
index fd6f171..f14def6a 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
@@ -37,8 +37,8 @@
     private TetheringConstants() { }
 
     /**
-     * Extra used for communicating with the TetherService. Includes the type of tethering to
-     * enable if any.
+     * Extra used for communicating with the TetherService and TetherProvisioningActivity.
+     * Includes the type of tethering to enable if any.
      */
     public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
     /**
@@ -56,8 +56,38 @@
      */
     public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
     /**
-     * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver}
-     * which will receive provisioning results. Can be left empty.
+     * Extra used for communicating with the TetherService and TetherProvisioningActivity.
+     * Contains the {@link ResultReceiver} which will receive provisioning results.
+     * Can not be empty.
      */
     public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
+
+    /**
+     * Extra used for communicating with the TetherService and TetherProvisioningActivity.
+     * Contains the subId of current active cellular upstream.
+     * @hide
+     */
+    public static final String EXTRA_TETHER_SUBID = "android.net.extra.TETHER_SUBID";
+
+    /**
+     * Extra used for telling TetherProvisioningActivity the entitlement package name and class
+     * name to start UI entitlement check.
+     * @hide
+     */
+    public static final String EXTRA_TETHER_UI_PROVISIONING_APP_NAME =
+            "android.net.extra.TETHER_UI_PROVISIONING_APP_NAME";
+
+    /**
+     * Extra used for telling TetherService the intent action to start silent entitlement check.
+     * @hide
+     */
+    public static final String EXTRA_TETHER_SILENT_PROVISIONING_ACTION =
+            "android.net.extra.TETHER_SILENT_PROVISIONING_ACTION";
+
+    /**
+     * Extra used for TetherService to receive the response of provisioning check.
+     * @hide
+     */
+    public static final String EXTRA_TETHER_PROVISIONING_RESPONSE =
+            "android.net.extra.TETHER_PROVISIONING_RESPONSE";
 }
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 3fd9ee9..1671dda 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -33,7 +33,6 @@
 import android.net.LinkProperties;
 import android.net.MacAddress;
 import android.net.RouteInfo;
-import android.net.TetherOffloadRuleParcel;
 import android.net.TetheredClient;
 import android.net.TetheringManager;
 import android.net.TetheringRequestParcel;
@@ -65,6 +64,8 @@
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.networkstack.tethering.BpfCoordinator;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
 import com.android.networkstack.tethering.PrivateAddressCoordinator;
 
 import java.io.IOException;
@@ -76,7 +77,6 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Objects;
 import java.util.Random;
@@ -225,6 +225,8 @@
 
     private final SharedLog mLog;
     private final INetd mNetd;
+    @NonNull
+    private final BpfCoordinator mBpfCoordinator;
     private final Callback mCallback;
     private final InterfaceController mInterfaceCtrl;
     private final PrivateAddressCoordinator mPrivateAddressCoordinator;
@@ -269,43 +271,6 @@
         }
     }
 
-    static class Ipv6ForwardingRule {
-        public final int upstreamIfindex;
-        public final int downstreamIfindex;
-        public final Inet6Address address;
-        public final MacAddress srcMac;
-        public final MacAddress dstMac;
-
-        Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, Inet6Address address,
-                MacAddress srcMac, MacAddress dstMac) {
-            this.upstreamIfindex = upstreamIfindex;
-            this.downstreamIfindex = downstreamIfIndex;
-            this.address = address;
-            this.srcMac = srcMac;
-            this.dstMac = dstMac;
-        }
-
-        public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) {
-            return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac,
-                    dstMac);
-        }
-
-        // Don't manipulate TetherOffloadRuleParcel directly because implementing onNewUpstream()
-        // would be error-prone due to generated stable AIDL classes not having a copy constructor.
-        public TetherOffloadRuleParcel toTetherOffloadRuleParcel() {
-            final TetherOffloadRuleParcel parcel = new TetherOffloadRuleParcel();
-            parcel.inputInterfaceIndex = upstreamIfindex;
-            parcel.outputInterfaceIndex = downstreamIfindex;
-            parcel.destination = address.getAddress();
-            parcel.prefixLength = 128;
-            parcel.srcL2Address = srcMac.toByteArray();
-            parcel.dstL2Address = dstMac.toByteArray();
-            return parcel;
-        }
-    }
-    private final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> mIpv6ForwardingRules =
-            new LinkedHashMap<>();
-
     private final IpNeighborMonitor mIpNeighborMonitor;
 
     private LinkAddress mIpv4Address;
@@ -314,11 +279,13 @@
     // object. It helps to reduce the arguments of the constructor.
     public IpServer(
             String ifaceName, Looper looper, int interfaceType, SharedLog log,
-            INetd netd, Callback callback, boolean usingLegacyDhcp, boolean usingBpfOffload,
+            INetd netd, @NonNull BpfCoordinator coordinator, Callback callback,
+            boolean usingLegacyDhcp, boolean usingBpfOffload,
             PrivateAddressCoordinator addressCoordinator, Dependencies deps) {
         super(ifaceName, looper);
         mLog = log.forSubComponent(ifaceName);
         mNetd = netd;
+        mBpfCoordinator = coordinator;
         mCallback = callback;
         mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
         mIfaceName = ifaceName;
@@ -749,6 +716,14 @@
             }
 
             upstreamIfindex = mDeps.getIfindex(upstreamIface);
+
+            // Add upstream index to name mapping for the tether stats usage in the coordinator.
+            // Although this mapping could be added by both class Tethering and IpServer, adding
+            // mapping from IpServer guarantees that the mapping is added before the adding
+            // forwarding rules. That is because there are different state machines in both
+            // classes. It is hard to guarantee the link property update order between multiple
+            // state machines.
+            mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfindex, upstreamIface);
         }
 
         // If v6only is null, we pass in null to setRaParams(), which handles
@@ -864,43 +839,29 @@
         // TODO: Perhaps remove this protection check.
         if (!mUsingBpfOffload) return;
 
-        try {
-            mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel());
-            mIpv6ForwardingRules.put(rule.address, rule);
-        } catch (RemoteException | ServiceSpecificException e) {
-            mLog.e("Could not add IPv6 downstream rule: ", e);
-        }
+        mBpfCoordinator.tetherOffloadRuleAdd(this, rule);
     }
 
-    private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule, boolean removeFromMap) {
-        // Theoretically, we don't need this check because IP neighbor monitor doesn't start if BPF
-        // offload is disabled. Add this check just in case.
+    private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule) {
         // TODO: Perhaps remove this protection check.
+        // See the related comment in #addIpv6ForwardingRule.
         if (!mUsingBpfOffload) return;
 
-        try {
-            mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel());
-            if (removeFromMap) {
-                mIpv6ForwardingRules.remove(rule.address);
-            }
-        } catch (RemoteException | ServiceSpecificException e) {
-            mLog.e("Could not remove IPv6 downstream rule: ", e);
-        }
+        mBpfCoordinator.tetherOffloadRuleRemove(this, rule);
     }
 
     private void clearIpv6ForwardingRules() {
-        for (Ipv6ForwardingRule rule : mIpv6ForwardingRules.values()) {
-            removeIpv6ForwardingRule(rule, false /*removeFromMap*/);
-        }
-        mIpv6ForwardingRules.clear();
+        if (!mUsingBpfOffload) return;
+
+        mBpfCoordinator.tetherOffloadRuleClear(this);
     }
 
-    // Convenience method to replace a rule with the same rule on a new upstream interface.
-    // Allows replacing the rules in one iteration pass without ConcurrentModificationExceptions.
-    // Relies on the fact that rules are in a map indexed by IP address.
-    private void updateIpv6ForwardingRule(Ipv6ForwardingRule rule, int newIfindex) {
-        addIpv6ForwardingRule(rule.onNewUpstream(newIfindex));
-        removeIpv6ForwardingRule(rule, false /*removeFromMap*/);
+    private void updateIpv6ForwardingRule(int newIfindex) {
+        // TODO: Perhaps remove this protection check.
+        // See the related comment in #addIpv6ForwardingRule.
+        if (!mUsingBpfOffload) return;
+
+        mBpfCoordinator.tetherOffloadRuleUpdate(this, newIfindex);
     }
 
     // Handles all updates to IPv6 forwarding rules. These can currently change only if the upstream
@@ -916,9 +877,7 @@
         // If the upstream interface has changed, remove all rules and re-add them with the new
         // upstream interface.
         if (prevUpstreamIfindex != upstreamIfindex) {
-            for (Ipv6ForwardingRule rule : mIpv6ForwardingRules.values()) {
-                updateIpv6ForwardingRule(rule, upstreamIfindex);
-            }
+            updateIpv6ForwardingRule(upstreamIfindex);
         }
 
         // If we're here to process a NeighborEvent, do so now.
@@ -938,7 +897,7 @@
         if (e.isValid()) {
             addIpv6ForwardingRule(rule);
         } else {
-            removeIpv6ForwardingRule(rule, true /*removeFromMap*/);
+            removeIpv6ForwardingRule(rule);
         }
     }
 
diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java
index dd67ddd..b17b4ba 100644
--- a/packages/Tethering/src/android/net/util/TetheringUtils.java
+++ b/packages/Tethering/src/android/net/util/TetheringUtils.java
@@ -15,19 +15,94 @@
  */
 package android.net.util;
 
+import android.net.TetherStatsParcel;
 import android.net.TetheringRequestParcel;
 
+import androidx.annotation.NonNull;
+
 import java.io.FileDescriptor;
 import java.net.SocketException;
 import java.util.Objects;
 
 /**
- * Native methods for tethering utilization.
+ * The classes and the methods for tethering utilization.
  *
  * {@hide}
  */
 public class TetheringUtils {
     /**
+     *  The object which records offload Tx/Rx forwarded bytes/packets.
+     *  TODO: Replace the inner class ForwardedStats of class OffloadHardwareInterface with
+     *  this class as well.
+     */
+    public static class ForwardedStats {
+        public final long rxBytes;
+        public final long rxPackets;
+        public final long txBytes;
+        public final long txPackets;
+
+        public ForwardedStats() {
+            rxBytes = 0;
+            rxPackets = 0;
+            txBytes = 0;
+            txPackets = 0;
+        }
+
+        public ForwardedStats(long rxBytes, long txBytes) {
+            this.rxBytes = rxBytes;
+            this.rxPackets = 0;
+            this.txBytes = txBytes;
+            this.txPackets = 0;
+        }
+
+        public ForwardedStats(long rxBytes, long rxPackets, long txBytes, long txPackets) {
+            this.rxBytes = rxBytes;
+            this.rxPackets = rxPackets;
+            this.txBytes = txBytes;
+            this.txPackets = txPackets;
+        }
+
+        public ForwardedStats(@NonNull TetherStatsParcel tetherStats) {
+            rxBytes = tetherStats.rxBytes;
+            rxPackets = tetherStats.rxPackets;
+            txBytes = tetherStats.txBytes;
+            txPackets = tetherStats.txPackets;
+        }
+
+        public ForwardedStats(@NonNull ForwardedStats other) {
+            rxBytes = other.rxBytes;
+            rxPackets = other.rxPackets;
+            txBytes = other.txBytes;
+            txPackets = other.txPackets;
+        }
+
+        /** Add Tx/Rx bytes/packets and return the result as a new object. */
+        @NonNull
+        public ForwardedStats add(@NonNull ForwardedStats other) {
+            return new ForwardedStats(rxBytes + other.rxBytes, rxPackets + other.rxPackets,
+                    txBytes + other.txBytes, txPackets + other.txPackets);
+        }
+
+        /** Subtract Tx/Rx bytes/packets and return the result as a new object. */
+        @NonNull
+        public ForwardedStats subtract(@NonNull ForwardedStats other) {
+            // TODO: Perhaps throw an exception if any negative difference value just in case.
+            final long rxBytesDiff = Math.max(rxBytes - other.rxBytes, 0);
+            final long rxPacketsDiff = Math.max(rxPackets - other.rxPackets, 0);
+            final long txBytesDiff = Math.max(txBytes - other.txBytes, 0);
+            final long txPacketsDiff = Math.max(txPackets - other.txPackets, 0);
+            return new ForwardedStats(rxBytesDiff, rxPacketsDiff, txBytesDiff, txPacketsDiff);
+        }
+
+        /** Returns the string representation of this object. */
+        @NonNull
+        public String toString() {
+            return String.format("ForwardedStats(rxb: %d, rxp: %d, txb: %d, txp: %d)", rxBytes,
+                    rxPackets, txBytes, txPackets);
+        }
+    }
+
+    /**
      * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
      * @param fd the socket's {@link FileDescriptor}.
      * @param ifIndex the interface index.
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
new file mode 100644
index 0000000..fc27b6add
--- /dev/null
+++ b/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -0,0 +1,637 @@
+/*
+ * 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.networkstack.tethering;
+
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStats.UID_TETHERING;
+import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
+
+import android.app.usage.NetworkStatsManager;
+import android.net.INetd;
+import android.net.MacAddress;
+import android.net.NetworkStats;
+import android.net.NetworkStats.Entry;
+import android.net.TetherOffloadRuleParcel;
+import android.net.TetherStatsParcel;
+import android.net.ip.IpServer;
+import android.net.netstats.provider.NetworkStatsProvider;
+import android.net.util.SharedLog;
+import android.net.util.TetheringUtils.ForwardedStats;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.Inet6Address;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Objects;
+
+/**
+ *  This coordinator is responsible for providing BPF offload relevant functionality.
+ *  - Get tethering stats.
+ *  - Set data limit.
+ *  - Set global alert.
+ *  - Add/remove forwarding rules.
+ *
+ * @hide
+ */
+public class BpfCoordinator {
+    private static final String TAG = BpfCoordinator.class.getSimpleName();
+    @VisibleForTesting
+    static final int DEFAULT_PERFORM_POLL_INTERVAL_MS = 5000; // TODO: Make it customizable.
+
+    @VisibleForTesting
+    enum StatsType {
+        STATS_PER_IFACE,
+        STATS_PER_UID,
+    }
+
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final INetd mNetd;
+    @NonNull
+    private final SharedLog mLog;
+    @NonNull
+    private final Dependencies mDeps;
+    @Nullable
+    private final BpfTetherStatsProvider mStatsProvider;
+
+    // Tracks whether BPF tethering is started or not. This is set by tethering before it
+    // starts the first IpServer and is cleared by tethering shortly before the last IpServer
+    // is stopped. Note that rule updates (especially deletions, but sometimes additions as
+    // well) may arrive when this is false. If they do, they must be communicated to netd.
+    // Changes in data limits may also arrive when this is false, and if they do, they must
+    // also be communicated to netd.
+    private boolean mPollingStarted = false;
+
+    // Tracking remaining alert quota. Unlike limit quota is subject to interface, the alert
+    // quota is interface independent and global for tether offload.
+    private long mRemainingAlertQuota = QUOTA_UNLIMITED;
+
+    // Maps upstream interface index to offloaded traffic statistics.
+    // Always contains the latest total bytes/packets, since each upstream was started, received
+    // from the BPF maps for each interface.
+    private final SparseArray<ForwardedStats> mStats = new SparseArray<>();
+
+    // Maps upstream interface names to interface quotas.
+    // Always contains the latest value received from the framework for each interface, regardless
+    // of whether offload is currently running (or is even supported) on that interface. Only
+    // includes interfaces that have a quota set. Note that this map is used for storing the quota
+    // which is set from the service. Because the service uses the interface name to present the
+    // interface, this map uses the interface name to be the mapping index.
+    private final HashMap<String, Long> mInterfaceQuotas = new HashMap<>();
+
+    // Maps upstream interface index to interface names.
+    // Store all interface name since boot. Used for lookup what interface name it is from the
+    // tether stats got from netd because netd reports interface index to present an interface.
+    // TODO: Remove the unused interface name.
+    private final SparseArray<String> mInterfaceNames = new SparseArray<>();
+
+    // Map of downstream rule maps. Each of these maps represents the IPv6 forwarding rules for a
+    // given downstream. Each map:
+    // - Is owned by the IpServer that is responsible for that downstream.
+    // - Must only be modified by that IpServer.
+    // - Is created when the IpServer adds its first rule, and deleted when the IpServer deletes
+    //   its last rule (or clears its rules).
+    // TODO: Perhaps seal the map and rule operations which communicates with netd into a class.
+    // TODO: Does this need to be a LinkedHashMap or can it just be a HashMap? Also, could it be
+    // a ConcurrentHashMap, in order to avoid the copies in tetherOffloadRuleClear
+    // and tetherOffloadRuleUpdate?
+    // TODO: Perhaps use one-dimensional map and access specific downstream rules via downstream
+    // index. For doing that, IpServer must guarantee that it always has a valid IPv6 downstream
+    // interface index while calling function to clear all rules. IpServer may be calling clear
+    // rules function without a valid IPv6 downstream interface index even if it may have one
+    // before. IpServer would need to call getInterfaceParams() in the constructor instead of when
+    // startIpv6() is called, and make mInterfaceParams final.
+    private final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>>
+            mIpv6ForwardingRules = new LinkedHashMap<>();
+
+    // Runnable that used by scheduling next polling of stats.
+    private final Runnable mScheduledPollingTask = () -> {
+        updateForwardedStatsFromNetd();
+        maybeSchedulePollingStats();
+    };
+
+    @VisibleForTesting
+    public static class Dependencies {
+        int getPerformPollInterval() {
+            // TODO: Consider make this configurable.
+            return DEFAULT_PERFORM_POLL_INTERVAL_MS;
+        }
+    }
+
+    @VisibleForTesting
+    public BpfCoordinator(@NonNull Handler handler, @NonNull INetd netd,
+            @NonNull NetworkStatsManager nsm, @NonNull SharedLog log, @NonNull Dependencies deps) {
+        mHandler = handler;
+        mNetd = netd;
+        mLog = log.forSubComponent(TAG);
+        BpfTetherStatsProvider provider = new BpfTetherStatsProvider();
+        try {
+            nsm.registerNetworkStatsProvider(getClass().getSimpleName(), provider);
+        } catch (RuntimeException e) {
+            // TODO: Perhaps not allow to use BPF offload because the reregistration failure
+            // implied that no data limit could be applies on a metered upstream if any.
+            Log.wtf(TAG, "Cannot register offload stats provider: " + e);
+            provider = null;
+        }
+        mStatsProvider = provider;
+        mDeps = deps;
+    }
+
+    /**
+     * Start BPF tethering offload stats polling when the first upstream is started.
+     * Note that this can be only called on handler thread.
+     * TODO: Perhaps check BPF support before starting.
+     * TODO: Start the stats polling only if there is any client on the downstream.
+     */
+    public void startPolling() {
+        if (mPollingStarted) return;
+
+        mPollingStarted = true;
+        maybeSchedulePollingStats();
+
+        mLog.i("Polling started");
+    }
+
+    /**
+     * Stop BPF tethering offload stats polling.
+     * The data limit cleanup and the tether stats maps cleanup are not implemented here.
+     * These cleanups rely on all IpServers calling #tetherOffloadRuleRemove. After the
+     * last rule is removed from the upstream, #tetherOffloadRuleRemove does the cleanup
+     * functionality.
+     * Note that this can be only called on handler thread.
+     */
+    public void stopPolling() {
+        if (!mPollingStarted) return;
+
+        // Stop scheduled polling tasks and poll the latest stats from BPF maps.
+        if (mHandler.hasCallbacks(mScheduledPollingTask)) {
+            mHandler.removeCallbacks(mScheduledPollingTask);
+        }
+        updateForwardedStatsFromNetd();
+        mPollingStarted = false;
+
+        mLog.i("Polling stopped");
+    }
+
+    /**
+     * Add forwarding rule. After adding the first rule on a given upstream, must add the data
+     * limit on the given upstream.
+     * Note that this can be only called on handler thread.
+     */
+    public void tetherOffloadRuleAdd(
+            @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) {
+        try {
+            // TODO: Perhaps avoid to add a duplicate rule.
+            mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel());
+        } catch (RemoteException | ServiceSpecificException e) {
+            mLog.e("Could not add IPv6 forwarding rule: ", e);
+            return;
+        }
+
+        if (!mIpv6ForwardingRules.containsKey(ipServer)) {
+            mIpv6ForwardingRules.put(ipServer, new LinkedHashMap<Inet6Address,
+                    Ipv6ForwardingRule>());
+        }
+        LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(ipServer);
+
+        // Setup the data limit on the given upstream if the first rule is added.
+        final int upstreamIfindex = rule.upstreamIfindex;
+        if (!isAnyRuleOnUpstream(upstreamIfindex)) {
+            // If failed to set a data limit, probably should not use this upstream, because
+            // the upstream may not want to blow through the data limit that was told to apply.
+            // TODO: Perhaps stop the coordinator.
+            boolean success = updateDataLimit(upstreamIfindex);
+            if (!success) {
+                final String iface = mInterfaceNames.get(upstreamIfindex);
+                mLog.e("Setting data limit for " + iface + " failed.");
+            }
+        }
+
+        // Must update the adding rule after calling #isAnyRuleOnUpstream because it needs to
+        // check if it is about adding a first rule for a given upstream.
+        rules.put(rule.address, rule);
+    }
+
+    /**
+     * Remove forwarding rule. After removing the last rule on a given upstream, must clear
+     * data limit, update the last tether stats and remove the tether stats in the BPF maps.
+     * Note that this can be only called on handler thread.
+     */
+    public void tetherOffloadRuleRemove(
+            @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) {
+        try {
+            // TODO: Perhaps avoid to remove a non-existent rule.
+            mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel());
+        } catch (RemoteException | ServiceSpecificException e) {
+            mLog.e("Could not remove IPv6 forwarding rule: ", e);
+            return;
+        }
+
+        LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(ipServer);
+        if (rules == null) return;
+
+        // Must remove rules before calling #isAnyRuleOnUpstream because it needs to check if
+        // the last rule is removed for a given upstream. If no rule is removed, return early.
+        // Avoid unnecessary work on a non-existent rule which may have never been added or
+        // removed already.
+        if (rules.remove(rule.address) == null) return;
+
+        // Remove the downstream entry if it has no more rule.
+        if (rules.isEmpty()) {
+            mIpv6ForwardingRules.remove(ipServer);
+        }
+
+        // Do cleanup functionality if there is no more rule on the given upstream.
+        final int upstreamIfindex = rule.upstreamIfindex;
+        if (!isAnyRuleOnUpstream(upstreamIfindex)) {
+            try {
+                final TetherStatsParcel stats =
+                        mNetd.tetherOffloadGetAndClearStats(upstreamIfindex);
+                // Update the last stats delta and delete the local cache for a given upstream.
+                updateQuotaAndStatsFromSnapshot(new TetherStatsParcel[] {stats});
+                mStats.remove(upstreamIfindex);
+            } catch (RemoteException | ServiceSpecificException e) {
+                Log.wtf(TAG, "Exception when cleanup tether stats for upstream index "
+                        + upstreamIfindex + ": ", e);
+            }
+        }
+    }
+
+    /**
+     * Clear all forwarding rules for a given downstream.
+     * Note that this can be only called on handler thread.
+     */
+    public void tetherOffloadRuleClear(@NonNull final IpServer ipServer) {
+        final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(
+                ipServer);
+        if (rules == null) return;
+
+        // Need to build a rule list because the rule map may be changed in the iteration.
+        for (final Ipv6ForwardingRule rule : new ArrayList<Ipv6ForwardingRule>(rules.values())) {
+            tetherOffloadRuleRemove(ipServer, rule);
+        }
+    }
+
+    /**
+     * Update existing forwarding rules to new upstream for a given downstream.
+     * Note that this can be only called on handler thread.
+     */
+    public void tetherOffloadRuleUpdate(@NonNull final IpServer ipServer, int newUpstreamIfindex) {
+        final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(
+                ipServer);
+        if (rules == null) return;
+
+        // Need to build a rule list because the rule map may be changed in the iteration.
+        for (final Ipv6ForwardingRule rule : new ArrayList<Ipv6ForwardingRule>(rules.values())) {
+            // Remove the old rule before adding the new one because the map uses the same key for
+            // both rules. Reversing the processing order causes that the new rule is removed as
+            // unexpected.
+            // TODO: Add new rule first to reduce the latency which has no rule.
+            tetherOffloadRuleRemove(ipServer, rule);
+            tetherOffloadRuleAdd(ipServer, rule.onNewUpstream(newUpstreamIfindex));
+        }
+    }
+
+    /**
+     * Add upstream name to lookup table. The lookup table is used for tether stats interface name
+     * lookup because the netd only reports interface index in BPF tether stats but the service
+     * expects the interface name in NetworkStats object.
+     * Note that this can be only called on handler thread.
+     */
+    public void addUpstreamNameToLookupTable(int upstreamIfindex, @NonNull String upstreamIface) {
+        if (upstreamIfindex == 0 || TextUtils.isEmpty(upstreamIface)) return;
+
+        // The same interface index to name mapping may be added by different IpServer objects or
+        // re-added by reconnection on the same upstream interface. Ignore the duplicate one.
+        final String iface = mInterfaceNames.get(upstreamIfindex);
+        if (iface == null) {
+            mInterfaceNames.put(upstreamIfindex, upstreamIface);
+        } else if (!TextUtils.equals(iface, upstreamIface)) {
+            Log.wtf(TAG, "The upstream interface name " + upstreamIface
+                    + " is different from the existing interface name "
+                    + iface + " for index " + upstreamIfindex);
+        }
+    }
+
+    /** IPv6 forwarding rule class. */
+    public static class Ipv6ForwardingRule {
+        public final int upstreamIfindex;
+        public final int downstreamIfindex;
+
+        @NonNull
+        public final Inet6Address address;
+        @NonNull
+        public final MacAddress srcMac;
+        @NonNull
+        public final MacAddress dstMac;
+
+        public Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex,
+                @NonNull Inet6Address address, @NonNull MacAddress srcMac,
+                @NonNull MacAddress dstMac) {
+            this.upstreamIfindex = upstreamIfindex;
+            this.downstreamIfindex = downstreamIfIndex;
+            this.address = address;
+            this.srcMac = srcMac;
+            this.dstMac = dstMac;
+        }
+
+        /** Return a new rule object which updates with new upstream index. */
+        @NonNull
+        public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) {
+            return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac,
+                    dstMac);
+        }
+
+        /**
+         * Don't manipulate TetherOffloadRuleParcel directly because implementing onNewUpstream()
+         * would be error-prone due to generated stable AIDL classes not having a copy constructor.
+         */
+        @NonNull
+        public TetherOffloadRuleParcel toTetherOffloadRuleParcel() {
+            final TetherOffloadRuleParcel parcel = new TetherOffloadRuleParcel();
+            parcel.inputInterfaceIndex = upstreamIfindex;
+            parcel.outputInterfaceIndex = downstreamIfindex;
+            parcel.destination = address.getAddress();
+            parcel.prefixLength = 128;
+            parcel.srcL2Address = srcMac.toByteArray();
+            parcel.dstL2Address = dstMac.toByteArray();
+            return parcel;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof Ipv6ForwardingRule)) return false;
+            Ipv6ForwardingRule that = (Ipv6ForwardingRule) o;
+            return this.upstreamIfindex == that.upstreamIfindex
+                    && this.downstreamIfindex == that.downstreamIfindex
+                    && Objects.equals(this.address, that.address)
+                    && Objects.equals(this.srcMac, that.srcMac)
+                    && Objects.equals(this.dstMac, that.dstMac);
+        }
+
+        @Override
+        public int hashCode() {
+            // TODO: if this is ever used in production code, don't pass ifindices
+            // to Objects.hash() to avoid autoboxing overhead.
+            return Objects.hash(upstreamIfindex, downstreamIfindex, address, srcMac, dstMac);
+        }
+    }
+
+    /**
+     * A BPF tethering stats provider to provide network statistics to the system.
+     * Note that this class' data may only be accessed on the handler thread.
+     */
+    @VisibleForTesting
+    class BpfTetherStatsProvider extends NetworkStatsProvider {
+        // The offloaded traffic statistics per interface that has not been reported since the
+        // last call to pushTetherStats. Only the interfaces that were ever tethering upstreams
+        // and has pending tether stats delta are included in this NetworkStats object.
+        private NetworkStats mIfaceStats = new NetworkStats(0L, 0);
+
+        // The same stats as above, but counts network stats per uid.
+        private NetworkStats mUidStats = new NetworkStats(0L, 0);
+
+        @Override
+        public void onRequestStatsUpdate(int token) {
+            mHandler.post(() -> pushTetherStats());
+        }
+
+        @Override
+        public void onSetAlert(long quotaBytes) {
+            mHandler.post(() -> updateAlertQuota(quotaBytes));
+        }
+
+        @Override
+        public void onSetLimit(@NonNull String iface, long quotaBytes) {
+            if (quotaBytes < QUOTA_UNLIMITED) {
+                throw new IllegalArgumentException("invalid quota value " + quotaBytes);
+            }
+
+            mHandler.post(() -> {
+                final Long curIfaceQuota = mInterfaceQuotas.get(iface);
+
+                if (null == curIfaceQuota && QUOTA_UNLIMITED == quotaBytes) return;
+
+                if (quotaBytes == QUOTA_UNLIMITED) {
+                    mInterfaceQuotas.remove(iface);
+                } else {
+                    mInterfaceQuotas.put(iface, quotaBytes);
+                }
+                maybeUpdateDataLimit(iface);
+            });
+        }
+
+        @VisibleForTesting
+        void pushTetherStats() {
+            try {
+                // The token is not used for now. See b/153606961.
+                notifyStatsUpdated(0 /* token */, mIfaceStats, mUidStats);
+
+                // Clear the accumulated tether stats delta after reported. Note that create a new
+                // empty object because NetworkStats#clear is @hide.
+                mIfaceStats = new NetworkStats(0L, 0);
+                mUidStats = new NetworkStats(0L, 0);
+            } catch (RuntimeException e) {
+                mLog.e("Cannot report network stats: ", e);
+            }
+        }
+
+        private void accumulateDiff(@NonNull NetworkStats ifaceDiff,
+                @NonNull NetworkStats uidDiff) {
+            mIfaceStats = mIfaceStats.add(ifaceDiff);
+            mUidStats = mUidStats.add(uidDiff);
+        }
+    }
+
+    private int getInterfaceIndexFromRules(@NonNull String ifName) {
+        for (LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules : mIpv6ForwardingRules
+                .values()) {
+            for (Ipv6ForwardingRule rule : rules.values()) {
+                final int upstreamIfindex = rule.upstreamIfindex;
+                if (TextUtils.equals(ifName, mInterfaceNames.get(upstreamIfindex))) {
+                    return upstreamIfindex;
+                }
+            }
+        }
+        return 0;
+    }
+
+    private long getQuotaBytes(@NonNull String iface) {
+        final Long limit = mInterfaceQuotas.get(iface);
+        final long quotaBytes = (limit != null) ? limit : QUOTA_UNLIMITED;
+
+        return quotaBytes;
+    }
+
+    private boolean sendDataLimitToNetd(int ifIndex, long quotaBytes) {
+        if (ifIndex == 0) {
+            Log.wtf(TAG, "Invalid interface index.");
+            return false;
+        }
+
+        try {
+            mNetd.tetherOffloadSetInterfaceQuota(ifIndex, quotaBytes);
+        } catch (RemoteException | ServiceSpecificException e) {
+            mLog.e("Exception when updating quota " + quotaBytes + ": ", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    // Handle the data limit update from the service which is the stats provider registered for.
+    private void maybeUpdateDataLimit(@NonNull String iface) {
+        // Set data limit only on a given upstream which has at least one rule. If we can't get
+        // an interface index for a given interface name, it means either there is no rule for
+        // a given upstream or the interface name is not an upstream which is monitored by the
+        // coordinator.
+        final int ifIndex = getInterfaceIndexFromRules(iface);
+        if (ifIndex == 0) return;
+
+        final long quotaBytes = getQuotaBytes(iface);
+        sendDataLimitToNetd(ifIndex, quotaBytes);
+    }
+
+    // Handle the data limit update while adding forwarding rules.
+    private boolean updateDataLimit(int ifIndex) {
+        final String iface = mInterfaceNames.get(ifIndex);
+        if (iface == null) {
+            mLog.e("Fail to get the interface name for index " + ifIndex);
+            return false;
+        }
+        final long quotaBytes = getQuotaBytes(iface);
+        return sendDataLimitToNetd(ifIndex, quotaBytes);
+    }
+
+    private boolean isAnyRuleOnUpstream(int upstreamIfindex) {
+        for (LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules : mIpv6ForwardingRules
+                .values()) {
+            for (Ipv6ForwardingRule rule : rules.values()) {
+                if (upstreamIfindex == rule.upstreamIfindex) return true;
+            }
+        }
+        return false;
+    }
+
+    @NonNull
+    private NetworkStats buildNetworkStats(@NonNull StatsType type, int ifIndex,
+            @NonNull final ForwardedStats diff) {
+        NetworkStats stats = new NetworkStats(0L, 0);
+        final String iface = mInterfaceNames.get(ifIndex);
+        if (iface == null) {
+            // TODO: Use Log.wtf once the coordinator owns full control of tether stats from netd.
+            // For now, netd may add the empty stats for the upstream which is not monitored by
+            // the coordinator. Silently ignore it.
+            return stats;
+        }
+        final int uid = (type == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL;
+        // Note that the argument 'metered', 'roaming' and 'defaultNetwork' are not recorded for
+        // network stats snapshot. See NetworkStatsRecorder#recordSnapshotLocked.
+        return stats.addEntry(new Entry(iface, uid, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, DEFAULT_NETWORK_NO, diff.rxBytes, diff.rxPackets,
+                diff.txBytes, diff.txPackets, 0L /* operations */));
+    }
+
+    private void updateAlertQuota(long newQuota) {
+        if (newQuota < QUOTA_UNLIMITED) {
+            throw new IllegalArgumentException("invalid quota value " + newQuota);
+        }
+        if (mRemainingAlertQuota == newQuota) return;
+
+        mRemainingAlertQuota = newQuota;
+        if (mRemainingAlertQuota == 0) {
+            mLog.i("onAlertReached");
+            if (mStatsProvider != null) mStatsProvider.notifyAlertReached();
+        }
+    }
+
+    private void updateQuotaAndStatsFromSnapshot(
+            @NonNull final TetherStatsParcel[] tetherStatsList) {
+        long usedAlertQuota = 0;
+        for (TetherStatsParcel tetherStats : tetherStatsList) {
+            final Integer ifIndex = tetherStats.ifIndex;
+            final ForwardedStats curr = new ForwardedStats(tetherStats);
+            final ForwardedStats base = mStats.get(ifIndex);
+            final ForwardedStats diff = (base != null) ? curr.subtract(base) : curr;
+            usedAlertQuota += diff.rxBytes + diff.txBytes;
+
+            // Update the local cache for counting tether stats delta.
+            mStats.put(ifIndex, curr);
+
+            // Update the accumulated tether stats delta to the stats provider for the service
+            // querying.
+            if (mStatsProvider != null) {
+                try {
+                    mStatsProvider.accumulateDiff(
+                            buildNetworkStats(StatsType.STATS_PER_IFACE, ifIndex, diff),
+                            buildNetworkStats(StatsType.STATS_PER_UID, ifIndex, diff));
+                } catch (ArrayIndexOutOfBoundsException e) {
+                    Log.wtf(TAG, "Fail to update the accumulated stats delta for interface index "
+                            + ifIndex + " : ", e);
+                }
+            }
+        }
+
+        if (mRemainingAlertQuota > 0 && usedAlertQuota > 0) {
+            // Trim to zero if overshoot.
+            final long newQuota = Math.max(mRemainingAlertQuota - usedAlertQuota, 0);
+            updateAlertQuota(newQuota);
+        }
+
+        // TODO: Count the used limit quota for notifying data limit reached.
+    }
+
+    private void updateForwardedStatsFromNetd() {
+        final TetherStatsParcel[] tetherStatsList;
+        try {
+            // The reported tether stats are total data usage for all currently-active upstream
+            // interfaces since tethering start.
+            tetherStatsList = mNetd.tetherOffloadGetStats();
+        } catch (RemoteException | ServiceSpecificException e) {
+            mLog.e("Problem fetching tethering stats: ", e);
+            return;
+        }
+        updateQuotaAndStatsFromSnapshot(tetherStatsList);
+    }
+
+    private void maybeSchedulePollingStats() {
+        if (!mPollingStarted) return;
+
+        if (mHandler.hasCallbacks(mScheduledPollingTask)) {
+            mHandler.removeCallbacks(mScheduledPollingTask);
+        }
+
+        mHandler.postDelayed(mScheduledPollingTask, mDeps.getPerformPollInterval());
+    }
+}
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
index 3c6e8d8..9dace70 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
@@ -19,6 +19,10 @@
 import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE;
 import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK;
 import static android.net.TetheringConstants.EXTRA_RUN_PROVISION;
+import static android.net.TetheringConstants.EXTRA_TETHER_PROVISIONING_RESPONSE;
+import static android.net.TetheringConstants.EXTRA_TETHER_SILENT_PROVISIONING_ACTION;
+import static android.net.TetheringConstants.EXTRA_TETHER_SUBID;
+import static android.net.TetheringConstants.EXTRA_TETHER_UI_PROVISIONING_APP_NAME;
 import static android.net.TetheringManager.TETHERING_BLUETOOTH;
 import static android.net.TetheringManager.TETHERING_ETHERNET;
 import static android.net.TetheringManager.TETHERING_INVALID;
@@ -69,7 +73,6 @@
     protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning";
     private static final String ACTION_PROVISIONING_ALARM =
             "com.android.networkstack.tethering.PROVISIONING_RECHECK_ALARM";
-    private static final String EXTRA_SUBID = "subId";
 
     private final ComponentName mSilentProvisioningService;
     private static final int MS_PER_HOUR = 60 * 60 * 1000;
@@ -197,9 +200,9 @@
         // till upstream change to cellular.
         if (mUsingCellularAsUpstream) {
             if (showProvisioningUi) {
-                runUiTetherProvisioning(downstreamType, config.activeDataSubId);
+                runUiTetherProvisioning(downstreamType, config);
             } else {
-                runSilentTetherProvisioning(downstreamType, config.activeDataSubId);
+                runSilentTetherProvisioning(downstreamType, config);
             }
             mNeedReRunProvisioningUi = false;
         } else {
@@ -262,9 +265,9 @@
             if (mCurrentEntitlementResults.indexOfKey(downstream) < 0) {
                 if (mNeedReRunProvisioningUi) {
                     mNeedReRunProvisioningUi = false;
-                    runUiTetherProvisioning(downstream, config.activeDataSubId);
+                    runUiTetherProvisioning(downstream, config);
                 } else {
-                    runSilentTetherProvisioning(downstream, config.activeDataSubId);
+                    runSilentTetherProvisioning(downstream, config);
                 }
             }
         }
@@ -361,7 +364,7 @@
      * @param subId default data subscription ID.
      */
     @VisibleForTesting
-    protected void runSilentTetherProvisioning(int type, int subId) {
+    protected Intent runSilentTetherProvisioning(int type, final TetheringConfiguration config) {
         if (DBG) mLog.i("runSilentTetherProvisioning: " + type);
         // For silent provisioning, settings would stop tethering when entitlement fail.
         ResultReceiver receiver = buildProxyReceiver(type, false/* notifyFail */, null);
@@ -369,17 +372,20 @@
         Intent intent = new Intent();
         intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
         intent.putExtra(EXTRA_RUN_PROVISION, true);
+        intent.putExtra(EXTRA_TETHER_SILENT_PROVISIONING_ACTION, config.provisioningAppNoUi);
+        intent.putExtra(EXTRA_TETHER_PROVISIONING_RESPONSE, config.provisioningResponse);
         intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
-        intent.putExtra(EXTRA_SUBID, subId);
+        intent.putExtra(EXTRA_TETHER_SUBID, config.activeDataSubId);
         intent.setComponent(mSilentProvisioningService);
         // Only admin user can change tethering and SilentTetherProvisioning don't need to
         // show UI, it is fine to always start setting's background service as system user.
         mContext.startService(intent);
+        return intent;
     }
 
-    private void runUiTetherProvisioning(int type, int subId) {
+    private void runUiTetherProvisioning(int type, final TetheringConfiguration config) {
         ResultReceiver receiver = buildProxyReceiver(type, true/* notifyFail */, null);
-        runUiTetherProvisioning(type, subId, receiver);
+        runUiTetherProvisioning(type, config, receiver);
     }
 
     /**
@@ -389,17 +395,20 @@
      * @param receiver to receive entitlement check result.
      */
     @VisibleForTesting
-    protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) {
+    protected Intent runUiTetherProvisioning(int type, final TetheringConfiguration config,
+            ResultReceiver receiver) {
         if (DBG) mLog.i("runUiTetherProvisioning: " + type);
 
         Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING_UI);
         intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
+        intent.putExtra(EXTRA_TETHER_UI_PROVISIONING_APP_NAME, config.provisioningApp);
         intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
-        intent.putExtra(EXTRA_SUBID, subId);
+        intent.putExtra(EXTRA_TETHER_SUBID, config.activeDataSubId);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         // Only launch entitlement UI for system user. Entitlement UI should not appear for other
         // user because only admin user is allowed to change tethering.
         mContext.startActivity(intent);
+        return intent;
     }
 
     // Not needed to check if this don't run on the handler thread because it's private.
@@ -631,7 +640,7 @@
             receiver.send(cacheValue, null);
         } else {
             ResultReceiver proxy = buildProxyReceiver(downstream, false/* notifyFail */, receiver);
-            runUiTetherProvisioning(downstream, config.activeDataSubId, proxy);
+            runUiTetherProvisioning(downstream, config, proxy);
         }
     }
 }
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 69eec8d..df67458 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -232,6 +232,7 @@
     private final TetheringThreadExecutor mExecutor;
     private final TetheringNotificationUpdater mNotificationUpdater;
     private final UserManager mUserManager;
+    private final BpfCoordinator mBpfCoordinator;
     private final PrivateAddressCoordinator mPrivateAddressCoordinator;
     private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
     // All the usage of mTetheringEventCallback should run in the same thread.
@@ -284,6 +285,8 @@
         mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
                 TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
         mForwardedDownstreams = new LinkedHashSet<>();
+        mBpfCoordinator = mDeps.getBpfCoordinator(
+                mHandler, mNetd, mLog, new BpfCoordinator.Dependencies());
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
@@ -1704,6 +1707,9 @@
                     chooseUpstreamType(true);
                     mTryCell = false;
                 }
+
+                // TODO: Check the upstream interface if it is managed by BPF offload.
+                mBpfCoordinator.startPolling();
             }
 
             @Override
@@ -1716,6 +1722,7 @@
                     mTetherUpstream = null;
                     reportUpstreamChanged(null);
                 }
+                mBpfCoordinator.stopPolling();
             }
 
             private boolean updateUpstreamWanted() {
@@ -2341,7 +2348,7 @@
 
         mLog.log("adding TetheringInterfaceStateMachine for: " + iface);
         final TetherState tetherState = new TetherState(
-                new IpServer(iface, mLooper, interfaceType, mLog, mNetd,
+                new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator,
                              makeControlCallback(), mConfig.enableLegacyDhcpServer,
                              mConfig.enableBpfOffload, mPrivateAddressCoordinator,
                              mDeps.getIpServerDependencies()));
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 48a600d..1d45f12 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -107,6 +107,7 @@
     public final String[] provisioningApp;
     public final String provisioningAppNoUi;
     public final int provisioningCheckPeriod;
+    public final String provisioningResponse;
 
     public final int activeDataSubId;
 
@@ -141,10 +142,13 @@
         enableLegacyDhcpServer = getEnableLegacyDhcpServer(res);
 
         provisioningApp = getResourceStringArray(res, R.array.config_mobile_hotspot_provision_app);
-        provisioningAppNoUi = getProvisioningAppNoUi(res);
+        provisioningAppNoUi = getResourceString(res,
+                R.string.config_mobile_hotspot_provision_app_no_ui);
         provisioningCheckPeriod = getResourceInteger(res,
                 R.integer.config_mobile_hotspot_provision_check_period,
                 0 /* No periodic re-check */);
+        provisioningResponse = getResourceString(res,
+                R.string.config_mobile_hotspot_provision_response);
 
         mOffloadPollInterval = getResourceInteger(res,
                 R.integer.config_tether_offload_poll_interval,
@@ -337,9 +341,9 @@
         return copy(LEGACY_DHCP_DEFAULT_RANGE);
     }
 
-    private static String getProvisioningAppNoUi(Resources res) {
+    private static String getResourceString(Resources res, final int resId) {
         try {
-            return res.getString(R.string.config_mobile_hotspot_provision_app_no_ui);
+            return res.getString(resId);
         } catch (Resources.NotFoundException e) {
             return "";
         }
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index ce546c7..d637c86 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -41,6 +41,17 @@
  */
 public abstract class TetheringDependencies {
     /**
+     * Get a reference to the BpfCoordinator to be used by tethering.
+     */
+    public @NonNull BpfCoordinator getBpfCoordinator(
+            @NonNull Handler handler, @NonNull INetd netd, @NonNull SharedLog log,
+            @NonNull BpfCoordinator.Dependencies deps) {
+        final NetworkStatsManager statsManager =
+                (NetworkStatsManager) getContext().getSystemService(Context.NETWORK_STATS_SERVICE);
+        return new BpfCoordinator(handler, netd, statsManager, log, deps);
+    }
+
+    /**
      * Get a reference to the offload hardware interface to be used by tethering.
      */
     public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) {
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
index d03deda..593d04a 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
@@ -24,8 +24,10 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.net.NetworkCapabilities;
@@ -253,6 +255,14 @@
     }
 
     @VisibleForTesting
+    static String getSettingsPackageName(@NonNull final PackageManager pm) {
+        final Intent settingsIntent = new Intent(Settings.ACTION_SETTINGS);
+        final ComponentName settingsComponent = settingsIntent.resolveActivity(pm);
+        return settingsComponent != null
+                ? settingsComponent.getPackageName() : "com.android.settings";
+    }
+
+    @VisibleForTesting
     void notifyTetheringDisabledByRestriction() {
         final Resources res = getResourcesForSubId(mContext, mActiveDataSubId);
         final String title = res.getString(R.string.disable_tether_notification_title);
@@ -262,8 +272,9 @@
         final PendingIntent pi = PendingIntent.getActivity(
                 mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
                 0 /* requestCode */,
-                new Intent(Settings.ACTION_TETHER_SETTINGS),
-                Intent.FLAG_ACTIVITY_NEW_TASK,
+                new Intent(Settings.ACTION_TETHER_SETTINGS)
+                        .setPackage(getSettingsPackageName(mContext.getPackageManager())),
+                Intent.FLAG_ACTIVITY_NEW_TASK | PendingIntent.FLAG_IMMUTABLE,
                 null /* options */);
 
         showNotification(R.drawable.stat_sys_tether_general, title, message,
@@ -284,7 +295,7 @@
                 mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
                 0 /* requestCode */,
                 intent,
-                0 /* flags */);
+                PendingIntent.FLAG_IMMUTABLE);
         final Action action = new Action.Builder(NO_ICON_ID, disableButton, pi).build();
 
         showNotification(R.drawable.stat_sys_tether_general, title, message,
@@ -305,8 +316,9 @@
         final PendingIntent pi = PendingIntent.getActivity(
                 mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
                 0 /* requestCode */,
-                new Intent(Settings.ACTION_TETHER_SETTINGS),
-                Intent.FLAG_ACTIVITY_NEW_TASK,
+                new Intent(Settings.ACTION_TETHER_SETTINGS)
+                        .setPackage(getSettingsPackageName(mContext.getPackageManager())),
+                Intent.FLAG_ACTIVITY_NEW_TASK | PendingIntent.FLAG_IMMUTABLE,
                 null /* options */);
 
         showNotification(R.drawable.stat_sys_tether_general, title, message,
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 0cda29a..c3bc915 100644
--- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -54,12 +54,14 @@
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.app.usage.NetworkStatsManager;
 import android.net.INetd;
 import android.net.InetAddresses;
 import android.net.InterfaceConfigurationParcel;
@@ -69,6 +71,7 @@
 import android.net.MacAddress;
 import android.net.RouteInfo;
 import android.net.TetherOffloadRuleParcel;
+import android.net.TetherStatsParcel;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.IDhcpEventCallbacks;
 import android.net.dhcp.IDhcpServer;
@@ -80,13 +83,17 @@
 import android.net.util.InterfaceSet;
 import android.net.util.PrefixUtils;
 import android.net.util.SharedLog;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.text.TextUtils;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.networkstack.tethering.BpfCoordinator;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
 import com.android.networkstack.tethering.PrivateAddressCoordinator;
 
 import org.junit.Before;
@@ -100,6 +107,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.util.Arrays;
 import java.util.List;
@@ -133,6 +141,7 @@
     @Mock private IpNeighborMonitor mIpNeighborMonitor;
     @Mock private IpServer.Dependencies mDependencies;
     @Mock private PrivateAddressCoordinator mAddressCoordinator;
+    @Mock private NetworkStatsManager mStatsManager;
 
     @Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
 
@@ -142,6 +151,7 @@
     private IpServer mIpServer;
     private InterfaceConfigurationParcel mInterfaceConfiguration;
     private NeighborEventConsumer mNeighborEventConsumer;
+    private BpfCoordinator mBpfCoordinator;
 
     private void initStateMachine(int interfaceType) throws Exception {
         initStateMachine(interfaceType, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD);
@@ -179,7 +189,7 @@
                 neighborCaptor.capture());
 
         mIpServer = new IpServer(
-                IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd,
+                IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator,
                 mCallback, usingLegacyDhcp, usingBpfOffload, mAddressCoordinator, mDependencies);
         mIpServer.start();
         mNeighborEventConsumer = neighborCaptor.getValue();
@@ -215,6 +225,10 @@
         MockitoAnnotations.initMocks(this);
         when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog);
         when(mAddressCoordinator.requestDownstreamAddress(any())).thenReturn(mTestAddress);
+
+        BpfCoordinator bc = new BpfCoordinator(new Handler(mLooper.getLooper()), mNetd,
+                mStatsManager, mSharedLog, new BpfCoordinator.Dependencies());
+        mBpfCoordinator = spy(bc);
     }
 
     @Test
@@ -222,8 +236,8 @@
         when(mDependencies.getIpNeighborMonitor(any(), any(), any()))
                 .thenReturn(mIpNeighborMonitor);
         mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
-                mNetd, mCallback, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD,
-                mAddressCoordinator, mDependencies);
+                mNetd, mBpfCoordinator, mCallback, false /* usingLegacyDhcp */,
+                DEFAULT_USING_BPF_OFFLOAD, mAddressCoordinator, mDependencies);
         mIpServer.start();
         mLooper.dispatchAll();
         verify(mCallback).updateInterfaceState(
@@ -619,6 +633,10 @@
      * (actual: "android.net.TetherOffloadRuleParcel@8c827b0" or some such), but at least it does
      * work.
      *
+     * TODO: consider making the error message more readable by adding a method that catching the
+     * AssertionFailedError and throwing a new assertion with more details. See
+     * NetworkMonitorTest#verifyNetworkTested.
+     *
      * See ConnectivityServiceTest#assertRoutesAdded for an alternative approach which solves the
      * TooManyActualInvocations problem described above by forcing the caller of the custom assert
      * method to specify all expected invocations in one call. This is useful when the stable
@@ -658,6 +676,27 @@
         return argThat(new TetherOffloadRuleParcelMatcher(upstreamIfindex, dst, dstMac));
     }
 
+    private static Ipv6ForwardingRule makeForwardingRule(
+            int upstreamIfindex, @NonNull InetAddress dst, @NonNull MacAddress dstMac) {
+        return new Ipv6ForwardingRule(upstreamIfindex, TEST_IFACE_PARAMS.index,
+                (Inet6Address) dst, TEST_IFACE_PARAMS.macAddr, dstMac);
+    }
+
+    private TetherStatsParcel buildEmptyTetherStatsParcel(int ifIndex) {
+        TetherStatsParcel parcel = new TetherStatsParcel();
+        parcel.ifIndex = ifIndex;
+        return parcel;
+    }
+
+    private void resetNetdAndBpfCoordinator() throws Exception {
+        reset(mNetd, mBpfCoordinator);
+        when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]);
+        when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX))
+                .thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX));
+        when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX2))
+                .thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX2));
+    }
+
     @Test
     public void addRemoveipv6ForwardingRules() throws Exception {
         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
@@ -675,75 +714,100 @@
         final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a");
         final MacAddress macB = MacAddress.fromString("11:22:33:00:00:0b");
 
-        reset(mNetd);
+        resetNetdAndBpfCoordinator();
+        verifyNoMoreInteractions(mBpfCoordinator, mNetd);
+
+        // TODO: Perhaps verify the interaction of tetherOffloadSetInterfaceQuota and
+        // tetherOffloadGetAndClearStats in netd while the rules are changed.
 
         // Events on other interfaces are ignored.
         recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, macA);
-        verifyNoMoreInteractions(mNetd);
+        verifyNoMoreInteractions(mBpfCoordinator, mNetd);
 
         // Events on this interface are received and sent to netd.
         recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
+        verify(mBpfCoordinator).tetherOffloadRuleAdd(
+                mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA));
         verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA));
-        reset(mNetd);
+        resetNetdAndBpfCoordinator();
 
         recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
+        verify(mBpfCoordinator).tetherOffloadRuleAdd(
+                mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB));
         verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB));
-        reset(mNetd);
+        resetNetdAndBpfCoordinator();
 
         // Link-local and multicast neighbors are ignored.
         recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, macA);
-        verifyNoMoreInteractions(mNetd);
+        verifyNoMoreInteractions(mBpfCoordinator, mNetd);
         recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, macA);
-        verifyNoMoreInteractions(mNetd);
+        verifyNoMoreInteractions(mBpfCoordinator, mNetd);
 
         // A neighbor that is no longer valid causes the rule to be removed.
         // NUD_FAILED events do not have a MAC address.
         recvNewNeigh(myIfindex, neighA, NUD_FAILED, null);
+        verify(mBpfCoordinator).tetherOffloadRuleRemove(
+                mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macNull));
         verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macNull));
-        reset(mNetd);
+        resetNetdAndBpfCoordinator();
 
         // A neighbor that is deleted causes the rule to be removed.
         recvDelNeigh(myIfindex, neighB, NUD_STALE, macB);
+        verify(mBpfCoordinator).tetherOffloadRuleRemove(
+                mIpServer,  makeForwardingRule(UPSTREAM_IFINDEX, neighB, macNull));
         verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macNull));
-        reset(mNetd);
+        resetNetdAndBpfCoordinator();
 
-        // Upstream changes result in deleting and re-adding the rules.
+        // Upstream changes result in updating the rules.
         recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
         recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
-        reset(mNetd);
+        resetNetdAndBpfCoordinator();
 
         InOrder inOrder = inOrder(mNetd);
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName(UPSTREAM_IFACE2);
         dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp, -1);
-        inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighA, macA));
+        verify(mBpfCoordinator).tetherOffloadRuleUpdate(mIpServer, UPSTREAM_IFINDEX2);
         inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA));
-        inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighB, macB));
+        inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighA, macA));
         inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB));
-        reset(mNetd);
+        inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighB, macB));
+        resetNetdAndBpfCoordinator();
 
         // When the upstream is lost, rules are removed.
         dispatchTetherConnectionChanged(null, null, 0);
+        // Clear function is called two times by:
+        // - processMessage CMD_TETHER_CONNECTION_CHANGED for the upstream is lost.
+        // - processMessage CMD_IPV6_TETHER_UPDATE for the IPv6 upstream is lost.
+        // See dispatchTetherConnectionChanged.
+        verify(mBpfCoordinator, times(2)).tetherOffloadRuleClear(mIpServer);
         verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX2, neighA, macA));
         verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX2, neighB, macB));
-        reset(mNetd);
+        resetNetdAndBpfCoordinator();
 
         // If the upstream is IPv4-only, no rules are added.
         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
-        reset(mNetd);
+        resetNetdAndBpfCoordinator();
         recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
-        verifyNoMoreInteractions(mNetd);
+        // Clear function is called by #updateIpv6ForwardingRules for the IPv6 upstream is lost.
+        verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer);
+        verifyNoMoreInteractions(mBpfCoordinator, mNetd);
 
         // Rules can be added again once upstream IPv6 connectivity is available.
         lp.setInterfaceName(UPSTREAM_IFACE);
         dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1);
         recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
+        verify(mBpfCoordinator).tetherOffloadRuleAdd(
+                mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB));
         verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB));
+        verify(mBpfCoordinator, never()).tetherOffloadRuleAdd(
+                mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA));
         verify(mNetd, never()).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA));
 
         // If upstream IPv6 connectivity is lost, rules are removed.
-        reset(mNetd);
+        resetNetdAndBpfCoordinator();
         dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0);
+        verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer);
         verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB));
 
         // When the interface goes down, rules are removed.
@@ -751,15 +815,20 @@
         dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1);
         recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
         recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
+        verify(mBpfCoordinator).tetherOffloadRuleAdd(
+                mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA));
         verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA));
+        verify(mBpfCoordinator).tetherOffloadRuleAdd(
+                mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB));
         verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB));
-        reset(mNetd);
+        resetNetdAndBpfCoordinator();
 
         mIpServer.stop();
         mLooper.dispatchAll();
+        verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer);
         verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA));
         verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB));
-        reset(mNetd);
+        resetNetdAndBpfCoordinator();
     }
 
     @Test
@@ -769,35 +838,46 @@
         final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a");
         final MacAddress macNull = MacAddress.fromString("00:00:00:00:00:00");
 
-        reset(mNetd);
-
         // Expect that rules can be only added/removed when the BPF offload config is enabled.
-        // Note that the usingBpfOffload false case is not a realistic test case. Because IP
+        // Note that the BPF offload disabled case is not a realistic test case. Because IP
         // neighbor monitor doesn't start if BPF offload is disabled, there should have no
         // neighbor event listening. This is used for testing the protection check just in case.
-        // TODO: Perhaps remove this test once we don't need this check anymore.
-        for (boolean usingBpfOffload : new boolean[]{true, false}) {
-            initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
-                    usingBpfOffload);
+        // TODO: Perhaps remove the BPF offload disabled case test once this check isn't needed
+        // anymore.
 
-            // A neighbor is added.
-            recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA);
-            if (usingBpfOffload) {
-                verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neigh, macA));
-            } else {
-                verify(mNetd, never()).tetherOffloadRuleAdd(any());
-            }
-            reset(mNetd);
+        // [1] Enable BPF offload.
+        // A neighbor that is added or deleted causes the rule to be added or removed.
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
+                true /* usingBpfOffload */);
+        resetNetdAndBpfCoordinator();
 
-            // A neighbor is deleted.
-            recvDelNeigh(myIfindex, neigh, NUD_STALE, macA);
-            if (usingBpfOffload) {
-                verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neigh, macNull));
-            } else {
-                verify(mNetd, never()).tetherOffloadRuleRemove(any());
-            }
-            reset(mNetd);
-        }
+        recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA);
+        verify(mBpfCoordinator).tetherOffloadRuleAdd(
+                mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macA));
+        verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neigh, macA));
+        resetNetdAndBpfCoordinator();
+
+        recvDelNeigh(myIfindex, neigh, NUD_STALE, macA);
+        verify(mBpfCoordinator).tetherOffloadRuleRemove(
+                mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macNull));
+        verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neigh, macNull));
+        resetNetdAndBpfCoordinator();
+
+        // [2] Disable BPF offload.
+        // A neighbor that is added or deleted doesn’t cause the rule to be added or removed.
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
+                false /* usingBpfOffload */);
+        resetNetdAndBpfCoordinator();
+
+        recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA);
+        verify(mBpfCoordinator, never()).tetherOffloadRuleAdd(any(), any());
+        verify(mNetd, never()).tetherOffloadRuleAdd(any());
+        resetNetdAndBpfCoordinator();
+
+        recvDelNeigh(myIfindex, neigh, NUD_STALE, macA);
+        verify(mBpfCoordinator, never()).tetherOffloadRuleRemove(any(), any());
+        verify(mNetd, never()).tetherOffloadRuleRemove(any());
+        resetNetdAndBpfCoordinator();
     }
 
     @Test
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
new file mode 100644
index 0000000..e2d7aab
--- /dev/null
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.networkstack.tethering;
+
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStats.UID_TETHERING;
+import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
+
+import static com.android.networkstack.tethering.BpfCoordinator
+        .DEFAULT_PERFORM_POLL_INTERVAL_MS;
+import static com.android.networkstack.tethering.BpfCoordinator.StatsType;
+import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE;
+import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID;
+
+import static junit.framework.Assert.assertNotNull;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.app.usage.NetworkStatsManager;
+import android.net.INetd;
+import android.net.NetworkStats;
+import android.net.TetherStatsParcel;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.TestableNetworkStatsProviderCbBinder;
+
+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.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BpfCoordinatorTest {
+    @Mock private NetworkStatsManager mStatsManager;
+    @Mock private INetd mNetd;
+    // Late init since methods must be called by the thread that created this object.
+    private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb;
+    private BpfCoordinator.BpfTetherStatsProvider mTetherStatsProvider;
+    private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
+            ArgumentCaptor.forClass(ArrayList.class);
+    private final TestLooper mTestLooper = new TestLooper();
+    private BpfCoordinator.Dependencies mDeps =
+            new BpfCoordinator.Dependencies() {
+            @Override
+            int getPerformPollInterval() {
+                return DEFAULT_PERFORM_POLL_INTERVAL_MS;
+            }
+    };
+
+    @Before public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    private void waitForIdle() {
+        mTestLooper.dispatchAll();
+    }
+
+    private void setupFunctioningNetdInterface() throws Exception {
+        when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]);
+    }
+
+    @NonNull
+    private BpfCoordinator makeBpfCoordinator() throws Exception {
+        BpfCoordinator coordinator = new BpfCoordinator(
+                new Handler(mTestLooper.getLooper()), mNetd, mStatsManager, new SharedLog("test"),
+                mDeps);
+        final ArgumentCaptor<BpfCoordinator.BpfTetherStatsProvider>
+                tetherStatsProviderCaptor =
+                ArgumentCaptor.forClass(BpfCoordinator.BpfTetherStatsProvider.class);
+        verify(mStatsManager).registerNetworkStatsProvider(anyString(),
+                tetherStatsProviderCaptor.capture());
+        mTetherStatsProvider = tetherStatsProviderCaptor.getValue();
+        assertNotNull(mTetherStatsProvider);
+        mTetherStatsProviderCb = new TestableNetworkStatsProviderCbBinder();
+        mTetherStatsProvider.setProviderCallbackBinder(mTetherStatsProviderCb);
+        return coordinator;
+    }
+
+    @NonNull
+    private static NetworkStats.Entry buildTestEntry(@NonNull StatsType how,
+            @NonNull String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+        return new NetworkStats.Entry(iface, how == STATS_PER_IFACE ? UID_ALL : UID_TETHERING,
+                SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes,
+                rxPackets, txBytes, txPackets, 0L);
+    }
+
+    @NonNull
+    private static TetherStatsParcel buildTestTetherStatsParcel(@NonNull Integer ifIndex,
+            long rxBytes, long rxPackets, long txBytes, long txPackets) {
+        final TetherStatsParcel parcel = new TetherStatsParcel();
+        parcel.ifIndex = ifIndex;
+        parcel.rxBytes = rxBytes;
+        parcel.rxPackets = rxPackets;
+        parcel.txBytes = txBytes;
+        parcel.txPackets = txPackets;
+        return parcel;
+    }
+
+    private void setTetherOffloadStatsList(TetherStatsParcel[] tetherStatsList) throws Exception {
+        when(mNetd.tetherOffloadGetStats()).thenReturn(tetherStatsList);
+        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+        waitForIdle();
+    }
+
+    @Test
+    public void testGetForwardedStats() throws Exception {
+        setupFunctioningNetdInterface();
+
+        final BpfCoordinator coordinator = makeBpfCoordinator();
+        coordinator.startPolling();
+
+        final String wlanIface = "wlan0";
+        final Integer wlanIfIndex = 100;
+        final String mobileIface = "rmnet_data0";
+        final Integer mobileIfIndex = 101;
+
+        // Add interface name to lookup table. In realistic case, the upstream interface name will
+        // be added by IpServer when IpServer has received with a new IPv6 upstream update event.
+        coordinator.addUpstreamNameToLookupTable(wlanIfIndex, wlanIface);
+        coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
+
+        // [1] Both interface stats are changed.
+        // Setup the tether stats of wlan and mobile interface. Note that move forward the time of
+        // the looper to make sure the new tether stats has been updated by polling update thread.
+        setTetherOffloadStatsList(new TetherStatsParcel[] {
+                buildTestTetherStatsParcel(wlanIfIndex, 1000, 100, 2000, 200),
+                buildTestTetherStatsParcel(mobileIfIndex, 3000, 300, 4000, 400)});
+
+        final NetworkStats expectedIfaceStats = new NetworkStats(0L, 2)
+                .addEntry(buildTestEntry(STATS_PER_IFACE, wlanIface, 1000, 100, 2000, 200))
+                .addEntry(buildTestEntry(STATS_PER_IFACE, mobileIface, 3000, 300, 4000, 400));
+
+        final NetworkStats expectedUidStats = new NetworkStats(0L, 2)
+                .addEntry(buildTestEntry(STATS_PER_UID, wlanIface, 1000, 100, 2000, 200))
+                .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 3000, 300, 4000, 400));
+
+        // Force pushing stats update to verify the stats reported.
+        // TODO: Perhaps make #expectNotifyStatsUpdated to use test TetherStatsParcel object for
+        // verifying the notification.
+        mTetherStatsProvider.pushTetherStats();
+        mTetherStatsProviderCb.expectNotifyStatsUpdated(expectedIfaceStats, expectedUidStats);
+
+        // [2] Only one interface stats is changed.
+        // The tether stats of mobile interface is accumulated and The tether stats of wlan
+        // interface is the same.
+        setTetherOffloadStatsList(new TetherStatsParcel[] {
+                buildTestTetherStatsParcel(wlanIfIndex, 1000, 100, 2000, 200),
+                buildTestTetherStatsParcel(mobileIfIndex, 3010, 320, 4030, 440)});
+
+        final NetworkStats expectedIfaceStatsDiff = new NetworkStats(0L, 2)
+                .addEntry(buildTestEntry(STATS_PER_IFACE, wlanIface, 0, 0, 0, 0))
+                .addEntry(buildTestEntry(STATS_PER_IFACE, mobileIface, 10, 20, 30, 40));
+
+        final NetworkStats expectedUidStatsDiff = new NetworkStats(0L, 2)
+                .addEntry(buildTestEntry(STATS_PER_UID, wlanIface, 0, 0, 0, 0))
+                .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 10, 20, 30, 40));
+
+        // Force pushing stats update to verify that only diff of stats is reported.
+        mTetherStatsProvider.pushTetherStats();
+        mTetherStatsProviderCb.expectNotifyStatsUpdated(expectedIfaceStatsDiff,
+                expectedUidStatsDiff);
+
+        // [3] Stop coordinator.
+        // Shutdown the coordinator and clear the invocation history, especially the
+        // tetherOffloadGetStats() calls.
+        coordinator.stopPolling();
+        clearInvocations(mNetd);
+
+        // Verify the polling update thread stopped.
+        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+        waitForIdle();
+        verify(mNetd, never()).tetherOffloadGetStats();
+    }
+
+    @Test
+    public void testOnSetAlert() throws Exception {
+        setupFunctioningNetdInterface();
+
+        final BpfCoordinator coordinator = makeBpfCoordinator();
+        coordinator.startPolling();
+
+        final String mobileIface = "rmnet_data0";
+        final Integer mobileIfIndex = 100;
+        coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
+
+        // Verify that set quota to 0 will immediately triggers a callback.
+        mTetherStatsProvider.onSetAlert(0);
+        waitForIdle();
+        mTetherStatsProviderCb.expectNotifyAlertReached();
+
+        // Verify that notifyAlertReached never fired if quota is not yet reached.
+        when(mNetd.tetherOffloadGetStats()).thenReturn(
+                new TetherStatsParcel[] {buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0)});
+        mTetherStatsProvider.onSetAlert(100);
+        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+        waitForIdle();
+        mTetherStatsProviderCb.assertNoCallback();
+
+        // Verify that notifyAlertReached fired when quota is reached.
+        when(mNetd.tetherOffloadGetStats()).thenReturn(
+                new TetherStatsParcel[] {buildTestTetherStatsParcel(mobileIfIndex, 50, 0, 50, 0)});
+        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+        waitForIdle();
+        mTetherStatsProviderCb.expectNotifyAlertReached();
+
+        // Verify that set quota with UNLIMITED won't trigger any callback.
+        mTetherStatsProvider.onSetAlert(QUOTA_UNLIMITED);
+        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+        waitForIdle();
+        mTetherStatsProviderCb.assertNoCallback();
+    }
+}
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
index 72fa916..354e753 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
@@ -16,8 +16,16 @@
 
 package com.android.networkstack.tethering;
 
+import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE;
+import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK;
+import static android.net.TetheringConstants.EXTRA_RUN_PROVISION;
+import static android.net.TetheringConstants.EXTRA_TETHER_PROVISIONING_RESPONSE;
+import static android.net.TetheringConstants.EXTRA_TETHER_SILENT_PROVISIONING_ACTION;
+import static android.net.TetheringConstants.EXTRA_TETHER_SUBID;
+import static android.net.TetheringConstants.EXTRA_TETHER_UI_PROVISIONING_APP_NAME;
 import static android.net.TetheringManager.TETHERING_BLUETOOTH;
 import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringManager.TETHERING_INVALID;
 import static android.net.TetheringManager.TETHERING_USB;
 import static android.net.TetheringManager.TETHERING_WIFI;
 import static android.net.TetheringManager.TETHERING_WIFI_P2P;
@@ -44,6 +52,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
 import android.net.util.SharedLog;
 import android.os.Bundle;
@@ -53,6 +62,7 @@
 import android.os.SystemProperties;
 import android.os.test.TestLooper;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
 
 import androidx.test.filters.SmallTest;
@@ -76,6 +86,7 @@
 
     private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
     private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
+    private static final String PROVISIONING_APP_RESPONSE = "app_response";
 
     @Mock private CarrierConfigManager mCarrierConfigManager;
     @Mock private Context mContext;
@@ -122,15 +133,51 @@
         }
 
         @Override
-        protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) {
+        protected Intent runUiTetherProvisioning(int type,
+                final TetheringConfiguration config, final ResultReceiver receiver) {
+            Intent intent = super.runUiTetherProvisioning(type, config, receiver);
+            assertUiTetherProvisioningIntent(type, config, receiver, intent);
             uiProvisionCount++;
             receiver.send(fakeEntitlementResult, null);
+            return intent;
+        }
+
+        private void assertUiTetherProvisioningIntent(int type, final TetheringConfiguration config,
+                final ResultReceiver receiver, final Intent intent) {
+            assertEquals(Settings.ACTION_TETHER_PROVISIONING_UI, intent.getAction());
+            assertEquals(type, intent.getIntExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_INVALID));
+            final String[] appName = intent.getStringArrayExtra(
+                    EXTRA_TETHER_UI_PROVISIONING_APP_NAME);
+            assertEquals(PROVISIONING_APP_NAME.length, appName.length);
+            for (int i = 0; i < PROVISIONING_APP_NAME.length; i++) {
+                assertEquals(PROVISIONING_APP_NAME[i], appName[i]);
+            }
+            assertEquals(receiver, intent.getParcelableExtra(EXTRA_PROVISION_CALLBACK));
+            assertEquals(config.activeDataSubId,
+                    intent.getIntExtra(EXTRA_TETHER_SUBID, INVALID_SUBSCRIPTION_ID));
         }
 
         @Override
-        protected void runSilentTetherProvisioning(int type, int subId) {
+        protected Intent runSilentTetherProvisioning(int type,
+                final TetheringConfiguration config) {
+            Intent intent = super.runSilentTetherProvisioning(type, config);
+            assertSilentTetherProvisioning(type, config, intent);
             silentProvisionCount++;
             addDownstreamMapping(type, fakeEntitlementResult);
+            return intent;
+        }
+
+        private void assertSilentTetherProvisioning(int type, final TetheringConfiguration config,
+                final Intent intent) {
+            assertEquals(type, intent.getIntExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_INVALID));
+            assertEquals(true, intent.getBooleanExtra(EXTRA_RUN_PROVISION, false));
+            assertEquals(PROVISIONING_NO_UI_APP_NAME,
+                    intent.getStringExtra(EXTRA_TETHER_SILENT_PROVISIONING_ACTION));
+            assertEquals(PROVISIONING_APP_RESPONSE,
+                    intent.getStringExtra(EXTRA_TETHER_PROVISIONING_RESPONSE));
+            assertTrue(intent.hasExtra(EXTRA_PROVISION_CALLBACK));
+            assertEquals(config.activeDataSubId,
+                    intent.getIntExtra(EXTRA_TETHER_SUBID, INVALID_SUBSCRIPTION_ID));
         }
     }
 
@@ -187,6 +234,8 @@
                 .thenReturn(PROVISIONING_APP_NAME);
         when(mResources.getString(R.string.config_mobile_hotspot_provision_app_no_ui))
                 .thenReturn(PROVISIONING_NO_UI_APP_NAME);
+        when(mResources.getString(R.string.config_mobile_hotspot_provision_response)).thenReturn(
+                PROVISIONING_APP_RESPONSE);
         // Act like the CarrierConfigManager is present and ready unless told otherwise.
         when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
                 .thenReturn(mCarrierConfigManager);
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index 1999ad7..3121863 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -61,6 +61,8 @@
     private final SharedLog mLog = new SharedLog("TetheringConfigurationTest");
 
     private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
+    private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
+    private static final String PROVISIONING_APP_RESPONSE = "app_response";
     @Mock private Context mContext;
     @Mock private TelephonyManager mTelephonyManager;
     @Mock private Resources mResources;
@@ -388,6 +390,8 @@
                 new MockTetheringConfiguration(mMockContext, mLog, anyValidSubId);
         assertEquals(mockCfg.provisioningApp[0], PROVISIONING_APP_NAME[0]);
         assertEquals(mockCfg.provisioningApp[1], PROVISIONING_APP_NAME[1]);
+        assertEquals(mockCfg.provisioningAppNoUi, PROVISIONING_NO_UI_APP_NAME);
+        assertEquals(mockCfg.provisioningResponse, PROVISIONING_APP_RESPONSE);
     }
 
     private void setUpResourceForSubId() {
@@ -403,6 +407,10 @@
                 new int[0]);
         when(mResourcesForSubId.getStringArray(
                 R.array.config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME);
+        when(mResourcesForSubId.getString(R.string.config_mobile_hotspot_provision_app_no_ui))
+                .thenReturn(PROVISIONING_NO_UI_APP_NAME);
+        when(mResourcesForSubId.getString(
+                R.string.config_mobile_hotspot_provision_response)).thenReturn(
+                PROVISIONING_APP_RESPONSE);
     }
-
 }
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
index 7d5471f..4b6bbac 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
@@ -19,6 +19,10 @@
 import android.app.Notification
 import android.app.NotificationManager
 import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
 import android.content.res.Resources
 import android.net.ConnectivityManager.TETHERING_WIFI
 import android.os.Handler
@@ -51,6 +55,7 @@
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.times
@@ -351,4 +356,26 @@
         notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES)
         verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
     }
+
+    @Test
+    fun testGetSettingsPackageName() {
+        val defaultSettingsPackageName = "com.android.settings"
+        val testSettingsPackageName = "com.android.test.settings"
+        val pm = mock(PackageManager::class.java)
+        doReturn(null).`when`(pm).resolveActivity(any(), anyInt())
+        assertEquals(defaultSettingsPackageName,
+                TetheringNotificationUpdater.getSettingsPackageName(pm))
+
+        val resolveInfo = ResolveInfo().apply {
+            activityInfo = ActivityInfo().apply {
+                name = "test"
+                applicationInfo = ApplicationInfo().apply {
+                    packageName = testSettingsPackageName
+                }
+            }
+        }
+        doReturn(resolveInfo).`when`(pm).resolveActivity(any(), anyInt())
+        assertEquals(testSettingsPackageName,
+                TetheringNotificationUpdater.getSettingsPackageName(pm))
+    }
 }
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 bb65b18..8146a58d 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
@@ -203,6 +203,7 @@
     @Mock private ConnectivityManager mCm;
     @Mock private EthernetManager mEm;
     @Mock private TetheringNotificationUpdater mNotificationUpdater;
+    @Mock private BpfCoordinator mBpfCoordinator;
 
     private final MockIpServerDependencies mIpServerDependencies =
             spy(new MockIpServerDependencies());
@@ -337,6 +338,12 @@
         }
 
         @Override
+        public BpfCoordinator getBpfCoordinator(Handler handler, INetd netd,
+                SharedLog log, BpfCoordinator.Dependencies deps) {
+            return mBpfCoordinator;
+        }
+
+        @Override
         public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) {
             return mOffloadHardwareInterface;
         }
diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
index e7a43b7..48895ad 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
@@ -93,7 +93,7 @@
     @Nullable
     private InlineFillUi mInlineFillUi;
     @GuardedBy("mLock")
-    private boolean mPreviousResponseIsNotEmpty;
+    private Boolean mPreviousResponseIsNotEmpty = null;
 
     @GuardedBy("mLock")
     private boolean mDestroyed = false;
@@ -213,7 +213,7 @@
             // if IME is visible, and response is not null, send the response
             InlineSuggestionsResponse response = mInlineFillUi.getInlineSuggestionsResponse();
             boolean isEmptyResponse = response.getInlineSuggestions().isEmpty();
-            if (isEmptyResponse && !mPreviousResponseIsNotEmpty) {
+            if (isEmptyResponse && Boolean.FALSE.equals(mPreviousResponseIsNotEmpty)) {
                 // No-op if both the previous response and current response are empty.
                 return;
             }
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 851e4cc..a7d0061 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -245,6 +245,11 @@
         if (inlineSuggestionsData == null || inlineSuggestionsData.isEmpty()
                 || inlineSuggestionsCallback == null || request == null
                 || remoteRenderService == null) {
+            // If it was an inline request and the response doesn't have any inline suggestions,
+            // we will send an empty response to IME.
+            if (inlineSuggestionsCallback != null && request != null) {
+                inlineSuggestionsCallback.apply(InlineFillUi.emptyUi(focusedId));
+            }
             return;
         }
         mCallbacks.setLastResponse(sessionId);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9e8a872..65e98ac 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -100,7 +100,7 @@
         "android.net.ipsec.ike.stubs.module_lib",
         "app-compat-annotations",
         "framework-tethering.stubs.module_lib",
-        "service-permission-stubs",
+        "service-permission.stubs.system_server",
     ],
 
     required: [
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 81de29c..b241bd1 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -988,4 +988,14 @@
      * Unblocks uninstall for all packages for the user.
      */
     public abstract void clearBlockUninstallForUser(@UserIdInt int userId);
+
+    /**
+     * Unsuspends all packages suspended by the given package for the user.
+     */
+    public abstract void unsuspendForSuspendingPackage(String suspendingPackage, int userId);
+
+    /**
+     * Returns {@code true} if the package is suspending any packages for the user.
+     */
+    public abstract boolean isSuspendingAnyPackages(String suspendingPackage, int userId);
 }
diff --git a/services/core/java/com/android/server/AppStateTracker.java b/services/core/java/com/android/server/AppStateTracker.java
index b765d81..74f79e0 100644
--- a/services/core/java/com/android/server/AppStateTracker.java
+++ b/services/core/java/com/android/server/AppStateTracker.java
@@ -1120,7 +1120,8 @@
                 return false;
             }
             final int userId = UserHandle.getUserId(uid);
-            if (mExemptedPackages.contains(userId, packageName)) {
+            if (mAppStandbyInternal.isAppIdleEnabled() && !mAppStandbyInternal.isInParole()
+                    && mExemptedPackages.contains(userId, packageName)) {
                 return false;
             }
             return mForceAllAppsStandby;
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 9080bdb..a3c164d 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1606,7 +1606,7 @@
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
                             PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)
-                            && idMatch(r.subId, subId, phoneId)) {
+                            && idMatchWithoutDefaultPhoneCheck(r.subId, subId)) {
                         try {
                             r.callback.onDisplayInfoChanged(telephonyDisplayInfo);
                         } catch (RemoteException ex) {
@@ -2726,6 +2726,24 @@
         Rlog.e(TAG, s);
     }
 
+    /**
+     * If the registrant specified a subId, then we should only notify it if subIds match.
+     * If the registrant registered with DEFAULT subId, we should notify only when the related subId
+     * is default subId (which could be INVALID if there's no default subId).
+     *
+     * This should be the correct way to check record ID match. in idMatch the record's phoneId is
+     * speculated based on subId passed by the registrant so it's not a good reference.
+     * But to avoid triggering potential regression only replace idMatch with it when an issue with
+     * idMatch is reported. Eventually this should replace all instances of idMatch.
+     */
+    private boolean idMatchWithoutDefaultPhoneCheck(int subIdInRecord, int subIdToNotify) {
+        if (subIdInRecord == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+            return (subIdToNotify == mDefaultSubId);
+        } else {
+            return (subIdInRecord == subIdToNotify);
+        }
+    }
+
     boolean idMatch(int rSubId, int subId, int phoneId) {
 
         if(subId < 0) {
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index 27ea471..df3b688 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -337,6 +337,7 @@
 
     class PortListenerImpl implements AdbConnectionPortListener {
         public void onPortReceived(int port) {
+            if (DEBUG) Slog.d(TAG, "Received tls port=" + port);
             Message msg = mHandler.obtainMessage(port > 0
                      ? AdbDebuggingHandler.MSG_SERVER_CONNECTED
                      : AdbDebuggingHandler.MSG_SERVER_DISCONNECTED);
@@ -392,6 +393,7 @@
 
                 mOutputStream = mSocket.getOutputStream();
                 mInputStream = mSocket.getInputStream();
+                mHandler.sendEmptyMessage(AdbDebuggingHandler.MSG_ADBD_SOCKET_CONNECTED);
             } catch (IOException ioe) {
                 Slog.e(TAG, "Caught an exception opening the socket: " + ioe);
                 closeSocketLocked();
@@ -504,6 +506,7 @@
             } catch (IOException ex) {
                 Slog.e(TAG, "Failed closing socket: " + ex);
             }
+            mHandler.sendEmptyMessage(AdbDebuggingHandler.MSG_ADBD_SOCKET_DISCONNECTED);
         }
 
         /** Call to stop listening on the socket and exit the thread. */
@@ -729,6 +732,10 @@
         static final int MSG_SERVER_CONNECTED = 24;
         // Notifies us the TLS server is disconnected
         static final int MSG_SERVER_DISCONNECTED = 25;
+        // Notification when adbd socket successfully connects.
+        static final int MSG_ADBD_SOCKET_CONNECTED = 26;
+        // Notification when adbd socket is disconnected.
+        static final int MSG_ADBD_SOCKET_DISCONNECTED = 27;
 
         // === Messages we can send to adbd ===========
         static final String MSG_DISCONNECT_DEVICE = "DD";
@@ -1170,6 +1177,28 @@
                     }
                     break;
                 }
+                case MSG_ADBD_SOCKET_CONNECTED: {
+                    if (DEBUG) Slog.d(TAG, "adbd socket connected");
+                    if (mAdbWifiEnabled) {
+                        // In scenarios where adbd is restarted, the tls port may change.
+                        mConnectionPortPoller =
+                                new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
+                        mConnectionPortPoller.start();
+                    }
+                    break;
+                }
+                case MSG_ADBD_SOCKET_DISCONNECTED: {
+                    if (DEBUG) Slog.d(TAG, "adbd socket disconnected");
+                    if (mConnectionPortPoller != null) {
+                        mConnectionPortPoller.cancelAndWait();
+                        mConnectionPortPoller = null;
+                    }
+                    if (mAdbWifiEnabled) {
+                        // In scenarios where adbd is restarted, the tls port may change.
+                        onAdbdWifiServerDisconnected(-1);
+                    }
+                    break;
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index e1f9a7a..ef81d71 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -241,12 +241,7 @@
     private AdbService(Context context) {
         mContext = context;
         mContentResolver = context.getContentResolver();
-
-        boolean secureAdbEnabled = AdbProperties.secure().orElse(false);
-        boolean dataEncrypted = "1".equals(SystemProperties.get("vold.decrypt"));
-        if (secureAdbEnabled && !dataEncrypted) {
-            mDebuggingManager = new AdbDebuggingManager(context);
-        }
+        mDebuggingManager = new AdbDebuggingManager(context);
 
         initAdbState();
         LocalServices.addService(AdbManagerInternal.class, new AdbManagerInternalImpl());
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c5c3caf..a7bf982 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -4918,52 +4918,6 @@
         if (isDeviceOwner) {
             return true;
         }
-
-        r.mInfoDenyWhileInUsePermissionInFgs =
-                "Background FGS start while-in-use permission restriction [callingPackage: "
-                + callingPackage
-                + "; callingUid: " + callingUid
-                + "; intent: " + intent
-                + "]";
         return false;
     }
-
-    // TODO: remove this toast after feature development is done
-    void showWhileInUseDebugToastLocked(int uid, int op, int mode) {
-        final UidRecord uidRec = mAm.mProcessList.getUidRecordLocked(uid);
-        if (uidRec == null) {
-            return;
-        }
-
-        for (int i = uidRec.procRecords.size() - 1; i >= 0; i--) {
-            ProcessRecord pr = uidRec.procRecords.valueAt(i);
-            if (pr.uid != uid) {
-                continue;
-            }
-            for (int j = pr.numberOfRunningServices() - 1; j >= 0; j--) {
-                ServiceRecord r = pr.getRunningServiceAt(j);
-                if (!r.isForeground) {
-                    continue;
-                }
-                if (mode == DEBUG_FGS_ALLOW_WHILE_IN_USE) {
-                    if (!r.mAllowWhileInUsePermissionInFgs
-                            && r.mInfoDenyWhileInUsePermissionInFgs != null) {
-                        final String msg = r.mInfoDenyWhileInUsePermissionInFgs
-                                + " affected while-in-use permission:"
-                                + AppOpsManager.opToPublicName(op);
-                        Slog.wtf(TAG, msg);
-                    }
-                } else if (mode == DEBUG_FGS_ENFORCE_TYPE) {
-                    final String msg =
-                            "FGS Missing foregroundServiceType in manifest file [callingPackage: "
-                            + r.mRecentCallingPackage
-                            + "; intent:" + r.intent.getIntent()
-                            + "] affected while-in-use permission:"
-                            + AppOpsManager.opToPublicName(op)
-                            + "; targetSdkVersion:" + r.appInfo.targetSdkVersion;
-                    Slog.wtf(TAG, msg);
-                }
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 933dc99..0795122 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1671,6 +1671,12 @@
      */
     @Nullable ContentCaptureManagerInternal mContentCaptureService;
 
+    /**
+     * Set of {@link ProcessRecord} that have either {@link ProcessRecord#hasTopUi()} or
+     * {@link ProcessRecord#runningRemoteAnimation} set to {@code true}.
+     */
+    final ArraySet<ProcessRecord> mTopUiOrRunningRemoteAnimApps = new ArraySet<>();
+
     final class UiHandler extends Handler {
         public UiHandler() {
             super(com.android.server.UiThread.get().getLooper(), null, true);
@@ -14702,6 +14708,7 @@
 
         mProcessesToGc.remove(app);
         mPendingPssProcesses.remove(app);
+        mTopUiOrRunningRemoteAnimApps.remove(app);
         ProcessList.abortNextPssTime(app.procStateMemTracker);
 
         // Dismiss any open dialogs.
@@ -18490,6 +18497,22 @@
         return proc;
     }
 
+    /**
+     * @return {@code true} if {@link #mTopUiOrRunningRemoteAnimApps} set contains {@code app} or when there are no apps
+     *         in this list, an false otherwise.
+     */
+    boolean containsTopUiOrRunningRemoteAnimOrEmptyLocked(ProcessRecord app) {
+        return mTopUiOrRunningRemoteAnimApps.isEmpty() || mTopUiOrRunningRemoteAnimApps.contains(app);
+    }
+
+    void addTopUiOrRunningRemoteAnim(ProcessRecord app) {
+        mTopUiOrRunningRemoteAnimApps.add(app);
+    }
+
+    void removeTopUiOrRunningRemoteAnim(ProcessRecord app) {
+        mTopUiOrRunningRemoteAnimApps.remove(app);
+    }
+
     @Override
     public boolean dumpHeap(String process, int userId, boolean managed, boolean mallocInfo,
             boolean runGc, String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
@@ -19671,15 +19694,6 @@
             return false;
         }
 
-        // TODO: remove this toast after feature development is done
-        @Override
-        public void showWhileInUseDebugToast(int uid, int op, int mode) {
-            synchronized (ActivityManagerService.this) {
-                ActivityManagerService.this.mServices.showWhileInUseDebugToastLocked(
-                        uid, op, mode);
-            }
-        }
-
         @Override
         public void setDeviceOwnerUid(int uid) {
             synchronized (ActivityManagerService.this) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 03ff3d0..58b0a15 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1151,8 +1151,17 @@
             // is currently showing UI.
             app.systemNoUi = true;
             if (app == topApp) {
+                // If specific system app has set ProcessRecord.mHasTopUi or is running a remote
+                // animation (ProcessRecord.runningRemoteAnimation), this will prevent topApp
+                // to use SCHED_GROUP_TOP_APP to ensure process with mHasTopUi will have exclusive
+                // access to configured cores.
+                if (mService.containsTopUiOrRunningRemoteAnimOrEmptyLocked(app)) {
+                    app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
+                } else {
+                    app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
+                }
                 app.systemNoUi = false;
-                app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
+
                 app.adjType = "pers-top-activity";
             } else if (app.hasTopUi()) {
                 // sched group/proc state adjustment is below
@@ -1193,10 +1202,20 @@
 
         boolean foregroundActivities = false;
         if (PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP && app == topApp) {
-            // The last app on the list is the foreground app.
-            adj = ProcessList.FOREGROUND_APP_ADJ;
-            schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
-            app.adjType = "top-activity";
+
+            // If specific system app has set ProcessRecord.mHasTopUi or is running a remote
+            // animation (ProcessRecord.runningRemoteAnimation), this will prevent topApp
+            // to use SCHED_GROUP_TOP_APP to ensure process with mHasTopUi will have exclusive
+            // access to configured cores.
+            if (mService.containsTopUiOrRunningRemoteAnimOrEmptyLocked(app)) {
+                adj = ProcessList.FOREGROUND_APP_ADJ;
+                schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
+                app.adjType = "top-activity";
+            } else {
+                adj = ProcessList.FOREGROUND_APP_ADJ;
+                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                app.adjType = "top-activity-behind-topui";
+            }
             foregroundActivities = true;
             procState = PROCESS_STATE_CUR_TOP;
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1508,16 +1527,7 @@
                     capabilityFromFGS |=
                             (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
                                     != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
-                } else {
-                    //The FGS has the location capability, but due to FGS BG start restriction it
-                    //lost the capability, use temp location capability to mark this case.
-                    //TODO: remove this block when development is done.
-                    capabilityFromFGS |=
-                            (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION) != 0
-                                    ? ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION
-                                    : 0;
-                }
-                if (s.mAllowWhileInUsePermissionInFgs) {
+
                     boolean enabled = false;
                     try {
                         enabled = mPlatformCompat.isChangeEnabled(
@@ -1527,23 +1537,13 @@
                     if (enabled) {
                         capabilityFromFGS |=
                                 (fgsType & FOREGROUND_SERVICE_TYPE_CAMERA)
-                                        != 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA
-                                        : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA;
+                                        != 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA : 0;
                         capabilityFromFGS |=
                                 (fgsType & FOREGROUND_SERVICE_TYPE_MICROPHONE)
-                                        != 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
-                                        : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
+                                        != 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE : 0;
                     } else {
-                        // Remove fgsType check and assign PROCESS_CAPABILITY_FOREGROUND_CAMERA
-                        // and MICROPHONE when finish debugging.
-                        capabilityFromFGS |=
-                                (fgsType & FOREGROUND_SERVICE_TYPE_CAMERA)
-                                        != 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA
-                                        : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q;
-                        capabilityFromFGS |=
-                                (fgsType & FOREGROUND_SERVICE_TYPE_MICROPHONE)
-                                        != 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
-                                        : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q;
+                        capabilityFromFGS |= PROCESS_CAPABILITY_FOREGROUND_CAMERA
+                                | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/am/PendingStartActivityUids.java b/services/core/java/com/android/server/am/PendingStartActivityUids.java
index 0ed99fe..6bf9d4e 100644
--- a/services/core/java/com/android/server/am/PendingStartActivityUids.java
+++ b/services/core/java/com/android/server/am/PendingStartActivityUids.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.os.SystemClock;
-import android.provider.Settings;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -58,8 +57,7 @@
             if (delay >= 1000 /*ms*/) {
                 Slog.i(TAG,
                         "PendingStartActivityUids startActivity to updateOomAdj delay:"
-                                + delay + "ms," + " uid:" + uid + " packageName:"
-                                + Settings.getPackageNameForUid(mContext, uid));
+                                + delay + "ms," + " uid:" + uid);
             }
             mPendingUids.delete(uid);
         }
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index c5152c0..4c75ab2 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1268,6 +1268,7 @@
     void setHasTopUi(boolean hasTopUi) {
         mHasTopUi = hasTopUi;
         mWindowProcessController.setHasTopUi(hasTopUi);
+        updateTopUiOrRunningRemoteAnim();
     }
 
     boolean hasTopUi() {
@@ -1518,10 +1519,19 @@
                 Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
                         + " for pid=" + pid);
             }
+            updateTopUiOrRunningRemoteAnim();
             mService.updateOomAdjLocked(this, true, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
         }
     }
 
+    void updateTopUiOrRunningRemoteAnim() {
+        if (runningRemoteAnimation || hasTopUi()) {
+            mService.addTopUiOrRunningRemoteAnim(this);
+        } else {
+            mService.removeTopUiOrRunningRemoteAnim(this);
+        }
+    }
+
     public long getInputDispatchingTimeout() {
         return mWindowProcessController.getInputDispatchingTimeout();
     }
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 4670d58..9c96e6e 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -138,10 +138,7 @@
     // allow while-in-use permissions in foreground service or not.
     // while-in-use permissions in FGS started from background might be restricted.
     boolean mAllowWhileInUsePermissionInFgs;
-    // information string what/why service is denied while-in-use permissions when
-    // foreground service is started from background.
-    // TODO: remove this field after feature development is done
-    String mInfoDenyWhileInUsePermissionInFgs;
+
     // the most recent package that start/bind this service.
     String mRecentCallingPackage;
 
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index ea70598..f2c4e44 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2515,7 +2515,7 @@
                 showUserSwitchDialog(fromToUserPair);
                 break;
             case CLEAR_USER_JOURNEY_SESSION_MSG:
-                clearSessionId(msg.arg1);
+                logAndClearSessionId(msg.arg1);
                 break;
         }
         return false;
@@ -2630,6 +2630,21 @@
     }
 
     /**
+     * Log a final event of the {@link UserJourneySession} and clear it.
+     */
+    private void logAndClearSessionId(@UserIdInt int userId) {
+        synchronized (mUserIdToUserJourneyMap) {
+            final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId);
+            if (userJourneySession != null) {
+                FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED,
+                        userJourneySession.mSessionId, userId, USER_LIFECYCLE_EVENT_UNKNOWN,
+                        USER_LIFECYCLE_EVENT_STATE_NONE);
+            }
+            clearSessionId(userId);
+        }
+    }
+
+    /**
      * Helper class to store user journey and session id.
      *
      * <p> User journey tracks a chain of user lifecycle events occurring during different user
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 435e825..73c98da 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -16,11 +16,6 @@
 
 package com.android.server.appop;
 
-import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA;
-import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q;
-import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION;
-import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
-import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
@@ -520,8 +515,6 @@
         public SparseBooleanArray foregroundOps;
         public boolean hasForegroundWatchers;
 
-        public long lastTimeShowDebugToast;
-
         public UidState(int uid) {
             this.uid = uid;
         }
@@ -557,44 +550,19 @@
                         case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION:
                             if ((capability & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) {
                                 return MODE_ALLOWED;
-                            } else if ((capability
-                                    & DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) {
-                                // The FGS has the location capability, but due to FGS BG start
-                                // restriction it lost the capability, use temp location capability
-                                // to mark this case.
-                                maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE);
-                                return MODE_IGNORED;
                             } else {
                                 return MODE_IGNORED;
                             }
                         case OP_CAMERA:
                             if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
                                 return MODE_ALLOWED;
-                            } else if ((capability & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q)
-                                    != 0) {
-                                maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
-                                return MODE_ALLOWED;
-                            } else if ((capability & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA)
-                                    != 0) {
-                                maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
-                                return MODE_IGNORED;
                             } else {
-                                maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE);
                                 return MODE_IGNORED;
                             }
                         case OP_RECORD_AUDIO:
                             if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) {
                                 return MODE_ALLOWED;
-                            } else if ((capability
-                                    & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q) != 0) {
-                                maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
-                                return MODE_ALLOWED;
-                            } else if  ((capability
-                                    & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) {
-                                maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
-                                return MODE_IGNORED;
                             } else {
-                                maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE);
                                 return MODE_IGNORED;
                             }
                         default:
@@ -612,15 +580,7 @@
                             return MODE_ALLOWED;
                         } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
                             return MODE_ALLOWED;
-                        } else if ((capability
-                                & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q) != 0) {
-                            maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
-                            return MODE_ALLOWED;
-                        } else if ((capability & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
-                            maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
-                            return MODE_IGNORED;
                         } else {
-                            maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE);
                             return MODE_IGNORED;
                         }
                     case OP_RECORD_AUDIO:
@@ -629,16 +589,7 @@
                             return MODE_ALLOWED;
                         } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) {
                             return MODE_ALLOWED;
-                        } else if ((capability & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q)
-                                != 0) {
-                            maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
-                            return MODE_ALLOWED;
-                        } else if ((capability & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE)
-                                != 0) {
-                            maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
-                            return MODE_IGNORED;
                         } else {
-                            maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE);
                             return MODE_IGNORED;
                         }
                     default:
@@ -692,27 +643,6 @@
             }
             foregroundOps = which;
         }
-
-        // TODO: remove this toast after feature development is done
-        // For DEBUG_FGS_ALLOW_WHILE_IN_USE, if the procstate is foreground service and while-in-use
-        // permission is denied, show a toast message and generate a WTF log so we know
-        // how many apps are impacted by the new background started foreground service while-in-use
-        // permission restriction.
-        // For DEBUG_FGS_ENFORCE_TYPE, The process has a foreground service that does not have
-        // camera/microphone foregroundServiceType in manifest file, and the process is asking
-        // AppOps for camera/microphone ops, show a toast message and generate a WTF log.
-        void maybeShowWhileInUseDebugToast(int op, int mode) {
-            if (mode == DEBUG_FGS_ALLOW_WHILE_IN_USE && state != UID_STATE_FOREGROUND_SERVICE) {
-                return;
-            }
-            final long now = SystemClock.elapsedRealtime();
-            if (lastTimeShowDebugToast == 0 ||  now - lastTimeShowDebugToast > 600000) {
-                lastTimeShowDebugToast = now;
-                mHandler.sendMessage(PooledLambda.obtainMessage(
-                        ActivityManagerInternal::showWhileInUseDebugToast,
-                        mActivityManagerInternal, uid, op, mode));
-            }
-        }
     }
 
     final static class Ops extends SparseArray<Op> {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index a498e38..65a1301 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2352,6 +2352,16 @@
         mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow);
         mStartInputHistory.addEntry(info);
 
+        // Seems that PackageManagerInternal#grantImplicitAccess() doesn't handle cross-user
+        // implicit visibility (e.g. IME[user=10] -> App[user=0]) thus we do this only for the
+        // same-user scenarios.
+        // That said ignoring cross-user scenario will never affect IMEs that do not have
+        // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
+        if (mSettings.getCurrentUserId() == UserHandle.getUserId(mCurClient.uid)) {
+            mPackageManagerInternal.grantImplicitAccess(mSettings.getCurrentUserId(),
+                    null /* intent */, UserHandle.getAppId(mCurMethodUid), mCurClient.uid, true);
+        }
+
         final SessionState session = mCurClient.curSession;
         executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(
                 MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */,
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 067bdcb..bfcbe46 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -231,7 +231,7 @@
     private final AppForegroundHelper mAppForegroundHelper;
     private final LocationUsageLogger mLocationUsageLogger;
 
-    @Nullable private GnssManagerService mGnssManagerService = null;
+    @Nullable private volatile GnssManagerService mGnssManagerService = null;
 
     private final PassiveLocationProviderManager mPassiveManager;
 
@@ -381,6 +381,10 @@
             // prepare providers
             initializeProvidersLocked();
         }
+
+        // initialize gnss last because it has no awareness of boot phases and blindly assumes that
+        // all other location providers are loaded at initialization
+        initializeGnss();
     }
 
     private void onAppOpChanged(String packageName) {
@@ -602,16 +606,19 @@
             }
             manager.setMockProvider(new MockProvider(properties));
         }
+    }
 
-        // initialize gnss last because it has no awareness of boot phases and blindly assumes that
-        // all other location providers are loaded at initialization
+    private void initializeGnss() {
+        // Do not hold mLock when calling GnssManagerService#isGnssSupported() which calls into HAL.
         if (GnssManagerService.isGnssSupported()) {
             mGnssManagerService = new GnssManagerService(mContext, mAppOpsHelper, mSettingsHelper,
                     mAppForegroundHelper, mLocationUsageLogger);
             mGnssManagerService.onSystemReady();
 
             LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER);
-            mProviderManagers.add(gnssManager);
+            synchronized (mLock) {
+                mProviderManagers.add(gnssManager);
+            }
             gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider());
 
             // bind to geofence proxy
diff --git a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
index d38ee67..7950fcf 100644
--- a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
+++ b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
@@ -104,8 +104,6 @@
                         // Generate auth-bound key to user 0 (since we the caller is user 0)
                         .setUserAuthenticationRequired(true)
                         .setUserAuthenticationValidityDurationSeconds(CACHE_TIMEOUT_SECONDS)
-                        // Only accessible after user 0's keyguard is unlocked
-                        .setUnlockedDeviceRequired(true)
                         .build());
                 key = generator.generateKey();
             } catch (GeneralSecurityException e) {
@@ -171,10 +169,14 @@
     public void removePassword(int userId) {
         synchronized (mEncryptedPasswords) {
             String keyName = getEncryptionKeyName(userId);
+            String legacyKeyName = getLegacyEncryptionKeyName(userId);
             try {
                 if (mKeyStore.containsAlias(keyName)) {
                     mKeyStore.deleteEntry(keyName);
                 }
+                if (mKeyStore.containsAlias(legacyKeyName)) {
+                    mKeyStore.deleteEntry(legacyKeyName);
+                }
             } catch (KeyStoreException e) {
                 Slog.d(TAG, "Cannot delete key", e);
             }
@@ -186,6 +188,14 @@
     }
 
     private static String getEncryptionKeyName(int userId) {
+        return "com.android.server.locksettings.unified_profile_cache_v2_" + userId;
+    }
+
+    /**
+     * Returns the legacy keystore key name when setUnlockedDeviceRequired() was set explicitly.
+     * Only existed during Android 11 internal testing period.
+     */
+    private static String getLegacyEncryptionKeyName(int userId) {
         return "com.android.server.locksettings.unified_profile_cache_" + userId;
     }
 }
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 2461b0c..30a636d 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -247,6 +247,7 @@
                 .setType(type)
                 .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
                 .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+                .setAddress(device.getAddress())
                 .build();
         return newBtRoute;
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5585e98..b64e991 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -92,6 +92,7 @@
 import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
+import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
@@ -1906,7 +1907,8 @@
         mMetricsLogger = new MetricsLogger();
         mRankingHandler = rankingHandler;
         mConditionProviders = conditionProviders;
-        mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders);
+        mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders,
+                new SysUiStatsEvent.BuilderFactory());
         mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
             @Override
             public void onConfigChanged() {
@@ -2046,12 +2048,15 @@
         mStripRemoteViewsSizeBytes = getContext().getResources().getInteger(
                 com.android.internal.R.integer.config_notificationStripRemoteViewSizeBytes);
 
-        mMsgPkgsAllowedAsConvos = Set.of(
-                getContext().getResources().getStringArray(
-                        com.android.internal.R.array.config_notificationMsgPkgsAllowedAsConvos));
+        mMsgPkgsAllowedAsConvos = Set.of(getStringArrayResource(
+                com.android.internal.R.array.config_notificationMsgPkgsAllowedAsConvos));
         mStatsManager = statsManager;
     }
 
+    protected String[] getStringArrayResource(int key) {
+        return getContext().getResources().getStringArray(key);
+    }
+
     @Override
     public void onStart() {
         SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), (userId, r, muteOnReturn) -> {
@@ -2189,6 +2194,12 @@
                 ConcurrentUtils.DIRECT_EXECUTOR,
                 mPullAtomCallback
         );
+        mStatsManager.setPullAtomCallback(
+                DND_MODE_RULE,
+                null, // use default PullAtomMetadata values
+                BackgroundThread.getExecutor(),
+                mPullAtomCallback
+        );
     }
 
     private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
@@ -2198,6 +2209,7 @@
                 case PACKAGE_NOTIFICATION_PREFERENCES:
                 case PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES:
                 case PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES:
+                case DND_MODE_RULE:
                     return pullNotificationStates(atomTag, data);
                 default:
                     throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
@@ -2216,6 +2228,9 @@
             case PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES:
                 mPreferencesHelper.pullPackageChannelGroupPreferencesStats(data);
                 break;
+            case DND_MODE_RULE:
+                mZenModeHelper.pullRules(data);
+                break;
         }
         return StatsManager.PULL_SUCCESS;
     }
@@ -2729,10 +2744,7 @@
     }
 
     protected void maybeRegisterMessageSent(NotificationRecord r) {
-        Context appContext = r.getSbn().getPackageContext(getContext());
-        Notification.Builder nb =
-                Notification.Builder.recoverBuilder(appContext, r.getNotification());
-        if (nb.getStyle() instanceof Notification.MessagingStyle) {
+        if (r.isConversation()) {
             if (r.getShortcutInfo() != null) {
                 if (mPreferencesHelper.setValidMessageSent(
                         r.getSbn().getPackageName(), r.getUid())) {
@@ -4015,7 +4027,7 @@
         private void cancelNotificationFromListenerLocked(ManagedServiceInfo info,
                 int callingUid, int callingPid, String pkg, String tag, int id, int userId) {
             cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
-                    FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE | FLAG_BUBBLE,
+                    FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE,
                     true,
                     userId, REASON_LISTENER_CANCEL, info);
         }
@@ -6248,6 +6260,13 @@
                         mUsageStats.registerClickedByUser(r);
                     }
 
+                    if (mReason == REASON_LISTENER_CANCEL
+                        && (r.getNotification().flags & FLAG_BUBBLE) != 0) {
+                        mNotificationDelegate.onBubbleNotificationSuppressionChanged(
+                            r.getKey(), /* suppressed */ true);
+                        return;
+                    }
+
                     if ((r.getNotification().flags & mMustHaveFlags) != mMustHaveFlags) {
                         return;
                     }
diff --git a/services/core/java/com/android/server/notification/SysUiStatsEvent.java b/services/core/java/com/android/server/notification/SysUiStatsEvent.java
index 9bc2346..90737bd 100644
--- a/services/core/java/com/android/server/notification/SysUiStatsEvent.java
+++ b/services/core/java/com/android/server/notification/SysUiStatsEvent.java
@@ -58,6 +58,11 @@
             mBuilder.writeBoolean(value);
             return this;
         }
+
+        public Builder writeByteArray(byte[] value) {
+            mBuilder.writeByteArray(value);
+            return this;
+        }
     }
 
     static class BuilderFactory {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index a490b9c..4931d3f 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -20,6 +20,10 @@
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED;
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED;
 import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
+import static android.service.notification.DNDModeProto.ROOT_CONFIG;
+
+import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID;
+import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 
 import android.app.AppOpsManager;
 import android.app.AutomaticZenRule;
@@ -67,6 +71,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.StatsEvent;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.R;
@@ -103,6 +108,7 @@
     private final SettingsObserver mSettingsObserver;
     private final AppOpsManager mAppOps;
     @VisibleForTesting protected final NotificationManager mNotificationManager;
+    private final SysUiStatsEvent.BuilderFactory mStatsEventBuilderFactory;
     @VisibleForTesting protected ZenModeConfig mDefaultConfig;
     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
     private final ZenModeFiltering mFiltering;
@@ -130,7 +136,8 @@
 
     private String[] mPriorityOnlyDndExemptPackages;
 
-    public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders) {
+    public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders,
+            SysUiStatsEvent.BuilderFactory statsEventBuilderFactory) {
         mContext = context;
         mHandler = new H(looper);
         addCallback(mMetrics);
@@ -148,6 +155,7 @@
         mFiltering = new ZenModeFiltering(mContext);
         mConditions = new ZenModeConditions(this, conditionProviders);
         mServiceConfig = conditionProviders.getConfig();
+        mStatsEventBuilderFactory = statsEventBuilderFactory;
     }
 
     public Looper getLooper() {
@@ -1170,6 +1178,72 @@
         }
     }
 
+    /**
+     * Generate pulled atoms about do not disturb configurations.
+     */
+    public void pullRules(List<StatsEvent> events) {
+        synchronized (mConfig) {
+            final int numConfigs = mConfigs.size();
+            int id = 0;
+            for (int i = 0; i < numConfigs; i++) {
+                final int user = mConfigs.keyAt(i);
+                final ZenModeConfig config = mConfigs.valueAt(i);
+                SysUiStatsEvent.Builder data = mStatsEventBuilderFactory.newBuilder()
+                        .setAtomId(DND_MODE_RULE)
+                        .writeInt(user)
+                        .writeBoolean(config.manualRule != null) // enabled
+                        .writeBoolean(config.areChannelsBypassingDnd)
+                        .writeInt(ROOT_CONFIG)
+                        .writeString("") // name, empty for root config
+                        .writeInt(Process.SYSTEM_UID) // system owns root config
+                        .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
+                        .writeByteArray(config.toZenPolicy().toProto());
+                events.add(data.build());
+                if (config.manualRule != null && config.manualRule.enabler != null) {
+                    ruleToProto(user, config.manualRule, events);
+                }
+                for (ZenRule rule : config.automaticRules.values()) {
+                    ruleToProto(user, rule, events);
+                }
+            }
+        }
+    }
+
+    private void ruleToProto(int user, ZenRule rule, List<StatsEvent> events) {
+        // Make the ID safe.
+        String id = rule.id == null ? "" : rule.id;
+        if (!ZenModeConfig.DEFAULT_RULE_IDS.contains(id)) {
+            id = "";
+        }
+
+        // Look for packages and enablers, enablers get priority.
+        String pkg = rule.pkg == null ? "" : rule.pkg;
+        if (rule.enabler != null) {
+            pkg = rule.enabler;
+            id = ZenModeConfig.MANUAL_RULE_ID;
+        }
+
+        // TODO: fetch the uid from the package manager
+        int uid = "android".equals(pkg) ? Process.SYSTEM_UID : 0;
+
+        SysUiStatsEvent.Builder data;
+        data = mStatsEventBuilderFactory.newBuilder()
+                .setAtomId(DND_MODE_RULE)
+                .writeInt(user)
+                .writeBoolean(rule.enabled)
+                .writeBoolean(false) // channels_bypassing unused for rules
+                .writeInt(rule.zenMode)
+                .writeString(id)
+                .writeInt(uid)
+                .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
+        byte[] policyProto = new byte[]{};
+        if (rule.zenPolicy != null) {
+            policyProto = rule.zenPolicy.toProto();
+        }
+        data.writeByteArray(policyProto);
+        events.add(data.build());
+    }
+
     @VisibleForTesting
     protected final class RingerModeDelegate implements AudioManagerInternal.RingerModeDelegate {
         @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5e52748..ae8b3a0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -11067,7 +11067,7 @@
         } else {
             pkgSetting = result.pkgSetting;
             if (originalPkgSetting != null) {
-                mSettings.addRenamedPackageLPw(parsedPackage.getPackageName(),
+                mSettings.addRenamedPackageLPw(parsedPackage.getRealPackage(),
                         originalPkgSetting.name);
                 mTransferredPackages.add(originalPkgSetting.name);
             }
@@ -11176,7 +11176,7 @@
     @GuardedBy("mLock")
     private @Nullable PackageSetting getOriginalPackageLocked(@NonNull AndroidPackage pkg,
             @Nullable String renamedPkgName) {
-        if (!isPackageRenamed(pkg, renamedPkgName)) {
+        if (isPackageRenamed(pkg, renamedPkgName)) {
             return null;
         }
         for (int i = ArrayUtils.size(pkg.getOriginalPackages()) - 1; i >= 0; --i) {
@@ -13518,6 +13518,17 @@
         removeSuspensionsBySuspendingPackage(allPackages, suspendingPackage::equals, userId);
     }
 
+    boolean isSuspendingAnyPackages(String suspendingPackage, int userId) {
+        synchronized (mLock) {
+            for (final PackageSetting ps : mSettings.mPackages.values()) {
+                if (ps.isSuspendedBy(suspendingPackage, userId)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * Removes any suspensions on given packages that were added by packages that pass the given
      * predicate.
@@ -23913,7 +23924,6 @@
                     callingUid);
         }
 
-
         @Override
         public boolean isPlatformSigned(String packageName) {
             PackageSetting packageSetting = mSettings.mPackages.get(packageName);
@@ -25018,6 +25028,16 @@
                 mSettings.writePackageRestrictionsLPr(userId);
             }
         }
+
+        @Override
+        public void unsuspendForSuspendingPackage(final String packageName, int affectedUser) {
+            PackageManagerService.this.unsuspendForSuspendingPackage(packageName, affectedUser);
+        }
+
+        @Override
+        public boolean isSuspendingAnyPackages(String suspendingPackage, int userId) {
+            return PackageManagerService.this.isSuspendingAnyPackages(suspendingPackage, userId);
+        }
     }
 
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 00a5fe76..834303c 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -422,6 +422,11 @@
         return readUserState(userId).suspended;
     }
 
+    boolean isSuspendedBy(String suspendingPackage, int userId) {
+        final PackageUserState state = readUserState(userId);
+        return state.suspendParams != null && state.suspendParams.containsKey(suspendingPackage);
+    }
+
     void addOrUpdateSuspension(String suspendingPackage, SuspendDialogInfo dialogInfo,
             PersistableBundle appExtras, PersistableBundle launcherExtras, int userId) {
         final PackageUserState existingUserState = modifyUserState(userId);
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 2614076..eb51cc3 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -69,6 +69,9 @@
             "exclude-annotation": "androidx.test.filters.Suppress"
         }
       ]
+    },
+    {
+      "name": "PackageManagerServiceHostTests"
     }
   ],
   "postsubmit": [
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 40fa798..2a6997c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -989,6 +989,15 @@
 
         ensureCanModifyQuietMode(
                 callingPackage, Binder.getCallingUid(), userId, target != null, dontAskCredential);
+
+        if (onlyIfCredentialNotRequired && callingPackage.equals(
+                getPackageManagerInternal().getSystemUiServiceComponent().getPackageName())) {
+            // This is to prevent SysUI from accidentally allowing the profile to turned on
+            // without password when keyguard is still locked.
+            throw new SecurityException("SystemUI is not allowed to set "
+                    + "QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED");
+        }
+
         final long identity = Binder.clearCallingIdentity();
         try {
             if (enableQuietMode) {
@@ -996,7 +1005,17 @@
                         userId, true /* enableQuietMode */, target, callingPackage);
                 return true;
             }
-            mLockPatternUtils.tryUnlockWithCachedUnifiedChallenge(userId);
+            if (mLockPatternUtils.isManagedProfileWithUnifiedChallenge(userId)) {
+                KeyguardManager km = mContext.getSystemService(KeyguardManager.class);
+                // Normally only attempt to auto-unlock unified challenge if keyguard is not showing
+                // (to stop turning profile on automatically via the QS tile), except when we
+                // are called with QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED, in which
+                // case always attempt to auto-unlock.
+                if (!km.isDeviceLocked(mLocalService.getProfileParentId(userId))
+                        || onlyIfCredentialNotRequired) {
+                    mLockPatternUtils.tryUnlockWithCachedUnifiedChallenge(userId);
+                }
+            }
             final boolean needToShowConfirmCredential = !dontAskCredential
                     && mLockPatternUtils.isSecure(userId)
                     && !StorageManager.isUserKeyUnlocked(userId);
@@ -1029,6 +1048,8 @@
      */
     private void ensureCanModifyQuietMode(String callingPackage, int callingUid,
             @UserIdInt int targetUserId, boolean startIntent, boolean dontAskCredential) {
+        verifyCallingPackage(callingPackage, callingUid);
+
         if (hasManageUsersPermission()) {
             return;
         }
@@ -1050,7 +1071,6 @@
             return;
         }
 
-        verifyCallingPackage(callingPackage, callingUid);
         final ShortcutServiceInternal shortcutInternal =
                 LocalServices.getService(ShortcutServiceInternal.class);
         if (shortcutInternal != null) {
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 58732b4..492b84a 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -96,6 +96,8 @@
 class UserSystemPackageInstaller {
     private static final String TAG = "UserManagerService";
 
+    private static final boolean DEBUG = false;
+
     /**
      * System Property whether to only install system packages on a user if they're whitelisted for
      * that user type. These are flags and can be freely combined.
@@ -328,12 +330,15 @@
         // Check whether all whitelisted packages are indeed on the system.
         final String notPresentFmt = "%s is whitelisted but not present.";
         final String notSystemFmt = "%s is whitelisted and present but not a system package.";
+        final String overlayPackageFmt = "%s is whitelisted but it's auto-generated RRO package.";
         for (String pkgName : allWhitelistedPackages) {
             final AndroidPackage pkg = pmInt.getPackage(pkgName);
             if (pkg == null) {
                 warnings.add(String.format(notPresentFmt, pkgName));
             } else if (!pkg.isSystem()) {
                 warnings.add(String.format(notSystemFmt, pkgName));
+            } else if (isAutoGeneratedRRO(pkg)) {
+                warnings.add(String.format(overlayPackageFmt, pkgName));
             }
         }
         return warnings;
@@ -358,7 +363,8 @@
         pmInt.forEachPackage(pkg -> {
             if (!pkg.isSystem()) return;
             final String pkgName = pkg.getManifestPackageName();
-            if (!allWhitelistedPackages.contains(pkgName)) {
+            if (!allWhitelistedPackages.contains(pkgName)
+                    && !isAutoGeneratedRRO(pmInt.getPackage(pkgName))) {
                 errors.add(String.format(logMessageFmt, pkgName));
             }
         });
@@ -407,6 +413,23 @@
         return isImplicitWhitelistSystemMode(getWhitelistMode());
     }
 
+    /**
+     * Whether package name has auto-generated RRO package name suffix.
+     */
+    @VisibleForTesting
+    static boolean hasAutoGeneratedRROSuffix(String name) {
+        return name.endsWith(".auto_generated_rro_product__")
+                || name.endsWith(".auto_generated_rro_vendor__");
+    }
+
+    /**
+     * Whether the package is auto-generated RRO package.
+     */
+    private static boolean isAutoGeneratedRRO(AndroidPackage pkg) {
+        return pkg.isOverlay()
+                && (hasAutoGeneratedRROSuffix(pkg.getManifestPackageName()));
+    }
+
     /** See {@link #isEnforceMode()}. */
     private static boolean isEnforceMode(int whitelistMode) {
         return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE) != 0;
@@ -518,7 +541,18 @@
     static boolean shouldInstallPackage(AndroidPackage sysPkg,
             @NonNull ArrayMap<String, Long> userTypeWhitelist,
             @NonNull Set<String> userWhitelist, boolean implicitlyWhitelist) {
-        final String pkgName = sysPkg.getManifestPackageName();
+        final String pkgName;
+        if (isAutoGeneratedRRO(sysPkg)) {
+            pkgName = sysPkg.getOverlayTarget();
+            if (DEBUG) {
+                Slog.i(TAG, "shouldInstallPackage(): " + sysPkg.getManifestPackageName()
+                        + " is auto-generated RRO package, will look for overlay system package: "
+                        + pkgName);
+            }
+        } else {
+            pkgName = sysPkg.getManifestPackageName();
+        }
+
         return (implicitlyWhitelist && !userTypeWhitelist.containsKey(pkgName))
                 || userWhitelist.contains(pkgName);
     }
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 0b95be1..8f0fd60 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -616,6 +616,7 @@
         } catch (RemoteException ex) {
             // Ignore
         }
+        FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_WAKE_REPORTED, reason);
     }
 
     /**
diff --git a/services/core/java/com/android/server/slice/SliceClientPermissions.java b/services/core/java/com/android/server/slice/SliceClientPermissions.java
index ab94a59..e241205 100644
--- a/services/core/java/com/android/server/slice/SliceClientPermissions.java
+++ b/services/core/java/com/android/server/slice/SliceClientPermissions.java
@@ -160,6 +160,9 @@
         // Get to the beginning of the provider.
         while (parser.getEventType() != XmlPullParser.START_TAG
                 || !TAG_CLIENT.equals(parser.getName())) {
+            if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+                throw new XmlPullParserException("Can't find client tag in xml");
+            }
             parser.next();
         }
         int depth = parser.getDepth();
@@ -173,6 +176,9 @@
         parser.next();
 
         while (parser.getDepth() > depth) {
+            if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+                return provider;
+            }
             if (parser.getEventType() == XmlPullParser.START_TAG
                     && TAG_AUTHORITY.equals(parser.getName())) {
                 try {
diff --git a/services/core/java/com/android/server/slice/SlicePermissionManager.java b/services/core/java/com/android/server/slice/SlicePermissionManager.java
index 1d1c28f..343d2e3 100644
--- a/services/core/java/com/android/server/slice/SlicePermissionManager.java
+++ b/services/core/java/com/android/server/slice/SlicePermissionManager.java
@@ -130,7 +130,7 @@
         }
         SliceClientPermissions client = getClient(pkgUser);
         client.clear();
-        mHandler.obtainMessage(H.MSG_REMOVE, pkgUser);
+        mHandler.obtainMessage(H.MSG_REMOVE, pkgUser).sendToTarget();
     }
 
     public String[] getAllPackagesGranted(String pkg) {
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 2f963b7..522e5e1 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -20,8 +20,6 @@
 import android.annotation.Nullable;
 import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
 import android.hardware.soundtrigger.V2_2.ISoundTriggerHw;
-import android.media.audio.common.AudioConfig;
-import android.media.audio.common.AudioOffloadInfo;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
 import android.media.soundtrigger_middleware.ModelParameterRange;
@@ -30,6 +28,7 @@
 import android.media.soundtrigger_middleware.PhraseSoundModel;
 import android.media.soundtrigger_middleware.RecognitionConfig;
 import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionStatus;
 import android.media.soundtrigger_middleware.SoundModel;
 import android.media.soundtrigger_middleware.SoundModelType;
 import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
@@ -579,7 +578,7 @@
                     @NonNull ISoundTriggerHwCallback.RecognitionEvent recognitionEvent,
                     int cookie) {
                 synchronized (SoundTriggerModule.this) {
-                    android.media.soundtrigger_middleware.RecognitionEvent aidlEvent =
+                    RecognitionEvent aidlEvent =
                             ConversionUtil.hidl2aidlRecognitionEvent(recognitionEvent);
                     aidlEvent.captureSession = mSession.mSessionHandle;
                     try {
@@ -589,8 +588,7 @@
                         // In any case, client callbacks are considered best effort.
                         Log.e(TAG, "Client callback execption.", e);
                     }
-                    if (aidlEvent.status
-                            != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
+                    if (aidlEvent.status != RecognitionStatus.FORCED) {
                         setState(ModelState.LOADED);
                     }
                 }
@@ -601,7 +599,7 @@
                     @NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent phraseRecognitionEvent,
                     int cookie) {
                 synchronized (SoundTriggerModule.this) {
-                    android.media.soundtrigger_middleware.PhraseRecognitionEvent aidlEvent =
+                    PhraseRecognitionEvent aidlEvent =
                             ConversionUtil.hidl2aidlPhraseRecognitionEvent(phraseRecognitionEvent);
                     aidlEvent.common.captureSession = mSession.mSessionHandle;
                     try {
@@ -611,8 +609,7 @@
                         // In any case, client callbacks are considered best effort.
                         Log.e(TAG, "Client callback execption.", e);
                     }
-                    if (aidlEvent.common.status
-                            != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
+                    if (aidlEvent.common.status != RecognitionStatus.FORCED) {
                         setState(ModelState.LOADED);
                     }
                 }
@@ -623,15 +620,13 @@
     /**
      * Creates a default-initialized recognition event.
      *
-     * Object fields are default constructed.
-     * Array fields are initialized to 0 length.
+     * Non-nullable object fields are default constructed.
+     * Non-nullable array fields are initialized to 0 length.
      *
      * @return The event.
      */
     private static RecognitionEvent newEmptyRecognitionEvent() {
         RecognitionEvent result = new RecognitionEvent();
-        result.audioConfig = new AudioConfig();
-        result.audioConfig.offloadInfo = new AudioOffloadInfo();
         result.data = new byte[0];
         return result;
     }
@@ -639,8 +634,8 @@
     /**
      * Creates a default-initialized phrase recognition event.
      *
-     * Object fields are default constructed.
-     * Array fields are initialized to 0 length.
+     * Non-nullable object fields are default constructed.
+     * Non-nullable array fields are initialized to 0 length.
      *
      * @return The event.
      */
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index c2bae1a..7fe21e3 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -209,14 +209,6 @@
     // Random seed stable for StatsPullAtomService life cycle - can be used for stable sampling
     private static final int RANDOM_SEED = new Random().nextInt();
 
-    /**
-     * Lowest available uid for apps.
-     *
-     * <p>Used to quickly discard memory snapshots of the zygote forks from native process
-     * measurements.
-     */
-    private static final int MIN_APP_UID = 10_000;
-
     private static final int DIMENSION_KEY_SIZE_HARD_LIMIT = 800;
     private static final int DIMENSION_KEY_SIZE_SOFT_LIMIT = 500;
     private static final long APP_OPS_SAMPLING_INITIALIZATION_DELAY_MILLIS = 45000;
@@ -1819,10 +1811,6 @@
         return StatsManager.PULL_SUCCESS;
     }
 
-    private static boolean isAppUid(int uid) {
-        return uid >= MIN_APP_UID;
-    }
-
     private void registerProcessMemoryHighWaterMark() {
         int tagId = FrameworkStatsLog.PROCESS_MEMORY_HIGH_WATER_MARK;
         mStatsManager.setPullAtomCallback(
@@ -1859,7 +1847,7 @@
         int size = processCmdlines.size();
         for (int i = 0; i < size; ++i) {
             final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(processCmdlines.keyAt(i));
-            if (snapshot == null || isAppUid(snapshot.uid)) {
+            if (snapshot == null) {
                 continue;
             }
             StatsEvent e = StatsEvent.newBuilder()
@@ -1920,7 +1908,7 @@
         for (int i = 0; i < size; ++i) {
             int pid = processCmdlines.keyAt(i);
             final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid);
-            if (snapshot == null || isAppUid(snapshot.uid)) {
+            if (snapshot == null) {
                 continue;
             }
             StatsEvent e = StatsEvent.newBuilder()
@@ -3577,7 +3565,16 @@
     private void processHistoricalOp(AppOpsManager.HistoricalOp op,
             List<AppOpEntry> opsList, int uid, int samplingRatio, String packageName,
             @Nullable String attributionTag) {
-        AppOpEntry entry = new AppOpEntry(packageName, attributionTag, op, uid);
+        int firstChar = 0;
+        if (attributionTag != null && attributionTag.startsWith(packageName)) {
+            firstChar = packageName.length();
+            if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar) == '.') {
+                firstChar++;
+            }
+        }
+        AppOpEntry entry = new AppOpEntry(packageName,
+                attributionTag == null ? null : attributionTag.substring(firstChar), op,
+                uid);
         if (entry.mHash < samplingRatio) {
             opsList.add(entry);
         }
diff --git a/services/core/java/com/android/server/textclassifier/FixedSizeQueue.java b/services/core/java/com/android/server/textclassifier/FixedSizeQueue.java
new file mode 100644
index 0000000..edb258d
--- /dev/null
+++ b/services/core/java/com/android/server/textclassifier/FixedSizeQueue.java
@@ -0,0 +1,103 @@
+/*
+ * 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.server.textclassifier;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayDeque;
+import java.util.Objects;
+import java.util.Queue;
+
+/**
+ * A fixed-size queue which automatically evicts the oldest element from the queue when it is full.
+ *
+ * <p>This class does not accept null element.
+ *
+ * @param <E> the type of elements held in this queue
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public final class FixedSizeQueue<E> {
+
+    private final Queue<E> mDelegate;
+
+    @Nullable
+    private final OnEntryEvictedListener<E> mOnEntryEvictedListener;
+
+    private final int mMaxSize;
+
+    public FixedSizeQueue(int maxSize, @Nullable OnEntryEvictedListener<E> onEntryEvictedListener) {
+        Preconditions.checkArgument(maxSize > 0, "maxSize (%s) must > 0", maxSize);
+        mDelegate = new ArrayDeque<>(maxSize);
+        mMaxSize = maxSize;
+        mOnEntryEvictedListener = onEntryEvictedListener;
+    }
+
+    /** Returns the number of items in the queue. */
+    public int size() {
+        return mDelegate.size();
+    }
+
+    /** Adds an element to the queue, evicts the oldest element if it reaches its max capacity. */
+    public boolean add(@NonNull E element) {
+        Objects.requireNonNull(element);
+        if (size() == mMaxSize) {
+            E removed = mDelegate.remove();
+            if (mOnEntryEvictedListener != null) {
+                mOnEntryEvictedListener.onEntryEvicted(removed);
+            }
+        }
+        mDelegate.add(element);
+        return true;
+    }
+
+    /**
+     * Returns and removes the head of the queue, or returns null if this queue is empty.
+     */
+    @Nullable
+    public E poll() {
+        return mDelegate.poll();
+    }
+
+    /**
+     * Removes an element from the queue, returns a boolean to indicate if an element is removed.
+     */
+    public boolean remove(@NonNull E element) {
+        Objects.requireNonNull(element);
+        return mDelegate.remove(element);
+    }
+
+    /** Returns whether the queue is empty. */
+    public boolean isEmpty() {
+        return mDelegate.isEmpty();
+    }
+
+    /**
+     * A listener to get notified when an element is evicted.
+     *
+     * @param <E> the type of element
+     */
+    public interface OnEntryEvictedListener<E> {
+        /**
+         * Notifies that an element is evicted because the queue is reaching its max capacity.
+         */
+        void onEntryEvicted(@NonNull E element);
+    }
+}
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 1c96a2e..1707d95 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -41,6 +41,7 @@
 import android.service.textclassifier.TextClassifierService.ConnectionState;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.LruCache;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.textclassifier.ConversationAction;
@@ -68,12 +69,10 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Queue;
 
 /**
  * A manager for TextClassifier services.
@@ -308,13 +307,15 @@
         Objects.requireNonNull(classificationContext);
         Objects.requireNonNull(classificationContext.getSystemTextClassifierMetadata());
 
+        synchronized (mLock) {
+            mSessionCache.put(sessionId, classificationContext);
+        }
         handleRequest(
                 classificationContext.getSystemTextClassifierMetadata(),
                 /* verifyCallingPackage= */ true,
                 /* attemptToBind= */ false,
                 service -> {
                     service.onCreateTextClassificationSession(classificationContext, sessionId);
-                    mSessionCache.put(sessionId, classificationContext);
                 },
                 "onCreateTextClassificationSession",
                 NO_OP_CALLBACK);
@@ -588,12 +589,14 @@
      * are cleaned up automatically when the client process is dead.
      */
     static final class SessionCache {
+        private static final int MAX_CACHE_SIZE = 100;
+
         @NonNull
         private final Object mLock;
         @NonNull
         @GuardedBy("mLock")
-        private final Map<TextClassificationSessionId, StrippedTextClassificationContext> mCache =
-                new ArrayMap<>();
+        private final LruCache<TextClassificationSessionId, StrippedTextClassificationContext>
+                mCache = new LruCache<>(MAX_CACHE_SIZE);
         @NonNull
         @GuardedBy("mLock")
         private final Map<TextClassificationSessionId, DeathRecipient> mDeathRecipients =
@@ -775,6 +778,8 @@
     }
 
     private final class ServiceState {
+        private static final int MAX_PENDING_REQUESTS = 20;
+
         @UserIdInt
         final int mUserId;
         @NonNull
@@ -786,7 +791,15 @@
         final int mBindServiceFlags;
         @NonNull
         @GuardedBy("mLock")
-        final Queue<PendingRequest> mPendingRequests = new ArrayDeque<>();
+        final FixedSizeQueue<PendingRequest> mPendingRequests =
+                new FixedSizeQueue<>(MAX_PENDING_REQUESTS,
+                        request -> {
+                            Slog.w(LOG_TAG,
+                                    String.format("Pending request[%s] is dropped", request.mName));
+                            if (request.mOnServiceFailure != null) {
+                                request.mOnServiceFailure.run();
+                            }
+                        });
         @Nullable
         @GuardedBy("mLock")
         ITextClassifierService mService;
@@ -910,7 +923,7 @@
                 pw.printPair("bindServiceFlags", mBindServiceFlags);
                 pw.printPair("boundServiceUid", mBoundServiceUid);
                 pw.printPair("binding", mBinding);
-                pw.printPair("numberRequests", mPendingRequests.size());
+                pw.printPair("numOfPendingRequests", mPendingRequests.size());
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f38a506..be2f9d4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7540,7 +7540,7 @@
     public String toString() {
         if (stringName != null) {
             return stringName + " t" + (task == null ? INVALID_TASK_ID : task.mTaskId) +
-                    (finishing ? " f}" : "") + (mIsExiting ? " mIsExiting=" : "") + "}";
+                    (finishing ? " f}" : "") + (mIsExiting ? " isExiting" : "") + "}";
         }
         StringBuilder sb = new StringBuilder(128);
         sb.append("ActivityRecord{");
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index c999809..4bede4c 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -3232,22 +3232,22 @@
 
     @Override
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
-        pw.println(prefix + "mStackId=" + getRootTaskId());
-        pw.println(prefix + "mDeferRemoval=" + mDeferRemoval);
-        pw.println(prefix + "mBounds=" + getRawBounds().toShortString());
-        for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
-            mChildren.get(taskNdx).dump(pw, prefix + "  ", dumpAll);
+        if (mDeferRemoval) {
+            pw.println(prefix + "mDeferRemoval=true");
         }
+        super.dump(pw, prefix, dumpAll);
         if (!mExitingActivities.isEmpty()) {
             pw.println();
-            pw.println("  Exiting application tokens:");
+            pw.println(prefix + "Exiting application tokens:");
+            final String doublePrefix = prefix + "  ";
             for (int i = mExitingActivities.size() - 1; i >= 0; i--) {
                 WindowToken token = mExitingActivities.get(i);
-                pw.print("  Exiting App #"); pw.print(i);
+                pw.print(doublePrefix + "Exiting App #" + i);
                 pw.print(' '); pw.print(token);
                 pw.println(':');
-                token.dump(pw, "    ", dumpAll);
+                token.dump(pw, doublePrefix, dumpAll);
             }
+            pw.println();
         }
         mAnimatingActivityRegistry.dump(pw, "AnimatingApps:", prefix);
     }
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index b7ca1a9..1f9e860 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -1886,7 +1886,7 @@
     public void dump(PrintWriter pw, String prefix) {
         pw.println();
         pw.println("ActivityStackSupervisor state:");
-        mRootWindowContainer.dump(pw, prefix);
+        mRootWindowContainer.dump(pw, prefix, true /* dumpAll */);
         getKeyguardController().dump(pw, prefix);
         mService.getLockTaskController().dump(pw, prefix);
         pw.print(prefix);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7c935d0..6dd1ea9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1685,6 +1685,11 @@
         final NeededUriGrants resultGrants = collectGrants(resultData, r.resultTo);
 
         synchronized (mGlobalLock) {
+            // Sanity check in case activity was removed before entering global lock.
+            if (!r.isInHistory()) {
+                return true;
+            }
+
             // Keep track of the root activity of the task before we finish it
             final Task tr = r.getTask();
             final ActivityRecord rootR = tr.getRootActivity();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 807cd9c..4e19a52 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2977,12 +2977,10 @@
         pw.println();
         mWallpaperController.dump(pw, "  ");
 
-        pw.println();
-        pw.print("mSystemGestureExclusion=");
         if (mSystemGestureExclusionListeners.getRegisteredCallbackCount() > 0) {
+            pw.println();
+            pw.print("  mSystemGestureExclusion=");
             pw.println(mSystemGestureExclusion);
-        } else {
-            pw.println("<no lstnrs>");
         }
 
         pw.println();
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 77bc37f..bf9a784 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -178,6 +178,7 @@
             if (imeSource != null && imeSource.isVisible()) {
                 imeSource = new InsetsSource(imeSource);
                 imeSource.setVisible(false);
+                imeSource.setFrame(0, 0, 0, 0);
                 state = new InsetsState(state);
                 state.addSource(imeSource);
             }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ae5adca..583663c 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3570,12 +3570,14 @@
         }
     }
 
-    public void dump(PrintWriter pw, String prefix) {
+    @Override
+    public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        super.dump(pw, prefix, dumpAll);
         pw.print(prefix);
         pw.println("topDisplayFocusedStack=" + getTopDisplayFocusedStack());
         for (int i = getChildCount() - 1; i >= 0; --i) {
             final DisplayContent display = getChildAt(i);
-            display.dump(pw, prefix, true /* dumpAll */);
+            display.dump(pw, prefix, dumpAll);
         }
         pw.println();
     }
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 1b77fd2..0e5d7d9 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -428,16 +428,11 @@
 
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("mLeash="); pw.print(mLeash);
-        if (mAnimationStartDelayed) {
-            pw.print(" mAnimationStartDelayed="); pw.println(mAnimationStartDelayed);
-        } else {
-            pw.println();
-        }
-        pw.print(prefix); pw.println("Animation:");
+        pw.print(" mAnimationType=" + mAnimationType);
+        pw.println(mAnimationStartDelayed ? " mAnimationStartDelayed=true" : "");
+        pw.print(prefix); pw.print("Animation: "); pw.println(mAnimation);
         if (mAnimation != null) {
             mAnimation.dump(pw, prefix + "  ");
-        } else {
-            pw.print(prefix); pw.println("null");
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 48609e1..ce2ae2a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1436,15 +1436,6 @@
             mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
         }
 
-        final boolean isRootTask = isRootTask();
-        if (isRootTask) {
-            final DisplayContent display = getDisplayContent();
-            if (display.isSingleTaskInstance()) {
-                mAtmService.notifySingleTaskDisplayEmpty(display.mDisplayId);
-            }
-            display.mDisplayContent.setLayoutNeeded();
-        }
-
         if (hasChild()) {
             updateEffectiveIntent();
 
@@ -1465,7 +1456,7 @@
         } else if (!mReuseTask && !mCreatedByOrganizer) {
             // Remove entire task if it doesn't have any activity left and it isn't marked for reuse
             // or created by task organizer.
-            if (!isRootTask) {
+            if (!isRootTask()) {
                 getStack().removeChild(this, reason);
             }
             EventLogTags.writeWmTaskRemoved(mTaskId,
@@ -2817,6 +2808,10 @@
         if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
         EventLogTags.writeWmTaskRemoved(mTaskId, "removeTask");
 
+        if (mDisplayContent != null && mDisplayContent.isSingleTaskInstance()) {
+            mAtmService.notifySingleTaskDisplayEmpty(mDisplayContent.mDisplayId);
+        }
+
         // If applicable let the TaskOrganizer know the Task is vanishing.
         setTaskOrganizer(null);
 
@@ -3519,20 +3514,17 @@
     @Override
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         super.dump(pw, prefix, dumpAll);
+        pw.println(prefix + "bounds=" + getBounds().toShortString());
         final String doublePrefix = prefix + "  ";
-
-        pw.println(prefix + "taskId=" + mTaskId);
-        pw.println(doublePrefix + "mBounds=" + getBounds().toShortString());
-        pw.println(doublePrefix + "appTokens=" + mChildren);
-
-        final String triplePrefix = doublePrefix + "  ";
-        final String quadruplePrefix = triplePrefix + "  ";
-
-        int[] index = { 0 };
-        forAllActivities((r) -> {
-            pw.println(triplePrefix + "Activity #" + index[0]++ + " " + r);
-            r.dump(pw, quadruplePrefix, dumpAll);
-        });
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowContainer<?> child = mChildren.get(i);
+            pw.println(prefix + "* " + child);
+            // Only dump non-activity because full activity info is already printed by
+            // RootWindowContainer#dumpActivities.
+            if (child.asActivityRecord() == null) {
+                child.dump(pw, doublePrefix, dumpAll);
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 22054db..102c2a6 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1772,16 +1772,20 @@
     @Override
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         pw.println(prefix + "TaskDisplayArea " + getName());
+        super.dump(pw, prefix, dumpAll);
         if (mPreferredTopFocusableStack != null) {
             pw.println(prefix + "  mPreferredTopFocusableStack=" + mPreferredTopFocusableStack);
         }
         if (mLastFocusedStack != null) {
             pw.println(prefix + "  mLastFocusedStack=" + mLastFocusedStack);
         }
-        pw.println(prefix + "  Application tokens in top down Z order:");
+        final String doublePrefix = prefix + "  ";
+        final String triplePrefix = doublePrefix + "  ";
+        pw.println(doublePrefix + "Application tokens in top down Z order:");
         for (int stackNdx = getChildCount() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = getChildAt(stackNdx);
-            stack.dump(pw, prefix + "    ", dumpAll);
+            pw.println(doublePrefix + "* " + stack);
+            stack.dump(pw, triplePrefix, dumpAll);
         }
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index df55b3b..10ad07c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2755,7 +2755,8 @@
         Slog.i(LOG_TAG, "Giving the PO additional power...");
         markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(poAdminComponent, poUserId);
         Slog.i(LOG_TAG, "Migrating DO policies to PO...");
-        moveDoPoliciesToProfileParentAdmin(doAdmin, poAdmin.getParentActiveAdmin());
+        moveDoPoliciesToProfileParentAdminLocked(doAdmin, poAdmin.getParentActiveAdmin());
+        migratePersonalAppSuspensionLocked(doUserId, poUserId, poAdmin);
         saveSettingsLocked(poUserId);
         Slog.i(LOG_TAG, "Clearing the DO...");
         final ComponentName doAdminReceiver = doAdmin.info.getComponent();
@@ -2775,6 +2776,25 @@
                 .write();
     }
 
+    @GuardedBy("getLockObject()")
+    private void migratePersonalAppSuspensionLocked(
+            int doUserId, int poUserId, ActiveAdmin poAdmin) {
+        final PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
+        if (!pmi.isSuspendingAnyPackages(PLATFORM_PACKAGE_NAME, doUserId)) {
+            Slog.i(LOG_TAG, "DO is not suspending any apps.");
+            return;
+        }
+
+        if (getTargetSdk(poAdmin.info.getPackageName(), poUserId) >= Build.VERSION_CODES.R) {
+            Slog.i(LOG_TAG, "PO is targeting R+, keeping personal apps suspended.");
+            getUserData(doUserId).mAppsSuspended = true;
+            poAdmin.mSuspendPersonalApps = true;
+        } else {
+            Slog.i(LOG_TAG, "PO isn't targeting R+, unsuspending personal apps.");
+            pmi.unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, doUserId);
+        }
+    }
+
     private void uninstallOrDisablePackage(String packageName, int userHandle) {
         final ApplicationInfo appInfo;
         try {
@@ -2816,7 +2836,9 @@
         pi.uninstall(packageName, 0 /* flags */, new IntentSender((IIntentSender) mLocalSender));
     }
 
-    private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
+    @GuardedBy("getLockObject()")
+    private void moveDoPoliciesToProfileParentAdminLocked(
+            ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
         // The following policies can be already controlled via parent instance, skip if so.
         if (parentAdmin.mPasswordPolicy.quality == PASSWORD_QUALITY_UNSPECIFIED) {
             parentAdmin.mPasswordPolicy = doAdmin.mPasswordPolicy;
@@ -16147,25 +16169,34 @@
         }
         Slog.i(LOG_TAG, String.format("%s personal apps for user %d",
                 suspended ? "Suspending" : "Unsuspending", userId));
+
+        if (suspended) {
+            suspendPersonalAppsInPackageManager(userId);
+        } else {
+            mInjector.getPackageManagerInternal().unsuspendForSuspendingPackage(
+                    PLATFORM_PACKAGE_NAME, userId);
+        }
+
+        synchronized (getLockObject()) {
+            getUserData(userId).mAppsSuspended = suspended;
+            saveSettingsLocked(userId);
+        }
+    }
+
+    private void suspendPersonalAppsInPackageManager(int userId) {
         mInjector.binderWithCleanCallingIdentity(() -> {
             try {
                 final String[] appsToSuspend = mInjector.getPersonalAppsForSuspension(userId);
-                final String[] failedPackages = mIPackageManager.setPackagesSuspendedAsUser(
-                        appsToSuspend, suspended, null, null, null, PLATFORM_PACKAGE_NAME, userId);
-                if (!ArrayUtils.isEmpty(failedPackages)) {
-                    Slog.wtf(LOG_TAG, String.format("Failed to %s packages: %s",
-                            suspended ? "suspend" : "unsuspend", String.join(",", failedPackages)));
+                final String[] failedApps = mIPackageManager.setPackagesSuspendedAsUser(
+                        appsToSuspend, true, null, null, null, PLATFORM_PACKAGE_NAME, userId);
+                if (!ArrayUtils.isEmpty(failedApps)) {
+                    Slog.wtf(LOG_TAG, "Failed to suspend apps: " + String.join(",", failedApps));
                 }
             } catch (RemoteException re) {
                 // Shouldn't happen.
                 Slog.e(LOG_TAG, "Failed talking to the package manager", re);
             }
         });
-
-        synchronized (getLockObject()) {
-            getUserData(userId).mAppsSuspended = suspended;
-            saveSettingsLocked(userId);
-        }
     }
 
     @GuardedBy("getLockObject()")
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index 2e60f2a..236ac84 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -16,6 +16,8 @@
 
 package com.android.server.people.prediction;
 
+import static java.util.Collections.reverseOrder;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -39,6 +41,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -85,7 +88,9 @@
         List<ShareTarget> shareTargets = getDirectShareTargets();
         SharesheetModelScorer.computeScore(shareTargets, getShareEventType(mIntentFilter),
                 System.currentTimeMillis());
-        Collections.sort(shareTargets, (t1, t2) -> -Float.compare(t1.getScore(), t2.getScore()));
+        Collections.sort(shareTargets,
+                Comparator.comparing(ShareTarget::getScore, reverseOrder())
+                        .thenComparing(t -> t.getAppTarget().getRank()));
         List<AppTarget> res = new ArrayList<>();
         for (int i = 0; i < Math.min(getPredictionContext().getPredictedTargetCount(),
                 shareTargets.size()); i++) {
@@ -135,6 +140,7 @@
                     new AppTargetId(shortcutInfo.getId()),
                     shortcutInfo)
                     .setClassName(shareShortcut.getTargetComponent().getClassName())
+                    .setRank(shortcutInfo.getRank())
                     .build();
             String packageName = shortcutInfo.getPackage();
             int userId = shortcutInfo.getUserId();
diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
new file mode 100644
index 0000000..dad001b
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -0,0 +1,33 @@
+// 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.
+
+java_test_host {
+    name: "PackageManagerServiceHostTests",
+    srcs: ["src/**/*.kt"],
+    libs: [
+        "tradefed",
+        "junit",
+        "truth-prebuilt",
+    ],
+    static_libs: [
+        "frameworks-base-hostutils",
+    ],
+    test_suites: ["general-tests"],
+    java_resources: [
+        ":PackageManagerDummyAppVersion1",
+        ":PackageManagerDummyAppVersion2",
+        ":PackageManagerDummyAppVersion3",
+        ":PackageManagerDummyAppOriginalOverride",
+    ]
+}
diff --git a/services/tests/PackageManagerServiceTests/host/AndroidTest.xml b/services/tests/PackageManagerServiceTests/host/AndroidTest.xml
new file mode 100644
index 0000000..dc8c811
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+
+<configuration description="Test module config for PackageManagerServiceHostTests">
+    <option name="test-tag" value="PackageManagerServiceHostTests" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <test class="com.android.tradefed.testtype.HostTest">
+        <option name="jar" value="PackageManagerServiceHostTests.jar" />
+    </test>
+</configuration>
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt
new file mode 100644
index 0000000..4927c45
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.server.pm.test
+
+import com.android.internal.util.test.SystemPreparer
+import com.android.tradefed.device.ITestDevice
+import java.io.File
+
+internal fun SystemPreparer.pushApk(file: String, partition: Partition) =
+        pushResourceFile(file, HostUtils.makePathForApk(file, partition))
+
+internal fun SystemPreparer.deleteApk(file: String, partition: Partition) =
+        deleteFile(partition.baseFolder.resolve(file.removeSuffix(".apk")).toString())
+
+internal object HostUtils {
+
+    fun getDataDir(device: ITestDevice, pkgName: String) =
+            device.executeShellCommand("dumpsys package $pkgName")
+                    .lineSequence()
+                    .map(String::trim)
+                    .single { it.startsWith("dataDir=") }
+                    .removePrefix("dataDir=")
+
+    fun makePathForApk(fileName: String, partition: Partition) =
+            makePathForApk(File(fileName), partition)
+
+    fun makePathForApk(file: File, partition: Partition) =
+            partition.baseFolder
+                    .resolve(file.nameWithoutExtension)
+                    .resolve(file.name)
+                    .toString()
+}
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt
new file mode 100644
index 0000000..90494c5
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.server.pm.test
+
+import com.android.internal.util.test.SystemPreparer
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.google.common.truth.Truth.assertThat
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class OriginalPackageMigrationTest : BaseHostJUnit4Test() {
+
+    companion object {
+        private const val TEST_PKG_NAME = "com.android.server.pm.test.dummy_app"
+        private const val VERSION_ONE = "PackageManagerDummyAppVersion1.apk"
+        private const val VERSION_TWO = "PackageManagerDummyAppVersion2.apk"
+        private const val VERSION_THREE = "PackageManagerDummyAppVersion3.apk"
+        private const val NEW_PKG = "PackageManagerDummyAppOriginalOverride.apk"
+
+        @get:ClassRule
+        val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
+    }
+
+    private val tempFolder = TemporaryFolder()
+    private val preparer: SystemPreparer = SystemPreparer(tempFolder,
+            SystemPreparer.RebootStrategy.START_STOP, deviceRebootRule) { this.device }
+
+    @get:Rule
+    val rules = RuleChain.outerRule(tempFolder).around(preparer)!!
+
+    @Test
+    fun lowerVersion() {
+        runForApk(VERSION_ONE)
+    }
+
+    @Test
+    fun sameVersion() {
+        runForApk(VERSION_TWO)
+    }
+
+    @Test
+    fun higherVersion() {
+        runForApk(VERSION_THREE)
+    }
+
+    // A bug was found where renamed the package during parsing was leading to an invalid version
+    // code check at scan time. A lower version package was being dropped after reboot. To test
+    // this, the override APK is defined as versionCode 2 and the original package is given
+    // versionCode 1, 2, and 3 from the other methods.
+    private fun runForApk(apk: String) {
+        preparer.pushApk(apk, Partition.SYSTEM)
+                .reboot()
+
+        device.getAppPackageInfo(TEST_PKG_NAME).run {
+            assertThat(codePath).contains(apk.removeSuffix(".apk"))
+        }
+
+        // Ensure data is preserved by writing to the original dataDir
+        val file = tempFolder.newFile().apply { writeText("Test") }
+        device.pushFile(file, "${HostUtils.getDataDir(device, TEST_PKG_NAME)}/files/test.txt")
+
+        preparer.deleteApk(apk, Partition.SYSTEM)
+                .pushApk(NEW_PKG, Partition.SYSTEM)
+                .reboot()
+
+        device.getAppPackageInfo(TEST_PKG_NAME)
+                .run {
+                    assertThat(this.toString()).isNotEmpty()
+                    assertThat(codePath)
+                            .contains(NEW_PKG.removeSuffix(".apk"))
+                }
+
+        // And then reading the data contents back
+        assertThat(device.pullFileContents(
+                "${HostUtils.getDataDir(device, TEST_PKG_NAME)}/files/test.txt"))
+                .isEqualTo("Test")
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt
new file mode 100644
index 0000000..35192a7
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.server.pm.test
+
+import java.nio.file.Path
+import java.nio.file.Paths
+
+// Unfortunately no easy way to access PMS SystemPartitions, so mock them here
+internal enum class Partition(val baseFolder: Path) {
+    SYSTEM("/system/app"),
+    VENDOR("/vendor/app"),
+    PRODUCT("/product/app"),
+    SYSTEM_EXT("/system_ext/app")
+    ;
+
+    constructor(baseFolder: String) : this(Paths.get(baseFolder))
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp
new file mode 100644
index 0000000..9568faa
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp
@@ -0,0 +1,33 @@
+// 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.
+
+android_test_helper_app {
+    name: "PackageManagerDummyAppVersion1",
+    manifest: "AndroidManifestVersion1.xml"
+}
+
+android_test_helper_app {
+    name: "PackageManagerDummyAppVersion2",
+    manifest: "AndroidManifestVersion2.xml"
+}
+
+android_test_helper_app {
+    name: "PackageManagerDummyAppVersion3",
+    manifest: "AndroidManifestVersion3.xml"
+}
+
+android_test_helper_app {
+    name: "PackageManagerDummyAppOriginalOverride",
+    manifest: "AndroidManifestOriginalOverride.xml"
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestOriginalOverride.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestOriginalOverride.xml
new file mode 100644
index 0000000..f16e1bc
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestOriginalOverride.xml
@@ -0,0 +1,25 @@
+<?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.
+  -->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.dummy_app.override"
+    android:versionCode="2"
+    >
+
+    <original-package android:name="com.android.server.pm.test.dummy_app"/>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml
new file mode 100644
index 0000000..d772050
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.dummy_app"
+    android:versionCode="1"
+    />
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml
new file mode 100644
index 0000000..53f836b
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.dummy_app"
+    android:versionCode="2"
+    />
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml
new file mode 100644
index 0000000..90ca9d0
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.dummy_app"
+    android:versionCode="3"
+    />
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index ff34ebd..b4e0f10 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -21,7 +21,7 @@
         "services.core",
         "services.net",
         "service-jobscheduler",
-        "service-permission",
+        "service-permission.impl",
         "service-blobstore",
         "androidx.test.runner",
         "androidx.test.ext.truth",
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index fde40aa..cdafd32 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -63,6 +63,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.AdditionalAnswers.answer;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyLong;
@@ -170,6 +171,7 @@
                 mock(OomAdjProfiler.class));
         doReturn(new ActivityManagerService.ProcessChangeItem()).when(sService)
                 .enqueueProcessChangeItemLocked(anyInt(), anyInt());
+        doReturn(true).when(sService).containsTopUiOrRunningRemoteAnimOrEmptyLocked(any());
         sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList,
                 mock(ActiveUids.class));
         sService.mOomAdjuster.mAdjSeq = 10000;
@@ -266,6 +268,21 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoOne_TopApp_PreemptedByTopUi() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+        doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
+        doReturn(app).when(sService).getTopAppLocked();
+        doReturn(false).when(sService).containsTopUiOrRunningRemoteAnimOrEmptyLocked(eq(app));
+        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
+        doReturn(null).when(sService).getTopAppLocked();
+
+        assertProcStates(app, PROCESS_STATE_TOP, FOREGROUND_APP_ADJ, SCHED_GROUP_DEFAULT);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_DoOne_RunningInstrumentation() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 40a1706..979f4e1 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -44,7 +44,7 @@
         "hamcrest-library",
         "servicestests-utils",
         "service-jobscheduler",
-        "service-permission",
+        "service-permission.impl",
         // TODO: remove once Android migrates to JUnit 4.12,
         // which provides assertThrows
         "testng",
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index a0b9d9d..3167820 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -18,17 +18,24 @@
 import static android.os.UserHandle.USER_SYSTEM;
 
 import static com.android.server.devicepolicy.DpmTestUtils.writeInputStreamToFile;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import static org.junit.Assert.assertArrayEquals;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.ComponentName;
+import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -354,8 +361,7 @@
         prepareAdmin1AsDo();
         prepareAdminAnotherPackageAsPo(COPE_PROFILE_USER_ID);
 
-        final DevicePolicyManagerServiceTestable dpms;
-        dpms = bootDpmsUp();
+        final DevicePolicyManagerServiceTestable dpms = bootDpmsUp();
 
         // DO should still be DO since no migration should happen.
         assertTrue(dpms.mOwners.hasDeviceOwner());
@@ -364,13 +370,12 @@
     @SmallTest
     public void testCompMigrationAffiliated() throws Exception {
         prepareAdmin1AsDo();
-        prepareAdmin1AsPo(COPE_PROFILE_USER_ID);
+        prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R);
 
         // Secure lock screen is needed for password policy APIs to work.
         when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);
 
-        final DevicePolicyManagerServiceTestable dpms;
-        dpms = bootDpmsUp();
+        final DevicePolicyManagerServiceTestable dpms = bootDpmsUp();
 
         // DO should cease to be DO.
         assertFalse(dpms.mOwners.hasDeviceOwner());
@@ -408,6 +413,66 @@
                     dpms.getProfileOwnerAdminLocked(COPE_PROFILE_USER_ID)
                             .getEffectiveRestrictions()
                             .containsKey(UserManager.DISALLOW_CONFIG_DATE_TIME));
+            assertEquals("Personal apps suspension wasn't migrated",
+                    DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED,
+                    dpm.getPersonalAppsSuspendedReasons(admin1));
+        });
+    }
+
+    @SmallTest
+    public void testCompMigration_keepSuspendedAppsWhenDpcIsRPlus() throws Exception {
+        prepareAdmin1AsDo();
+        prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R);
+
+        // Pretend some packages are suspended.
+        when(getServices().packageManagerInternal.isSuspendingAnyPackages(
+                PLATFORM_PACKAGE_NAME, USER_SYSTEM)).thenReturn(true);
+
+        final DevicePolicyManagerServiceTestable dpms = bootDpmsUp();
+
+        verify(getServices().packageManagerInternal, never())
+                .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM);
+
+        sendBroadcastWithUser(dpms, Intent.ACTION_USER_STARTED, USER_SYSTEM);
+
+        // Verify that actual package suspension state is not modified after user start
+        verify(getServices().packageManagerInternal, never())
+                .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM);
+        verify(getServices().ipackageManager, never()).setPackagesSuspendedAsUser(
+                any(), anyBoolean(), any(), any(), any(), any(), anyInt());
+
+        final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext);
+        poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID);
+
+        runAsCaller(poContext, dpms, dpm -> {
+            assertEquals("Personal apps suspension wasn't migrated",
+                    DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY,
+                    dpm.getPersonalAppsSuspendedReasons(admin1));
+        });
+    }
+
+    @SmallTest
+    public void testCompMigration_unsuspendAppsWhenDpcNotRPlus() throws Exception {
+        prepareAdmin1AsDo();
+        prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.Q);
+
+        // Pretend some packages are suspended.
+        when(getServices().packageManagerInternal.isSuspendingAnyPackages(
+                PLATFORM_PACKAGE_NAME, USER_SYSTEM)).thenReturn(true);
+
+        final DevicePolicyManagerServiceTestable dpms = bootDpmsUp();
+
+        // Verify that apps get unsuspended.
+        verify(getServices().packageManagerInternal)
+                .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM);
+
+        final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext);
+        poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID);
+
+        runAsCaller(poContext, dpms, dpm -> {
+            assertEquals("Personal apps weren't unsuspended",
+                    DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED,
+                    dpm.getPersonalAppsSuspendedReasons(admin1));
         });
     }
 
@@ -439,22 +504,23 @@
                         .getAbsoluteFile());
     }
 
-    private void prepareAdmin1AsPo(int profileUserId) throws Exception {
+    private void prepareAdmin1AsPo(int profileUserId, int targetSdk) throws Exception {
         preparePo(profileUserId, admin1, R.raw.comp_profile_owner_same_package,
-                R.raw.comp_policies_profile_same_package, COPE_ADMIN1_APP_ID);
+                R.raw.comp_policies_profile_same_package, COPE_ADMIN1_APP_ID, targetSdk);
     }
 
     private void prepareAdminAnotherPackageAsPo(int profileUserId) throws Exception {
         preparePo(profileUserId, adminAnotherPackage, R.raw.comp_profile_owner_another_package,
-                R.raw.comp_policies_profile_another_package, COPE_ANOTHER_ADMIN_APP_ID);
+                R.raw.comp_policies_profile_another_package, COPE_ANOTHER_ADMIN_APP_ID,
+                Build.VERSION.SDK_INT);
     }
 
     private void preparePo(int profileUserId, ComponentName admin, int profileOwnerXmlResId,
-            int policyXmlResId, int adminAppId) throws Exception {
+            int policyXmlResId, int adminAppId, int targetSdk) throws Exception {
         final File profileDir = getServices().addUser(profileUserId, 0,
                 UserManager.USER_TYPE_PROFILE_MANAGED, USER_SYSTEM /* profile group */);
-        setUpPackageManagerForFakeAdmin(
-                admin, UserHandle.getUid(profileUserId, adminAppId), admin1);
+        setUpPackageManagerForFakeAdmin(admin, UserHandle.getUid(profileUserId, adminAppId),
+                /* enabledSetting =*/ null, targetSdk, admin1);
         writeInputStreamToFile(getRawStream(policyXmlResId),
                 (new File(profileDir, "device_policies.xml")).getAbsoluteFile());
         writeInputStreamToFile(getRawStream(profileOwnerXmlResId),
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 4a77489..daaabf8 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -254,7 +254,7 @@
 
     @Override
     protected void tearDown() throws Exception {
-        flushTasks();
+        flushTasks(dpms);
         getMockTransferMetadataManager().deleteMetadataFile();
         super.tearDown();
     }
@@ -4961,7 +4961,7 @@
 
         // CertificateMonitor.updateInstalledCertificates is called on the background thread,
         // let it finish with system uid, otherwise it will throw and crash.
-        flushTasks();
+        flushTasks(dpms);
 
         mContext.binder.restoreCallingIdentity(ident);
     }
@@ -5459,7 +5459,7 @@
         getServices().injectBroadcast(mServiceContext, new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED)
                 .putExtra(Intent.EXTRA_USER_HANDLE, callerUser.getIdentifier()),
                 callerUser.getIdentifier());
-        flushTasks();
+        flushTasks(dpms);
 
         final List<String> ownerInstalledCaCerts = new ArrayList<>();
 
@@ -5486,7 +5486,7 @@
         getServices().injectBroadcast(mServiceContext, new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED)
                 .putExtra(Intent.EXTRA_USER_HANDLE, callerUser.getIdentifier()),
                 callerUser.getIdentifier());
-        flushTasks();
+        flushTasks(dpms);
 
         // Verify that the CA cert is no longer reported as installed by the Device Owner / Profile
         // Owner.
@@ -5530,7 +5530,7 @@
         getServices().injectBroadcast(mServiceContext, new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED)
                 .putExtra(Intent.EXTRA_USER_HANDLE, callerUser.getIdentifier()),
                 callerUser.getIdentifier());
-        flushTasks();
+        flushTasks(dpms);
 
         // Removing the Profile Owner should clear the information on which CA certs were installed
         runAsCaller(admin1Context, dpms, dpm -> dpm.clearProfileOwner(admin1));
@@ -6311,7 +6311,7 @@
         clearInvocations(getServices().alarmManager);
 
         setUserUnlocked(CALLER_USER_HANDLE, false);
-        sendBroadcastWithUser(Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE);
+        sendBroadcastWithUser(dpms, Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE);
 
         // Verify the alarm was scheduled for time when the warning should be shown.
         verify(getServices().alarmManager, times(1))
@@ -6325,7 +6325,7 @@
 
         // Pretend the alarm went off.
         dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_WARNING_TIME + 10);
-        sendBroadcastWithUser(ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE);
+        sendBroadcastWithUser(dpms, ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE);
 
         // Verify the alarm was scheduled for the actual deadline this time.
         verify(getServices().alarmManager, times(1)).set(anyInt(), eq(PROFILE_OFF_DEADLINE), any());
@@ -6340,7 +6340,7 @@
 
         // Pretend the alarm went off.
         dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_DEADLINE + 10);
-        sendBroadcastWithUser(ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE);
+        sendBroadcastWithUser(dpms, ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE);
 
         // Verify the alarm was not set.
         verifyZeroInteractions(getServices().alarmManager);
@@ -6364,10 +6364,10 @@
 
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         setUserUnlocked(CALLER_USER_HANDLE, false);
-        sendBroadcastWithUser(Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE);
+        sendBroadcastWithUser(dpms, Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE);
         clearInvocations(getServices().alarmManager);
         setUserUnlocked(CALLER_USER_HANDLE, true);
-        sendBroadcastWithUser(Intent.ACTION_USER_UNLOCKED, CALLER_USER_HANDLE);
+        sendBroadcastWithUser(dpms, Intent.ACTION_USER_UNLOCKED, CALLER_USER_HANDLE);
 
         // Verify that the alarm got discharged.
         verify(getServices().alarmManager, times(1)).cancel((PendingIntent) null);
@@ -6384,16 +6384,16 @@
 
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         setUserUnlocked(CALLER_USER_HANDLE, false);
-        sendBroadcastWithUser(Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE);
+        sendBroadcastWithUser(dpms, Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE);
 
         // Pretend the alarm went off.
         dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_WARNING_TIME + 10);
-        sendBroadcastWithUser(ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE);
+        sendBroadcastWithUser(dpms, ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE);
 
         clearInvocations(getServices().alarmManager);
         clearInvocations(getServices().notificationManager);
         setUserUnlocked(CALLER_USER_HANDLE, true);
-        sendBroadcastWithUser(Intent.ACTION_USER_UNLOCKED, CALLER_USER_HANDLE);
+        sendBroadcastWithUser(dpms, Intent.ACTION_USER_UNLOCKED, CALLER_USER_HANDLE);
 
         // Verify that the alarm got discharged.
         verify(getServices().alarmManager, times(1)).cancel((PendingIntent) null);
@@ -6413,24 +6413,24 @@
 
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         setUserUnlocked(CALLER_USER_HANDLE, false);
-        sendBroadcastWithUser(Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE);
+        sendBroadcastWithUser(dpms, Intent.ACTION_USER_STOPPED, CALLER_USER_HANDLE);
 
         // Pretend the alarm went off after the deadline.
         dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_DEADLINE + 10);
-        sendBroadcastWithUser(ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE);
+        sendBroadcastWithUser(dpms, ACTION_PROFILE_OFF_DEADLINE, CALLER_USER_HANDLE);
 
         clearInvocations(getServices().alarmManager);
         clearInvocations(getServices().notificationManager);
         clearInvocations(getServices().ipackageManager);
 
         // Pretend the user clicked on the "apps suspended" notification to turn the profile on.
-        sendBroadcastWithUser(ACTION_TURN_PROFILE_ON_NOTIFICATION, CALLER_USER_HANDLE);
+        sendBroadcastWithUser(dpms, ACTION_TURN_PROFILE_ON_NOTIFICATION, CALLER_USER_HANDLE);
         // Verify that the profile is turned on.
         verify(getServices().userManager, times(1))
                 .requestQuietModeEnabled(eq(false), eq(UserHandle.of(CALLER_USER_HANDLE)));
 
         setUserUnlocked(CALLER_USER_HANDLE, true);
-        sendBroadcastWithUser(Intent.ACTION_USER_UNLOCKED, CALLER_USER_HANDLE);
+        sendBroadcastWithUser(dpms, Intent.ACTION_USER_UNLOCKED, CALLER_USER_HANDLE);
 
         // Verify that the notification is removed (at this point DPC should show it).
         verify(getServices().notificationManager, times(1))
@@ -6454,13 +6454,6 @@
                 .isEqualTo(DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT);
     }
 
-    private void sendBroadcastWithUser(String action, int userHandle) throws Exception {
-        final Intent intent = new Intent(action);
-        intent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle);
-        getServices().injectBroadcast(mServiceContext, intent, userHandle);
-        flushTasks();
-    }
-
     private void setUserUnlocked(int userHandle, boolean unlocked) {
         when(getServices().userManager.isUserUnlocked(eq(userHandle))).thenReturn(unlocked);
     }
@@ -6471,10 +6464,6 @@
 
         when(getServices().userManager.isUserUnlocked()).thenReturn(true);
 
-        // Pretend our admin handles CHECK_POLICY_COMPLIANCE intent.
-        final Intent intent = new Intent(ACTION_CHECK_POLICY_COMPLIANCE);
-        intent.setPackage(admin1.getPackageName());
-
         doReturn(Collections.singletonList(new ResolveInfo()))
                 .when(getServices().packageManager).queryIntentActivitiesAsUser(
                         any(Intent.class), anyInt(), eq(CALLER_USER_HANDLE));
@@ -6674,12 +6663,4 @@
         return new StringParceledListSlice(Arrays.asList(s));
     }
 
-    private void flushTasks() throws Exception {
-        dpms.mHandler.runWithScissors(() -> {}, 0 /*now*/);
-        dpms.mBackgroundHandler.runWithScissors(() -> {}, 0 /*now*/);
-
-        // We can't let exceptions happen on the background thread. Throw them here if they happen
-        // so they still cause the test to fail despite being suppressed.
-        getServices().rethrowBackgroundBroadcastExceptions();
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index 9a1a5fb..41d54e9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -83,6 +83,23 @@
         return mServices;
     }
 
+    protected void sendBroadcastWithUser(DevicePolicyManagerServiceTestable dpms, String action,
+            int userHandle) throws Exception {
+        final Intent intent = new Intent(action);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle);
+        getServices().injectBroadcast(getContext(), intent, userHandle);
+        flushTasks(dpms);
+    }
+
+    protected void flushTasks(DevicePolicyManagerServiceTestable dpms) throws Exception {
+        dpms.mHandler.runWithScissors(() -> { }, 0 /*now*/);
+        dpms.mBackgroundHandler.runWithScissors(() -> { }, 0 /*now*/);
+
+        // We can't let exceptions happen on the background thread. Throw them here if they happen
+        // so they still cause the test to fail despite being suppressed.
+        getServices().rethrowBackgroundBroadcastExceptions();
+    }
+
     protected interface DpmRunnable {
         void run(DevicePolicyManager dpm) throws Exception;
     }
@@ -180,7 +197,7 @@
      * @param copyFromAdmin package information for {@code admin} will be built based on this
      *    component's information.
      */
-    private void setUpPackageManagerForFakeAdmin(ComponentName admin, int packageUid,
+    protected void setUpPackageManagerForFakeAdmin(ComponentName admin, int packageUid,
             Integer enabledSetting, Integer appTargetSdk, ComponentName copyFromAdmin)
             throws Exception {
 
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
index 60104d3..b09a3c3 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
@@ -117,10 +117,10 @@
 
     @Test
     public void testPredictTargets() {
-        mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1"));
-        mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2"));
-        mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3"));
-        mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4"));
+        mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1", 0));
+        mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2", 0));
+        mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3", 0));
+        mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4", 0));
 
         when(mPackageData1.getConversationInfo("sc1")).thenReturn(mock(ConversationInfo.class));
         when(mPackageData1.getConversationInfo("sc2")).thenReturn(mock(ConversationInfo.class));
@@ -165,12 +165,12 @@
 
     @Test
     public void testPredictTargets_reachTargetsLimit() {
-        mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1"));
-        mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2"));
-        mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3"));
-        mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4"));
-        mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc5"));
-        mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc6"));
+        mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1", 0));
+        mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2", 0));
+        mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3", 0));
+        mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4", 0));
+        mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc5", 0));
+        mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc6", 0));
 
         when(mPackageData1.getConversationInfo("sc1")).thenReturn(mock(ConversationInfo.class));
         when(mPackageData1.getConversationInfo("sc2")).thenReturn(mock(ConversationInfo.class));
@@ -250,6 +250,41 @@
     }
 
     @Test
+    public void testPredictTargets_noSharingHistoryRankedByShortcutRank() {
+        mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1", 3));
+        mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2", 2));
+        mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3", 1));
+        mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4", 0));
+
+        when(mPackageData1.getConversationInfo("sc1")).thenReturn(mock(ConversationInfo.class));
+        when(mPackageData1.getConversationInfo("sc2")).thenReturn(mock(ConversationInfo.class));
+        when(mPackageData2.getConversationInfo("sc3")).thenReturn(mock(ConversationInfo.class));
+        // "sc4" does not have a ConversationInfo.
+
+        mPredictor.predictTargets();
+
+        verify(mUpdatePredictionsMethod).accept(mAppTargetCaptor.capture());
+        List<AppTarget> res = mAppTargetCaptor.getValue();
+        assertEquals(4, res.size());
+
+        assertEquals("sc4", res.get(0).getId().getId());
+        assertEquals(CLASS_2, res.get(0).getClassName());
+        assertEquals(PACKAGE_2, res.get(0).getPackageName());
+
+        assertEquals("sc3", res.get(1).getId().getId());
+        assertEquals(CLASS_2, res.get(1).getClassName());
+        assertEquals(PACKAGE_2, res.get(1).getPackageName());
+
+        assertEquals("sc2", res.get(2).getId().getId());
+        assertEquals(CLASS_1, res.get(2).getClassName());
+        assertEquals(PACKAGE_1, res.get(2).getPackageName());
+
+        assertEquals("sc1", res.get(3).getId().getId());
+        assertEquals(CLASS_1, res.get(3).getClassName());
+        assertEquals(PACKAGE_1, res.get(3).getPackageName());
+    }
+
+    @Test
     public void testSortTargets() {
         AppTarget appTarget1 = new AppTarget.Builder(
                 new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
@@ -348,19 +383,20 @@
     }
 
     private static ShareShortcutInfo buildShareShortcut(
-            String packageName, String className, String shortcutId) {
-        ShortcutInfo shortcutInfo = buildShortcut(packageName, shortcutId);
+            String packageName, String className, String shortcutId, int rank) {
+        ShortcutInfo shortcutInfo = buildShortcut(packageName, shortcutId, rank);
         ComponentName componentName = new ComponentName(packageName, className);
         return new ShareShortcutInfo(shortcutInfo, componentName);
     }
 
-    private static ShortcutInfo buildShortcut(String packageName, String shortcutId) {
+    private static ShortcutInfo buildShortcut(String packageName, String shortcutId, int rank) {
         Context mockContext = mock(Context.class);
         when(mockContext.getPackageName()).thenReturn(packageName);
         when(mockContext.getUserId()).thenReturn(USER_ID);
         when(mockContext.getUser()).thenReturn(UserHandle.of(USER_ID));
         ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mockContext, shortcutId)
                 .setShortLabel(shortcutId)
+                .setRank(rank)
                 .setIntent(new Intent("TestIntent"));
         return builder.build();
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
index c55df51..87979fb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
@@ -366,9 +366,75 @@
         for (PackageInfo p : packageInfos) {
             actualPackages.add(p.packageName);
         }
+
+        // Add auto-generated RRO package to expectedPackages since they are not (supposed to be)
+        // in the whitelist but they should be installed.
+        for (PackageInfo p : packageInfos) {
+            if (p.isOverlayPackage()
+                        && UserSystemPackageInstaller.hasAutoGeneratedRROSuffix(p.packageName)
+                        && expectedPackages.contains(p.overlayTarget)) {
+                expectedPackages.add(p.packageName);
+            }
+        }
         checkPackageDifferences(expectedPackages, actualPackages);
     }
 
+    @Test
+    public void testAutoGeneratedRROMatchesSuffix() {
+        final List<PackageInfo> packageInfos = mContext.getPackageManager()
+                .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES);
+
+        Log.v(TAG, "Found total packages: " + packageInfos.size());
+
+        for (PackageInfo p : packageInfos) {
+            if (p.packageName.contains(".auto_generated_rro_")) {
+                assertTrue("Auto-generated RRO package name does not match the suffix: "
+                        + p.packageName,
+                        UserSystemPackageInstaller.hasAutoGeneratedRROSuffix(p.packageName));
+            }
+        }
+    }
+
+    /**
+     * Test that overlay package not in whitelist should be installed for all user at Explicit mode.
+     */
+    @Test
+    public void testInstallOverlayPackagesExplicitMode() {
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
+
+        final String[] userTypes = new String[]{"type"};
+        final long maskOfType = 0b0001L;
+
+        final String packageName1 = "whitelistedPkg";
+        final String packageName2 = "nonWhitelistedPkg";
+        final String overlayName1 = String.format("%s.auto_generated_rro_product__", packageName1);
+        final String overlayName2 = String.format("%s.auto_generated_rro_product__", packageName2);
+
+        final AndroidPackage overlayPackage1 = ((ParsedPackage) PackageImpl.forTesting(overlayName1)
+                .setOverlay(true)
+                .setOverlayTarget(packageName1)
+                .hideAsParsed())
+                .hideAsFinal();
+
+        final AndroidPackage overlayPackage2 = ((ParsedPackage) PackageImpl.forTesting(overlayName2)
+                .setOverlay(true)
+                .setOverlayTarget(packageName2)
+                .hideAsParsed())
+                .hideAsFinal();
+
+        final ArrayMap<String, Long> userTypeWhitelist = new ArrayMap<>();
+        userTypeWhitelist.put(packageName1, maskOfType);
+
+        final Set<String> userWhitelist = new ArraySet<>();
+        userWhitelist.add(packageName1);
+
+        boolean implicit = false;
+        assertTrue("Overlay for package1 should be installed", UserSystemPackageInstaller
+                .shouldInstallPackage(overlayPackage1, userTypeWhitelist, userWhitelist, implicit));
+        assertFalse("Overlay for package2 should not be installed", UserSystemPackageInstaller
+                .shouldInstallPackage(overlayPackage2, userTypeWhitelist, userWhitelist, implicit));
+    }
+
     /** Asserts that actual is a subset of expected. */
     private void checkPackageDifferences(Set<String> expected, Set<String> actual) {
         final Set<String> uniqueToExpected = new ArraySet<>(expected);
diff --git a/services/tests/servicestests/src/com/android/server/textclassifier/FixedSizeQueueTest.java b/services/tests/servicestests/src/com/android/server/textclassifier/FixedSizeQueueTest.java
new file mode 100644
index 0000000..90527b8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/textclassifier/FixedSizeQueueTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.server.textclassifier;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FixedSizeQueueTest {
+
+    @Test
+    public void add_belowMaxCapacity() {
+        FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null);
+        assertThat(queue.size()).isEqualTo(0);
+
+        queue.add(1);
+
+        assertThat(queue.size()).isEqualTo(1);
+        assertThat(queue.poll()).isEqualTo(1);
+    }
+
+    @Test
+    public void add_exceedMaxCapacity() {
+        FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(2, /* onEntryEvictedListener= */ null);
+
+        queue.add(1);
+        queue.add(2);
+        queue.add(3);
+
+        assertThat(queue.size()).isEqualTo(2);
+        assertThat(queue.poll()).isEqualTo(2);
+        assertThat(queue.poll()).isEqualTo(3);
+    }
+
+    @Test
+    public void poll() {
+        FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null);
+
+        queue.add(1);
+
+        assertThat(queue.poll()).isEqualTo(1);
+        assertThat(queue.poll()).isNull();
+    }
+
+    @Test
+    public void remove() {
+        FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null);
+
+        queue.add(1);
+
+        assertThat(queue.remove(1)).isTrue();
+        assertThat(queue.isEmpty()).isTrue();
+    }
+
+    @Test
+    public void remove_noSuchElement() {
+        FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null);
+
+        queue.add(1);
+
+        assertThat(queue.remove(2)).isFalse();
+    }
+
+    @Test
+    public void isEmpty_true() {
+        FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null);
+
+        assertThat(queue.isEmpty()).isTrue();
+    }
+
+    @Test
+    public void isEmpty_false() {
+        FixedSizeQueue<Integer> queue = new FixedSizeQueue<>(1, /* onEntryEvictedListener= */ null);
+
+        queue.add(1);
+
+        assertThat(queue.isEmpty()).isFalse();
+    }
+
+    @Test
+    public void onEntryEvicted() {
+        List<Integer> onEntryEvictedElements = new ArrayList<>();
+        FixedSizeQueue<Integer> queue =
+                new FixedSizeQueue<>(1, onEntryEvictedElements::add);
+
+        queue.add(1);
+        queue.add(2);
+        queue.add(3);
+
+        assertThat(onEntryEvictedElements).containsExactly(1, 2).inOrder();
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index de9b77c..ef4d5db 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -178,6 +178,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -347,6 +348,11 @@
             super.setNotificationAssistantAccessGrantedForUserInternal(assistant, userId, granted);
         }
 
+        @Override
+        protected String[] getStringArrayResource(int key) {
+            return new String[] {PKG_O};
+        }
+
         private void setNotificationAssistantAccessGrantedCallback(
                 @Nullable NotificationAssistantAccessGrantedCallback callback) {
             this.mNotificationAssistantAccessGrantedCallback = callback;
@@ -434,7 +440,8 @@
 
         // Setup managed services
         mListener = mListeners.new ManagedServiceInfo(
-                null, new ComponentName(PKG, "test_class"), mUid, true, null, 0);
+                null, new ComponentName(PKG, "test_class"),
+                UserHandle.getUserId(mUid), true, null, 0);
         ComponentName defaultComponent = ComponentName.unflattenFromString("config/device");
         ArraySet<ComponentName> components = new ArraySet<>();
         components.add(defaultComponent);
@@ -444,7 +451,7 @@
         when(mAssistants.getDefaultComponents()).thenReturn(components);
         when(mAssistants.queryPackageForServices(
                 anyString(), anyInt(), anyInt())).thenReturn(components);
-        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+        when(mListeners.checkServiceTokenLocked(null)).thenReturn(mListener);
         ManagedServices.Config listenerConfig = new ManagedServices.Config();
         listenerConfig.xmlTag = NotificationListeners.TAG_ENABLED_NOTIFICATION_LISTENERS;
         when(mListeners.getConfig()).thenReturn(listenerConfig);
@@ -497,6 +504,10 @@
 
         mBinderService.createNotificationChannels(
                 PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
+        mBinderService.createNotificationChannels(
+                PKG_P, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
+        mBinderService.createNotificationChannels(
+                PKG_O, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
         assertNotNull(mBinderService.getNotificationChannel(
                 PKG, mContext.getUserId(), PKG, TEST_CHANNEL_ID));
         clearInvocations(mRankingHandler);
@@ -583,7 +594,7 @@
                 .setGroupSummary(isSummary);
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id,
                 "tag" + System.currentTimeMillis(), mUid, 0,
-                nb.build(), new UserHandle(mUid), null, 0);
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
 
@@ -603,7 +614,7 @@
             nb.extend(extender);
         }
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
-                nb.build(), new UserHandle(mUid), null, 0);
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
 
@@ -635,7 +646,7 @@
         Notification.Builder nb = getMessageStyleNotifBuilder(addMetadata, groupKey, isSummary);
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id,
                 tag, mUid, 0,
-                nb.build(), new UserHandle(mUid), null, 0);
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
 
@@ -1328,6 +1339,7 @@
     }
 
     @Test
+    @Ignore
     public void testPostCancelPostNotifiesListeners() throws Exception {
         // WHEN a notification is posted
         final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
@@ -1687,7 +1699,7 @@
                         .setSmallIcon(android.R.drawable.sym_def_app_icon)
                         .build();
         StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, null, mUid, 0,
-                n, new UserHandle(mUid), null, 0);
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
@@ -2191,7 +2203,8 @@
     public void testCreateChannelNotifyListener() throws Exception {
         List<String> associations = new ArrayList<>();
         associations.add("a");
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(associations);
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(mTestNotificationChannel.getId()), anyBoolean()))
@@ -2219,7 +2232,8 @@
     public void testCreateChannelGroupNotifyListener() throws Exception {
         List<String> associations = new ArrayList<>();
         associations.add("a");
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(associations);
         mService.setPreferencesHelper(mPreferencesHelper);
         NotificationChannelGroup group1 = new NotificationChannelGroup("a", "b");
         NotificationChannelGroup group2 = new NotificationChannelGroup("n", "m");
@@ -2239,7 +2253,8 @@
     public void testUpdateChannelNotifyListener() throws Exception {
         List<String> associations = new ArrayList<>();
         associations.add("a");
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(associations);
         mService.setPreferencesHelper(mPreferencesHelper);
         mTestNotificationChannel.setLightColor(Color.CYAN);
         when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -2257,7 +2272,8 @@
     public void testDeleteChannelNotifyListener() throws Exception {
         List<String> associations = new ArrayList<>();
         associations.add("a");
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(associations);
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(mTestNotificationChannel.getId()), anyBoolean()))
@@ -2273,7 +2289,8 @@
     public void testDeleteChannelGroupNotifyListener() throws Exception {
         List<String> associations = new ArrayList<>();
         associations.add("a");
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(associations);
         NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c");
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mPreferencesHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt()))
@@ -2290,7 +2307,8 @@
         mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
         associations.add("a");
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(associations);
         when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(mTestNotificationChannel.getId()), anyBoolean()))
                 .thenReturn(mTestNotificationChannel);
@@ -2310,7 +2328,8 @@
     public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(associations);
 
         try {
             mBinderService.updateNotificationChannelFromPrivilegedListener(
@@ -2333,7 +2352,8 @@
         mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
         associations.add("a");
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(associations);
         mListener = mock(ManagedServices.ManagedServiceInfo.class);
         mListener.component = new ComponentName(PKG, PKG);
         when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
@@ -2360,7 +2380,8 @@
         mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
         associations.add("a");
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(associations);
 
         mBinderService.getNotificationChannelsFromPrivilegedListener(
                 null, PKG, Process.myUserHandle());
@@ -2373,7 +2394,8 @@
     public void testGetNotificationChannelFromPrivilegedListener_cdm_noAccess() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(associations);
 
         try {
             mBinderService.getNotificationChannelsFromPrivilegedListener(
@@ -2391,7 +2413,8 @@
     public void testGetNotificationChannelFromPrivilegedListener_assistant_success()
             throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(new ArrayList<>());
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(new ArrayList<>());
         when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
 
         mBinderService.getNotificationChannelsFromPrivilegedListener(
@@ -2405,7 +2428,8 @@
     public void testGetNotificationChannelFromPrivilegedListener_assistant_noAccess()
             throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(new ArrayList<>());
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(new ArrayList<>());
         when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(false);
 
         try {
@@ -2425,7 +2449,8 @@
         mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
         associations.add("a");
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(associations);
         mListener = mock(ManagedServices.ManagedServiceInfo.class);
         when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
         when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
@@ -2447,7 +2472,8 @@
         mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
         associations.add("a");
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(associations);
 
         mBinderService.getNotificationChannelGroupsFromPrivilegedListener(
                 null, PKG, Process.myUserHandle());
@@ -2459,7 +2485,8 @@
     public void testGetNotificationChannelGroupsFromPrivilegedListener_noAccess() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(associations);
 
         try {
             mBinderService.getNotificationChannelGroupsFromPrivilegedListener(
@@ -2476,7 +2503,8 @@
     public void testGetNotificationChannelGroupsFromPrivilegedListener_badUser() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
         List<String> associations = new ArrayList<>();
-        when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+        when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+                .thenReturn(associations);
         mListener = mock(ManagedServices.ManagedServiceInfo.class);
         when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
         when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
@@ -3026,8 +3054,6 @@
         mBinderService.setNotificationAssistantAccessGranted(c, true);
 
         verify(mListeners).migrateToXml();
-        verify(mListeners).notifyNotificationChannelChanged(anyString(), any(), any(),
-                anyInt());
         verify(mConditionProviders).setPackageOrComponentEnabled(
                 anyString(), anyInt(), anyBoolean(), anyBoolean());
         verify(mAssistants).migrateToXml();
@@ -3041,8 +3067,6 @@
         mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true);
 
         verify(mListeners).migrateToXml();
-        verify(mListeners).notifyNotificationChannelChanged(anyString(), any(), any(),
-                anyInt());
         verify(mConditionProviders).setPackageOrComponentEnabled(
                 anyString(), anyInt(), anyBoolean(), anyBoolean());
         verify(mAssistants).migrateToXml();
@@ -3178,7 +3202,7 @@
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
                 "testNoFakeColorizedPermission", mUid, 0,
-                nb.build(), new UserHandle(mUid), null, 0);
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
@@ -3216,7 +3240,7 @@
                 .build();
 
         StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "tag", mUid, 0,
-                n, new UserHandle(mUid), null, 0);
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord otherPackage =
                 new NotificationRecord(mContext, sbn, mTestNotificationChannel);
         mService.addEnqueuedNotification(otherPackage);
@@ -3224,7 +3248,7 @@
 
         // Same notifications are enqueued as posted, everything counts b/c id and tag don't match
         // anything that's currently enqueued or posted
-        int userId = new UserHandle(mUid).getIdentifier();
+        int userId = UserHandle.getUserId(mUid);
         assertEquals(40,
                 mService.getNotificationCountLocked(PKG, userId, 0, null));
         assertEquals(40,
@@ -3441,8 +3465,8 @@
 
         StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9,
                 "testBumpFGImportance_noChannelChangePreOApp",
-                Binder.getCallingUid(), 0, nb.build(), new UserHandle(Binder.getCallingUid()), null,
-                0);
+                Binder.getCallingUid(), 0, nb.build(),
+                UserHandle.getUserHandleForUid(Binder.getCallingUid()), null, 0);
 
         mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), sbn.getOpPkg(),
                 sbn.getTag(), sbn.getId(), sbn.getNotification(), sbn.getUserId());
@@ -3459,7 +3483,7 @@
 
         sbn = new StatusBarNotification(preOPkg, preOPkg, 9,
                 "testBumpFGImportance_noChannelChangePreOApp", Binder.getCallingUid(),
-                0, nb.build(), new UserHandle(Binder.getCallingUid()), null, 0);
+                0, nb.build(), UserHandle.getUserHandleForUid(Binder.getCallingUid()), null, 0);
 
         mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg,
                 "testBumpFGImportance_noChannelChangePreOApp",
@@ -3813,7 +3837,8 @@
                         .addMessage(message1)
                         .addMessage(message2));
         NotificationRecord recordA = new NotificationRecord(mContext, new StatusBarNotification(
-                PKG, PKG, 0, "tag", mUid, 0, nbA.build(), new UserHandle(mUid), null, 0), c);
+                PKG, PKG, 0, "tag", mUid, 0, nbA.build(), UserHandle.getUserHandleForUid(mUid),
+                null, 0), c);
 
         // First post means we grant access to both
         reset(mUgm);
@@ -3831,7 +3856,8 @@
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
                 .setStyle(new Notification.MessagingStyle("").addMessage(message2));
         NotificationRecord recordB = new NotificationRecord(mContext, new StatusBarNotification(PKG,
-                PKG, 0, "tag", mUid, 0, nbB.build(), new UserHandle(mUid), null, 0), c);
+                PKG, 0, "tag", mUid, 0, nbB.build(), UserHandle.getUserHandleForUid(mUid), null, 0),
+                c);
 
         // Update means we drop access to first
         reset(mUgmInternal);
@@ -3870,7 +3896,8 @@
                 .setStyle(new Notification.MessagingStyle("")
                         .addMessage(message1));
         NotificationRecord recordA = new NotificationRecord(mContext, new StatusBarNotification(
-                PKG, PKG, 0, "tag", mUid, 0, nbA.build(), new UserHandle(mUid), null, 0), c);
+                PKG, PKG, 0, "tag", mUid, 0, nbA.build(), UserHandle.getUserHandleForUid(mUid),
+                null, 0), c);
 
         doThrow(new SecurityException("no access")).when(mUgm)
                 .grantUriPermissionFromOwner(
@@ -4029,7 +4056,7 @@
         Notification.Builder nb1 = new Notification.Builder(mContext, "")
                 .setContentTitle("foo");
         StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb1.build(), new UserHandle(mUid), null, 0);
+                nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r1 =
                 new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class));
 
@@ -4037,7 +4064,7 @@
                 .setFlag(FLAG_FOREGROUND_SERVICE, true)
                 .setContentTitle("bar");
         StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb2.build(), new UserHandle(mUid), null, 0);
+                nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r2 =
                 new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class));
 
@@ -4049,14 +4076,14 @@
         Notification.Builder nb1 = new Notification.Builder(mContext, "")
                 .setContentTitle("foo");
         StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb1.build(), new UserHandle(mUid), null, 0);
+                nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r1 =
                 new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class));
 
         Notification.Builder nb2 = new Notification.Builder(mContext, "")
                 .setContentTitle("bar");
         StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb2.build(), new UserHandle(mUid), null, 0);
+                nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r2 =
                 new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class));
 
@@ -4069,7 +4096,7 @@
                 .setStyle(new Notification.InboxStyle()
                     .addLine("line1").addLine("line2"));
         StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb1.build(), new UserHandle(mUid), null, 0);
+                nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r1 =
                 new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class));
 
@@ -4077,7 +4104,7 @@
                 .setStyle(new Notification.InboxStyle()
                         .addLine("line1").addLine("line2_changed"));
         StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb2.build(), new UserHandle(mUid), null, 0);
+                nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r2 =
                 new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class));
 
@@ -4087,7 +4114,7 @@
                 .setStyle(new Notification.InboxStyle()
                         .addLine("line1"));
         StatusBarNotification sbn3 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb3.build(), new UserHandle(mUid), null, 0);
+                nb3.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r3 =
                 new NotificationRecord(mContext, sbn3, mock(NotificationChannel.class));
 
@@ -4097,7 +4124,7 @@
                 .setStyle(new Notification.InboxStyle()
                         .addLine("line1").addLine("line2").addLine("line3"));
         StatusBarNotification sbn4 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb4.build(), new UserHandle(mUid), null, 0);
+                nb4.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r4 =
                 new NotificationRecord(mContext, sbn4, mock(NotificationChannel.class));
 
@@ -4106,7 +4133,7 @@
         Notification.Builder nb5 = new Notification.Builder(mContext, "")
             .setContentText("not an inbox");
         StatusBarNotification sbn5 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb5.build(), new UserHandle(mUid), null, 0);
+                nb5.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r5 =
                 new NotificationRecord(mContext, sbn5, mock(NotificationChannel.class));
 
@@ -4118,14 +4145,14 @@
         Notification.Builder nb1 = new Notification.Builder(mContext, "")
                 .setContentText("foo");
         StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb1.build(), new UserHandle(mUid), null, 0);
+                nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r1 =
                 new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class));
 
         Notification.Builder nb2 = new Notification.Builder(mContext, "")
                 .setContentText("bar");
         StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb2.build(), new UserHandle(mUid), null, 0);
+                nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r2 =
                 new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class));
 
@@ -4137,14 +4164,14 @@
         Notification.Builder nb1 = new Notification.Builder(mContext, "")
                 .setContentText("foo");
         StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb1.build(), new UserHandle(mUid), null, 0);
+                nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r1 =
                 new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class));
 
         Notification.Builder nb2 = new Notification.Builder(mContext, "")
                 .setContentText("foo");
         StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb2.build(), new UserHandle(mUid), null, 0);
+                nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r2 =
                 new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class));
 
@@ -4156,14 +4183,14 @@
         Notification.Builder nb1 = new Notification.Builder(mContext, "")
                 .setContentText(Html.fromHtml("<b>foo</b>"));
         StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb1.build(), new UserHandle(mUid), null, 0);
+                nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r1 =
                 new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class));
 
         Notification.Builder nb2 = new Notification.Builder(mContext, "")
                 .setContentText(Html.fromHtml("<b>foo</b>"));
         StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb2.build(), new UserHandle(mUid), null, 0);
+                nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r2 =
                 new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class));
 
@@ -4175,14 +4202,14 @@
         Notification.Builder nb1 = new Notification.Builder(mContext, "")
                 .setContentText(Html.fromHtml("<b>foo</b>"));
         StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb1.build(), new UserHandle(mUid), null, 0);
+                nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r1 =
                 new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class));
 
         Notification.Builder nb2 = new Notification.Builder(mContext, "")
                 .setContentText(Html.fromHtml("<b>bar</b>"));
         StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb2.build(), new UserHandle(mUid), null, 0);
+                nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r2 =
                 new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class));
 
@@ -4194,14 +4221,14 @@
         Notification.Builder nb1 = new Notification.Builder(mContext, "")
                 .setProgress(100, 90, false);
         StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb1.build(), new UserHandle(mUid), null, 0);
+                nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r1 =
                 new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class));
 
         Notification.Builder nb2 = new Notification.Builder(mContext, "")
                 .setProgress(100, 100, false);
         StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb2.build(), new UserHandle(mUid), null, 0);
+                nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r2 =
                 new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class));
 
@@ -4213,14 +4240,14 @@
         Notification.Builder nb1 = new Notification.Builder(mContext, "")
                 .setProgress(100, 90, false);
         StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb1.build(), new UserHandle(mUid), null, 0);
+                nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r1 =
                 new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class));
 
         Notification.Builder nb2 = new Notification.Builder(mContext, "")
                 .setProgress(100, 91, false);
         StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb2.build(), new UserHandle(mUid), null, 0);
+                nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r2 =
                 new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class));
 
@@ -4232,14 +4259,14 @@
         Notification.Builder nb1 = new Notification.Builder(mContext, "")
                 .setProgress(100, 100, false);
         StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb1.build(), new UserHandle(mUid), null, 0);
+                nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r1 =
                 new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class));
 
         Notification.Builder nb2 = new Notification.Builder(mContext, "")
                 .setProgress(100, 100, false);
         StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb2.build(), new UserHandle(mUid), null, 0);
+                nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r2 =
                 new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class));
 
@@ -4253,7 +4280,7 @@
                 .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
                 .setContentText("foo");
         StatusBarNotification sbn1 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb1.build(), new UserHandle(mUid), null, 0);
+                nb1.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r1 =
                 new NotificationRecord(mContext, sbn1, mock(NotificationChannel.class));
 
@@ -4262,7 +4289,7 @@
                 .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
                 .setContentText("bar");
         StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb2.build(), new UserHandle(mUid), null, 0);
+                nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r2 =
                 new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class));
 
@@ -4276,7 +4303,7 @@
                 .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
                 .setContentText("bar");
         StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
-                nb2.build(), new UserHandle(mUid), null, 0);
+                nb2.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r2 =
                 new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class));
 
@@ -4694,7 +4721,7 @@
         n.flags |= FLAG_FOREGROUND_SERVICE;
 
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 9, null, mUid, 0,
-                n, new UserHandle(mUid), null, 0);
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mService.addEnqueuedNotification(r);
@@ -4713,7 +4740,7 @@
         n.flags |= FLAG_FOREGROUND_SERVICE;
 
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 9, null, mUid, 0,
-                n, new UserHandle(mUid), null, 0);
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mService.addNotification(r);
@@ -5026,7 +5053,7 @@
                         .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
         StatusBarNotification sbn = new StatusBarNotification(PKG, "opPkg", 0, "tag", mUid, 0,
-                nb.build(), new UserHandle(mUid), null, 0);
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r =  new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mService.reportSeen(r);
@@ -5050,7 +5077,7 @@
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, r.getSbn().getId(),
                 r.getSbn().getTag(), mUid, 0,
                 new Notification.Builder(mContext, mTestNotificationChannel.getId()).build(),
-                new UserHandle(mUid), null, 0);
+                UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord update = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
         mService.addEnqueuedNotification(update);
         assertNull(update.getSbn().getNotification().getSmallIcon());
@@ -5084,7 +5111,7 @@
     }
 
     @Test
-    public void testgetNotificationChannels_crossUser() throws Exception {
+    public void testGetNotificationChannels_crossUser() throws Exception {
         // same user no problem
         mBinderService.getNotificationChannels("src", "target", mContext.getUserId());
 
@@ -5273,7 +5300,7 @@
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
                 .setBubbleMetadata(getBubbleMetadata());
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
-                nb.build(), new UserHandle(mUid), null, 0);
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         // Say we're foreground
@@ -5319,7 +5346,7 @@
         nb.setShortcutId(null);
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
                 null, mUid, 0,
-                nb.build(), new UserHandle(mUid), null, 0);
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
@@ -5363,7 +5390,7 @@
 
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
                 "testFlagBubbleNotifs_noFlag_notBubble", mUid, 0,
-                nb.build(), new UserHandle(mUid), null, 0);
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         // Post the notification
@@ -5455,25 +5482,49 @@
     }
 
     @Test
-    public void testCancelNotificationsFromListener_ignoresBubbles() throws Exception {
-        final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel);
-        final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
-        nrBubble.getSbn().getNotification().flags |= FLAG_BUBBLE;
+    public void testCancelNotificationsFromListener_cancelsNonBubble() throws Exception {
+        // Add non-bubble notif
+        final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+        mService.addNotification(nr);
 
-        mService.addNotification(nrNormal);
-        mService.addNotification(nrBubble);
-
-        String[] keys = {nrNormal.getSbn().getKey(), nrBubble.getSbn().getKey()};
+        // Cancel via listener
+        String[] keys = {nr.getSbn().getKey()};
         mService.getBinderService().cancelNotificationsFromListener(null, keys);
         waitForIdle();
 
+        // Notif not active anymore
+        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+        assertEquals(0, notifs.length);
+        assertEquals(0, mService.getNotificationRecordCount());
+        // Cancel event is logged
+        assertEquals(1, mNotificationRecordLogger.numCalls());
+        assertEquals(NotificationRecordLogger.NotificationCancelledEvent
+            .NOTIFICATION_CANCEL_LISTENER_CANCEL, mNotificationRecordLogger.event(0));
+    }
+
+    @Test
+    public void testCancelNotificationsFromListener_suppressesBubble() throws Exception {
+        // Add bubble notif
+        setUpPrefsForBubbles(PKG, mUid,
+            true /* global */,
+            BUBBLE_PREFERENCE_ALL /* app */,
+            true /* channel */);
+        NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag");
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+            nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        // Cancel via listener
+        String[] keys = {nr.getSbn().getKey()};
+        mService.getBinderService().cancelNotificationsFromListener(null, keys);
+        waitForIdle();
+
+        // Bubble notif active and suppressed
         StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
         assertEquals(1, notifs.length);
         assertEquals(1, mService.getNotificationRecordCount());
-
-        assertEquals(1, mNotificationRecordLogger.numCalls());
-        assertEquals(NotificationRecordLogger.NotificationCancelledEvent
-                .NOTIFICATION_CANCEL_LISTENER_CANCEL, mNotificationRecordLogger.event(0));
+        assertTrue(notifs[0].getNotification().getBubbleMetadata().isNotificationSuppressed());
     }
 
     @Test
@@ -6154,7 +6205,7 @@
         nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
         nb.setBubbleMetadata(metadata);
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
-                "tag", mUid, 0, nb.build(), new UserHandle(mUid), null, 0);
+                "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         // Test: Send the bubble notification
@@ -6179,7 +6230,7 @@
         // Test: Remove the shortcut
         when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null);
         launcherAppsCallback.getValue().onShortcutsChanged(PKG, Collections.emptyList(),
-                new UserHandle(mUid));
+                UserHandle.getUserHandleForUid(mUid));
         waitForIdle();
 
         // Verify:
@@ -6214,7 +6265,7 @@
         nb.setShortcutId(shortcutId);
         nb.setBubbleMetadata(metadata);
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
-                "tag", mUid, 0, nb.build(), new UserHandle(mUid), null, 0);
+                "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         // Pretend the shortcut exists
@@ -6662,15 +6713,43 @@
 
     @Test
     public void testRecordMessages_invalidMsg() throws RemoteException {
-        NotificationRecord nr =
-                generateMessageBubbleNotifRecord(mTestNotificationChannel,
-                        "testRecordMessages_invalidMsg");
+        Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+                null /* groupKey */, false /* isSummary */);
+        nb.setShortcutId(null);
+        StatusBarNotification sbn = new StatusBarNotification(PKG_P, PKG_P, 1,
+                "testRecordMessages_invalidMsg", mUid, 0, nb.build(),
+                UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
         when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+        mBinderService.enqueueNotificationWithTag(PKG_P, PKG_P, nr.getSbn().getTag(),
                 nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
-        assertTrue(mBinderService.isInInvalidMsgState(PKG, mUid));
+        assertTrue(mBinderService.isInInvalidMsgState(PKG_P, mUid));
+    }
+
+    @Test
+    public void testRecordMessages_invalidMsg_notMessageStyle() throws RemoteException {
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .setShortcutId(null)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setCategory(Notification.CATEGORY_MESSAGE);
+        StatusBarNotification sbn = new StatusBarNotification(PKG_O, PKG_O, 1,
+                "testRecordMessages_invalidMsg_notMessageStyle", mUid, 0, nb.build(),
+                UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null);
+        mBinderService.enqueueNotificationWithTag(PKG_O, PKG_O, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        // PKG_O is allowed to be in conversation space b/c of override in
+        // TestableNotificationManagerService
+        assertTrue(mBinderService.isInInvalidMsgState(PKG_O, mUid));
     }
 
     @Test
@@ -6678,24 +6757,25 @@
         Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
                 null /* groupKey */, false /* isSummary */);
         nb.setShortcutId(null);
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
-                "testRecordMessages_validMsg", mUid, 0, nb.build(), new UserHandle(mUid), null, 0);
+        StatusBarNotification sbn = new StatusBarNotification(PKG_P, PKG_P, 1,
+                "testRecordMessages_validMsg", mUid, 0, nb.build(),
+                UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+        mBinderService.enqueueNotificationWithTag(PKG_P, PKG_P, nr.getSbn().getTag(),
                 nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
-        assertTrue(mBinderService.isInInvalidMsgState(PKG, mUid));
+        assertTrue(mBinderService.isInInvalidMsgState(PKG_P, mUid));
 
         nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
                 "testRecordMessages_validMsg");
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+        mBinderService.enqueueNotificationWithTag(PKG_P, PKG_P, nr.getSbn().getTag(),
                 nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
         waitForIdle();
 
-        assertFalse(mBinderService.isInInvalidMsgState(PKG, mUid));
+        assertFalse(mBinderService.isInInvalidMsgState(PKG_P, mUid));
     }
 
     @Test
@@ -6715,7 +6795,7 @@
         nb.setShortcutId(null);
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
                 "testRecordMessages_invalidMsg_afterValidMsg_2", mUid, 0, nb.build(),
-                new UserHandle(mUid), null, 0);
+                UserHandle.getUserHandleForUid(mUid), null, 0);
          nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java b/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java
index f4f64d7..89adc72 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java
@@ -88,11 +88,21 @@
             return index < mValues.size() ? mValues.get(index) : null;
         }
 
-        /** useful to make assertTrue() statemetns more readable. */
+        /** useful to make assertTrue() statements more readable. */
         public boolean getBoolean(int index) {
             return (Boolean) mValues.get(index);
         }
 
+        /** useful to make assertTrue() statements more readable. */
+        public int getInt(int index) {
+            return (Integer) mValues.get(index);
+        }
+
+        /** useful to make assertTrue() statements more readable. */
+        public String getString(int index) {
+            return (String) mValues.get(index);
+        }
+
         private void addValue(Object value) {
             mLastIndex = mValues.size();
             mValues.add(value);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index f7b435e..013a994 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -101,11 +101,39 @@
 
         Policy expectedPolicy = new Policy(priorityCategories, priorityCallSenders,
                 priorityMessageSenders, suppressedVisualEffects, 0, priorityConversationsSenders);
-
         assertEquals(expectedPolicy, config.toNotificationPolicy(zenPolicy));
     }
 
     @Test
+    public void testZenConfigToZenPolicy() {
+        ZenPolicy expected = new ZenPolicy.Builder()
+                .allowAlarms(true)
+                .allowReminders(true)
+                .allowEvents(true)
+                .showLights(false)
+                .showBadges(false)
+                .showInAmbientDisplay(false)
+                .build();
+
+        ZenModeConfig config = getMutedAllConfig();
+        config.allowAlarms = true;
+        config.allowReminders = true;
+        config.allowEvents = true;
+        config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE;
+        config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS;
+        config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT;
+        ZenPolicy actual = config.toZenPolicy();
+
+        assertEquals(expected.getVisualEffectBadge(), actual.getVisualEffectBadge());
+        assertEquals(expected.getPriorityCategoryAlarms(), actual.getPriorityCategoryAlarms());
+        assertEquals(expected.getPriorityCategoryReminders(),
+                actual.getPriorityCategoryReminders());
+        assertEquals(expected.getPriorityCategoryEvents(), actual.getPriorityCategoryEvents());
+        assertEquals(expected.getVisualEffectLights(), actual.getVisualEffectLights());
+        assertEquals(expected.getVisualEffectAmbient(), actual.getVisualEffectAmbient());
+    }
+
+    @Test
     public void testPriorityOnlyMutingAll() {
         ZenModeConfig config = getMutedAllConfig();
         assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 108af89..3c7206f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -27,16 +27,25 @@
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM;
 import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
-import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS;
 import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_STARRED;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+
+import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID;
+import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
+import static com.android.os.AtomsProto.DNDModeProto.CHANNELS_BYPASSING_FIELD_NUMBER;
+import static com.android.os.AtomsProto.DNDModeProto.ENABLED_FIELD_NUMBER;
+import static com.android.os.AtomsProto.DNDModeProto.ID_FIELD_NUMBER;
+import static com.android.os.AtomsProto.DNDModeProto.UID_FIELD_NUMBER;
+import static com.android.os.AtomsProto.DNDModeProto.ZEN_MODE_FIELD_NUMBER;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.fail;
 
 import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.any;
@@ -70,10 +79,12 @@
 import android.media.AudioSystem;
 import android.media.VolumePolicy;
 import android.net.Uri;
+import android.os.Process;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.service.notification.Condition;
+import android.service.notification.DNDModeProto;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ScheduleInfo;
 import android.service.notification.ZenPolicy;
@@ -82,6 +93,7 @@
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.StatsEvent;
 import android.util.Xml;
 
 import com.android.internal.R;
@@ -106,6 +118,9 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Objects;
 
 @SmallTest
@@ -115,6 +130,7 @@
 
     private static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE";
     private static final String SCHEDULE_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE";
+    private static final int ZEN_MODE_FOR_TESTING = 99;
 
     ConditionProviders mConditionProviders;
     @Mock NotificationManager mNotificationManager;
@@ -124,6 +140,7 @@
     private Context mContext;
     private ContentResolver mContentResolver;
     @Mock AppOpsManager mAppOps;
+    private WrappedSysUiStatsEvent.WrappedBuilderFactory mStatsEventBuilderFactory;
 
     @Before
     public void setUp() {
@@ -140,6 +157,7 @@
             Log.d("ZenModeHelperTest", "Couldn't mock default zen mode config xml file err=" +
                     e.toString());
         }
+        mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory();
 
         when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOps);
         when(mContext.getSystemService(NotificationManager.class)).thenReturn(mNotificationManager);
@@ -147,7 +165,7 @@
                 AppGlobals.getPackageManager());
         mConditionProviders.addSystemProvider(new CountdownConditionProvider());
         mZenModeHelperSpy = spy(new ZenModeHelper(mContext, mTestableLooper.getLooper(),
-                mConditionProviders));
+                mConditionProviders, mStatsEventBuilderFactory));
     }
 
     private XmlResourceParser getDefaultConfigParser() throws IOException, XmlPullParserException {
@@ -212,6 +230,10 @@
     }
 
     private ArrayMap<String, ZenModeConfig.ZenRule> getCustomAutomaticRules() {
+        return getCustomAutomaticRules(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+    }
+
+    private ArrayMap<String, ZenModeConfig.ZenRule> getCustomAutomaticRules(int zenMode) {
         ArrayMap<String, ZenModeConfig.ZenRule> automaticRules = new ArrayMap<>();
         ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule();
         final ScheduleInfo customRuleInfo = new ScheduleInfo();
@@ -219,10 +241,10 @@
         customRule.creationTime = 0;
         customRule.id = "customRule";
         customRule.name = "Custom Rule";
-        customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        customRule.zenMode = zenMode;
         customRule.conditionId = ZenModeConfig.toScheduleConditionId(customRuleInfo);
         customRule.configurationActivity =
-                new ComponentName("android", "ScheduleConditionProvider");
+                new ComponentName("not.android", "ScheduleConditionProvider");
         customRule.pkg = customRule.configurationActivity.getPackageName();
         automaticRules.put("customRule", customRule);
         return automaticRules;
@@ -244,7 +266,7 @@
 
     @Test
     public void testZenOn_NotificationApplied() {
-        mZenModeHelperSpy.mZenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         // The most permissive policy
         mZenModeHelperSpy.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS |
                 PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES
@@ -267,7 +289,7 @@
 
     @Test
     public void testZenOn_StarredCallers_CallTypesBlocked() {
-        mZenModeHelperSpy.mZenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         // The most permissive policy
         mZenModeHelperSpy.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS |
                 PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES
@@ -287,7 +309,7 @@
 
     @Test
     public void testZenOn_AllCallers_CallTypesAllowed() {
-        mZenModeHelperSpy.mZenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         // The most permissive policy
         mZenModeHelperSpy.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS |
                 PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES
@@ -307,7 +329,7 @@
 
     @Test
     public void testZenOn_AllowAlarmsMedia_NoAlarmMediaMuteApplied() {
-        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelperSpy.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS |
                 PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0);
 
@@ -320,7 +342,7 @@
 
     @Test
     public void testZenOn_DisallowAlarmsMedia_AlarmMediaMuteApplied() {
-        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelperSpy.applyRestrictions();
         verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true, true,
@@ -406,7 +428,7 @@
     public void testZenAllCannotBypass() {
         // Only audio attributes with SUPPRESIBLE_NEVER can bypass
         // with special case USAGE_ASSISTANCE_SONIFICATION
-        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelperSpy.applyRestrictions();
 
@@ -428,7 +450,7 @@
     @Test
     public void testApplyRestrictions_whitelist_priorityOnlyMode() {
         mZenModeHelperSpy.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
-        mZenModeHelperSpy.mZenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelperSpy.applyRestrictions();
 
@@ -479,7 +501,7 @@
         Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
         mZenModeHelperSpy.mIsBootComplete = true;
         mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
-        mZenModeHelperSpy.setZenModeSetting(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+        mZenModeHelperSpy.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
         verify(mZenModeHelperSpy, times(1)).createZenUpgradeNotification();
         verify(mNotificationManager, times(1)).notify(eq(ZenModeHelper.TAG),
@@ -494,7 +516,7 @@
         Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
         Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
         mZenModeHelperSpy.mIsBootComplete = true;
-        mZenModeHelperSpy.setZenModeSetting(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+        mZenModeHelperSpy.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
         verify(mZenModeHelperSpy, never()).createZenUpgradeNotification();
         verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
@@ -507,7 +529,7 @@
         Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
         Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 1);
         mZenModeHelperSpy.mIsBootComplete = true;
-        mZenModeHelperSpy.setZenModeSetting(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+        mZenModeHelperSpy.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
         verify(mZenModeHelperSpy, never()).createZenUpgradeNotification();
         verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
@@ -524,7 +546,7 @@
         // 1. Current ringer is normal
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         // Set zen to priority-only with all notification sounds muted (so ringer will be muted)
-        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelperSpy.mConfig.allowReminders = false;
         mZenModeHelperSpy.mConfig.allowCalls = false;
         mZenModeHelperSpy.mConfig.allowMessages = false;
@@ -568,7 +590,7 @@
         // ringtone, notification and system streams are affected by ringer mode
         mZenModeHelperSpy.mConfig.allowAlarms = true;
         mZenModeHelperSpy.mConfig.allowReminders = true;
-        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerMuted =
                 mZenModeHelperSpy.new RingerModeDelegate();
 
@@ -615,7 +637,7 @@
 
         // 1. Current ringer is normal
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelperSpy.mConfig.allowReminders = true;
 
         // 2. apply priority only zen - verify ringer is normal
@@ -640,7 +662,7 @@
 
         // 1. Current ringer is silent
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
-        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelperSpy.mConfig.allowReminders = true;
 
         // 2. apply priority only zen - verify ringer is silent
@@ -666,7 +688,7 @@
         // 1. Current ringer is normal
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         // Set zen to priority-only with all notification sounds muted (so ringer will be muted)
-        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelperSpy.mConfig.allowReminders = true;
 
         // 2. apply priority only zen - verify zen will still be normal
@@ -728,7 +750,7 @@
 
         // apply zen off multiple times - verify ringer is not set to normal
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
-        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
             mZenModeHelperSpy.evaluateZenMode("test", true);
@@ -756,7 +778,7 @@
 
         // apply zen off multiple times - verify ringer is not set to normal
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
-        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
             mZenModeHelperSpy.evaluateZenMode("test", true);
@@ -768,7 +790,7 @@
 
     @Test
     public void testParcelConfig() {
-        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelperSpy.mConfig.allowAlarms = false;
         mZenModeHelperSpy.mConfig.allowMedia = false;
         mZenModeHelperSpy.mConfig.allowSystem = false;
@@ -792,7 +814,7 @@
 
     @Test
     public void testWriteXml() throws Exception {
-        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelperSpy.mConfig.allowAlarms = false;
         mZenModeHelperSpy.mConfig.allowMedia = false;
         mZenModeHelperSpy.mConfig.allowSystem = false;
@@ -806,7 +828,7 @@
         mZenModeHelperSpy.mConfig.suppressedVisualEffects = SUPPRESSED_EFFECT_BADGE;
         mZenModeHelperSpy.mConfig.manualRule = new ZenModeConfig.ZenRule();
         mZenModeHelperSpy.mConfig.manualRule.zenMode =
-                Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+                ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelperSpy.mConfig.manualRule.component = new ComponentName("a", "a");
         mZenModeHelperSpy.mConfig.manualRule.pkg = "a";
         mZenModeHelperSpy.mConfig.manualRule.enabled = true;
@@ -822,6 +844,102 @@
     }
 
     @Test
+    public void testProto() {
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mConfig.manualRule = new ZenModeConfig.ZenRule();
+
+        int n = mZenModeHelperSpy.mConfig.automaticRules.size();
+        List<String> ids = new ArrayList<>(n);
+        for (ZenModeConfig.ZenRule rule : mZenModeHelperSpy.mConfig.automaticRules.values()) {
+            ids.add(rule.id);
+        }
+        ids.add("");
+
+        List<StatsEvent> events = new LinkedList<>();
+        mZenModeHelperSpy.pullRules(events);
+        assertEquals(n + 1, events.size());
+        for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
+            if (builder.getAtomId() == DND_MODE_RULE) {
+                if (builder.getInt(ZEN_MODE_FIELD_NUMBER) == DNDModeProto.ROOT_CONFIG) {
+                    assertTrue(builder.getBoolean(ENABLED_FIELD_NUMBER));
+                    assertFalse(builder.getBoolean(CHANNELS_BYPASSING_FIELD_NUMBER));
+                }
+                assertEquals(Process.SYSTEM_UID, builder.getInt(UID_FIELD_NUMBER));
+                assertTrue(builder.getBooleanAnnotation(UID_FIELD_NUMBER, ANNOTATION_ID_IS_UID));
+                String name = (String) builder.getValue(ID_FIELD_NUMBER);
+                assertTrue("unexpected rule id", ids.contains(name));
+                ids.remove(name);
+            } else {
+                fail("unexpected atom id: " + builder.getAtomId());
+            }
+        }
+        assertEquals("extra rule in output", 0, ids.size());
+    }
+
+    @Test
+    public void testProtoWithAutoRule() throws Exception {
+        setupZenConfig();
+        // one enabled automatic rule
+        mZenModeHelperSpy.mConfig.automaticRules = getCustomAutomaticRules(ZEN_MODE_FOR_TESTING);
+
+        List<StatsEvent> events = new LinkedList<>();
+        mZenModeHelperSpy.pullRules(events);
+
+        boolean foundCustomEvent = false;
+        for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
+            if (builder.getAtomId() == DND_MODE_RULE) {
+                if (ZEN_MODE_FOR_TESTING == builder.getInt(ZEN_MODE_FIELD_NUMBER)) {
+                    foundCustomEvent = true;
+                    assertEquals(0, builder.getInt(UID_FIELD_NUMBER));
+                    assertTrue(builder.getBoolean(ENABLED_FIELD_NUMBER));
+                }
+            } else {
+                fail("unexpected atom id: " + builder.getAtomId());
+            }
+        }
+        assertTrue("couldn't find custom rule", foundCustomEvent);
+    }
+
+    @Test
+    public void testProtoRedactsIds() throws Exception {
+        setupZenConfig();
+        // one enabled automatic rule
+        mZenModeHelperSpy.mConfig.automaticRules = getCustomAutomaticRules();
+
+        List<StatsEvent> events = new LinkedList<>();
+        mZenModeHelperSpy.pullRules(events);
+
+        boolean foundCustomEvent = false;
+        for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
+            if (builder.getAtomId() == DND_MODE_RULE
+                    && "customRule".equals(builder.getString(ID_FIELD_NUMBER))) {
+                fail("non-default IDs should be redacted");
+            }
+        }
+    }
+
+    @Test
+    public void testProtoWithManualRule() throws Exception {
+        setupZenConfig();
+        mZenModeHelperSpy.mConfig.automaticRules = getCustomAutomaticRules();
+        mZenModeHelperSpy.mConfig.manualRule = new ZenModeConfig.ZenRule();
+        mZenModeHelperSpy.mConfig.manualRule.enabled = true;
+        mZenModeHelperSpy.mConfig.manualRule.enabler = "com.enabler";
+
+        List<StatsEvent> events = new LinkedList<>();
+        mZenModeHelperSpy.pullRules(events);
+
+        boolean foundManualRule = false;
+        for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
+            if (builder.getAtomId() == DND_MODE_RULE
+                    && ZenModeConfig.MANUAL_RULE_ID.equals(builder.getString(ID_FIELD_NUMBER))) {
+                assertEquals(0, builder.getInt(UID_FIELD_NUMBER));
+                foundManualRule = true;
+            }
+        }
+        assertTrue("couldn't find manual rule", foundManualRule);    }
+
+    @Test
     public void testWriteXml_onlyBackupsTargetUser() throws Exception {
         // Setup configs for user 10 and 11.
         setupZenConfig();
@@ -906,7 +1024,7 @@
         customRule.creationTime = 0;
         customRule.id = "customRule";
         customRule.name = "Custom Rule";
-        customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         customRule.conditionId = ZenModeConfig.toScheduleConditionId(customRuleInfo);
         customRule.configurationActivity =
                 new ComponentName("android", "ScheduleConditionProvider");
@@ -951,7 +1069,7 @@
         customRule.creationTime = 0;
         customRule.id = ruleId;
         customRule.name = "Custom Rule";
-        customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         customRule.conditionId = ZenModeConfig.toScheduleConditionId(customRuleInfo);
         customRule.configurationActivity =
                 new ComponentName("android", "ScheduleConditionProvider");
@@ -986,7 +1104,7 @@
         final ScheduleInfo weeknights = new ScheduleInfo();
         customRule.enabled = true;
         customRule.name = "Custom Rule";
-        customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         customRule.conditionId = ZenModeConfig.toScheduleConditionId(weeknights);
         customRule.component = new ComponentName("android", "ScheduleConditionProvider");
         enabledAutoRule.put("customRule", customRule);
@@ -1151,7 +1269,7 @@
         final ScheduleInfo weeknights = new ScheduleInfo();
         customRule.enabled = false;
         customRule.name = "Custom Rule";
-        customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         customRule.conditionId = ZenModeConfig.toScheduleConditionId(weeknights);
         customRule.component = new ComponentName("android", "ScheduleConditionProvider");
         disabledAutoRule.put("customRule", customRule);
@@ -1187,7 +1305,7 @@
         final ScheduleInfo customRuleInfo = new ScheduleInfo();
         customRule.enabled = false;
         customRule.name = "Custom Rule";
-        customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         customRule.conditionId = ZenModeConfig.toScheduleConditionId(customRuleInfo);
         customRule.component = new ComponentName("android", "ScheduleConditionProvider");
         customRule.zenPolicy = new ZenPolicy.Builder()
@@ -1200,7 +1318,7 @@
         final ScheduleInfo defaultScheduleRuleInfo = new ScheduleInfo();
         defaultScheduleRule.enabled = false;
         defaultScheduleRule.name = "Default Schedule Rule";
-        defaultScheduleRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        defaultScheduleRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         defaultScheduleRule.conditionId = ZenModeConfig.toScheduleConditionId(
                 defaultScheduleRuleInfo);
         customRule.component = new ComponentName("android", "ScheduleConditionProvider");
@@ -1238,7 +1356,7 @@
         final ScheduleInfo customRuleInfo = new ScheduleInfo();
         customRule.enabled = false;
         customRule.name = "Custom Rule";
-        customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         customRule.conditionId = ZenModeConfig.toScheduleConditionId(customRuleInfo);
         customRule.component = new ComponentName("android", "ScheduleConditionProvider");
         customRule.zenPolicy = new ZenPolicy.Builder()
@@ -1251,7 +1369,7 @@
         final ScheduleInfo defaultScheduleRuleInfo = new ScheduleInfo();
         defaultScheduleRule.enabled = false;
         defaultScheduleRule.name = "Default Schedule Rule";
-        defaultScheduleRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        defaultScheduleRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         defaultScheduleRule.conditionId = ZenModeConfig.toScheduleConditionId(
                 defaultScheduleRuleInfo);
         defaultScheduleRule.id = ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID;
@@ -1265,7 +1383,7 @@
         final ScheduleInfo defaultEventRuleInfo = new ScheduleInfo();
         defaultEventRule.enabled = false;
         defaultEventRule.name = "Default Event Rule";
-        defaultEventRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        defaultEventRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         defaultEventRule.conditionId = ZenModeConfig.toScheduleConditionId(
                 defaultEventRuleInfo);
         defaultEventRule.id = ZenModeConfig.EVENTS_DEFAULT_RULE_ID;
@@ -1295,6 +1413,10 @@
         assertTrue(rules.containsKey("customRule"));
 
         setupZenConfigMaintained();
+
+        List<StatsEvent> events = new LinkedList<>();
+        mZenModeHelperSpy.pullRules(events);
+        assertEquals(4, events.size());
     }
 
     @Test
@@ -1323,10 +1445,12 @@
 
     @Test
     public void testEmptyDefaultRulesMap() {
+        List<StatsEvent> events = new LinkedList<>();
         ZenModeConfig config = new ZenModeConfig();
         config.automaticRules = new ArrayMap<>();
         mZenModeHelperSpy.mConfig = config;
         mZenModeHelperSpy.updateDefaultZenRules(); // shouldn't throw null pointer
+        mZenModeHelperSpy.pullRules(events); // shouldn't throw null pointer
     }
 
     @Test
@@ -1342,7 +1466,7 @@
         updatedDefaultRule.creationTime = 0;
         updatedDefaultRule.id = SCHEDULE_DEFAULT_RULE_ID;
         updatedDefaultRule.name = "Schedule Default Rule";
-        updatedDefaultRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        updatedDefaultRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         updatedDefaultRule.conditionId = ZenModeConfig.toScheduleConditionId(new ScheduleInfo());
         updatedDefaultRule.component = new ComponentName("android", "ScheduleConditionProvider");
 
@@ -1368,7 +1492,7 @@
         updatedDefaultRule.creationTime = 0;
         updatedDefaultRule.id = SCHEDULE_DEFAULT_RULE_ID;
         updatedDefaultRule.name = "Schedule Default Rule";
-        updatedDefaultRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        updatedDefaultRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         updatedDefaultRule.conditionId = ZenModeConfig.toScheduleConditionId(new ScheduleInfo());
         updatedDefaultRule.component = new ComponentName("android", "ScheduleConditionProvider");
 
@@ -1395,7 +1519,7 @@
         customDefaultRule.creationTime = 0;
         customDefaultRule.id = SCHEDULE_DEFAULT_RULE_ID;
         customDefaultRule.name = "Schedule Default Rule";
-        customDefaultRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        customDefaultRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         customDefaultRule.conditionId = ZenModeConfig.toScheduleConditionId(new ScheduleInfo());
         customDefaultRule.component = new ComponentName("android", "ScheduleConditionProvider");
 
@@ -1433,7 +1557,7 @@
     }
 
     private void setupZenConfig() {
-        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelperSpy.mConfig.allowAlarms = false;
         mZenModeHelperSpy.mConfig.allowMedia = false;
         mZenModeHelperSpy.mConfig.allowSystem = false;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index a0ee417..b16ca8b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -17,14 +17,18 @@
 package com.android.server.notification;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.fail;
 
 import android.service.notification.ZenPolicy;
+import android.service.notification.nano.DNDPolicyProto;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
 
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -190,6 +194,7 @@
         ZenPolicy policy = builder.build();
         assertEquals(ZenPolicy.STATE_UNSET, policy.getPriorityCategoryMessages());
         assertEquals(ZenPolicy.PEOPLE_TYPE_UNSET, policy.getPriorityMessageSenders());
+        assertProtoMatches(policy, policy.toProto());
     }
 
     @Test
@@ -201,6 +206,7 @@
         ZenPolicy policy = builder.build();
         assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryCalls());
         assertEquals(ZenPolicy.PEOPLE_TYPE_ANYONE, policy.getPriorityCallSenders());
+        assertProtoMatches(policy, policy.toProto());
     }
 
     @Test
@@ -210,6 +216,7 @@
         ZenPolicy policy = builder.build();
         assertAllPriorityCategoriesUnsetExcept(policy, -1);
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
     }
 
     @Test
@@ -221,12 +228,14 @@
         assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_REMINDERS);
         assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryReminders());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
 
         builder.allowReminders(false);
         policy = builder.build();
         assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_REMINDERS);
         assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryReminders());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
     }
 
     @Test
@@ -238,12 +247,14 @@
         assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_EVENTS);
         assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryEvents());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
 
         builder.allowEvents(false);
         policy = builder.build();
         assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_EVENTS);
         assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryEvents());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
     }
 
     @Test
@@ -256,6 +267,7 @@
         assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryMessages());
         assertEquals(ZenPolicy.PEOPLE_TYPE_ANYONE, policy.getPriorityMessageSenders());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
 
         builder.allowMessages(ZenPolicy.PEOPLE_TYPE_CONTACTS);
         policy = builder.build();
@@ -263,6 +275,7 @@
         assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryMessages());
         assertEquals(ZenPolicy.PEOPLE_TYPE_CONTACTS, policy.getPriorityMessageSenders());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
 
         builder.allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED);
         policy = builder.build();
@@ -270,6 +283,7 @@
         assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryMessages());
         assertEquals(ZenPolicy.PEOPLE_TYPE_STARRED, policy.getPriorityMessageSenders());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
 
         builder.allowMessages(ZenPolicy.PEOPLE_TYPE_NONE);
         policy = builder.build();
@@ -277,11 +291,13 @@
         assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryMessages());
         assertEquals(ZenPolicy.PEOPLE_TYPE_NONE, policy.getPriorityMessageSenders());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
 
         builder.allowMessages(ZenPolicy.PEOPLE_TYPE_UNSET);
         policy = builder.build();
         assertAllPriorityCategoriesUnsetExcept(policy, -1);
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
     }
 
     @Test
@@ -294,6 +310,7 @@
         assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryCalls());
         assertEquals(ZenPolicy.PEOPLE_TYPE_ANYONE, policy.getPriorityCallSenders());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
 
         builder.allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS);
         policy = builder.build();
@@ -301,6 +318,7 @@
         assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryCalls());
         assertEquals(ZenPolicy.PEOPLE_TYPE_CONTACTS, policy.getPriorityCallSenders());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
 
         builder.allowCalls(ZenPolicy.PEOPLE_TYPE_STARRED);
         policy = builder.build();
@@ -308,6 +326,7 @@
         assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryCalls());
         assertEquals(ZenPolicy.PEOPLE_TYPE_STARRED, policy.getPriorityCallSenders());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
 
         builder.allowCalls(ZenPolicy.PEOPLE_TYPE_NONE);
         policy = builder.build();
@@ -315,11 +334,13 @@
         assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryCalls());
         assertEquals(ZenPolicy.PEOPLE_TYPE_NONE, policy.getPriorityCallSenders());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
 
         builder.allowCalls(ZenPolicy.PEOPLE_TYPE_UNSET);
         policy = builder.build();
         assertAllPriorityCategoriesUnsetExcept(policy, -1);
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
     }
 
     @Test
@@ -331,12 +352,14 @@
         assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS);
         assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryRepeatCallers());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
 
         builder.allowRepeatCallers(false);
         policy = builder.build();
         assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS);
         assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryRepeatCallers());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
     }
 
     @Test
@@ -348,12 +371,14 @@
         assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_ALARMS);
         assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryAlarms());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
 
         builder.allowAlarms(false);
         policy = builder.build();
         assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_ALARMS);
         assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryAlarms());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
     }
 
     @Test
@@ -365,12 +390,14 @@
         assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_MEDIA);
         assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryMedia());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
 
         builder.allowMedia(false);
         policy = builder.build();
         assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_MEDIA);
         assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryMedia());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
     }
 
     @Test
@@ -382,12 +409,119 @@
         assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_SYSTEM);
         assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategorySystem());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
 
         builder.allowSystem(false);
         policy = builder.build();
         assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_SYSTEM);
         assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategorySystem());
         assertAllVisualEffectsUnsetExcept(policy, -1);
+        assertProtoMatches(policy, policy.toProto());
+    }
+
+    @Test
+    public void tesShowFullScreenIntent() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+
+        builder.showFullScreenIntent(true);
+        ZenPolicy policy = builder.build();
+        assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_FULL_SCREEN_INTENT);
+        assertProtoMatches(policy, policy.toProto());
+
+        builder.showFullScreenIntent(false);
+        policy = builder.build();
+        assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_FULL_SCREEN_INTENT);
+        assertProtoMatches(policy, policy.toProto());
+    }
+
+    @Test
+    public void tesShowLights() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+
+        builder.showLights(true);
+        ZenPolicy policy = builder.build();
+        assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_LIGHTS);
+        assertProtoMatches(policy, policy.toProto());
+
+        builder.showLights(false);
+        policy = builder.build();
+        assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_LIGHTS);
+        assertProtoMatches(policy, policy.toProto());
+    }
+
+    @Test
+    public void tesShowPeeking() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+
+        builder.showPeeking(true);
+        ZenPolicy policy = builder.build();
+        assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_PEEK);
+        assertProtoMatches(policy, policy.toProto());
+
+        builder.showPeeking(false);
+        policy = builder.build();
+        assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_PEEK);
+        assertProtoMatches(policy, policy.toProto());
+    }
+
+    @Test
+    public void tesShowStatusBarIcons() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+
+        builder.showStatusBarIcons(true);
+        ZenPolicy policy = builder.build();
+        assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_STATUS_BAR);
+        assertProtoMatches(policy, policy.toProto());
+
+        builder.showStatusBarIcons(false);
+        policy = builder.build();
+        assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_STATUS_BAR);
+        assertProtoMatches(policy, policy.toProto());
+    }
+
+    @Test
+    public void tesShowBadges() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+
+        builder.showBadges(true);
+        ZenPolicy policy = builder.build();
+        assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_BADGE);
+        assertProtoMatches(policy, policy.toProto());
+
+        builder.showBadges(false);
+        policy = builder.build();
+        assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_BADGE);
+        assertProtoMatches(policy, policy.toProto());
+    }
+
+    @Test
+    public void tesShowInAmbientDisplay() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+
+        builder.showInAmbientDisplay(true);
+        ZenPolicy policy = builder.build();
+        assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_AMBIENT);
+        assertProtoMatches(policy, policy.toProto());
+
+        builder.showInAmbientDisplay(false);
+        policy = builder.build();
+        assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_AMBIENT);
+        assertProtoMatches(policy, policy.toProto());
+    }
+
+    @Test
+    public void tesShowInNotificationList() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+
+        builder.showInNotificationList(true);
+        ZenPolicy policy = builder.build();
+        assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST);
+        assertProtoMatches(policy, policy.toProto());
+
+        builder.showInNotificationList(false);
+        policy = builder.build();
+        assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST);
+        assertProtoMatches(policy, policy.toProto());
     }
 
     private void assertAllPriorityCategoriesUnsetExcept(ZenPolicy policy, int except) {
@@ -453,4 +587,35 @@
             assertEquals(ZenPolicy.STATE_UNSET, policy.getVisualEffectNotificationList());
         }
     }
+
+    private void assertProtoMatches(ZenPolicy policy, byte[] bytes) {
+        try {
+            DNDPolicyProto proto = DNDPolicyProto.parseFrom(bytes);
+
+            assertEquals(policy.getPriorityCategoryCalls(), proto.calls);
+            assertEquals(policy.getPriorityCategoryRepeatCallers(), proto.repeatCallers);
+            assertEquals(policy.getPriorityCategoryMessages(), proto.messages);
+            assertEquals(policy.getPriorityCategoryConversations(), proto.conversations);
+            assertEquals(policy.getPriorityCategoryReminders(), proto.reminders);
+            assertEquals(policy.getPriorityCategoryEvents(), proto.events);
+            assertEquals(policy.getPriorityCategoryAlarms(), proto.alarms);
+            assertEquals(policy.getPriorityCategoryMedia(), proto.media);
+            assertEquals(policy.getPriorityCategorySystem(), proto.system);
+
+            assertEquals(policy.getVisualEffectFullScreenIntent(), proto.fullscreen);
+            assertEquals(policy.getVisualEffectLights(), proto.lights);
+            assertEquals(policy.getVisualEffectPeek(), proto.peek);
+            assertEquals(policy.getVisualEffectStatusBar(), proto.statusBar);
+            assertEquals(policy.getVisualEffectBadge(), proto.badge);
+            assertEquals(policy.getVisualEffectAmbient(), proto.ambient);
+            assertEquals(policy.getVisualEffectNotificationList(), proto.notificationList);
+
+            assertEquals(policy.getPriorityCallSenders(), proto.allowCallsFrom);
+            assertEquals(policy.getPriorityMessageSenders(), proto.allowMessagesFrom);
+            assertEquals(policy.getPriorityConversationSenders(), proto.allowConversationsFrom);
+        } catch (InvalidProtocolBufferNanoException e) {
+            fail("could not parse proto bytes");
+        }
+
+    }
 }
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 30df0d4..4040fa6 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -54,6 +54,8 @@
         <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityViewTestActivity" />
         <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityInActivityView"
                   android:resizeableActivity="true" />
+        <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityLaunchesNewActivityInActivityView"
+            android:resizeableActivity="true" />
         <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$LandscapeActivity"
                   android:screenOrientation="sensorLandscape"
                   android:showWhenLocked="true"
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index 2c17bbe..2bd3424 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -299,6 +299,20 @@
         waitForCallback(singleTaskDisplayDrawnLatch);
     }
 
+    public static class ActivityLaunchesNewActivityInActivityView extends TestActivity {
+        private boolean mActivityBLaunched = false;
+
+        @Override
+        protected void onPostResume() {
+            super.onPostResume();
+            if (mActivityBLaunched) {
+                return;
+            }
+            mActivityBLaunched = true;
+            startActivity(new Intent(this, ActivityB.class));
+        }
+    }
+
     @Test
     public void testSingleTaskDisplayEmpty() throws Exception {
         final Instrumentation instrumentation = getInstrumentation();
@@ -335,13 +349,20 @@
         });
         waitForCallback(activityViewReadyLatch);
 
+        // 1. start ActivityLaunchesNewActivityInActivityView in an ActivityView
+        // 2. ActivityLaunchesNewActivityInActivityView launches ActivityB
+        // 3. ActivityB finishes self.
+        // 4. Verify ITaskStackListener#onSingleTaskDisplayEmpty is not called yet.
         final Context context = instrumentation.getContext();
-        Intent intent = new Intent(context, ActivityInActivityView.class);
+        Intent intent = new Intent(context, ActivityLaunchesNewActivityInActivityView.class);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
         activityView.startActivity(intent);
         waitForCallback(singleTaskDisplayDrawnLatch);
+        UiDevice.getInstance(getInstrumentation()).waitForIdle();
         assertEquals(1, singleTaskDisplayEmptyLatch.getCount());
 
+        // 5. Release the container, and ActivityLaunchesNewActivityInActivityView finishes.
+        // 6. Verify ITaskStackListener#onSingleTaskDisplayEmpty is called.
         activityView.release();
         waitForCallback(activityViewDestroyedLatch);
         waitForCallback(singleTaskDisplayEmptyLatch);
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index b3d7c0d..4606fb4 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.res.Resources;
 import android.os.SystemConfigManager;
 import android.os.UserHandle;
 import android.permission.PermissionManager;
@@ -30,9 +29,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
-import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.util.ArrayUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -162,12 +159,9 @@
         try {
             for (ApplicationInfo ai : candidates) {
                 String packageName = ai.packageName;
-                String[] restrictedCarrierApps = Resources.getSystem().getStringArray(
-                        R.array.config_restrictedPreinstalledCarrierApps);
                 boolean hasPrivileges = telephonyManager != null
                         && telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName)
-                                == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
-                        && !ArrayUtils.contains(restrictedCarrierApps, packageName);
+                                == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
 
                 // add hiddenUntilInstalled flag for carrier apps and associated apps
                 packageManager.setSystemAppState(
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index 1993550..e6279dc 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -366,7 +366,7 @@
         .append(" mPci=").append(mPci)
         .append(" mTac=").append(mTac)
         .append(" mEarfcn=").append(mEarfcn)
-        .append(" mBands=").append(mBands)
+        .append(" mBands=").append(Arrays.toString(mBands))
         .append(" mBandwidth=").append(mBandwidth)
         .append(" mMcc=").append(mMccStr)
         .append(" mMnc=").append(mMncStr)
diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java
index 8dd7bdd..e34bbfc 100644
--- a/telephony/java/android/telephony/CellIdentityNr.java
+++ b/telephony/java/android/telephony/CellIdentityNr.java
@@ -242,7 +242,7 @@
                 .append(" mPci = ").append(mPci)
                 .append(" mTac = ").append(mTac)
                 .append(" mNrArfcn = ").append(mNrArfcn)
-                .append(" mBands = ").append(mBands)
+                .append(" mBands = ").append(Arrays.toString(mBands))
                 .append(" mMcc = ").append(mMccStr)
                 .append(" mMnc = ").append(mMncStr)
                 .append(" mNci = ").append(mNci)
diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
index de65ba2..a4d8353 100644
--- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
@@ -44,7 +44,7 @@
             setPartialConnectivityAcceptable(false)
             setUnvalidatedConnectivityAcceptable(true)
         }.build()
-        assertParcelSane(config, 9)
+        assertParcelSane(config, 10)
     }
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
diff --git a/tests/utils/hostutils/Android.bp b/tests/utils/hostutils/Android.bp
new file mode 100644
index 0000000..c9ad702
--- /dev/null
+++ b/tests/utils/hostutils/Android.bp
@@ -0,0 +1,28 @@
+// 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.
+//
+
+java_library_host {
+    name: "frameworks-base-hostutils",
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+
+    libs: [
+        "tradefed",
+        "junit",
+    ],
+}
diff --git a/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java b/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java
new file mode 100644
index 0000000..6bd6985
--- /dev/null
+++ b/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java
@@ -0,0 +1,362 @@
+/*
+ * 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.internal.util.test;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.rules.ExternalResource;
+import org.junit.rules.TemporaryFolder;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import javax.annotation.Nullable;
+
+/**
+ * Allows pushing files onto the device and various options for rebooting. Useful for installing
+ * APKs/files to system partitions which otherwise wouldn't be easily changed.
+ *
+ * It's strongly recommended to pass in a {@link ClassRule} annotated {@link TestRuleDelegate} to
+ * do a full reboot at the end of a test to ensure the device is in a valid state, assuming the
+ * default {@link RebootStrategy#FULL} isn't used.
+ */
+public class SystemPreparer extends ExternalResource {
+    private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000;
+
+    // The paths of the files pushed onto the device through this rule.
+    private ArrayList<String> mPushedFiles = new ArrayList<>();
+
+    // The package names of packages installed through this rule.
+    private ArrayList<String> mInstalledPackages = new ArrayList<>();
+
+    private final TemporaryFolder mHostTempFolder;
+    private final DeviceProvider mDeviceProvider;
+    private final RebootStrategy mRebootStrategy;
+    private final TearDownRule mTearDownRule;
+
+    public SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) {
+        this(hostTempFolder, RebootStrategy.FULL, null, deviceProvider);
+    }
+
+    public SystemPreparer(TemporaryFolder hostTempFolder, RebootStrategy rebootStrategy,
+            @Nullable TestRuleDelegate testRuleDelegate, DeviceProvider deviceProvider) {
+        mHostTempFolder = hostTempFolder;
+        mDeviceProvider = deviceProvider;
+        mRebootStrategy = rebootStrategy;
+        mTearDownRule = new TearDownRule(mDeviceProvider);
+        if (testRuleDelegate != null) {
+            testRuleDelegate.setDelegate(mTearDownRule);
+        }
+    }
+
+    /** Copies a file within the host test jar to a path on device. */
+    public SystemPreparer pushResourceFile(String filePath, String outputPath)
+            throws DeviceNotAvailableException, IOException {
+        final ITestDevice device = mDeviceProvider.getDevice();
+        remount();
+        assertTrue(device.pushFile(copyResourceToTemp(filePath), outputPath));
+        mPushedFiles.add(outputPath);
+        return this;
+    }
+
+    /** Copies a file directly from the host file system to a path on device. */
+    public SystemPreparer pushFile(File file, String outputPath)
+            throws DeviceNotAvailableException {
+        final ITestDevice device = mDeviceProvider.getDevice();
+        remount();
+        assertTrue(device.pushFile(file, outputPath));
+        mPushedFiles.add(outputPath);
+        return this;
+    }
+
+    /** Deletes the given path from the device */
+    public SystemPreparer deleteFile(String file) throws DeviceNotAvailableException {
+        final ITestDevice device = mDeviceProvider.getDevice();
+        remount();
+        device.deleteFile(file);
+        return this;
+    }
+
+    /** Installs an APK within the host test jar onto the device. */
+    public SystemPreparer installResourceApk(String resourcePath, String packageName)
+            throws DeviceNotAvailableException, IOException {
+        final ITestDevice device = mDeviceProvider.getDevice();
+        final File tmpFile = copyResourceToTemp(resourcePath);
+        final String result = device.installPackage(tmpFile, true /* reinstall */);
+        Assert.assertNull(result);
+        mInstalledPackages.add(packageName);
+        return this;
+    }
+
+    /** Sets the enable state of an overlay package. */
+    public SystemPreparer setOverlayEnabled(String packageName, boolean enabled)
+            throws DeviceNotAvailableException {
+        final ITestDevice device = mDeviceProvider.getDevice();
+        final String enable = enabled ? "enable" : "disable";
+
+        // Wait for the overlay to change its enabled state.
+        final long endMillis = System.currentTimeMillis() + OVERLAY_ENABLE_TIMEOUT_MS;
+        String result;
+        while (System.currentTimeMillis() <= endMillis) {
+            device.executeShellCommand(String.format("cmd overlay %s %s", enable, packageName));
+            result = device.executeShellCommand("cmd overlay dump isenabled "
+                    + packageName);
+            if (((enabled) ? "true\n" : "false\n").equals(result)) {
+                return this;
+            }
+
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException ignore) {
+            }
+        }
+
+        throw new IllegalStateException(String.format("Failed to %s overlay %s:\n%s", enable,
+                packageName, device.executeShellCommand("cmd overlay list")));
+    }
+
+    /** Restarts the device and waits until after boot is completed. */
+    public SystemPreparer reboot() throws DeviceNotAvailableException {
+        ITestDevice device = mDeviceProvider.getDevice();
+        switch (mRebootStrategy) {
+            case FULL:
+                device.reboot();
+                break;
+            case UNTIL_ONLINE:
+                device.rebootUntilOnline();
+                break;
+            case USERSPACE:
+                device.rebootUserspace();
+                break;
+            case USERSPACE_UNTIL_ONLINE:
+                device.rebootUserspaceUntilOnline();
+                break;
+            case START_STOP:
+                device.executeShellCommand("stop");
+                device.executeShellCommand("start");
+                ITestDevice.RecoveryMode cachedRecoveryMode = device.getRecoveryMode();
+                device.setRecoveryMode(ITestDevice.RecoveryMode.ONLINE);
+
+                if (device.isEncryptionSupported()) {
+                    if (device.isDeviceEncrypted()) {
+                        LogUtil.CLog.e("Device is encrypted after userspace reboot!");
+                        device.unlockDevice();
+                    }
+                }
+
+                device.setRecoveryMode(cachedRecoveryMode);
+                device.waitForDeviceAvailable();
+                break;
+        }
+        return this;
+    }
+
+    public SystemPreparer remount() throws DeviceNotAvailableException {
+        mTearDownRule.remount();
+        return this;
+    }
+
+    /** Copies a file within the host test jar to a temporary file on the host machine. */
+    private File copyResourceToTemp(String resourcePath) throws IOException {
+        final File tempFile = mHostTempFolder.newFile();
+        final ClassLoader classLoader = getClass().getClassLoader();
+        try (InputStream assetIs = classLoader.getResource(resourcePath).openStream();
+             FileOutputStream assetOs = new FileOutputStream(tempFile)) {
+            if (assetIs == null) {
+                throw new IllegalStateException("Failed to find resource " + resourcePath);
+            }
+
+            int b;
+            while ((b = assetIs.read()) >= 0) {
+                assetOs.write(b);
+            }
+        }
+
+        return tempFile;
+    }
+
+    /** Removes installed packages and files that were pushed to the device. */
+    @Override
+    protected void after() {
+        final ITestDevice device = mDeviceProvider.getDevice();
+        try {
+            remount();
+            for (final String file : mPushedFiles) {
+                device.deleteFile(file);
+            }
+            for (final String packageName : mInstalledPackages) {
+                device.uninstallPackage(packageName);
+            }
+            reboot();
+        } catch (DeviceNotAvailableException e) {
+            Assert.fail(e.toString());
+        }
+    }
+
+    /**
+     * A hacky workaround since {@link org.junit.AfterClass} and {@link ClassRule} require static
+     * members. Will defer assignment of the actual {@link TestRule} to execute until after any
+     * test case has been run.
+     *
+     * In effect, this makes the {@link ITestDevice} to be accessible after all test cases have
+     * been executed, allowing {@link ITestDevice#reboot()} to be used to fully restore the device.
+     */
+    public static class TestRuleDelegate implements TestRule {
+
+        private boolean mThrowOnNull;
+
+        @Nullable
+        private TestRule mTestRule;
+
+        public TestRuleDelegate(boolean throwOnNull) {
+            mThrowOnNull = throwOnNull;
+        }
+
+        public void setDelegate(TestRule testRule) {
+            mTestRule = testRule;
+        }
+
+        @Override
+        public Statement apply(Statement base, Description description) {
+            if (mTestRule == null) {
+                if (mThrowOnNull) {
+                    throw new IllegalStateException("TestRule delegate was not set");
+                } else {
+                    return new Statement() {
+                        @Override
+                        public void evaluate() throws Throwable {
+                            base.evaluate();
+                        }
+                    };
+                }
+            }
+
+            Statement statement = mTestRule.apply(base, description);
+            mTestRule = null;
+            return statement;
+        }
+    }
+
+    /**
+     * Forces a full reboot at the end of the test class to restore any device state.
+     */
+    private static class TearDownRule extends ExternalResource {
+
+        private DeviceProvider mDeviceProvider;
+        private boolean mInitialized;
+        private boolean mWasVerityEnabled;
+        private boolean mWasAdbRoot;
+        private boolean mIsVerityEnabled;
+
+        TearDownRule(DeviceProvider deviceProvider) {
+            mDeviceProvider = deviceProvider;
+        }
+
+        @Override
+        protected void before() {
+            // This method will never be run
+        }
+
+        @Override
+        protected void after() {
+            try {
+                initialize();
+                ITestDevice device = mDeviceProvider.getDevice();
+                if (mWasVerityEnabled != mIsVerityEnabled) {
+                    device.executeShellCommand(
+                            mWasVerityEnabled ? "enable-verity" : "disable-verity");
+                }
+                device.reboot();
+                if (!mWasAdbRoot) {
+                    device.disableAdbRoot();
+                }
+            } catch (DeviceNotAvailableException e) {
+                Assert.fail(e.toString());
+            }
+        }
+
+        /**
+         * Remount is done inside this class so that the verity state can be tracked.
+         */
+        public void remount() throws DeviceNotAvailableException {
+            initialize();
+            ITestDevice device = mDeviceProvider.getDevice();
+            device.enableAdbRoot();
+            if (mIsVerityEnabled) {
+                mIsVerityEnabled = false;
+                device.executeShellCommand("disable-verity");
+                device.reboot();
+            }
+            device.executeShellCommand("remount");
+            device.waitForDeviceAvailable();
+        }
+
+        private void initialize() throws DeviceNotAvailableException {
+            if (mInitialized) {
+                return;
+            }
+            mInitialized = true;
+            ITestDevice device = mDeviceProvider.getDevice();
+            mWasAdbRoot = device.isAdbRoot();
+            device.enableAdbRoot();
+            String veritySystem = device.getProperty("partition.system.verified");
+            String verityVendor = device.getProperty("partition.vendor.verified");
+            mWasVerityEnabled = (veritySystem != null && !veritySystem.isEmpty())
+                    || (verityVendor != null && !verityVendor.isEmpty());
+            mIsVerityEnabled = mWasVerityEnabled;
+        }
+    }
+
+    public interface DeviceProvider {
+        ITestDevice getDevice();
+    }
+
+    /**
+     * How to reboot the device. Ordered from slowest to fastest.
+     */
+    public enum RebootStrategy {
+        /** @see ITestDevice#reboot() */
+        FULL,
+
+        /** @see ITestDevice#rebootUntilOnline() () */
+        UNTIL_ONLINE,
+
+        /** @see ITestDevice#rebootUserspace() */
+        USERSPACE,
+
+        /** @see ITestDevice#rebootUserspaceUntilOnline() () */
+        USERSPACE_UNTIL_ONLINE,
+
+        /**
+         * Uses shell stop && start to "reboot" the device. May leave invalid state after each test.
+         * Whether this matters or not depends on what's being tested.
+         */
+        START_STOP
+    }
+}
diff --git a/wifi/Android.bp b/wifi/Android.bp
index 9c5b7b6..941ff61 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -12,6 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+java_defaults {
+    name: "wifi-module-sdk-version-defaults",
+    min_sdk_version: "30",
+    target_sdk_version: "30",
+}
+
 filegroup {
     name: "framework-wifi-updatable-exported-aidl-sources",
     srcs: ["aidl-export/**/*.aidl"],
@@ -73,6 +79,7 @@
 // classes before they are renamed.
 java_library {
     name: "framework-wifi-pre-jarjar",
+    defaults: ["wifi-module-sdk-version-defaults"],
     sdk_version: "module_current",
     static_libs: [
         "framework-wifi-util-lib",
@@ -98,7 +105,10 @@
 // post-jarjar version of framework-wifi
 java_sdk_library {
     name: "framework-wifi",
-    defaults: ["framework-module-defaults"],
+    defaults: [
+        "framework-module-defaults",
+        "wifi-module-sdk-version-defaults",
+    ],
     static_libs: [
         "framework-wifi-util-lib",
         "android.hardware.wifi-V1.0-java-constants",
diff --git a/wifi/java/android/net/wifi/SoftApCapability.java b/wifi/java/android/net/wifi/SoftApCapability.java
index 18b26db..dcb57ec 100644
--- a/wifi/java/android/net/wifi/SoftApCapability.java
+++ b/wifi/java/android/net/wifi/SoftApCapability.java
@@ -102,7 +102,9 @@
     /**
      * Returns true when all of the queried features are supported, otherwise false.
      *
-     * @param features One or combination of the features from {@link @HotspotFeatures}
+     * @param features One or combination of the following features:
+     * {@link #SOFTAP_FEATURE_ACS_OFFLOAD}, {@link #SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT} or
+     * {@link #SOFTAP_FEATURE_WPA3_SAE}.
      */
     public boolean areFeaturesSupported(@HotspotFeatures long features) {
         return (mSupportedFeatures & features) == features;
@@ -122,7 +124,9 @@
      * Constructor with combination of the feature.
      * Zero to no supported feature.
      *
-     * @param features One or combination of the features from {@link @HotspotFeatures}.
+     * @param features One or combination of the following features:
+     * {@link #SOFTAP_FEATURE_ACS_OFFLOAD}, {@link #SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT} or
+     * {@link #SOFTAP_FEATURE_WPA3_SAE}.
      * @hide
      */
     public SoftApCapability(@HotspotFeatures long features) {
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index 457e0db..2bcd4f4 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -165,7 +165,8 @@
 
     /**
      * The operating band of the AP.
-     * One of the band types from {@link @BandType}.
+     * One or combination of the following band type:
+     * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, {@link #BAND_6GHZ}.
      */
     private final @BandType int mBand;
 
@@ -181,7 +182,11 @@
 
     /**
      * The operating security type of the AP.
-     * One of the security types from {@link @SecurityType}
+     * One of the following security types:
+     * {@link #SECURITY_TYPE_OPEN},
+     * {@link #SECURITY_TYPE_WPA2_PSK},
+     * {@link #SECURITY_TYPE_WPA3_SAE_TRANSITION},
+     * {@link #SECURITY_TYPE_WPA3_SAE}
      */
     private final @SecurityType int mSecurityType;
 
@@ -393,8 +398,12 @@
     }
 
     /**
-     * Returns {@link BandType} set to be the band for the AP.
-     * {@link Builder#setBand(@BandType int)}.
+     * Returns band type set to be the band for the AP.
+     *
+     * One or combination of the following band type:
+     * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, {@link #BAND_6GHZ}.
+     *
+     * {@link Builder#setBand(int)}.
      *
      * @hide
      */
@@ -679,15 +688,19 @@
         /**
          * Specifies that this AP should use specific security type with the given ASCII passphrase.
          *
-         * @param securityType one of the security types from {@link @SecurityType}.
-         * @param passphrase The passphrase to use for sepcific {@link @SecurityType} configuration
-         * or null with {@link @SecurityType#SECURITY_TYPE_OPEN}.
+         * @param securityType One of the following security types:
+         * {@link #SECURITY_TYPE_OPEN},
+         * {@link #SECURITY_TYPE_WPA2_PSK},
+         * {@link #SECURITY_TYPE_WPA3_SAE_TRANSITION},
+         * {@link #SECURITY_TYPE_WPA3_SAE}.
+         * @param passphrase The passphrase to use for sepcific {@code securityType} configuration
+         * or null with {@link #SECURITY_TYPE_OPEN}.
          *
          * @return Builder for chaining.
          * @throws IllegalArgumentException when the passphrase length is invalid and
-         *         {@code securityType} is not {@link @SecurityType#SECURITY_TYPE_OPEN}
+         *         {@code securityType} is not {@link #SECURITY_TYPE_OPEN}
          *         or non-null passphrase and {@code securityType} is
-         *         {@link @SecurityType#SECURITY_TYPE_OPEN}.
+         *         {@link #SECURITY_TYPE_OPEN}.
          */
         @NonNull
         public Builder setPassphrase(@Nullable String passphrase, @SecurityType int securityType) {
@@ -735,9 +748,10 @@
         /**
          * Specifies the band for the AP.
          * <p>
-         * <li>If not set, defaults to BAND_2GHZ {@link @BandType}.</li>
+         * <li>If not set, defaults to {@link #BAND_2GHZ}.</li>
          *
-         * @param band One or combination of the band types from {@link @BandType}.
+         * @param band One or combination of the following band type:
+         * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, {@link #BAND_6GHZ}.
          * @return Builder for chaining.
          */
         @NonNull
@@ -758,7 +772,7 @@
          * <p>
          * The default for the channel is a the special value 0 to have the framework
          * auto-select a valid channel from the band configured with
-         * {@link #setBand(@BandType int)}.
+         * {@link #setBand(int)}.
          *
          * The channel auto selection will offload to driver when
          * {@link SoftApCapability#areFeaturesSupported(
diff --git a/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java
index 3215246..4116234 100644
--- a/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -1039,11 +1039,11 @@
      * The result depends on the on the country code that has been set.
      *
      * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
-     * The following bands are supported {@link @WifiScanner.WifiBandBasic}:
-     * WifiScanner.WIFI_BAND_24_GHZ
-     * WifiScanner.WIFI_BAND_5_GHZ
-     * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY
-     * WifiScanner.WIFI_BAND_6_GHZ
+     * The following bands are supported:
+     * {@link WifiScanner#WIFI_BAND_24_GHZ},
+     * {@link WifiScanner#WIFI_BAND_5_GHZ},
+     * {@link WifiScanner#WIFI_BAND_5_GHZ_DFS_ONLY},
+     * {@link WifiScanner#WIFI_BAND_6_GHZ}
      * @return frequencies vector of valid frequencies (MHz), or an empty array for error.
      * @throws IllegalArgumentException if band is not recognized.
      */