Merge "Keep window expanded when blurring it" into rvc-dev
diff --git a/Android.bp b/Android.bp
index 3c51256..8adf48d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -986,7 +986,6 @@
     srcs: [
         "core/java/android/os/incremental/IIncrementalService.aidl",
         "core/java/android/os/incremental/IncrementalNewFileParams.aidl",
-        "core/java/android/os/incremental/IncrementalSignature.aidl",
     ],
     path: "core/java",
 }
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 7f23df7..da9f165 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -41,7 +41,7 @@
 ]
 
 stubs_defaults {
-    name: "metalava-non-updatable-api-stubs-default",
+    name: "metalava-base-api-stubs-default",
     srcs: [
         ":framework-non-updatable-sources",
         "core/java/**/*.logtags",
@@ -70,12 +70,18 @@
 }
 
 stubs_defaults {
-    name: "metalava-api-stubs-default",
-    defaults: ["metalava-non-updatable-api-stubs-default"],
+    name: "metalava-full-api-stubs-default",
+    defaults: ["metalava-base-api-stubs-default"],
     srcs: [":framework-updatable-sources"],
     sdk_version: "core_platform",
 }
 
+stubs_defaults {
+    name: "metalava-non-updatable-api-stubs-default",
+    defaults: ["metalava-base-api-stubs-default"],
+    sdk_version: "system_current",
+}
+
 /////////////////////////////////////////////////////////////////////
 // *-api-stubs-docs modules providing source files for the stub libraries
 /////////////////////////////////////////////////////////////////////
@@ -85,7 +91,7 @@
 // modules
 droidstubs {
     name: "api-stubs-docs",
-    defaults: ["metalava-api-stubs-default"],
+    defaults: ["metalava-full-api-stubs-default"],
     api_filename: "public_api.txt",
     private_api_filename: "private.txt",
     removed_api_filename: "removed.txt",
@@ -124,7 +130,7 @@
 
 droidstubs {
     name: "system-api-stubs-docs",
-    defaults: ["metalava-api-stubs-default"],
+    defaults: ["metalava-full-api-stubs-default"],
     api_tag_name: "SYSTEM",
     api_filename: "system-api.txt",
     private_api_filename: "system-private.txt",
@@ -155,7 +161,7 @@
 
 droidstubs {
     name: "test-api-stubs-docs",
-    defaults: ["metalava-api-stubs-default"],
+    defaults: ["metalava-full-api-stubs-default"],
     api_tag_name: "TEST",
     api_filename: "test-api.txt",
     removed_api_filename: "test-removed.txt",
@@ -188,7 +194,7 @@
 
 droidstubs {
     name: "module-lib-api",
-    defaults: ["metalava-api-stubs-default"],
+    defaults: ["metalava-full-api-stubs-default"],
     arg_files: ["core/res/AndroidManifest.xml"],
     args: metalava_framework_docs_args + module_libs,
     check_api: {
@@ -216,7 +222,7 @@
 
 droidstubs {
     name: "module-lib-api-stubs-docs",
-    defaults: ["metalava-api-stubs-default"],
+    defaults: ["metalava-non-updatable-api-stubs-default"],
     arg_files: ["core/res/AndroidManifest.xml"],
     args: metalava_framework_docs_args + priv_apps + module_libs,
 }
@@ -266,6 +272,7 @@
     name: "android_module_lib_stubs_current",
     srcs: [ ":module-lib-api-stubs-docs" ],
     defaults: ["framework-stubs-default"],
+    libs: ["android_system_stubs_current"],
 }
 
 /////////////////////////////////////////////////////////////////////
@@ -317,7 +324,7 @@
 
 droidstubs {
     name: "hiddenapi-lists-docs",
-    defaults: ["metalava-api-stubs-default"],
+    defaults: ["metalava-full-api-stubs-default"],
     arg_files: [
         "core/res/AndroidManifest.xml",
     ],
@@ -332,7 +339,7 @@
 
 droidstubs {
     name: "hiddenapi-mappings",
-    defaults: ["metalava-api-stubs-default"],
+    defaults: ["metalava-full-api-stubs-default"],
     srcs: [
         ":opt-telephony-common-srcs",
     ],
diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
index 02df5e2..23f025b 100644
--- a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
+++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
@@ -121,8 +121,9 @@
     }
 
     private DummyBlobData prepareDataBlob(int fileSizeInMb) throws Exception {
-        final DummyBlobData blobData = new DummyBlobData(mContext,
-                fileSizeInMb * 1024 * 1024 /* bytes */);
+        final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+                .setFileSize(fileSizeInMb * 1024 * 1024 /* bytes */)
+                .build();
         blobData.prepare();
         return blobData;
     }
diff --git a/apex/Android.bp b/apex/Android.bp
index 1510911..39137fb 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -49,7 +49,7 @@
 stubs_defaults {
     name: "framework-module-stubs-defaults-systemapi",
     args: mainline_stubs_args + priv_apps,
-    srcs: [":framework-annotations"],
+    libs: ["framework-annotations-lib"],
     installable: false,
     sdk_version: "system_current",
 }
@@ -62,7 +62,7 @@
 stubs_defaults {
     name: "framework-module-api-defaults-module_libs_api",
     args: mainline_stubs_args + module_libs,
-    srcs: [":framework-annotations"],
+    libs: ["framework-annotations-lib"],
     installable: false,
     sdk_version: "module_current",
 }
@@ -70,7 +70,7 @@
 stubs_defaults {
     name: "framework-module-stubs-defaults-module_libs_api",
     args: mainline_stubs_args + module_libs + priv_apps,
-    srcs: [":framework-annotations"],
+    libs: ["framework-annotations-lib"],
     installable: false,
     sdk_version: "module_current",
 }
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
index 9746dd0..80062d5 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
@@ -32,21 +32,21 @@
     private final long mId;
     private final long mExpiryTimeMs;
     private final CharSequence mLabel;
-    private final List<AccessorInfo> mAccessors;
+    private final List<LeaseInfo> mLeaseInfos;
 
     public BlobInfo(long id, long expiryTimeMs, CharSequence label,
-            List<AccessorInfo> accessors) {
+            List<LeaseInfo> leaseInfos) {
         mId = id;
         mExpiryTimeMs = expiryTimeMs;
         mLabel = label;
-        mAccessors = accessors;
+        mLeaseInfos = leaseInfos;
     }
 
     private BlobInfo(Parcel in) {
         mId = in.readLong();
         mExpiryTimeMs = in.readLong();
         mLabel = in.readCharSequence();
-        mAccessors = in.readArrayList(null /* classloader */);
+        mLeaseInfos = in.readArrayList(null /* classloader */);
     }
 
     public long getId() {
@@ -61,8 +61,8 @@
         return mLabel;
     }
 
-    public List<AccessorInfo> getAccessors() {
-        return Collections.unmodifiableList(mAccessors);
+    public List<LeaseInfo> getLeases() {
+        return Collections.unmodifiableList(mLeaseInfos);
     }
 
     @Override
@@ -70,7 +70,7 @@
         dest.writeLong(mId);
         dest.writeLong(mExpiryTimeMs);
         dest.writeCharSequence(mLabel);
-        dest.writeList(mAccessors);
+        dest.writeList(mLeaseInfos);
     }
 
     @Override
@@ -83,7 +83,7 @@
                 + "id: " + mId + ","
                 + "expiryMs: " + mExpiryTimeMs + ","
                 + "label: " + mLabel + ","
-                + "accessors: " + AccessorInfo.toShortString(mAccessors) + ","
+                + "leases: " + LeaseInfo.toShortString(mLeaseInfos) + ","
                 + "}";
     }
 
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
index 814ab6d..c339351 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
@@ -21,6 +21,7 @@
 import android.annotation.IdRes;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.content.Context;
@@ -522,6 +523,50 @@
     }
 
     /**
+     * Return the {@link BlobHandle BlobHandles} corresponding to the data blobs that
+     * the calling app has acquired a lease on using {@link #acquireLease(BlobHandle, int)} or
+     * one of it's other variants.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public List<BlobHandle> getLeasedBlobs() throws IOException {
+        try {
+            return mService.getLeasedBlobs(mContext.getOpPackageName());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return {@link LeaseInfo} representing a lease acquired using
+     * {@link #acquireLease(BlobHandle, int)} or one of it's other variants,
+     * or {@code null} if there is no lease acquired.
+     *
+     * @throws SecurityException when the blob represented by the {@code blobHandle} does not
+     *                           exist or the caller does not have access to it.
+     * @throws IllegalArgumentException when {@code blobHandle} is invalid.
+     *
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle) throws IOException {
+        try {
+            return mService.getLeaseInfo(blobHandle, mContext.getOpPackageName());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Represents an ongoing session of a blob's contribution to the blob store managed by the
      * system.
      *
diff --git a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl
index e783813..20c15ab 100644
--- a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl
+++ b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl
@@ -18,6 +18,7 @@
 import android.app.blob.BlobHandle;
 import android.app.blob.BlobInfo;
 import android.app.blob.IBlobStoreSession;
+import android.app.blob.LeaseInfo;
 import android.os.RemoteCallback;
 
 /** {@hide} */
@@ -35,4 +36,7 @@
 
     List<BlobInfo> queryBlobsForUser(int userId);
     void deleteBlob(long blobId);
+
+    List<BlobHandle> getLeasedBlobs(in String packageName);
+    LeaseInfo getLeaseInfo(in BlobHandle blobHandle, in String packageName);
 }
\ No newline at end of file
diff --git a/apex/blobstore/framework/java/android/app/blob/LeaseInfo.aidl b/apex/blobstore/framework/java/android/app/blob/LeaseInfo.aidl
new file mode 100644
index 0000000..9088857
--- /dev/null
+++ b/apex/blobstore/framework/java/android/app/blob/LeaseInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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 android.app.blob;
+
+/** {@hide} */
+parcelable LeaseInfo;
\ No newline at end of file
diff --git a/apex/blobstore/framework/java/android/app/blob/AccessorInfo.java b/apex/blobstore/framework/java/android/app/blob/LeaseInfo.java
similarity index 61%
rename from apex/blobstore/framework/java/android/app/blob/AccessorInfo.java
rename to apex/blobstore/framework/java/android/app/blob/LeaseInfo.java
index 3725ad4..fef50c9 100644
--- a/apex/blobstore/framework/java/android/app/blob/AccessorInfo.java
+++ b/apex/blobstore/framework/java/android/app/blob/LeaseInfo.java
@@ -16,50 +16,61 @@
 
 package android.app.blob;
 
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.IdRes;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import java.util.List;
 
 /**
- * Class to provide information about an accessor of a shared blob.
+ * Class to provide information about a lease (acquired using
+ * {@link BlobStoreManager#acquireLease(BlobHandle, int)} or one of it's variants)
+ * for a shared blob.
  *
  * @hide
  */
-public final class AccessorInfo implements Parcelable {
+@TestApi
+public final class LeaseInfo implements Parcelable {
     private final String mPackageName;
-    private final long mExpiryTimeMs;
+    private final long mExpiryTimeMillis;
     private final int mDescriptionResId;
     private final CharSequence mDescription;
 
-    public AccessorInfo(String packageName, long expiryTimeMs,
-            int descriptionResId, CharSequence description) {
+    public LeaseInfo(@NonNull String packageName, @CurrentTimeMillisLong long expiryTimeMs,
+            @IdRes int descriptionResId, @Nullable CharSequence description) {
         mPackageName = packageName;
-        mExpiryTimeMs = expiryTimeMs;
+        mExpiryTimeMillis = expiryTimeMs;
         mDescriptionResId = descriptionResId;
         mDescription = description;
     }
 
-    private AccessorInfo(Parcel in) {
+    private LeaseInfo(Parcel in) {
         mPackageName = in.readString();
-        mExpiryTimeMs = in.readLong();
+        mExpiryTimeMillis = in.readLong();
         mDescriptionResId = in.readInt();
         mDescription = in.readCharSequence();
     }
 
+    @NonNull
     public String getPackageName() {
         return mPackageName;
     }
 
-    public long getExpiryTimeMs() {
-        return mExpiryTimeMs;
+    @CurrentTimeMillisLong
+    public long getExpiryTimeMillis() {
+        return mExpiryTimeMillis;
     }
 
+    @IdRes
     public int getDescriptionResId() {
         return mDescriptionResId;
     }
 
+    @Nullable
     public CharSequence getDescription() {
         return mDescription;
     }
@@ -67,16 +78,16 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mPackageName);
-        dest.writeLong(mExpiryTimeMs);
+        dest.writeLong(mExpiryTimeMillis);
         dest.writeInt(mDescriptionResId);
         dest.writeCharSequence(mDescription);
     }
 
     @Override
     public String toString() {
-        return "AccessorInfo {"
+        return "LeaseInfo {"
                 + "package: " + mPackageName + ","
-                + "expiryMs: " + mExpiryTimeMs + ","
+                + "expiryMs: " + mExpiryTimeMillis + ","
                 + "descriptionResId: " + mDescriptionResId + ","
                 + "description: " + mDescription + ","
                 + "}";
@@ -86,11 +97,11 @@
         return mPackageName;
     }
 
-    public static String toShortString(List<AccessorInfo> accessors) {
+    static String toShortString(List<LeaseInfo> leaseInfos) {
         final StringBuilder sb = new StringBuilder();
         sb.append("[");
-        for (int i = 0, size = accessors.size(); i < size; ++i) {
-            sb.append(accessors.get(i).toShortString());
+        for (int i = 0, size = leaseInfos.size(); i < size; ++i) {
+            sb.append(leaseInfos.get(i).toShortString());
             sb.append(",");
         }
         sb.append("]");
@@ -103,17 +114,17 @@
     }
 
     @NonNull
-    public static final Creator<AccessorInfo> CREATOR = new Creator<AccessorInfo>() {
+    public static final Creator<LeaseInfo> CREATOR = new Creator<LeaseInfo>() {
         @Override
         @NonNull
-        public AccessorInfo createFromParcel(Parcel source) {
-            return new AccessorInfo(source);
+        public LeaseInfo createFromParcel(Parcel source) {
+            return new LeaseInfo(source);
         }
 
         @Override
         @NonNull
-        public AccessorInfo[] newArray(int size) {
-            return new AccessorInfo[size];
+        public LeaseInfo[] newArray(int size) {
+            return new LeaseInfo[size];
         }
     };
 }
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
index 970766d..8b640ca 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -38,6 +38,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.blob.BlobHandle;
+import android.app.blob.LeaseInfo;
 import android.content.Context;
 import android.content.res.ResourceId;
 import android.content.res.Resources;
@@ -281,6 +282,25 @@
         return false;
     }
 
+    @Nullable
+    LeaseInfo getLeaseInfo(@NonNull String packageName, int uid) {
+        synchronized (mMetadataLock) {
+            for (int i = 0, size = mLeasees.size(); i < size; ++i) {
+                final Leasee leasee = mLeasees.valueAt(i);
+                if (leasee.uid == uid && leasee.packageName.equals(packageName)) {
+                    final int descriptionResId = leasee.descriptionResEntryName == null
+                            ? Resources.ID_NULL
+                            : BlobStoreUtils.getDescriptionResourceId(
+                                    mContext, leasee.descriptionResEntryName, leasee.packageName,
+                                    UserHandle.getUserId(leasee.uid));
+                    return new LeaseInfo(packageName, leasee.expiryTimeMillis,
+                            descriptionResId, leasee.description);
+                }
+            }
+        }
+        return null;
+    }
+
     void forEachLeasee(Consumer<Leasee> consumer) {
         mLeasees.forEach(consumer);
     }
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 53a97ce..f4b8f0f 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -45,11 +45,11 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.app.blob.AccessorInfo;
 import android.app.blob.BlobHandle;
 import android.app.blob.BlobInfo;
 import android.app.blob.IBlobStoreManager;
 import android.app.blob.IBlobStoreSession;
+import android.app.blob.LeaseInfo;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -454,17 +454,17 @@
                 return packageResources;
             };
             getUserBlobsLocked(userId).forEach((blobHandle, blobMetadata) -> {
-                final ArrayList<AccessorInfo> accessorInfos = new ArrayList<>();
+                final ArrayList<LeaseInfo> leaseInfos = new ArrayList<>();
                 blobMetadata.forEachLeasee(leasee -> {
                     final int descriptionResId = leasee.descriptionResEntryName == null
                             ? Resources.ID_NULL
                             : getDescriptionResourceId(resourcesGetter.apply(leasee.packageName),
                                     leasee.descriptionResEntryName, leasee.packageName);
-                    accessorInfos.add(new AccessorInfo(leasee.packageName, leasee.expiryTimeMillis,
+                    leaseInfos.add(new LeaseInfo(leasee.packageName, leasee.expiryTimeMillis,
                             descriptionResId, leasee.description));
                 });
                 blobInfos.add(new BlobInfo(blobMetadata.getBlobId(),
-                        blobHandle.getExpiryTimeMillis(), blobHandle.getLabel(), accessorInfos));
+                        blobHandle.getExpiryTimeMillis(), blobHandle.getLabel(), leaseInfos));
             });
         }
         return blobInfos;
@@ -482,6 +482,31 @@
         }
     }
 
+    private List<BlobHandle> getLeasedBlobsInternal(int callingUid,
+            @NonNull String callingPackage) {
+        final ArrayList<BlobHandle> leasedBlobs = new ArrayList<>();
+        forEachBlobInUser(blobMetadata -> {
+            if (blobMetadata.isALeasee(callingPackage, callingUid)) {
+                leasedBlobs.add(blobMetadata.getBlobHandle());
+            }
+        }, UserHandle.getUserId(callingUid));
+        return leasedBlobs;
+    }
+
+    private LeaseInfo getLeaseInfoInternal(BlobHandle blobHandle,
+            int callingUid, @NonNull String callingPackage) {
+        synchronized (mBlobsLock) {
+            final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
+                    .get(blobHandle);
+            if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
+                    callingPackage, callingUid)) {
+                throw new SecurityException("Caller not allowed to access " + blobHandle
+                        + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
+            }
+            return blobMetadata.getLeaseInfo(callingPackage, callingUid);
+        }
+    }
+
     private void verifyCallingPackage(int callingUid, String callingPackage) {
         if (mPackageManagerInternal.getPackageUid(
                 callingPackage, 0, UserHandle.getUserId(callingUid)) != callingUid) {
@@ -1267,6 +1292,12 @@
             final int callingUid = Binder.getCallingUid();
             verifyCallingPackage(callingUid, packageName);
 
+            if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
+                    packageName, UserHandle.getUserId(callingUid))) {
+                throw new SecurityException("Caller not allowed to open blob; "
+                        + "callingUid=" + callingUid + ", callingPackage=" + packageName);
+            }
+
             try {
                 acquireLeaseInternal(blobHandle, descriptionResId, description,
                         leaseExpiryTimeMillis, callingUid, packageName);
@@ -1284,6 +1315,12 @@
             final int callingUid = Binder.getCallingUid();
             verifyCallingPackage(callingUid, packageName);
 
+            if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
+                    packageName, UserHandle.getUserId(callingUid))) {
+                throw new SecurityException("Caller not allowed to open blob; "
+                        + "callingUid=" + callingUid + ", callingPackage=" + packageName);
+            }
+
             releaseLeaseInternal(blobHandle, callingUid, packageName);
         }
 
@@ -1320,6 +1357,36 @@
         }
 
         @Override
+        @NonNull
+        public List<BlobHandle> getLeasedBlobs(@NonNull String packageName) {
+            Objects.requireNonNull(packageName, "packageName must not be null");
+
+            final int callingUid = Binder.getCallingUid();
+            verifyCallingPackage(callingUid, packageName);
+
+            return getLeasedBlobsInternal(callingUid, packageName);
+        }
+
+        @Override
+        @Nullable
+        public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle, @NonNull String packageName) {
+            Objects.requireNonNull(blobHandle, "blobHandle must not be null");
+            blobHandle.assertIsValid();
+            Objects.requireNonNull(packageName, "packageName must not be null");
+
+            final int callingUid = Binder.getCallingUid();
+            verifyCallingPackage(callingUid, packageName);
+
+            if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
+                    packageName, UserHandle.getUserId(callingUid))) {
+                throw new SecurityException("Caller not allowed to open blob; "
+                        + "callingUid=" + callingUid + ", callingPackage=" + packageName);
+            }
+
+            return getLeaseInfoInternal(blobHandle, callingUid, packageName);
+        }
+
+        @Override
         public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
                 @Nullable String[] args) {
             // TODO: add proto-based version of this.
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java
index 6af540a..fabce76 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java
@@ -47,4 +47,13 @@
             @NonNull String resourceEntryName, @NonNull String packageName) {
         return resources.getIdentifier(resourceEntryName, DESC_RES_TYPE_STRING, packageName);
     }
+
+    @IdRes
+    static int getDescriptionResourceId(@NonNull Context context,
+            @NonNull String resourceEntryName, @NonNull String packageName, int userId) {
+        final Resources resources = getPackageResources(context, packageName, userId);
+        return resources == null
+                ? Resources.ID_NULL
+                : getDescriptionResourceId(resources, resourceEntryName, packageName);
+    }
 }
diff --git a/api/current.txt b/api/current.txt
index 716735a..5e8158b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6912,7 +6912,6 @@
     method @Nullable public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName);
     method @Nullable public java.util.List<java.lang.String> getPermittedInputMethods(@NonNull android.content.ComponentName);
     method public int getPersonalAppsSuspendedReasons(@NonNull android.content.ComponentName);
-    method @NonNull public java.util.List<java.lang.String> getProtectedPackages(@NonNull android.content.ComponentName);
     method public long getRequiredStrongAuthTimeout(@Nullable android.content.ComponentName);
     method public boolean getScreenCaptureDisabled(@Nullable android.content.ComponentName);
     method public java.util.List<android.os.UserHandle> getSecondaryUsers(@NonNull android.content.ComponentName);
@@ -6923,6 +6922,7 @@
     method @Nullable public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy();
     method @Nullable public android.os.PersistableBundle getTransferOwnershipBundle();
     method @Nullable public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(@Nullable android.content.ComponentName, @NonNull android.content.ComponentName);
+    method @NonNull public java.util.List<java.lang.String> getUserControlDisabledPackages(@NonNull android.content.ComponentName);
     method @NonNull public android.os.Bundle getUserRestrictions(@NonNull android.content.ComponentName);
     method @Nullable public String getWifiMacAddress(@NonNull android.content.ComponentName);
     method public boolean grantKeyPairToApp(@Nullable android.content.ComponentName, @NonNull String, @NonNull String);
@@ -7042,7 +7042,6 @@
     method public void setPersonalAppsSuspended(@NonNull android.content.ComponentName, boolean);
     method public void setProfileEnabled(@NonNull android.content.ComponentName);
     method public void setProfileName(@NonNull android.content.ComponentName, String);
-    method public void setProtectedPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
     method public void setRecommendedGlobalProxy(@NonNull android.content.ComponentName, @Nullable android.net.ProxyInfo);
     method public void setRequiredStrongAuthTimeout(@NonNull android.content.ComponentName, long);
     method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
@@ -7061,6 +7060,7 @@
     method public boolean setTimeZone(@NonNull android.content.ComponentName, String);
     method public void setTrustAgentConfiguration(@NonNull android.content.ComponentName, @NonNull android.content.ComponentName, android.os.PersistableBundle);
     method public void setUninstallBlocked(@Nullable android.content.ComponentName, String, boolean);
+    method public void setUserControlDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
     method public void setUserIcon(@NonNull android.content.ComponentName, android.graphics.Bitmap);
     method public int startUserInBackground(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
     method public int stopUser(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
@@ -11896,6 +11896,7 @@
     method public void setAppIcon(@Nullable android.graphics.Bitmap);
     method public void setAppLabel(@Nullable CharSequence);
     method public void setAppPackageName(@Nullable String);
+    method public void setAutoRevokePermissionsMode(boolean);
     method public void setInstallLocation(int);
     method public void setInstallReason(int);
     method public void setMultiPackage();
@@ -12025,6 +12026,7 @@
     method public boolean hasSigningCertificate(int, @NonNull byte[], int);
     method public abstract boolean hasSystemFeature(@NonNull String);
     method public abstract boolean hasSystemFeature(@NonNull String, int);
+    method @RequiresPermission(value="android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS", conditional=true) public boolean isAutoRevokeWhitelisted(@NonNull String);
     method public boolean isAutoRevokeWhitelisted();
     method public boolean isDefaultApplicationIcon(@NonNull android.graphics.drawable.Drawable);
     method public boolean isDeviceUpgrading();
@@ -12050,6 +12052,7 @@
     method @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int);
     method public abstract void setApplicationCategoryHint(@NonNull String, int);
     method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(@NonNull String, int, int);
+    method @RequiresPermission(value="android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS", conditional=true) public boolean setAutoRevokeWhitelisted(@NonNull String, boolean);
     method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(@NonNull android.content.ComponentName, int, int);
     method public abstract void setInstallerPackageName(@NonNull String, @Nullable String);
     method public void setMimeGroup(@NonNull String, @NonNull java.util.Set<java.lang.String>);
@@ -17356,7 +17359,7 @@
   public final class CameraManager {
     method @NonNull public android.hardware.camera2.CameraCharacteristics getCameraCharacteristics(@NonNull String) throws android.hardware.camera2.CameraAccessException;
     method @NonNull public String[] getCameraIdList() throws android.hardware.camera2.CameraAccessException;
-    method @NonNull public java.util.Set<java.util.Set<java.lang.String>> getConcurrentStreamingCameraIds() throws android.hardware.camera2.CameraAccessException;
+    method @NonNull public java.util.Set<java.util.Set<java.lang.String>> getConcurrentCameraIds() throws android.hardware.camera2.CameraAccessException;
     method @RequiresPermission(android.Manifest.permission.CAMERA) public boolean isConcurrentSessionConfigurationSupported(@NonNull java.util.Map<java.lang.String,android.hardware.camera2.params.SessionConfiguration>) throws android.hardware.camera2.CameraAccessException;
     method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, @NonNull android.hardware.camera2.CameraDevice.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
@@ -20098,8 +20101,7 @@
     method public T numberFormatterSecond(android.icu.number.UnlocalizedNumberFormatter);
   }
 
-  public abstract class Precision implements java.lang.Cloneable {
-    method public Object clone();
+  public abstract class Precision {
     method public static android.icu.number.CurrencyPrecision currency(android.icu.util.Currency.CurrencyUsage);
     method public static android.icu.number.FractionPrecision fixedFraction(int);
     method public static android.icu.number.Precision fixedSignificantDigits(int);
@@ -20122,8 +20124,7 @@
     method public static android.icu.number.Scale powerOfTen(int);
   }
 
-  public class ScientificNotation extends android.icu.number.Notation implements java.lang.Cloneable {
-    method public Object clone();
+  public class ScientificNotation extends android.icu.number.Notation {
     method public android.icu.number.ScientificNotation withExponentSignDisplay(android.icu.number.NumberFormatter.SignDisplay);
     method public android.icu.number.ScientificNotation withMinExponentDigits(int);
   }
@@ -27050,7 +27051,7 @@
     method public abstract void onVolumeUpdateRequest(android.media.MediaRouter.RouteInfo, int);
   }
 
-  public class MediaRouter2 {
+  public final class MediaRouter2 {
     method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers();
     method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context);
     method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes();
@@ -37159,12 +37160,10 @@
     method @Nullable public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int);
     method @NonNull public android.os.VibrationEffect compose();
     field public static final int PRIMITIVE_CLICK = 1; // 0x1
-    field public static final int PRIMITIVE_LIGHT_TICK = 7; // 0x7
     field public static final int PRIMITIVE_QUICK_FALL = 6; // 0x6
     field public static final int PRIMITIVE_QUICK_RISE = 4; // 0x4
     field public static final int PRIMITIVE_SLOW_RISE = 5; // 0x5
-    field public static final int PRIMITIVE_SPIN = 3; // 0x3
-    field public static final int PRIMITIVE_THUD = 2; // 0x2
+    field public static final int PRIMITIVE_TICK = 7; // 0x7
   }
 
   public abstract class Vibrator {
@@ -47834,6 +47833,7 @@
     method @Deprecated public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent);
     method public void sendMultipartTextMessage(String, String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>);
     method public void sendMultipartTextMessage(@NonNull String, @Nullable String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, long);
+    method public void sendMultipartTextMessage(@NonNull String, @Nullable String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String);
     method public void sendTextMessage(String, String, String, android.app.PendingIntent, android.app.PendingIntent);
     method public void sendTextMessage(@NonNull String, @Nullable String, @NonNull String, @Nullable android.app.PendingIntent, @Nullable android.app.PendingIntent, long);
     method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.SEND_SMS}) public void sendTextMessageWithoutPersisting(String, String, String, android.app.PendingIntent, android.app.PendingIntent);
@@ -48999,8 +48999,8 @@
     ctor public RegistrationManager.RegistrationCallback();
     method public void onRegistered(int);
     method public void onRegistering(int);
-    method public void onTechnologyChangeFailed(int, @Nullable android.telephony.ims.ImsReasonInfo);
-    method public void onUnregistered(@Nullable android.telephony.ims.ImsReasonInfo);
+    method public void onTechnologyChangeFailed(int, @NonNull android.telephony.ims.ImsReasonInfo);
+    method public void onUnregistered(@NonNull android.telephony.ims.ImsReasonInfo);
   }
 
 }
@@ -53608,10 +53608,11 @@
 
   public class SurfaceControlViewHost {
     ctor public SurfaceControlViewHost(@NonNull android.content.Context, @NonNull android.view.Display, @Nullable android.os.IBinder);
-    method public void addView(@NonNull android.view.View, int, int);
     method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage getSurfacePackage();
+    method @Nullable public android.view.View getView();
     method public void relayout(int, int);
     method public void release();
+    method public void setView(@NonNull android.view.View, int, int);
   }
 
   public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable {
@@ -55586,7 +55587,7 @@
 
   public interface WindowInsetsController {
     method public void addOnControllableInsetsChangedListener(@NonNull android.view.WindowInsetsController.OnControllableInsetsChangedListener);
-    method @NonNull public android.os.CancellationSignal controlWindowInsetsAnimation(int, long, @Nullable android.view.animation.Interpolator, @NonNull android.view.WindowInsetsAnimationControlListener);
+    method public void controlWindowInsetsAnimation(int, long, @Nullable android.view.animation.Interpolator, @Nullable android.os.CancellationSignal, @NonNull android.view.WindowInsetsAnimationControlListener);
     method public int getSystemBarsAppearance();
     method public int getSystemBarsBehavior();
     method public void hide(int);
@@ -56810,7 +56811,7 @@
   public static final class InlinePresentationSpec.Builder {
     ctor public InlinePresentationSpec.Builder(@NonNull android.util.Size, @NonNull android.util.Size);
     method @NonNull public android.view.inline.InlinePresentationSpec build();
-    method @NonNull public android.view.inline.InlinePresentationSpec.Builder setStyle(@Nullable android.os.Bundle);
+    method @NonNull public android.view.inline.InlinePresentationSpec.Builder setStyle(@NonNull android.os.Bundle);
   }
 
 }
@@ -57022,7 +57023,7 @@
     ctor public InlineSuggestionsRequest.Builder(@NonNull java.util.List<android.view.inline.InlinePresentationSpec>);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder addPresentationSpecs(@NonNull android.view.inline.InlinePresentationSpec);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest build();
-    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@Nullable android.os.Bundle);
+    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@NonNull android.os.Bundle);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setMaxSuggestionCount(int);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setSupportedLocales(@NonNull android.os.LocaleList);
   }
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index 69b52693..ee997f1 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -29,7 +29,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient.AddressInfo> CREATOR;
   }
 
-  public class TetheringConstants {
+  public final class TetheringConstants {
     field public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
     field public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
     field public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
diff --git a/api/system-current.txt b/api/system-current.txt
index 460fb05..fd10d2a 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -235,6 +235,7 @@
     field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
     field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
     field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
+    field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
     field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
     field public static final String WIFI_SET_DEVICE_MOBILITY_STATE = "android.permission.WIFI_SET_DEVICE_MOBILITY_STATE";
     field public static final String WIFI_UPDATE_USABILITY_STATS_SCORE = "android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE";
@@ -407,7 +408,6 @@
     field public static final String OPSTR_POST_NOTIFICATION = "android:post_notification";
     field public static final String OPSTR_PROJECT_MEDIA = "android:project_media";
     field public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard";
-    field public static final String OPSTR_READ_DEVICE_IDENTIFIERS = "android:read_device_identifiers";
     field public static final String OPSTR_READ_ICC_SMS = "android:read_icc_sms";
     field public static final String OPSTR_READ_MEDIA_AUDIO = "android:read_media_audio";
     field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
@@ -757,7 +757,6 @@
   public class StatusBarManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo();
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
-    method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
   }
 
   public static final class StatusBarManager.DisableInfo {
@@ -2132,6 +2131,7 @@
   public static class PackageInstaller.SessionInfo implements android.os.Parcelable {
     method public boolean getAllocateAggressive();
     method @Deprecated public boolean getAllowDowngrade();
+    method public int getAutoRevokePermissionsMode();
     method public boolean getDontKillApp();
     method public boolean getEnableRollback();
     method @Nullable public String[] getGrantedRuntimePermissions();
@@ -2280,7 +2280,6 @@
     field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
     field public static final int MATCH_ANY_USER = 4194304; // 0x400000
     field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
-    field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000
     field public static final int MATCH_INSTANT = 8388608; // 0x800000
     field public static final int MODULE_APEX_NAME = 1; // 0x1
     field public static final int RESTRICTION_HIDE_FROM_SUGGESTIONS = 1; // 0x1
@@ -5364,8 +5363,9 @@
     field public static final int SIGNAL_TYPE_UNDEFINED = 0; // 0x0
   }
 
-  public static class AnalogFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.AnalogFrontendSettings.Builder> {
+  public static class AnalogFrontendSettings.Builder {
     method @NonNull public android.media.tv.tuner.frontend.AnalogFrontendSettings build();
+    method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.AnalogFrontendSettings.Builder setFrequency(int);
     method @NonNull public android.media.tv.tuner.frontend.AnalogFrontendSettings.Builder setSifStandard(int);
     method @NonNull public android.media.tv.tuner.frontend.AnalogFrontendSettings.Builder setSignalType(int);
   }
@@ -5429,10 +5429,11 @@
     field public static final int TIME_INTERLEAVE_MODE_UNDEFINED = 0; // 0x0
   }
 
-  public static class Atsc3FrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.Atsc3FrontendSettings.Builder> {
+  public static class Atsc3FrontendSettings.Builder {
     method @NonNull public android.media.tv.tuner.frontend.Atsc3FrontendSettings build();
     method @NonNull public android.media.tv.tuner.frontend.Atsc3FrontendSettings.Builder setBandwidth(int);
     method @NonNull public android.media.tv.tuner.frontend.Atsc3FrontendSettings.Builder setDemodOutputFormat(int);
+    method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.Atsc3FrontendSettings.Builder setFrequency(int);
     method @NonNull public android.media.tv.tuner.frontend.Atsc3FrontendSettings.Builder setPlpSettings(@NonNull android.media.tv.tuner.frontend.Atsc3PlpSettings[]);
   }
 
@@ -5473,8 +5474,9 @@
     field public static final int MODULATION_UNDEFINED = 0; // 0x0
   }
 
-  public static class AtscFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.AtscFrontendSettings.Builder> {
+  public static class AtscFrontendSettings.Builder {
     method @NonNull public android.media.tv.tuner.frontend.AtscFrontendSettings build();
+    method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.AtscFrontendSettings.Builder setFrequency(int);
     method @NonNull public android.media.tv.tuner.frontend.AtscFrontendSettings.Builder setModulation(int);
   }
 
@@ -5487,7 +5489,7 @@
   public class DvbcFrontendSettings extends android.media.tv.tuner.frontend.FrontendSettings {
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder builder(@NonNull android.content.Context);
     method public int getAnnex();
-    method public long getFec();
+    method public long getInnerFec();
     method public int getModulation();
     method public int getOuterFec();
     method public int getSpectralInversion();
@@ -5512,10 +5514,11 @@
     field public static final int SPECTRAL_INVERSION_UNDEFINED = 0; // 0x0
   }
 
-  public static class DvbcFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder> {
+  public static class DvbcFrontendSettings.Builder {
     method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings build();
     method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setAnnex(int);
-    method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setFec(long);
+    method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setFrequency(int);
+    method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setInnerFec(long);
     method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setModulation(int);
     method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setOuterFec(int);
     method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setSpectralInversion(int);
@@ -5590,9 +5593,10 @@
     field public static final int VCM_MODE_UNDEFINED = 0; // 0x0
   }
 
-  public static class DvbsFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder> {
+  public static class DvbsFrontendSettings.Builder {
     method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings build();
     method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setCodeRate(@Nullable android.media.tv.tuner.frontend.DvbsCodeRate);
+    method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setFrequency(int);
     method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setInputStreamId(int);
     method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setModulation(int);
     method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setPilot(int);
@@ -5689,10 +5693,11 @@
     field public static final int TRANSMISSION_MODE_UNDEFINED = 0; // 0x0
   }
 
-  public static class DvbtFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder> {
+  public static class DvbtFrontendSettings.Builder {
     method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings build();
     method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setBandwidth(int);
     method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setConstellation(int);
+    method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setFrequency(int);
     method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setGuardInterval(int);
     method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setHierarchy(int);
     method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setHighPriority(boolean);
@@ -5773,17 +5778,13 @@
     field public static final int TYPE_UNDEFINED = 0; // 0x0
   }
 
-  public abstract static class FrontendSettings.Builder<T extends android.media.tv.tuner.frontend.FrontendSettings.Builder<T>> {
-    method @IntRange(from=1) @NonNull public T setFrequency(int);
-  }
-
   public class FrontendStatus {
     method public int getAgc();
     method @NonNull public android.media.tv.tuner.frontend.FrontendStatus.Atsc3PlpInfo[] getAtsc3PlpInfo();
     method public int getBer();
-    method public long getFec();
     method public int getFreqOffset();
     method public int getHierarchy();
+    method public long getInnerFec();
     method @NonNull public boolean[] getLayerErrors();
     method public int getLnbVoltage();
     method public int getMer();
@@ -5868,9 +5869,10 @@
     field public static final int ROLLOFF_UNDEFINED = 0; // 0x0
   }
 
-  public static class Isdbs3FrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder> {
+  public static class Isdbs3FrontendSettings.Builder {
     method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings build();
     method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setCodeRate(int);
+    method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setFrequency(int);
     method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setModulation(int);
     method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setRolloff(int);
     method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setStreamId(int);
@@ -5910,9 +5912,10 @@
     field public static final int STREAM_ID_TYPE_RELATIVE_NUMBER = 1; // 0x1
   }
 
-  public static class IsdbsFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder> {
+  public static class IsdbsFrontendSettings.Builder {
     method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings build();
     method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setCodeRate(int);
+    method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setFrequency(int);
     method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setModulation(int);
     method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setRolloff(int);
     method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setStreamId(int);
@@ -5955,10 +5958,11 @@
     field public static final int MODULATION_UNDEFINED = 0; // 0x0
   }
 
-  public static class IsdbtFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder> {
+  public static class IsdbtFrontendSettings.Builder {
     method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings build();
     method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setBandwidth(int);
     method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setCodeRate(int);
+    method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setFrequency(int);
     method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setGuardInterval(int);
     method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setMode(int);
     method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setModulation(int);
@@ -6089,7 +6093,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl();
     method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
-    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
     method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
     method @Deprecated public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int, int, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
@@ -6098,7 +6102,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
     method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler);
     method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int);
-    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider);
     method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
     field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
     field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
@@ -6286,7 +6290,6 @@
     method @Nullable public android.net.Network getNetwork();
     method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
     method public void onAutomaticReconnectDisabled();
-    method public void onBandwidthUpdateRequested();
     method public void onNetworkUnwanted();
     method public void onRemoveKeepalivePacketFilter(int);
     method public void onSaveAcceptUnvalidated(boolean);
@@ -6300,23 +6303,17 @@
     method public void sendNetworkScore(int);
     method public void sendSocketKeepaliveEvent(int, int);
     method public void setConnected();
-    method @Deprecated public void setLegacyExtraInfo(@Nullable String);
-    method @Deprecated public void setLegacySubtype(int, @NonNull String);
     method public void unregister();
     field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
     field public static final int VALIDATION_STATUS_VALID = 1; // 0x1
-    field public final int providerId;
   }
 
   public final class NetworkAgentConfig implements android.os.Parcelable {
     method public int describeContents();
     method public int getLegacyType();
     method @NonNull public String getLegacyTypeName();
-    method @Nullable public String getSubscriberId();
     method public boolean isExplicitlySelected();
-    method public boolean isNat64DetectionEnabled();
     method public boolean isPartialConnectivityAcceptable();
-    method public boolean isProvisioningNotificationEnabled();
     method public boolean isUnvalidatedConnectivityAcceptable();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR;
@@ -6325,18 +6322,14 @@
   public static class NetworkAgentConfig.Builder {
     ctor public NetworkAgentConfig.Builder();
     method @NonNull public android.net.NetworkAgentConfig build();
-    method @NonNull public android.net.NetworkAgentConfig.Builder disableNat64Detection();
-    method @NonNull public android.net.NetworkAgentConfig.Builder disableProvisioningNotification();
     method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean);
     method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int);
     method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String);
     method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean);
-    method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String);
     method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean);
   }
 
   public final class NetworkCapabilities implements android.os.Parcelable {
-    method public boolean deduceRestrictedCapability();
     method @NonNull public java.util.List<java.lang.Integer> getAdministratorUids();
     method @Nullable public String getSSID();
     method @NonNull public int[] getTransportTypes();
@@ -6361,27 +6354,9 @@
     field public final android.net.WifiKey wifiKey;
   }
 
-  public class NetworkPolicyManager {
-    method @NonNull public android.telephony.SubscriptionPlan[] getSubscriptionPlans(int, @NonNull String);
-    method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void registerSubscriptionCallback(@NonNull android.net.NetworkPolicyManager.SubscriptionCallback);
-    method public void setSubscriptionOverride(int, int, int, long, @NonNull String);
-    method public void setSubscriptionPlans(int, @NonNull android.telephony.SubscriptionPlan[], @NonNull String);
-    method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void unregisterSubscriptionCallback(@NonNull android.net.NetworkPolicyManager.SubscriptionCallback);
-    field public static final int SUBSCRIPTION_OVERRIDE_CONGESTED = 2; // 0x2
-    field public static final int SUBSCRIPTION_OVERRIDE_UNMETERED = 1; // 0x1
-  }
-
-  public static class NetworkPolicyManager.SubscriptionCallback {
-    ctor public NetworkPolicyManager.SubscriptionCallback();
-    method public void onSubscriptionOverride(int, int, int);
-    method public void onSubscriptionPlansChanged(int, @NonNull android.telephony.SubscriptionPlan[]);
-  }
-
   public class NetworkProvider {
     ctor public NetworkProvider(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void declareNetworkRequestUnfulfillable(@NonNull android.net.NetworkRequest);
-    method @Nullable public android.os.Messenger getMessenger();
-    method @NonNull public String getName();
     method public int getProviderId();
     method public void onNetworkRequested(@NonNull android.net.NetworkRequest, int, int);
     method public void onRequestWithdrawn(@NonNull android.net.NetworkRequest);
@@ -7488,24 +7463,7 @@
   }
 
   public final class WifiMigration {
-    method @Nullable public static android.net.wifi.WifiMigration.ConfigStoreMigrationData loadFromConfigStore();
     method @NonNull public static android.net.wifi.WifiMigration.SettingsMigrationData loadFromSettings(@NonNull android.content.Context);
-    method public static void removeConfigStore();
-  }
-
-  public static final class WifiMigration.ConfigStoreMigrationData implements android.os.Parcelable {
-    method public int describeContents();
-    method @Nullable public java.util.List<android.net.wifi.WifiConfiguration> getUserSavedNetworkConfigurations();
-    method @Nullable public android.net.wifi.SoftApConfiguration getUserSoftApConfiguration();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiMigration.ConfigStoreMigrationData> CREATOR;
-  }
-
-  public static final class WifiMigration.ConfigStoreMigrationData.Builder {
-    ctor public WifiMigration.ConfigStoreMigrationData.Builder();
-    method @NonNull public android.net.wifi.WifiMigration.ConfigStoreMigrationData build();
-    method @NonNull public android.net.wifi.WifiMigration.ConfigStoreMigrationData.Builder setUserSavedNetworkConfigurations(@NonNull java.util.List<android.net.wifi.WifiConfiguration>);
-    method @NonNull public android.net.wifi.WifiMigration.ConfigStoreMigrationData.Builder setUserSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
   }
 
   public static final class WifiMigration.SettingsMigrationData implements android.os.Parcelable {
@@ -9369,7 +9327,6 @@
     field public static final String AUTOFILL_USER_DATA_MAX_VALUE_LENGTH = "autofill_user_data_max_value_length";
     field public static final String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH = "autofill_user_data_min_value_length";
     field public static final String AUTO_REVOKE_DISABLED = "auto_revoke_disabled";
-    field public static final String CARRIER_APPS_HANDLED = "carrier_apps_handled";
     field public static final String COMPLETED_CATEGORY_PREFIX = "suggested.completed_category.";
     field public static final String DOZE_ALWAYS_ON = "doze_always_on";
     field public static final String HUSH_GESTURE_USED = "hush_gesture_used";
@@ -11305,7 +11262,6 @@
 
   public class ServiceState implements android.os.Parcelable {
     method public void fillInNotifierBundle(@NonNull android.os.Bundle);
-    method public int getDataNetworkType();
     method @Nullable public android.telephony.NetworkRegistrationInfo getNetworkRegistrationInfo(int, int);
     method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForDomain(int);
     method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForTransportType(int);
@@ -11451,7 +11407,6 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_MESSAGES_ON_ICC) public java.util.List<android.telephony.SmsMessage> getMessagesFromIcc();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getPremiumSmsConsent(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSmsCapacityOnIcc();
-    method public void sendMultipartTextMessage(@NonNull String, @Nullable String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPremiumSmsConsent(@NonNull String, int);
     field public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3; // 0x3
@@ -11540,7 +11495,6 @@
 
   public class TelephonyManager {
     method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int changeIccLockPassword(@NonNull String, @NonNull String);
     method public int checkCarrierPrivilegesForPackage(String);
     method public int checkCarrierPrivilegesForPackageAnyPhone(String);
     method public void dial(String);
@@ -11551,6 +11505,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getAidForAppType(int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getAllowedNetworkTypes();
+    method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CallForwardingInfo getCallForwarding(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCallWaitingStatus();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int);
@@ -11564,18 +11519,16 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin(int);
     method public String getCdmaPrlVersion();
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaRoamingMode();
     method public int getCurrentPhoneType();
     method public int getCurrentPhoneType(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getDataActivationState();
     method @Deprecated public boolean getDataEnabled();
     method @Deprecated public boolean getDataEnabled(int);
-    method @Nullable public static android.content.ComponentName getDefaultRespondViaMessageApplication(@NonNull android.content.Context, boolean);
+    method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getDefaultRespondViaMessageApplication();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDeviceSoftwareVersion(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getIsimImpu();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
     method public int getMaxNumberOfSimultaneouslyActiveSims();
@@ -11602,12 +11555,10 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataAllowedInVoiceCall();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataConnectionAllowed();
     method public boolean isDataConnectivityPossible();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean isIccLockEnabled();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isInEmergencySmsMode();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
@@ -11641,13 +11592,9 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCallWaitingStatus(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaRoamingMode(int);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaSubscriptionMode(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setDataAllowedDuringVoiceCall(boolean);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setIccLockEnabled(boolean, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
@@ -11697,10 +11644,6 @@
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
     field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
     field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
-    field public static final int CDMA_SUBSCRIPTION_NV = 1; // 0x1
-    field public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // 0x0
-    field public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; // 0xffffffff
-    field public static final int CHANGE_ICC_LOCK_SUCCESS = 2147483647; // 0x7fffffff
     field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
     field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
     field @Deprecated public static final String EXTRA_APN_PROTOCOL = "apnProto";
@@ -12322,10 +12265,6 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsExternalCallState> CREATOR;
   }
 
-  public class ImsManager {
-    field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
-  }
-
   public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
     method @Deprecated @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getFeatureState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>) throws android.telephony.ims.ImsException;
@@ -12591,82 +12530,12 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
-    field public static final int KEY_1X_EPDG_TIMER_SEC = 64; // 0x40
-    field public static final int KEY_1X_THRESHOLD = 59; // 0x3b
-    field public static final int KEY_AMR_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 50; // 0x32
-    field public static final int KEY_AMR_CODEC_MODE_SET_VALUES = 0; // 0x0
-    field public static final int KEY_AMR_DEFAULT_ENCODING_MODE = 53; // 0x35
-    field public static final int KEY_AMR_OCTET_ALIGNED_PAYLOAD_TYPE = 49; // 0x31
-    field public static final int KEY_AMR_WB_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 48; // 0x30
-    field public static final int KEY_AMR_WB_CODEC_MODE_SET_VALUES = 1; // 0x1
-    field public static final int KEY_AMR_WB_OCTET_ALIGNED_PAYLOAD_TYPE = 47; // 0x2f
-    field public static final int KEY_DTMF_NB_PAYLOAD_TYPE = 52; // 0x34
-    field public static final int KEY_DTMF_WB_PAYLOAD_TYPE = 51; // 0x33
-    field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19
-    field public static final int KEY_ENABLE_SILENT_REDIAL = 6; // 0x6
-    field public static final int KEY_LOCAL_BREAKOUT_PCSCF_ADDRESS = 31; // 0x1f
-    field public static final int KEY_LTE_EPDG_TIMER_SEC = 62; // 0x3e
-    field public static final int KEY_LTE_THRESHOLD_1 = 56; // 0x38
-    field public static final int KEY_LTE_THRESHOLD_2 = 57; // 0x39
-    field public static final int KEY_LTE_THRESHOLD_3 = 58; // 0x3a
-    field public static final int KEY_MINIMUM_SIP_SESSION_EXPIRATION_TIMER_SEC = 3; // 0x3
-    field public static final int KEY_MOBILE_DATA_ENABLED = 29; // 0x1d
-    field public static final int KEY_MULTIENDPOINT_ENABLED = 65; // 0x41
-    field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13
-    field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12
-    field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14
-    field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11
-    field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17
-    field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16
-    field public static final int KEY_RCS_PUBLISH_OFFLINE_AVAILABILITY_TIMER_SEC = 16; // 0x10
-    field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15
-    field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf
-    field public static final int KEY_REGISTRATION_DOMAIN_NAME = 12; // 0xc
-    field public static final int KEY_REGISTRATION_RETRY_BASE_TIME_SEC = 33; // 0x21
-    field public static final int KEY_REGISTRATION_RETRY_MAX_TIME_SEC = 34; // 0x22
-    field public static final int KEY_RTP_SPEECH_END_PORT = 36; // 0x24
-    field public static final int KEY_RTP_SPEECH_START_PORT = 35; // 0x23
-    field public static final int KEY_RTT_ENABLED = 66; // 0x42
-    field public static final int KEY_SIP_ACK_RECEIPT_WAIT_TIME_MS = 43; // 0x2b
-    field public static final int KEY_SIP_ACK_RETRANSMIT_WAIT_TIME_MS = 44; // 0x2c
-    field public static final int KEY_SIP_INVITE_ACK_WAIT_TIME_MS = 38; // 0x26
-    field public static final int KEY_SIP_INVITE_CANCELLATION_TIMER_MS = 4; // 0x4
-    field public static final int KEY_SIP_INVITE_REQUEST_TRANSMIT_INTERVAL_MS = 37; // 0x25
-    field public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_INTERVAL_MS = 42; // 0x2a
-    field public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_WAIT_TIME_MS = 39; // 0x27
-    field public static final int KEY_SIP_KEEP_ALIVE_ENABLED = 32; // 0x20
-    field public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMISSION_WAIT_TIME_MS = 45; // 0x2d
-    field public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMIT_INTERVAL_MS = 40; // 0x28
-    field public static final int KEY_SIP_NON_INVITE_RESPONSE_RETRANSMISSION_WAIT_TIME_MS = 46; // 0x2e
-    field public static final int KEY_SIP_NON_INVITE_TRANSACTION_TIMEOUT_TIMER_MS = 41; // 0x29
-    field public static final int KEY_SIP_SESSION_TIMER_SEC = 2; // 0x2
-    field public static final int KEY_SMS_FORMAT = 13; // 0xd
-    field public static final int KEY_SMS_OVER_IP_ENABLED = 14; // 0xe
-    field public static final int KEY_SMS_PUBLIC_SERVICE_IDENTITY = 54; // 0x36
-    field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7
-    field public static final int KEY_T2_TIMER_VALUE_MS = 8; // 0x8
-    field public static final int KEY_TF_TIMER_VALUE_MS = 9; // 0x9
-    field public static final int KEY_TRANSITION_TO_LTE_DELAY_MS = 5; // 0x5
-    field public static final int KEY_USE_GZIP_FOR_LIST_SUBSCRIPTION = 24; // 0x18
-    field public static final int KEY_VIDEO_QUALITY = 55; // 0x37
-    field public static final int KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE = 28; // 0x1c
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
-    field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa
-    field public static final int KEY_VOLTE_USER_OPT_IN_STATUS = 30; // 0x1e
-    field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb
-    field public static final int KEY_WIFI_EPDG_TIMER_SEC = 63; // 0x3f
-    field public static final int KEY_WIFI_THRESHOLD_A = 60; // 0x3c
-    field public static final int KEY_WIFI_THRESHOLD_B = 61; // 0x3d
-    field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
     field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
-    field public static final int SMS_FORMAT_3GPP = 1; // 0x1
-    field public static final int SMS_FORMAT_3GPP2 = 0; // 0x0
     field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
     field public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY";
-    field public static final int VIDEO_QUALITY_HIGH = 1; // 0x1
-    field public static final int VIDEO_QUALITY_LOW = 0; // 0x0
   }
 
   public static class ProvisioningManager.Callback {
@@ -12675,56 +12544,6 @@
     method public void onProvisioningStringChanged(int, @NonNull String);
   }
 
-  public final class RcsContactUceCapability implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public java.util.List<java.lang.String> getCapableExtensionTags();
-    method @NonNull public android.net.Uri getContactUri();
-    method @Nullable public android.net.Uri getServiceUri(long);
-    method public boolean isCapable(long);
-    method public boolean isCapable(@NonNull String);
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field public static final int CAPABILITY_CALL_COMPOSER = 4194304; // 0x400000
-    field public static final int CAPABILITY_CHAT_BOT = 67108864; // 0x4000000
-    field public static final int CAPABILITY_CHAT_BOT_ROLE = 134217728; // 0x8000000
-    field public static final int CAPABILITY_CHAT_SESSION = 2; // 0x2
-    field public static final int CAPABILITY_CHAT_SESSION_STORE_FORWARD = 4; // 0x4
-    field public static final int CAPABILITY_CHAT_STANDALONE = 1; // 0x1
-    field public static final int CAPABILITY_DISCOVERY_VIA_PRESENCE = 4096; // 0x1000
-    field public static final int CAPABILITY_FILE_TRANSFER = 8; // 0x8
-    field public static final int CAPABILITY_FILE_TRANSFER_HTTP = 64; // 0x40
-    field public static final int CAPABILITY_FILE_TRANSFER_SMS = 128; // 0x80
-    field public static final int CAPABILITY_FILE_TRANSFER_STORE_FORWARD = 32; // 0x20
-    field public static final int CAPABILITY_FILE_TRANSFER_THUMBNAIL = 16; // 0x10
-    field public static final int CAPABILITY_GEOLOCATION_PULL = 131072; // 0x20000
-    field public static final int CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER = 262144; // 0x40000
-    field public static final int CAPABILITY_GEOLOCATION_PUSH = 32768; // 0x8000
-    field public static final int CAPABILITY_GEOLOCATION_PUSH_SMS = 65536; // 0x10000
-    field public static final int CAPABILITY_IMAGE_SHARE = 256; // 0x100
-    field public static final int CAPABILITY_IP_VIDEO_CALL = 16384; // 0x4000
-    field public static final int CAPABILITY_IP_VOICE_CALL = 8192; // 0x2000
-    field public static final int CAPABILITY_MMTEL_CALL_COMPOSER = 1073741824; // 0x40000000
-    field public static final int CAPABILITY_PLUG_IN = 268435456; // 0x10000000
-    field public static final int CAPABILITY_POST_CALL = 8388608; // 0x800000
-    field public static final int CAPABILITY_RCS_VIDEO_CALL = 1048576; // 0x100000
-    field public static final int CAPABILITY_RCS_VIDEO_ONLY_CALL = 2097152; // 0x200000
-    field public static final int CAPABILITY_RCS_VOICE_CALL = 524288; // 0x80000
-    field public static final int CAPABILITY_SHARED_MAP = 16777216; // 0x1000000
-    field public static final int CAPABILITY_SHARED_SKETCH = 33554432; // 0x2000000
-    field public static final int CAPABILITY_SOCIAL_PRESENCE = 2048; // 0x800
-    field public static final int CAPABILITY_STANDALONE_CHAT_BOT = 536870912; // 0x20000000
-    field public static final int CAPABILITY_VIDEO_SHARE = 1024; // 0x400
-    field public static final int CAPABILITY_VIDEO_SHARE_DURING_CS_CALL = 512; // 0x200
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactUceCapability> CREATOR;
-  }
-
-  public static class RcsContactUceCapability.Builder {
-    ctor public RcsContactUceCapability.Builder(@NonNull android.net.Uri);
-    method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(long, @NonNull android.net.Uri);
-    method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(long);
-    method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(@NonNull String);
-    method @NonNull public android.telephony.ims.RcsContactUceCapability build();
-  }
-
   public class RcsUceAdapter {
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException;
   }
@@ -12971,7 +12790,6 @@
     method public int transact(android.os.Bundle);
     method public int updateCallBarring(int, int, String[]);
     method public int updateCallBarringForServiceClass(int, int, String[], int);
-    method public int updateCallBarringWithPassword(int, int, @Nullable String[], int, @NonNull String);
     method public int updateCallForward(int, int, String, int, int);
     method public int updateCallWaiting(boolean, int);
     method public int updateClip(boolean);
diff --git a/api/test-current.txt b/api/test-current.txt
index 7c71c77..a1a652f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -597,9 +597,22 @@
 package android.app.blob {
 
   public class BlobStoreManager {
+    method @Nullable public android.app.blob.LeaseInfo getLeaseInfo(@NonNull android.app.blob.BlobHandle) throws java.io.IOException;
+    method @NonNull public java.util.List<android.app.blob.BlobHandle> getLeasedBlobs() throws java.io.IOException;
     method public void waitForIdle(long) throws java.lang.InterruptedException, java.util.concurrent.TimeoutException;
   }
 
+  public final class LeaseInfo implements android.os.Parcelable {
+    ctor public LeaseInfo(@NonNull String, long, @IdRes int, @Nullable CharSequence);
+    method public int describeContents();
+    method @Nullable public CharSequence getDescription();
+    method @IdRes public int getDescriptionResId();
+    method public long getExpiryTimeMillis();
+    method @NonNull public String getPackageName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.blob.LeaseInfo> CREATOR;
+  }
+
 }
 
 package android.app.prediction {
@@ -844,6 +857,7 @@
     method @NonNull public android.content.integrity.RuleSet getCurrentRuleSet();
     method @NonNull public String getCurrentRuleSetProvider();
     method @NonNull public String getCurrentRuleSetVersion();
+    method @NonNull public java.util.List<java.lang.String> getWhitelistedRuleProviders();
     method public void updateRuleSet(@NonNull android.content.integrity.RuleSet, @NonNull android.content.IntentSender);
     field public static final String EXTRA_STATUS = "android.content.integrity.extra.STATUS";
     field public static final int STATUS_FAILURE = 1; // 0x1
@@ -920,6 +934,7 @@
   }
 
   public static class PackageInstaller.SessionInfo implements android.os.Parcelable {
+    method public int getAutoRevokePermissionsMode();
     method public int getRollbackDataPolicy();
     method @NonNull public java.util.Set<java.lang.String> getWhitelistedRestrictedPermissions();
   }
@@ -3420,13 +3435,15 @@
 
 package android.service.quickaccesswallet {
 
-  public interface QuickAccessWalletClient {
+  public interface QuickAccessWalletClient extends java.io.Closeable {
     method public void addWalletServiceEventListener(@NonNull android.service.quickaccesswallet.QuickAccessWalletClient.WalletServiceEventListener);
+    method public void addWalletServiceEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.WalletServiceEventListener);
     method @NonNull public static android.service.quickaccesswallet.QuickAccessWalletClient create(@NonNull android.content.Context);
     method @Nullable public android.content.Intent createWalletIntent();
     method @Nullable public android.content.Intent createWalletSettingsIntent();
     method public void disconnect();
     method public void getWalletCards(@NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback);
+    method public void getWalletCards(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback);
     method public boolean isWalletFeatureAvailable();
     method public boolean isWalletFeatureAvailableWhenDeviceLocked();
     method public boolean isWalletServiceAvailable();
@@ -3721,7 +3738,6 @@
 
   public final class SmsManager {
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int checkSmsShortCodeDestination(String, String);
-    method public void sendMultipartTextMessage(@NonNull String, @Nullable String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String);
     field public static final int SMS_CATEGORY_FREE_SHORT_CODE = 1; // 0x1
     field public static final int SMS_CATEGORY_NOT_SHORT_CODE = 0; // 0x0
     field public static final int SMS_CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE = 3; // 0x3
@@ -3742,9 +3758,10 @@
   public class TelephonyManager {
     method public int addDevicePolicyOverrideApn(@NonNull android.content.Context, @NonNull android.telephony.data.ApnSetting);
     method public int checkCarrierPrivilegesForPackage(String);
+    method @Nullable @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
     method public int getCarrierIdListVersion();
     method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
-    method @Nullable public static android.content.ComponentName getDefaultRespondViaMessageApplication(@NonNull android.content.Context, boolean);
+    method @Nullable @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public android.content.ComponentName getDefaultRespondViaMessageApplication();
     method @NonNull public java.util.List<android.telephony.data.ApnSetting> getDevicePolicyOverrideApns(@NonNull android.content.Context);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public int getEmergencyNumberDbVersion();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag();
@@ -4020,10 +4037,6 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsExternalCallState> CREATOR;
   }
 
-  public class ImsManager {
-    field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
-  }
-
   public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
     method @Deprecated @NonNull @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getFeatureState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>) throws android.telephony.ims.ImsException;
@@ -4285,82 +4298,12 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
-    field public static final int KEY_1X_EPDG_TIMER_SEC = 64; // 0x40
-    field public static final int KEY_1X_THRESHOLD = 59; // 0x3b
-    field public static final int KEY_AMR_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 50; // 0x32
-    field public static final int KEY_AMR_CODEC_MODE_SET_VALUES = 0; // 0x0
-    field public static final int KEY_AMR_DEFAULT_ENCODING_MODE = 53; // 0x35
-    field public static final int KEY_AMR_OCTET_ALIGNED_PAYLOAD_TYPE = 49; // 0x31
-    field public static final int KEY_AMR_WB_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 48; // 0x30
-    field public static final int KEY_AMR_WB_CODEC_MODE_SET_VALUES = 1; // 0x1
-    field public static final int KEY_AMR_WB_OCTET_ALIGNED_PAYLOAD_TYPE = 47; // 0x2f
-    field public static final int KEY_DTMF_NB_PAYLOAD_TYPE = 52; // 0x34
-    field public static final int KEY_DTMF_WB_PAYLOAD_TYPE = 51; // 0x33
-    field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19
-    field public static final int KEY_ENABLE_SILENT_REDIAL = 6; // 0x6
-    field public static final int KEY_LOCAL_BREAKOUT_PCSCF_ADDRESS = 31; // 0x1f
-    field public static final int KEY_LTE_EPDG_TIMER_SEC = 62; // 0x3e
-    field public static final int KEY_LTE_THRESHOLD_1 = 56; // 0x38
-    field public static final int KEY_LTE_THRESHOLD_2 = 57; // 0x39
-    field public static final int KEY_LTE_THRESHOLD_3 = 58; // 0x3a
-    field public static final int KEY_MINIMUM_SIP_SESSION_EXPIRATION_TIMER_SEC = 3; // 0x3
-    field public static final int KEY_MOBILE_DATA_ENABLED = 29; // 0x1d
-    field public static final int KEY_MULTIENDPOINT_ENABLED = 65; // 0x41
-    field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13
-    field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12
-    field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14
-    field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11
-    field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17
-    field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16
-    field public static final int KEY_RCS_PUBLISH_OFFLINE_AVAILABILITY_TIMER_SEC = 16; // 0x10
-    field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15
-    field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf
-    field public static final int KEY_REGISTRATION_DOMAIN_NAME = 12; // 0xc
-    field public static final int KEY_REGISTRATION_RETRY_BASE_TIME_SEC = 33; // 0x21
-    field public static final int KEY_REGISTRATION_RETRY_MAX_TIME_SEC = 34; // 0x22
-    field public static final int KEY_RTP_SPEECH_END_PORT = 36; // 0x24
-    field public static final int KEY_RTP_SPEECH_START_PORT = 35; // 0x23
-    field public static final int KEY_RTT_ENABLED = 66; // 0x42
-    field public static final int KEY_SIP_ACK_RECEIPT_WAIT_TIME_MS = 43; // 0x2b
-    field public static final int KEY_SIP_ACK_RETRANSMIT_WAIT_TIME_MS = 44; // 0x2c
-    field public static final int KEY_SIP_INVITE_ACK_WAIT_TIME_MS = 38; // 0x26
-    field public static final int KEY_SIP_INVITE_CANCELLATION_TIMER_MS = 4; // 0x4
-    field public static final int KEY_SIP_INVITE_REQUEST_TRANSMIT_INTERVAL_MS = 37; // 0x25
-    field public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_INTERVAL_MS = 42; // 0x2a
-    field public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_WAIT_TIME_MS = 39; // 0x27
-    field public static final int KEY_SIP_KEEP_ALIVE_ENABLED = 32; // 0x20
-    field public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMISSION_WAIT_TIME_MS = 45; // 0x2d
-    field public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMIT_INTERVAL_MS = 40; // 0x28
-    field public static final int KEY_SIP_NON_INVITE_RESPONSE_RETRANSMISSION_WAIT_TIME_MS = 46; // 0x2e
-    field public static final int KEY_SIP_NON_INVITE_TRANSACTION_TIMEOUT_TIMER_MS = 41; // 0x29
-    field public static final int KEY_SIP_SESSION_TIMER_SEC = 2; // 0x2
-    field public static final int KEY_SMS_FORMAT = 13; // 0xd
-    field public static final int KEY_SMS_OVER_IP_ENABLED = 14; // 0xe
-    field public static final int KEY_SMS_PUBLIC_SERVICE_IDENTITY = 54; // 0x36
-    field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7
-    field public static final int KEY_T2_TIMER_VALUE_MS = 8; // 0x8
-    field public static final int KEY_TF_TIMER_VALUE_MS = 9; // 0x9
-    field public static final int KEY_TRANSITION_TO_LTE_DELAY_MS = 5; // 0x5
-    field public static final int KEY_USE_GZIP_FOR_LIST_SUBSCRIPTION = 24; // 0x18
-    field public static final int KEY_VIDEO_QUALITY = 55; // 0x37
-    field public static final int KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE = 28; // 0x1c
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
-    field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa
-    field public static final int KEY_VOLTE_USER_OPT_IN_STATUS = 30; // 0x1e
-    field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb
-    field public static final int KEY_WIFI_EPDG_TIMER_SEC = 63; // 0x3f
-    field public static final int KEY_WIFI_THRESHOLD_A = 60; // 0x3c
-    field public static final int KEY_WIFI_THRESHOLD_B = 61; // 0x3d
-    field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
     field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
-    field public static final int SMS_FORMAT_3GPP = 1; // 0x1
-    field public static final int SMS_FORMAT_3GPP2 = 0; // 0x0
     field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
     field public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY";
-    field public static final int VIDEO_QUALITY_HIGH = 1; // 0x1
-    field public static final int VIDEO_QUALITY_LOW = 0; // 0x0
   }
 
   public static class ProvisioningManager.Callback {
@@ -4369,56 +4312,6 @@
     method public void onProvisioningStringChanged(int, @NonNull String);
   }
 
-  public final class RcsContactUceCapability implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public java.util.List<java.lang.String> getCapableExtensionTags();
-    method @NonNull public android.net.Uri getContactUri();
-    method @Nullable public android.net.Uri getServiceUri(long);
-    method public boolean isCapable(long);
-    method public boolean isCapable(@NonNull String);
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field public static final int CAPABILITY_CALL_COMPOSER = 4194304; // 0x400000
-    field public static final int CAPABILITY_CHAT_BOT = 67108864; // 0x4000000
-    field public static final int CAPABILITY_CHAT_BOT_ROLE = 134217728; // 0x8000000
-    field public static final int CAPABILITY_CHAT_SESSION = 2; // 0x2
-    field public static final int CAPABILITY_CHAT_SESSION_STORE_FORWARD = 4; // 0x4
-    field public static final int CAPABILITY_CHAT_STANDALONE = 1; // 0x1
-    field public static final int CAPABILITY_DISCOVERY_VIA_PRESENCE = 4096; // 0x1000
-    field public static final int CAPABILITY_FILE_TRANSFER = 8; // 0x8
-    field public static final int CAPABILITY_FILE_TRANSFER_HTTP = 64; // 0x40
-    field public static final int CAPABILITY_FILE_TRANSFER_SMS = 128; // 0x80
-    field public static final int CAPABILITY_FILE_TRANSFER_STORE_FORWARD = 32; // 0x20
-    field public static final int CAPABILITY_FILE_TRANSFER_THUMBNAIL = 16; // 0x10
-    field public static final int CAPABILITY_GEOLOCATION_PULL = 131072; // 0x20000
-    field public static final int CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER = 262144; // 0x40000
-    field public static final int CAPABILITY_GEOLOCATION_PUSH = 32768; // 0x8000
-    field public static final int CAPABILITY_GEOLOCATION_PUSH_SMS = 65536; // 0x10000
-    field public static final int CAPABILITY_IMAGE_SHARE = 256; // 0x100
-    field public static final int CAPABILITY_IP_VIDEO_CALL = 16384; // 0x4000
-    field public static final int CAPABILITY_IP_VOICE_CALL = 8192; // 0x2000
-    field public static final int CAPABILITY_MMTEL_CALL_COMPOSER = 1073741824; // 0x40000000
-    field public static final int CAPABILITY_PLUG_IN = 268435456; // 0x10000000
-    field public static final int CAPABILITY_POST_CALL = 8388608; // 0x800000
-    field public static final int CAPABILITY_RCS_VIDEO_CALL = 1048576; // 0x100000
-    field public static final int CAPABILITY_RCS_VIDEO_ONLY_CALL = 2097152; // 0x200000
-    field public static final int CAPABILITY_RCS_VOICE_CALL = 524288; // 0x80000
-    field public static final int CAPABILITY_SHARED_MAP = 16777216; // 0x1000000
-    field public static final int CAPABILITY_SHARED_SKETCH = 33554432; // 0x2000000
-    field public static final int CAPABILITY_SOCIAL_PRESENCE = 2048; // 0x800
-    field public static final int CAPABILITY_STANDALONE_CHAT_BOT = 536870912; // 0x20000000
-    field public static final int CAPABILITY_VIDEO_SHARE = 1024; // 0x400
-    field public static final int CAPABILITY_VIDEO_SHARE_DURING_CS_CALL = 512; // 0x200
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactUceCapability> CREATOR;
-  }
-
-  public static class RcsContactUceCapability.Builder {
-    ctor public RcsContactUceCapability.Builder(@NonNull android.net.Uri);
-    method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(long, @NonNull android.net.Uri);
-    method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(long);
-    method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(@NonNull String);
-    method @NonNull public android.telephony.ims.RcsContactUceCapability build();
-  }
-
   public class RcsUceAdapter {
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException;
   }
@@ -4665,7 +4558,6 @@
     method public int transact(android.os.Bundle);
     method public int updateCallBarring(int, int, String[]);
     method public int updateCallBarringForServiceClass(int, int, String[], int);
-    method public int updateCallBarringWithPassword(int, int, @Nullable String[], int, @NonNull String);
     method public int updateCallForward(int, int, String, int, int);
     method public int updateCallWaiting(boolean, int);
     method public int updateClip(boolean);
@@ -4943,8 +4835,8 @@
   }
 
   public class SurfaceControlViewHost {
-    method public void addView(@NonNull android.view.View, android.view.WindowManager.LayoutParams);
     method public void relayout(android.view.WindowManager.LayoutParams);
+    method public void setView(@NonNull android.view.View, @NonNull android.view.WindowManager.LayoutParams);
   }
 
   @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index bd5bdc6..713e923 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -3332,16 +3332,12 @@
     optional int32 uid = 1 [(is_uid) = true];
 
     // The operation's name.
-    // To the extent possible, preserve the mapping from AppOpsManager.OP_ constants.
-    // Only these named ops are actually logged.
-    enum AppOpName {
-        OP_NONE = -1; // Also represents UNKNOWN.
-        OP_COARSE_LOCATION = 0;
-        OP_FINE_LOCATION = 1;
-        OP_CAMERA = 26;
-        OP_RECORD_AUDIO = 27;
-    }
-    optional AppOpName app_op_name = 2 [default = OP_NONE];
+    // Only following four ops are logged
+    // COARSE_LOCATION = 0
+    // FINE_LOCATION = 1
+    // CAMERA = 26
+    // RECORD_AUDIO = 27
+    optional android.app.AppOpEnum app_op_name = 2 [default = APP_OP_NONE];
 
     // The uid's permission mode for accessing the AppOp during this fgs session.
     enum Mode {
@@ -7571,8 +7567,8 @@
     // Name of the package performing the op
     optional string package_name = 2;
 
-    // operation id; maps to the OP_* constants in AppOpsManager.java
-    optional int32 op_id = 3;
+    // operation id
+    optional android.app.AppOpEnum op_id = 3 [default = APP_OP_NONE];
 
     // The number of times the op was granted while the app was in the
     // foreground (only for trusted requests)
@@ -7617,8 +7613,8 @@
     // above.
     optional string tag = 3;
 
-    // operation id; maps to the OPSTR_* constants in AppOpsManager.java
-    optional string op = 4;
+    // operation id
+    optional android.app.AppOpEnum op = 4 [default = APP_OP_NONE];
 
     // The number of times the op was granted while the app was in the
     // foreground (only for trusted requests)
@@ -8490,6 +8486,7 @@
         DEFAULT = 0;
         UNIFORM = 1;
         RARELY_USED = 2;
+        BOOT_TIME_SAMPLING = 3;
     }
 
     // sampling strategy used to collect this message
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 258f84d..b515d0a 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -350,17 +350,19 @@
     return false;
 }
 
-void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last) {
+void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
     int32_t value = readNextValue<int32_t>();
     addToValues(pos, depth, value, last);
+    parseAnnotations(numAnnotations);
 }
 
-void LogEvent::parseInt64(int32_t* pos, int32_t depth, bool* last) {
+void LogEvent::parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
     int64_t value = readNextValue<int64_t>();
     addToValues(pos, depth, value, last);
+    parseAnnotations(numAnnotations);
 }
 
-void LogEvent::parseString(int32_t* pos, int32_t depth, bool* last) {
+void LogEvent::parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
     int32_t numBytes = readNextValue<int32_t>();
     if ((uint32_t)numBytes > mRemainingLen) {
         mValid = false;
@@ -371,20 +373,23 @@
     mBuf += numBytes;
     mRemainingLen -= numBytes;
     addToValues(pos, depth, value, last);
+    parseAnnotations(numAnnotations);
 }
 
-void LogEvent::parseFloat(int32_t* pos, int32_t depth, bool* last) {
+void LogEvent::parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
     float value = readNextValue<float>();
     addToValues(pos, depth, value, last);
+    parseAnnotations(numAnnotations);
 }
 
-void LogEvent::parseBool(int32_t* pos, int32_t depth, bool* last) {
+void LogEvent::parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
     // cast to int32_t because FieldValue does not support bools
     int32_t value = (int32_t)readNextValue<uint8_t>();
     addToValues(pos, depth, value, last);
+    parseAnnotations(numAnnotations);
 }
 
-void LogEvent::parseByteArray(int32_t* pos, int32_t depth, bool* last) {
+void LogEvent::parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
     int32_t numBytes = readNextValue<int32_t>();
     if ((uint32_t)numBytes > mRemainingLen) {
         mValid = false;
@@ -395,9 +400,10 @@
     mBuf += numBytes;
     mRemainingLen -= numBytes;
     addToValues(pos, depth, value, last);
+    parseAnnotations(numAnnotations);
 }
 
-void LogEvent::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last) {
+void LogEvent::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
     int32_t numPairs = readNextValue<uint8_t>();
 
     for (pos[1] = 1; pos[1] <= numPairs; pos[1]++) {
@@ -405,56 +411,79 @@
 
         // parse key
         pos[2] = 1;
-        parseInt32(pos, 2, last);
+        parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0);
 
         // parse value
         last[2] = true;
-        uint8_t typeId = getTypeId(readNextValue<uint8_t>());
-        switch (typeId) {
+
+        uint8_t typeInfo = readNextValue<uint8_t>();
+        switch (getTypeId(typeInfo)) {
             case INT32_TYPE:
                 pos[2] = 2; // pos[2] determined by index of type in KeyValuePair in atoms.proto
-                parseInt32(pos, 2, last);
+                parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0);
                 break;
             case INT64_TYPE:
                 pos[2] = 3;
-                parseInt64(pos, 2, last);
+                parseInt64(pos, /*depth=*/2, last, /*numAnnotations=*/0);
                 break;
             case STRING_TYPE:
                 pos[2] = 4;
-                parseString(pos, 2, last);
+                parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0);
                 break;
             case FLOAT_TYPE:
                 pos[2] = 5;
-                parseFloat(pos, 2, last);
+                parseFloat(pos, /*depth=*/2, last, /*numAnnotations=*/0);
                 break;
             default:
                 mValid = false;
         }
     }
 
+    parseAnnotations(numAnnotations);
+
     pos[1] = pos[2] = 1;
     last[1] = last[2] = false;
 }
 
-void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last) {
+void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last,
+                                     uint8_t numAnnotations) {
     int32_t numNodes = readNextValue<uint8_t>();
     for (pos[1] = 1; pos[1] <= numNodes; pos[1]++) {
         last[1] = (pos[1] == numNodes);
 
         // parse uid
         pos[2] = 1;
-        parseInt32(pos, 2, last);
+        parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0);
 
         // parse tag
         pos[2] = 2;
         last[2] = true;
-        parseString(pos, 2, last);
+        parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0);
     }
 
+    parseAnnotations(numAnnotations);
+
     pos[1] = pos[2] = 1;
     last[1] = last[2] = false;
 }
 
+// TODO(b/151109630): store annotation information within LogEvent
+void LogEvent::parseAnnotations(uint8_t numAnnotations) {
+    for (uint8_t i = 0; i < numAnnotations; i++) {
+        /*uint8_t annotationId = */ readNextValue<uint8_t>();
+        uint8_t annotationType = readNextValue<uint8_t>();
+        switch (annotationType) {
+            case BOOL_TYPE:
+                /*bool annotationValue = */ readNextValue<uint8_t>();
+                break;
+            case INT32_TYPE:
+                /*int32_t annotationValue =*/ readNextValue<int32_t>();
+                break;
+            default:
+                mValid = false;
+        }
+    }
+}
 
 // This parsing logic is tied to the encoding scheme used in StatsEvent.java and
 // stats_event.c
@@ -475,6 +504,7 @@
     typeInfo = readNextValue<uint8_t>();
     if (getTypeId(typeInfo) != INT64_TYPE) mValid = false;
     mElapsedTimestampNs = readNextValue<int64_t>();
+    parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations
     numElements--;
 
     typeInfo = readNextValue<uint8_t>();
@@ -484,37 +514,36 @@
 
 
     for (pos[0] = 1; pos[0] <= numElements && mValid; pos[0]++) {
+        last[0] = (pos[0] == numElements);
+
         typeInfo = readNextValue<uint8_t>();
         uint8_t typeId = getTypeId(typeInfo);
 
-        last[0] = (pos[0] == numElements);
-
         // TODO(b/144373276): handle errors passed to the socket
-        // TODO(b/144373257): parse annotations
         switch(typeId) {
             case BOOL_TYPE:
-                parseBool(pos, 0, last);
+                parseBool(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
                 break;
             case INT32_TYPE:
-                parseInt32(pos, 0, last);
+                parseInt32(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
                 break;
             case INT64_TYPE:
-                parseInt64(pos, 0, last);
+                parseInt64(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
                 break;
             case FLOAT_TYPE:
-                parseFloat(pos, 0, last);
+                parseFloat(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
                 break;
             case BYTE_ARRAY_TYPE:
-                parseByteArray(pos, 0, last);
+                parseByteArray(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
                 break;
             case STRING_TYPE:
-                parseString(pos, 0, last);
+                parseString(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
                 break;
             case KEY_VALUE_PAIRS_TYPE:
-                parseKeyValuePairs(pos, 0, last);
+                parseKeyValuePairs(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
                 break;
             case ATTRIBUTION_CHAIN_TYPE:
-                parseAttributionChain(pos, 0, last);
+                parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
                 break;
             default:
                 mValid = false;
@@ -531,7 +560,7 @@
 }
 
 uint8_t LogEvent::getNumAnnotations(uint8_t typeInfo) {
-    return (typeInfo >> 4) & 0x0F;
+    return (typeInfo >> 4) & 0x0F; // num annotations in upper 4 bytes
 }
 
 int64_t LogEvent::GetLong(size_t key, status_t* err) const {
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index b68eeb8..6537f13 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -232,14 +232,15 @@
      */
     LogEvent(const LogEvent&);
 
-    void parseInt32(int32_t* pos, int32_t depth, bool* last);
-    void parseInt64(int32_t* pos, int32_t depth, bool* last);
-    void parseString(int32_t* pos, int32_t depth, bool* last);
-    void parseFloat(int32_t* pos, int32_t depth, bool* last);
-    void parseBool(int32_t* pos, int32_t depth, bool* last);
-    void parseByteArray(int32_t* pos, int32_t depth, bool* last);
-    void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last);
-    void parseAttributionChain(int32_t* pos, int32_t depth, bool* last);
+    void parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseAnnotations(uint8_t numAnnotations);
 
     /**
      * The below three variables are only valid during the execution of
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index ec11043..489a0de 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -34,6 +34,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Activity manager local system service interface.
@@ -124,6 +125,12 @@
     public abstract int getUidProcessState(int uid);
 
     /**
+     * Get a map of pid and package name that process of that pid Android/data and Android/obb
+     * directory is not mounted to lowerfs.
+     */
+    public abstract Map<Integer, String> getProcessesWithPendingBindMounts(int userId);
+
+    /**
      * @return {@code true} if system is ready, {@code false} otherwise.
      */
     public abstract boolean isSystemReady();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index f6a79cd..fa4aa19 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -696,6 +696,10 @@
     public static final int SAMPLING_STRATEGY_RARELY_USED =
             FrameworkStatsLog.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__RARELY_USED;
 
+    /** @hide */
+    public static final int SAMPLING_STRATEGY_BOOT_TIME_SAMPLING =
+            FrameworkStatsLog.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__BOOT_TIME_SAMPLING;
+
     /**
      * Strategies used for message sampling
      * @hide
@@ -704,7 +708,8 @@
     @IntDef(prefix = {"SAMPLING_STRATEGY_"}, value = {
             SAMPLING_STRATEGY_DEFAULT,
             SAMPLING_STRATEGY_UNIFORM,
-            SAMPLING_STRATEGY_RARELY_USED
+            SAMPLING_STRATEGY_RARELY_USED,
+            SAMPLING_STRATEGY_BOOT_TIME_SAMPLING
     })
     public @interface SamplingStrategy {}
 
@@ -1071,9 +1076,17 @@
     /** @hide Auto-revoke app permissions if app is unused for an extended period */
     public static final int OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED = 97;
 
+    /**
+     * Whether {@link #OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED} is allowed to be changed by
+     * the installer
+     *
+     * @hide
+     */
+    public static final int OP_AUTO_REVOKE_MANAGED_BY_INSTALLER = 98;
+
     /** @hide */
     @UnsupportedAppUsage
-    public static final int _NUM_OP = 98;
+    public static final int _NUM_OP = 99;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1356,7 +1369,6 @@
     @SystemApi
     public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility";
     /** @hide Read device identifiers */
-    @SystemApi
     public static final String OPSTR_READ_DEVICE_IDENTIFIERS = "android:read_device_identifiers";
     /** @hide Query all packages on device */
     public static final String OPSTR_QUERY_ALL_PACKAGES = "android:query_all_packages";
@@ -1373,6 +1385,10 @@
     public static final String OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED =
             "android:auto_revoke_permissions_if_unused";
 
+    /** @hide Auto-revoke app permissions if app is unused for an extended period */
+    public static final String OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER =
+            "android:auto_revoke_managed_by_installer";
+
     /** @hide Communicate cross-profile within the same profile group. */
     @SystemApi
     public static final String OPSTR_INTERACT_ACROSS_PROFILES = "android:interact_across_profiles";
@@ -1463,6 +1479,7 @@
             OP_LOADER_USAGE_STATS,
             OP_ACCESS_CALL_AUDIO,
             OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
+            OP_AUTO_REVOKE_MANAGED_BY_INSTALLER,
     };
 
     /**
@@ -1572,6 +1589,7 @@
             OP_LOADER_USAGE_STATS,              // LOADER_USAGE_STATS
             OP_ACCESS_CALL_AUDIO,               // ACCESS_CALL_AUDIO
             OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, //AUTO_REVOKE_PERMISSIONS_IF_UNUSED
+            OP_AUTO_REVOKE_MANAGED_BY_INSTALLER, //OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
     };
 
     /**
@@ -1676,6 +1694,7 @@
             OPSTR_LOADER_USAGE_STATS,
             OPSTR_ACCESS_CALL_AUDIO,
             OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
+            OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER,
     };
 
     /**
@@ -1781,6 +1800,7 @@
             "LOADER_USAGE_STATS",
             "ACCESS_CALL_AUDIO",
             "AUTO_REVOKE_PERMISSIONS_IF_UNUSED",
+            "AUTO_REVOKE_MANAGED_BY_INSTALLER",
     };
 
     /**
@@ -1887,6 +1907,7 @@
             android.Manifest.permission.LOADER_USAGE_STATS,
             Manifest.permission.ACCESS_CALL_AUDIO,
             null, // no permission for OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
+            null, // no permission for OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
     };
 
     /**
@@ -1993,6 +2014,7 @@
             null, // LOADER_USAGE_STATS
             null, // ACCESS_CALL_AUDIO
             null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
+            null, // AUTO_REVOKE_MANAGED_BY_INSTALLER
     };
 
     /**
@@ -2098,6 +2120,7 @@
             null, // LOADER_USAGE_STATS
             null, // ACCESS_CALL_AUDIO
             null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
+            null, // AUTO_REVOKE_MANAGED_BY_INSTALLER
     };
 
     /**
@@ -2202,6 +2225,7 @@
             AppOpsManager.MODE_DEFAULT, // LOADER_USAGE_STATS
             AppOpsManager.MODE_DEFAULT, // ACCESS_CALL_AUDIO
             AppOpsManager.MODE_DEFAULT, // OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
+            AppOpsManager.MODE_ALLOWED, // OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
     };
 
     /**
@@ -2310,9 +2334,118 @@
             false, // LOADER_USAGE_STATS
             false, // ACCESS_CALL_AUDIO
             false, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
+            false, // AUTO_REVOKE_MANAGED_BY_INSTALLER
     };
 
     /**
+     * This maps each operation to its statsd logging code.
+     */
+    private static int[] sOpToLoggingId = new int[]{
+            AppProtoEnums.APP_OP_COARSE_LOCATION, // OP_COARSE_LOCATION
+            AppProtoEnums.APP_OP_FINE_LOCATION, // OP_FINE_LOCATION
+            AppProtoEnums.APP_OP_GPS, // OP_ID__GPS
+            AppProtoEnums.APP_OP_VIBRATE, // OP_VIBRATE
+            AppProtoEnums.APP_OP_READ_CONTACTS, // OP_READ_CONTACTS
+            AppProtoEnums.APP_OP_WRITE_CONTACTS, // OP_WRITE_CONTACTS
+            AppProtoEnums.APP_OP_READ_CALL_LOG, // OP_READ_CALL_LOG
+            AppProtoEnums.APP_OP_WRITE_CALL_LOG, // OP_WRITE_CALL_LOG
+            AppProtoEnums.APP_OP_READ_CALENDAR, // OP_READ_CALENDAR
+            AppProtoEnums.APP_OP_WRITE_CALENDAR, // OP_WRITE_CALENDAR
+            AppProtoEnums.APP_OP_WIFI_SCAN, // OP_WIFI_SCAN
+            AppProtoEnums.APP_OP_POST_NOTIFICATION, // OP_POST_NOTIFICATION
+            AppProtoEnums.APP_OP_NEIGHBORING_CELLS, // OP_NEIGHBORING_CELLS
+            AppProtoEnums.APP_OP_CALL_PHONE, // OP_CALL_PHONE
+            AppProtoEnums.APP_OP_READ_SMS, // OP_READ_SMS
+            AppProtoEnums.APP_OP_WRITE_SMS, // OP_WRITE_SMS
+            AppProtoEnums.APP_OP_RECEIVE_SMS, // OP_RECEIVE_SMS
+            AppProtoEnums.APP_OP_RECEIVE_EMERGENCY_SMS, // OP_RECEIVE_EMERGENCY_SMS
+            AppProtoEnums.APP_OP_RECEIVE_MMS, // OP_RECEIVE_MMS
+            AppProtoEnums.APP_OP_RECEIVE_WAP_PUSH, // OP_RECEIVE_WAP_PUSH
+            AppProtoEnums.APP_OP_SEND_SMS, // OP_SEND_SMS
+            AppProtoEnums.APP_OP_READ_ICC_SMS, // OP_READ_ICC_SMS
+            AppProtoEnums.APP_OP_WRITE_ICC_SMS, // OP_WRITE_ICC_SMS
+            AppProtoEnums.APP_OP_WRITE_SETTINGS, // OP_WRITE_SETTINGS
+            AppProtoEnums.APP_OP_SYSTEM_ALERT_WINDOW, // OP_SYSTEM_ALERT_WINDOW
+            AppProtoEnums.APP_OP_ACCESS_NOTIFICATIONS, // OP_ACCESS_NOTIFICATIONS
+            AppProtoEnums.APP_OP_CAMERA, // OP_CAMERA
+            AppProtoEnums.APP_OP_RECORD_AUDIO, // OP_RECORD_AUDIO
+            AppProtoEnums.APP_OP_PLAY_AUDIO, // OP_PLAY_AUDIO
+            AppProtoEnums.APP_OP_READ_CLIPBOARD, // OP_READ_CLIPBOARD
+            AppProtoEnums.APP_OP_WRITE_CLIPBOARD, // OP_WRITE_CLIPBOARD
+            AppProtoEnums.APP_OP_TAKE_MEDIA_BUTTONS, // OP_TAKE_MEDIA_BUTTONS
+            AppProtoEnums.APP_OP_TAKE_AUDIO_FOCUS, // OP_TAKE_AUDIO_FOCUS
+            AppProtoEnums.APP_OP_AUDIO_MASTER_VOLUME, // OP_AUDIO_MASTER_VOLUME
+            AppProtoEnums.APP_OP_AUDIO_VOICE_VOLUME, // OP_AUDIO_VOICE_VOLUME
+            AppProtoEnums.APP_OP_AUDIO_RING_VOLUME, // OP_AUDIO_RING_VOLUME
+            AppProtoEnums.APP_OP_AUDIO_MEDIA_VOLUME, // OP_AUDIO_MEDIA_VOLUME
+            AppProtoEnums.APP_OP_AUDIO_ALARM_VOLUME, // OP_AUDIO_ALARM_VOLUME
+            AppProtoEnums.APP_OP_AUDIO_NOTIFICATION_VOLUME, // OP_AUDIO_NOTIFICATION_VOLUME
+            AppProtoEnums.APP_OP_AUDIO_BLUETOOTH_VOLUME, // OP_AUDIO_BLUETOOTH_VOLUME
+            AppProtoEnums.APP_OP_WAKE_LOCK, // OP_WAKE_LOCK
+            AppProtoEnums.APP_OP_MONITOR_LOCATION, // OP_MONITOR_LOCATION
+            AppProtoEnums.APP_OP_MONITOR_HIGH_POWER_LOCATION, // OP_MONITOR_HIGH_POWER_LOCATION
+            AppProtoEnums.APP_OP_GET_USAGE_STATS, // OP_GET_USAGE_STATS
+            AppProtoEnums.APP_OP_MUTE_MICROPHONE, //OP_MUTE_MICROPHONE
+            AppProtoEnums.APP_OP_TOAST_WINDOW, // OP_TOAST_WINDOW
+            AppProtoEnums.APP_OP_PROJECT_MEDIA, // OP_PROJECT_MEDIA
+            AppProtoEnums.APP_OP_ACTIVATE_VPN, // OP_ACTIVATE_VPN
+            AppProtoEnums.APP_OP_WRITE_WALLPAPER, // OP_WRITE_WALLPAPER
+            AppProtoEnums.APP_OP_ASSIST_STRUCTURE, // OP_ASSIST_STRUCTURE
+            AppProtoEnums.APP_OP_ASSIST_SCREENSHOT, // OP_ASSIST_SCREENSHOT
+            AppProtoEnums.APP_OP_READ_PHONE_STATE, // OP_READ_PHONE_STATE
+            AppProtoEnums.APP_OP_ADD_VOICEMAIL, // OP_ADD_VOICEMAIL
+            AppProtoEnums.APP_OP_USE_SIP, // OP_USE_SIP
+            AppProtoEnums.APP_OP_PROCESS_OUTGOING_CALLS, // OP_PROCESS_OUTGOING_CALLS
+            AppProtoEnums.APP_OP_USE_FINGERPRINT, // OP_USE_FINGERPRINT
+            AppProtoEnums.APP_OP_BODY_SENSORS, // OP_BODY_SENSORS
+            AppProtoEnums.APP_OP_READ_CELL_BROADCASTS, // OP_READ_CELL_BROADCASTS
+            AppProtoEnums.APP_OP_MOCK_LOCATION, // OP_MOCK_LOCATION
+            AppProtoEnums.APP_OP_READ_EXTERNAL_STORAGE, // OP_READ_EXTERNAL_STORAGE
+            AppProtoEnums.APP_OP_WRITE_EXTERNAL_STORAGE, // OP_WRITE_EXTERNAL_STORAGE
+            AppProtoEnums.APP_OP_TURN_SCREEN_ON, // OP_TURN_SCREEN_ON
+            AppProtoEnums.APP_OP_GET_ACCOUNTS, // OP_GET_ACCOUNTS
+            AppProtoEnums.APP_OP_RUN_IN_BACKGROUND, // OP_RUN_IN_BACKGROUND
+            AppProtoEnums.APP_OP_AUDIO_ACCESSIBILITY_VOLUME, // OP_AUDIO_ACCESSIBILITY_VOLUME
+            AppProtoEnums.APP_OP_READ_PHONE_NUMBERS, // OP_READ_PHONE_NUMBERS
+            AppProtoEnums.APP_OP_REQUEST_INSTALL_PACKAGES, // OP_REQUEST_INSTALL_PACKAGES
+            AppProtoEnums.APP_OP_PICTURE_IN_PICTURE, // OP_PICTURE_IN_PICTURE
+            AppProtoEnums.APP_OP_INSTANT_APP_START_FOREGROUND, // OP_INSTANT_APP_START_FOREGROUND
+            AppProtoEnums.APP_OP_ANSWER_PHONE_CALLS, // OP_ANSWER_PHONE_CALLS
+            AppProtoEnums.APP_OP_RUN_ANY_IN_BACKGROUND, // OP_RUN_ANY_IN_BACKGROUND
+            AppProtoEnums.APP_OP_CHANGE_WIFI_STATE, // OP_CHANGE_WIFI_STATE
+            AppProtoEnums.APP_OP_REQUEST_DELETE_PACKAGES, // OP_REQUEST_DELETE_PACKAGES
+            AppProtoEnums.APP_OP_BIND_ACCESSIBILITY_SERVICE, // OP_BIND_ACCESSIBILITY_SERVICE
+            AppProtoEnums.APP_OP_ACCEPT_HANDOVER, // OP_ACCEPT_HANDOVER
+            AppProtoEnums.APP_OP_MANAGE_IPSEC_TUNNELS, // OP_MANAGE_IPSEC_TUNNELS
+            AppProtoEnums.APP_OP_START_FOREGROUND, // OP_START_FOREGROUND
+            AppProtoEnums.APP_OP_BLUETOOTH_SCAN, // OP_BLUETOOTH_SCAN
+            AppProtoEnums.APP_OP_USE_BIOMETRIC, // OP_USE_BIOMETRIC
+            AppProtoEnums.APP_OP_ACTIVITY_RECOGNITION, // OP_ACTIVITY_RECOGNITION
+            AppProtoEnums.APP_OP_SMS_FINANCIAL_TRANSACTIONS, // OP_SMS_FINANCIAL_TRANSACTIONS
+            AppProtoEnums.APP_OP_READ_MEDIA_AUDIO, // OP_READ_MEDIA_AUDIO
+            AppProtoEnums.APP_OP_WRITE_MEDIA_AUDIO, // OP_WRITE_MEDIA_AUDIO
+            AppProtoEnums.APP_OP_READ_MEDIA_VIDEO, // OP_READ_MEDIA_VIDEO
+            AppProtoEnums.APP_OP_WRITE_MEDIA_VIDEO, // OP_WRITE_MEDIA_VIDEO
+            AppProtoEnums.APP_OP_READ_MEDIA_IMAGES, // OP_READ_MEDIA_IMAGES
+            AppProtoEnums.APP_OP_WRITE_MEDIA_IMAGES, // OP_WRITE_MEDIA_IMAGES
+            AppProtoEnums.APP_OP_LEGACY_STORAGE, // OP_LEGACY_STORAGE
+            AppProtoEnums.APP_OP_ACCESS_ACCESSIBILITY, // OP_ACCESS_ACCESSIBILITY
+            AppProtoEnums.APP_OP_READ_DEVICE_IDENTIFIERS, // OP_READ_DEVICE_IDENTIFIERS
+            AppProtoEnums.APP_OP_ACCESS_MEDIA_LOCATION, // OP_ACCESS_MEDIA_LOCATION
+            AppProtoEnums.APP_OP_QUERY_ALL_PACKAGES, // OP_QUERY_ALL_PACKAGES
+            AppProtoEnums.APP_OP_MANAGE_EXTERNAL_STORAGE, // OP_MANAGE_EXTERNAL_STORAGE
+            AppProtoEnums.APP_OP_INTERACT_ACROSS_PROFILES, // OP_INTERACT_ACROSS_PROFILES
+            AppProtoEnums.APP_OP_ACTIVATE_PLATFORM_VPN, // OP_ACTIVATE_PLATFORM_VPN
+            AppProtoEnums.APP_OP_LOADER_USAGE_STATS, // OP_LOADER_USAGE_STATS
+            AppProtoEnums.APP_OP_ACCESS_CALL_AUDIO, // OP_ACCESS_CALL_AUDIO
+            AppProtoEnums.APP_OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
+            // OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
+            AppProtoEnums.APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER 
+            //OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
+    };
+
+
+    /**
      * Mapping from an app op name to the app op code.
      */
     private static HashMap<String, Integer> sOpStrToOp = new HashMap<>();
@@ -2353,6 +2486,10 @@
             throw new IllegalStateException("sOpToString length " + sOpToString.length
                     + " should be " + _NUM_OP);
         }
+        if (sOpToLoggingId.length != _NUM_OP) {
+            throw new IllegalStateException("sOpToLoggingId length " + sOpToLoggingId.length
+                    + " should be " + _NUM_OP);
+        }
         if (sOpNames.length != _NUM_OP) {
             throw new IllegalStateException("sOpNames length " + sOpNames.length
                     + " should be " + _NUM_OP);
@@ -2437,6 +2574,15 @@
     }
 
     /**
+     * Retrieve a logging id for the operation.
+     *
+     * @hide
+     */
+    public static int opToLoggingId(int op) {
+        return sOpToLoggingId[op];
+    }
+
+    /**
      * @hide
      */
     public static int strDebugOpToOp(String op) {
@@ -5890,6 +6036,11 @@
             return mOp;
         }
 
+        /** @hide */
+        public int getLoggingOpCode() {
+            return AppOpsManager.opToLoggingId(mOp);
+        }
+
         /**
          * Gets the number times the op was accessed (performed) in the foreground.
          *
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index bfaa2be..18df401 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -840,6 +840,27 @@
     }
 
     @Override
+    public boolean setAutoRevokeWhitelisted(
+            @NonNull String packageName, boolean whitelisted) {
+        try {
+            final int userId = getUserId();
+            return mPermissionManager.setAutoRevokeWhitelisted(packageName, whitelisted, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean isAutoRevokeWhitelisted(@NonNull String packageName) {
+        try {
+            final int userId = getUserId();
+            return mPermissionManager.isAutoRevokeWhitelisted(packageName, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
     public boolean removeWhitelistedRestrictedPermission(@NonNull String packageName,
             @NonNull String permName, @PermissionWhitelistFlags int flags) {
         try {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 56e6aee..9ccfe8d 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1899,8 +1899,9 @@
     public Object getSystemService(String name) {
         // Check incorrect Context usage.
         if (isUiComponent(name) && !isUiContext() && vmIncorrectContextUseEnabled()) {
-            final String errorMessage = "Tried to access visual service " + name
-                    + " from a non-visual Context.";
+            final String errorMessage = "Tried to access visual service "
+                    + SystemServiceRegistry.getSystemServiceClassName(name)
+                    + " from a non-visual Context. ";
             final String message = "Visual services, such as WindowManager, WallpaperService or "
                     + "LayoutInflater should be accessed from Activity or other visual Context. "
                     + "Use an Activity or a Context created with "
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 061e5ff..864af3d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7526,9 +7526,11 @@
                     p,
                     bindResult);
             addExtras(mBuilder.mN.extras);
-            // also update the end margin if there is an image
-            contentView.setViewLayoutMarginEnd(R.id.notification_messaging,
-                    bindResult.getIconMarginEnd());
+            if (!CONVERSATION_LAYOUT_ENABLED) {
+                // also update the end margin if there is an image
+                contentView.setViewLayoutMarginEnd(R.id.notification_messaging,
+                        bindResult.getIconMarginEnd());
+            }
             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
                     mBuilder.isColorized(p) ? mBuilder.getPrimaryTextColor(p)
                             : mBuilder.resolveContrastColor(p));
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index dc8269f..81396fe 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -34,6 +34,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.contentcapture.ContentCaptureManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -306,7 +307,8 @@
  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/MessengerServiceActivities.java
  *      bind}
  */
-public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
+public abstract class Service extends ContextWrapper implements ComponentCallbacks2,
+        ContentCaptureManager.ContentCaptureClient {
     private static final String TAG = "Service";
 
     /**
@@ -817,8 +819,16 @@
         writer.println("nothing to dump");
     }
 
+    @Override
+    protected void attachBaseContext(Context newBase) {
+        super.attachBaseContext(newBase);
+        if (newBase != null) {
+            newBase.setContentCaptureOptions(getContentCaptureOptions());
+        }
+    }
+
     // ------------------ Internal API ------------------
-    
+
     /**
      * @hide
      */
@@ -835,6 +845,7 @@
         mActivityManager = (IActivityManager)activityManager;
         mStartCompatibility = getApplicationInfo().targetSdkVersion
                 < Build.VERSION_CODES.ECLAIR;
+        setContentCaptureOptions(application.getContentCaptureOptions());
     }
 
     /**
@@ -849,6 +860,18 @@
         return mClassName;
     }
 
+    /** @hide */
+    @Override
+    public final ContentCaptureManager.ContentCaptureClient getContentCaptureClient() {
+        return this;
+    }
+
+    /** @hide */
+    @Override
+    public final ComponentName contentCaptureClientGetComponentName() {
+        return new ComponentName(this, mClassName);
+    }
+
     // set by the thread after the constructor and before onCreate(Bundle icicle) is called.
     @UnsupportedAppUsage
     private ActivityThread mThread = null;
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 42563b5..1329fa4 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -399,7 +399,6 @@
      *                 {@code false}, re-enables expansion of the status bar.
      * @hide
      */
-    @SystemApi
     @TestApi
     @RequiresPermission(android.Manifest.permission.STATUS_BAR)
     public void setDisabledForSimNetworkLock(boolean disabled) {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index a3bcc9c..e8f30df 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -19,6 +19,7 @@
 import android.accounts.AccountManager;
 import android.accounts.IAccountManager;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.app.ContextImpl.ServiceInitializationState;
 import android.app.admin.DevicePolicyManager;
@@ -227,6 +228,8 @@
             new ArrayMap<Class<?>, String>();
     private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
             new ArrayMap<String, ServiceFetcher<?>>();
+    private static final Map<String, String> SYSTEM_SERVICE_CLASS_NAMES = new ArrayMap<>();
+
     private static int sServiceCacheSize;
 
     private static volatile boolean sInitializing;
@@ -1389,6 +1392,19 @@
             @NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
         SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
         SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
+        SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
+    }
+
+    /**
+     * Returns system service class name by system service name. This method is mostly an inverse of
+     * {@link #getSystemServiceName(Class)}
+     *
+     * @return system service class name. {@code null} if service name is invalid.
+     * @hide
+     */
+    @Nullable
+    public static String getSystemServiceClassName(@NonNull String name) {
+        return SYSTEM_SERVICE_CLASS_NAMES.get(name);
     }
 
     /**
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 1a84547..b2dd0ef 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -23,6 +23,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.os.Parcel;
 import android.os.RemoteException;
@@ -161,6 +162,13 @@
      */
     public @WindowConfiguration.ActivityType int topActivityType;
 
+    /**
+     * The {@link ActivityInfo} of the top activity in this task.
+     * @hide
+     */
+    @Nullable
+    public ActivityInfo topActivityInfo;
+
     TaskInfo() {
         // Do nothing
     }
@@ -217,8 +225,11 @@
         token = IWindowContainer.Stub.asInterface(source.readStrongBinder());
         topActivityType = source.readInt();
         pictureInPictureParams = source.readInt() != 0
-            ? PictureInPictureParams.CREATOR.createFromParcel(source)
-            : null;
+                ? PictureInPictureParams.CREATOR.createFromParcel(source)
+                : null;
+        topActivityInfo = source.readInt() != 0
+                ? ActivityInfo.CREATOR.createFromParcel(source)
+                : null;
     }
 
     /**
@@ -262,6 +273,12 @@
             dest.writeInt(1);
             pictureInPictureParams.writeToParcel(dest, flags);
         }
+        if (topActivityInfo == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            topActivityInfo.writeToParcel(dest, flags);
+        }
     }
 
     @Override
@@ -278,6 +295,7 @@
                 + " resizeMode=" + resizeMode
                 + " token=" + token
                 + " topActivityType=" + topActivityType
-                + " pictureInPictureParams=" + pictureInPictureParams;
+                + " pictureInPictureParams=" + pictureInPictureParams
+                + " topActivityInfo=" + topActivityInfo;
     }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 347648a..bc8d05e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5215,6 +5215,10 @@
      * <p>Because this method might take several seconds to complete, it should only be called from
      * a worker thread. This method returns {@code null} when called from the main thread.
      *
+     * <p>This method is not thread-safe, calling it from multiple threads at the same time will
+     * result in undefined behavior. If the calling thread is interrupted while the invocation is
+     * in-flight, it will eventually terminate and return {@code null}.
+     *
      * <p>Note: If the provided {@code alias} is of an existing alias, all former grants that apps
      * have been given to access the key and certificates associated with this alias will be
      * revoked.
@@ -11848,18 +11852,19 @@
     }
 
     /**
-     * Called by Device owner to set packages as protected. User will not be able to clear app
-     * data or force-stop protected packages.
+     * Called by Device owner to disable user control over apps. User will not be able to clear
+     * app data or force-stop packages.
      *
      * @param admin which {@link DeviceAdminReceiver} this request is associated with
-     * @param packages The package names to protect.
+     * @param packages The package names for the apps.
      * @throws SecurityException if {@code admin} is not a device owner.
      */
-    public void setProtectedPackages(@NonNull ComponentName admin, @NonNull List<String> packages) {
-        throwIfParentInstance("setProtectedPackages");
+    public void setUserControlDisabledPackages(@NonNull ComponentName admin,
+            @NonNull List<String> packages) {
+        throwIfParentInstance("setUserControlDisabledPackages");
         if (mService != null) {
             try {
-                mService.setProtectedPackages(admin, packages);
+                mService.setUserControlDisabledPackages(admin, packages);
             } catch (RemoteException re) {
                 throw re.rethrowFromSystemServer();
             }
@@ -11867,16 +11872,16 @@
     }
 
     /**
-     * Returns the list of packages protected by the device owner.
+     * Returns the list of packages over which user control is disabled by the device owner.
      *
      * @param admin which {@link DeviceAdminReceiver} this request is associated with
      * @throws SecurityException if {@code admin} is not a device owner.
      */
-    public @NonNull List<String> getProtectedPackages(@NonNull ComponentName admin) {
-        throwIfParentInstance("getProtectedPackages");
+    public @NonNull List<String> getUserControlDisabledPackages(@NonNull ComponentName admin) {
+        throwIfParentInstance("getUserControlDisabledPackages");
         if (mService != null) {
             try {
-                return mService.getProtectedPackages(admin);
+                return mService.getUserControlDisabledPackages(admin);
             } catch (RemoteException re) {
                 throw re.rethrowFromSystemServer();
             }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index f071239..514677e 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -467,9 +467,9 @@
 
     boolean setKeyGrantForApp(in ComponentName admin, String callerPackage, String alias, String packageName, boolean hasGrant);
 
-    void setProtectedPackages(in ComponentName admin, in List<String> packages);
+    void setUserControlDisabledPackages(in ComponentName admin, in List<String> packages);
 
-    List<String> getProtectedPackages(in ComponentName admin);
+    List<String> getUserControlDisabledPackages(in ComponentName admin);
 
     void setCommonCriteriaModeEnabled(in ComponentName admin, boolean enabled);
     boolean isCommonCriteriaModeEnabled(in ComponentName admin);
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 052c920..b434072 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -435,10 +435,11 @@
         final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
         final int opMode = (forDataDelivery)
                 ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, attributionTag, message)
-                : appOpsManager.unsafeCheckOpNoThrow(op, uid, packageName);
+                : appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName);
 
         switch (opMode) {
-            case AppOpsManager.MODE_ALLOWED: {
+            case AppOpsManager.MODE_ALLOWED:
+            case AppOpsManager.MODE_FOREGROUND: {
                 return PERMISSION_GRANTED;
             }
             case AppOpsManager.MODE_DEFAULT: {
@@ -467,12 +468,14 @@
         final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
         final int opMode = (forDataDelivery)
                 ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, attributionTag, message)
-                : appOpsManager.unsafeCheckOpNoThrow(op, uid, packageName);
+                : appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName);
 
-        if (opMode == AppOpsManager.MODE_ALLOWED) {
-            return PERMISSION_GRANTED;
-        } else {
-            return PERMISSION_SOFT_DENIED;
+        switch (opMode) {
+            case AppOpsManager.MODE_ALLOWED:
+            case AppOpsManager.MODE_FOREGROUND:
+                return PERMISSION_GRANTED;
+            default:
+                return PERMISSION_SOFT_DENIED;
         }
     }
 }
diff --git a/core/java/android/content/integrity/AppIntegrityManager.java b/core/java/android/content/integrity/AppIntegrityManager.java
index 9f95d4d..2869abb 100644
--- a/core/java/android/content/integrity/AppIntegrityManager.java
+++ b/core/java/android/content/integrity/AppIntegrityManager.java
@@ -25,6 +25,8 @@
 import android.content.pm.ParceledListSlice;
 import android.os.RemoteException;
 
+import java.util.List;
+
 /**
  * Class for pushing rules used to check the integrity of app installs.
  *
@@ -121,4 +123,21 @@
             throw e.rethrowAsRuntimeException();
         }
     }
+
+    /**
+     * Get the package names of all whitelisted rule providers.
+     *
+     * <p>Warning: this method is only used for tests.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public List<String> getWhitelistedRuleProviders() {
+        try {
+            return mManager.getWhitelistedRuleProviders();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
 }
diff --git a/core/java/android/content/integrity/IAppIntegrityManager.aidl b/core/java/android/content/integrity/IAppIntegrityManager.aidl
index 4714ad7..94197bb 100644
--- a/core/java/android/content/integrity/IAppIntegrityManager.aidl
+++ b/core/java/android/content/integrity/IAppIntegrityManager.aidl
@@ -19,6 +19,7 @@
 import android.content.integrity.Rule;
 import android.content.IntentSender;
 import android.content.pm.ParceledListSlice;
+import java.util.List;
 
 /** @hide */
 interface IAppIntegrityManager {
@@ -26,4 +27,5 @@
     String getCurrentRuleSetVersion();
     String getCurrentRuleSetProvider();
     ParceledListSlice<Rule> getCurrentRules();
+    List<String> getWhitelistedRuleProviders();
 }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index ec3590f..50bee85 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -16,6 +16,10 @@
 
 package android.content.pm;
 
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_IGNORED;
+
 import android.Manifest;
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.IntDef;
@@ -1456,6 +1460,8 @@
         /** {@hide} */
         public List<String> whitelistedRestrictedPermissions;
         /** {@hide} */
+        public int autoRevokePermissionsMode = MODE_DEFAULT;
+        /** {@hide} */
         public String installerPackageName;
         /** {@hide} */
         public boolean isMultiPackage;
@@ -1498,6 +1504,7 @@
             volumeUuid = source.readString();
             grantedRuntimePermissions = source.readStringArray();
             whitelistedRestrictedPermissions = source.createStringArrayList();
+            autoRevokePermissionsMode = source.readInt();
             installerPackageName = source.readString();
             isMultiPackage = source.readBoolean();
             isStaged = source.readBoolean();
@@ -1528,6 +1535,7 @@
             ret.volumeUuid = volumeUuid;
             ret.grantedRuntimePermissions = grantedRuntimePermissions;
             ret.whitelistedRestrictedPermissions = whitelistedRestrictedPermissions;
+            ret.autoRevokePermissionsMode = autoRevokePermissionsMode;
             ret.installerPackageName = installerPackageName;
             ret.isMultiPackage = isMultiPackage;
             ret.isStaged = isStaged;
@@ -1691,6 +1699,22 @@
         }
 
         /**
+         * Sets whether permissions should be auto-revoked if this package is unused for an
+         * extended periodd of time.
+         *
+         * It's disabled by default but generally the installer should enable it for most packages,
+         * excluding only those where doing so might cause breakage that cannot be easily addressed
+         * by simply re-requesting the permission(s).
+         *
+         * If user explicitly enabled or disabled it via settings, this call is ignored.
+         *
+         * @param shouldAutoRevoke whether permissions should be auto-revoked.
+         */
+        public void setAutoRevokePermissionsMode(boolean shouldAutoRevoke) {
+            autoRevokePermissionsMode = shouldAutoRevoke ? MODE_ALLOWED : MODE_IGNORED;
+        }
+
+        /**
          * Request that rollbacks be enabled or disabled for the given upgrade with rollback data
          * policy set to RESTORE.
          *
@@ -1932,6 +1956,7 @@
             pw.printPair("volumeUuid", volumeUuid);
             pw.printPair("grantedRuntimePermissions", grantedRuntimePermissions);
             pw.printPair("whitelistedRestrictedPermissions", whitelistedRestrictedPermissions);
+            pw.printPair("autoRevokePermissions", autoRevokePermissionsMode);
             pw.printPair("installerPackageName", installerPackageName);
             pw.printPair("isMultiPackage", isMultiPackage);
             pw.printPair("isStaged", isStaged);
@@ -1964,6 +1989,7 @@
             dest.writeString(volumeUuid);
             dest.writeStringArray(grantedRuntimePermissions);
             dest.writeStringList(whitelistedRestrictedPermissions);
+            dest.writeInt(autoRevokePermissionsMode);
             dest.writeString(installerPackageName);
             dest.writeBoolean(isMultiPackage);
             dest.writeBoolean(isStaged);
@@ -2085,6 +2111,8 @@
         public String[] grantedRuntimePermissions;
         /** {@hide}*/
         public List<String> whitelistedRestrictedPermissions;
+        /** {@hide}*/
+        public int autoRevokePermissionsMode = MODE_DEFAULT;
         /** {@hide} */
         public int installFlags;
         /** {@hide} */
@@ -2147,6 +2175,7 @@
             referrerUri = source.readParcelable(null);
             grantedRuntimePermissions = source.readStringArray();
             whitelistedRestrictedPermissions = source.createStringArrayList();
+            autoRevokePermissionsMode = source.readInt();
 
             installFlags = source.readInt();
             isMultiPackage = source.readBoolean();
@@ -2374,6 +2403,24 @@
         }
 
         /**
+         * Get the status of whether permission auto-revocation should be allowed, ignored, or
+         * deferred to manifest data.
+         *
+         * @see android.app.AppOpsManager#MODE_ALLOWED
+         * @see android.app.AppOpsManager#MODE_IGNORED
+         * @see android.app.AppOpsManager#MODE_DEFAULT
+         *
+         * @return the status of auto-revoke for this package
+         *
+         * @hide
+         */
+        @TestApi
+        @SystemApi
+        public int getAutoRevokePermissionsMode() {
+            return autoRevokePermissionsMode;
+        }
+
+        /**
          * Get the value set in {@link SessionParams#setAllowDowngrade(boolean)}.
          *
          * @deprecated use {@link #getRequestDowngrade()}.
@@ -2660,6 +2707,7 @@
             dest.writeParcelable(referrerUri, flags);
             dest.writeStringArray(grantedRuntimePermissions);
             dest.writeStringList(whitelistedRestrictedPermissions);
+            dest.writeInt(autoRevokePermissionsMode);
             dest.writeInt(installFlags);
             dest.writeBoolean(isMultiPackage);
             dest.writeBoolean(isStaged);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 3764734..05ff830 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -557,7 +557,6 @@
      * Internal {@link PackageInfo} flag used to indicate that a package is a hidden system app.
      * @hide
      */
-    @SystemApi
     public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS =  0x20000000;
 
     /**
@@ -1978,7 +1977,7 @@
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device's main front and back cameras can stream
      * concurrently as described in  {@link
-     * android.hardware.camera2.CameraManager#getConcurrentStreamingCameraIds()}
+     * android.hardware.camera2.CameraManager#getConcurrentCameraIds()}
      */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_CAMERA_CONCURRENT = "android.hardware.camera.concurrent";
@@ -4576,6 +4575,53 @@
     }
 
     /**
+     * Marks an application exempt from having its permissions be automatically revoked when
+     * the app is unused for an extended period of time.
+     *
+     * Only the installer on record that installed the given package, or a holder of
+     * {@code WHITELIST_AUTO_REVOKE_PERMISSIONS} is allowed to call this.
+     *
+     * Packages start in whitelisted state, and it is the installer's responsibility to
+     * un-whitelist the packages it installs, unless auto-revoking permissions from that package
+     * would cause breakages beyond having to re-request the permission(s).
+     *
+     * @param packageName The app for which to set exemption.
+     * @param whitelisted Whether the app should be whitelisted.
+     *
+     * @return whether any change took effect.
+     *
+     * @see #isAutoRevokeWhitelisted
+     *
+     * @throws SecurityException if you you have no access to modify this.
+     */
+    @RequiresPermission(value = Manifest.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS,
+            conditional = true)
+    public boolean setAutoRevokeWhitelisted(@NonNull String packageName, boolean whitelisted) {
+        return false;
+    }
+
+    /**
+     * Checks whether an application is exempt from having its permissions be automatically revoked
+     * when the app is unused for an extended period of time.
+     *
+     * Only the installer on record that installed the given package, or a holder of
+     * {@code WHITELIST_AUTO_REVOKE_PERMISSIONS} is allowed to call this.
+     * @param packageName The app for which to set exemption.
+     *
+     * @return Whether the app is whitelisted.
+     *
+     * @see #setAutoRevokeWhitelisted
+     *
+     * @throws SecurityException if you you have no access to this.
+     */
+    @RequiresPermission(value = Manifest.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS,
+            conditional = true)
+    public boolean isAutoRevokeWhitelisted(@NonNull String packageName) {
+        return false;
+    }
+
+
+    /**
      * Gets whether you should show UI with rationale for requesting a permission.
      * You should do this only if you do not have the permission and the context in
      * which the permission is requested does not clearly communicate to the user
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index b3a1ee2..7e72b73d 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2884,12 +2884,12 @@
      * generated according to the documented
      * {@link android.hardware.camera2.CameraDevice#createCaptureSession guideline} for each device
      * which has its Id present in the set returned by
-     * {@link android.hardware.camera2.CameraManager#getConcurrentStreamingCameraIds}.
+     * {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds}.
      * Clients can use the array as a quick reference to find an appropriate camera stream
      * combination.
      * The mandatory stream combination array will be {@code null} in case the device is not a part
      * of at least one set of combinations returned by
-     * {@link android.hardware.camera2.CameraManager#getConcurrentStreamingCameraIds}.</p>
+     * {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds}.</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      */
     @PublicKey
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index cc0c1a30..30ee326 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -681,7 +681,7 @@
      * </p>
      *
      *<p>Devices capable of streaming concurrently with other devices as described by
-     * {@link android.hardware.camera2.CameraManager#getConcurrentStreamingCameraIds} have the
+     * {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds} have the
      * following guaranteed streams (when streaming concurrently with other devices)</p>
      *
      * <table>
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 972b0f55..e81c649 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -160,8 +160,8 @@
      * @throws CameraAccessException if the camera device has been disconnected.
      */
     @NonNull
-    public Set<Set<String>> getConcurrentStreamingCameraIds() throws CameraAccessException {
-        return CameraManagerGlobal.get().getConcurrentStreamingCameraIds();
+    public Set<Set<String>> getConcurrentCameraIds() throws CameraAccessException {
+        return CameraManagerGlobal.get().getConcurrentCameraIds();
     }
 
     /**
@@ -189,11 +189,11 @@
      *
      * @return {@code true} if the given combination of session configurations and corresponding
      *                      camera ids are concurrently supported by the camera sub-system,
-     *         {@code false} otherwise.
+     *         {@code false} otherwise OR if the set of camera devices provided is not a subset of
+     *                       those returned by {@link #getConcurrentCameraIds}.
      *
-     * @throws IllegalArgumentException if the set of camera devices provided is not a subset of
-     *                                  those returned by getConcurrentStreamingCameraIds()
      * @throws CameraAccessException if one of the camera devices queried is no longer connected.
+     *
      */
     @RequiresPermission(android.Manifest.permission.CAMERA)
     public boolean isConcurrentSessionConfigurationSupported(
@@ -1183,7 +1183,7 @@
 
             try {
                 ConcurrentCameraIdCombination[] cameraIdCombinations =
-                        cameraService.getConcurrentStreamingCameraIds();
+                        cameraService.getConcurrentCameraIds();
                 for (ConcurrentCameraIdCombination comb : cameraIdCombinations) {
                     mConcurrentCameraIdCombinations.add(comb.getConcurrentCameraIdCombination());
                 }
@@ -1372,7 +1372,7 @@
             return cameraIds;
         }
 
-        public @NonNull Set<Set<String>> getConcurrentStreamingCameraIds() {
+        public @NonNull Set<Set<String>> getConcurrentCameraIds() {
             Set<Set<String>> concurrentStreamingCameraIds = null;
             synchronized (mLock) {
                 // Try to make sure we have an up-to-date list of concurrent camera devices.
@@ -1398,7 +1398,7 @@
 
             synchronized (mLock) {
                 // Go through all the elements and check if the camera ids are valid at least /
-                // belong to one of the combinations returned by getConcurrentStreamingCameraIds()
+                // belong to one of the combinations returned by getConcurrentCameraIds()
                 boolean subsetFound = false;
                 for (Set<String> combination : mConcurrentCameraIdCombinations) {
                     if (combination.containsAll(cameraIdsAndSessionConfigurations.keySet())) {
@@ -1406,9 +1406,9 @@
                     }
                 }
                 if (!subsetFound) {
-                    throw new IllegalArgumentException(
-                            "The set of camera ids provided is not a subset of"
-                            + "getConcurrentStreamingCameraIds");
+                    Log.v(TAG, "isConcurrentSessionConfigurationSupported called with a subset of"
+                            + "camera ids not returned by getConcurrentCameraIds");
+                    return false;
                 }
                 CameraIdAndSessionConfiguration [] cameraIdsAndConfigs =
                         new CameraIdAndSessionConfiguration[size];
@@ -1436,10 +1436,10 @@
 
       /**
         * Helper function to find out if a camera id is in the set of combinations returned by
-        * getConcurrentStreamingCameraIds()
+        * getConcurrentCameraIds()
         * @param cameraId the unique identifier of the camera device to query
         * @return Whether the camera device was found in the set of combinations returned by
-        *         getConcurrentStreamingCameraIds
+        *         getConcurrentCameraIds
         */
         public boolean cameraIdHasConcurrentStreamsLocked(String cameraId) {
             if (!mDeviceStatus.containsKey(cameraId)) {
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index f0fab6a..20d9c30 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -721,7 +721,7 @@
         /**
           * Retrieve a list of all available mandatory concurrent stream combinations.
           * This method should only be called for devices which are listed in combinations returned
-          * by CameraManager.getConcurrentStreamingCameraIds.
+          * by CameraManager.getConcurrentCameraIds.
           *
           * @return a non-modifiable list of supported mandatory concurrent stream combinations.
           */
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java
index d009144..1710ccb 100644
--- a/core/java/android/net/ConnectivityDiagnosticsManager.java
+++ b/core/java/android/net/ConnectivityDiagnosticsManager.java
@@ -368,7 +368,14 @@
 
     /** Class that includes information for a suspected data stall on a specific Network */
     public static final class DataStallReport implements Parcelable {
+        /**
+         * Indicates that the Data Stall was detected using DNS events.
+         */
         public static final int DETECTION_METHOD_DNS_EVENTS = 1;
+
+        /**
+         * Indicates that the Data Stall was detected using TCP metrics.
+         */
         public static final int DETECTION_METHOD_TCP_METRICS = 2;
 
         /** @hide */
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index fc6954f..81735ac 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -3222,7 +3222,9 @@
 
     /** {@hide} - returns the factory serial number */
     @UnsupportedAppUsage
-    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_FACTORY})
     public int registerNetworkFactory(Messenger messenger, String name) {
         try {
             return mService.registerNetworkFactory(messenger, name);
@@ -3233,7 +3235,9 @@
 
     /** {@hide} */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_FACTORY})
     public void unregisterNetworkFactory(Messenger messenger) {
         try {
             mService.unregisterNetworkFactory(messenger);
@@ -3253,7 +3257,9 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_FACTORY})
     public int registerNetworkProvider(@NonNull NetworkProvider provider) {
         if (provider.getProviderId() != NetworkProvider.ID_NONE) {
             throw new IllegalStateException("NetworkProviders can only be registered once");
@@ -3276,7 +3282,9 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_FACTORY})
     public void unregisterNetworkProvider(@NonNull NetworkProvider provider) {
         try {
             mService.unregisterNetworkProvider(provider.getMessenger());
@@ -3288,7 +3296,9 @@
 
 
     /** @hide exposed via the NetworkProvider class. */
-    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_FACTORY})
     public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) {
         try {
             mService.declareNetworkRequestUnfulfillable(request);
@@ -3306,7 +3316,9 @@
      * Register a NetworkAgent with ConnectivityService.
      * @return Network corresponding to NetworkAgent.
      */
-    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_FACTORY})
     public Network registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
             NetworkCapabilities nc, int score, NetworkAgentConfig config) {
         return registerNetworkAgent(messenger, ni, lp, nc, score, config, NetworkProvider.ID_NONE);
@@ -3317,7 +3329,9 @@
      * Register a NetworkAgent with ConnectivityService.
      * @return Network corresponding to NetworkAgent.
      */
-    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_FACTORY})
     public Network registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
             NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) {
         try {
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index c5681cb..6f5471b 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -61,6 +61,7 @@
 public class Network implements Parcelable {
 
     /**
+     * The unique id of the network.
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index fef353f..5c754a1 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -78,6 +78,7 @@
     /**
      * The ID of the {@link NetworkProvider} that created this object, or
      * {@link NetworkProvider#ID_NONE} if unknown.
+     * @hide
      */
     public final int providerId;
 
@@ -584,6 +585,7 @@
      *
      * @deprecated this is for backward compatibility only.
      * @param legacySubtype the legacy subtype.
+     * @hide
      */
     @Deprecated
     public void setLegacySubtype(final int legacySubtype, @NonNull final String legacySubtypeName) {
@@ -608,6 +610,7 @@
      *
      * @deprecated this is for backward compatibility only.
      * @param extraInfo the ExtraInfo.
+     * @hide
      */
     @Deprecated
     public void setLegacyExtraInfo(@Nullable final String extraInfo) {
@@ -711,6 +714,7 @@
     /**
      * Called when ConnectivityService request a bandwidth update. The parent factory
      * shall try to overwrite this method and produce a bandwidth update if capable.
+     * @hide
      */
     public void onBandwidthUpdateRequested() {
         pollLceData();
diff --git a/core/java/android/net/NetworkAgentConfig.java b/core/java/android/net/NetworkAgentConfig.java
index 7e2db4a..ca9328a 100644
--- a/core/java/android/net/NetworkAgentConfig.java
+++ b/core/java/android/net/NetworkAgentConfig.java
@@ -108,6 +108,7 @@
     /**
      *
      * @return whether the sign in to network notification is enabled by this configuration.
+     * @hide
      */
     public boolean isProvisioningNotificationEnabled() {
         return !provisioningNotificationDisabled;
@@ -122,6 +123,7 @@
 
     /**
      * @return the subscriber ID, or null if none.
+     * @hide
      */
     @Nullable
     public String getSubscriberId() {
@@ -138,6 +140,7 @@
 
     /**
      * @return whether NAT64 prefix detection is enabled.
+     * @hide
      */
     public boolean isNat64DetectionEnabled() {
         return !skip464xlat;
@@ -247,6 +250,7 @@
          * Sets the subscriber ID for this network.
          *
          * @return this builder, to facilitate chaining.
+         * @hide
          */
         @NonNull
         public Builder setSubscriberId(@Nullable String subscriberId) {
@@ -259,6 +263,7 @@
          * and reduce idle traffic on networks that are known to be IPv6-only without a NAT64.
          *
          * @return this builder, to facilitate chaining.
+         * @hide
          */
         @NonNull
         public Builder disableNat64Detection() {
@@ -271,6 +276,7 @@
          * perform its own carrier-specific provisioning procedure.
          *
          * @return this builder, to facilitate chaining.
+         * @hide
          */
         @NonNull
         public Builder disableProvisioningNotification() {
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 83f9980..116e343 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -613,7 +613,6 @@
      * @return {@code true} if the network should be restricted.
      * @hide
      */
-    @SystemApi
     public boolean deduceRestrictedCapability() {
         // Check if we have any capability that forces the network to be restricted.
         final boolean forceRestrictedCapability =
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 14442a2..1922b6d 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -21,7 +21,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.ActivityManager;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -56,7 +55,6 @@
  * @hide
  */
 @SystemService(Context.NETWORK_POLICY_SERVICE)
-@SystemApi
 public class NetworkPolicyManager {
 
     /* POLICY_* are masks and can be ORed, although currently they are not.*/
@@ -162,11 +160,13 @@
 
     /**
      * Mask used to check if an override value is marked as unmetered.
+     * @hide
      */
     public static final int SUBSCRIPTION_OVERRIDE_UNMETERED = 1 << 0;
 
     /**
      * Mask used to check if an override value is marked as congested.
+     * @hide
      */
     public static final int SUBSCRIPTION_OVERRIDE_CONGESTED = 1 << 1;
 
@@ -294,7 +294,6 @@
 
     /** @hide */
     @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY)
-    @SystemApi
     public void registerSubscriptionCallback(@NonNull SubscriptionCallback callback) {
         if (callback == null) {
             throw new NullPointerException("Callback cannot be null.");
@@ -309,7 +308,6 @@
 
     /** @hide */
     @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY)
-    @SystemApi
     public void unregisterSubscriptionCallback(@NonNull SubscriptionCallback callback) {
         if (callback == null) {
             throw new NullPointerException("Callback cannot be null.");
@@ -373,6 +371,7 @@
      *            requested state until explicitly cleared, or the next reboot,
      *            whichever happens first
      * @param callingPackage the name of the package making the call.
+     * @hide
      */
     public void setSubscriptionOverride(int subId, @SubscriptionOverrideMask int overrideMask,
             @SubscriptionOverrideMask int overrideValue, long timeoutMillis,
@@ -391,6 +390,7 @@
      * @param subId the subscriber this relationship applies to.
      * @param plans the list of plans.
      * @param callingPackage the name of the package making the call
+     * @hide
      */
     public void setSubscriptionPlans(int subId, @NonNull SubscriptionPlan[] plans,
             @NonNull String callingPackage) {
@@ -406,6 +406,7 @@
      *
      * @param subId the subscriber to get the subscription plans for.
      * @param callingPackage the name of the package making the call.
+     * @hide
      */
     @NonNull
     public SubscriptionPlan[] getSubscriptionPlans(int subId, @NonNull String callingPackage) {
@@ -549,7 +550,6 @@
     }
 
     /** @hide */
-    @SystemApi
     public static class SubscriptionCallback {
         /**
          * Notify clients of a new override about a given subscription.
diff --git a/core/java/android/net/NetworkProvider.java b/core/java/android/net/NetworkProvider.java
index 2c0e4aa..418d691 100644
--- a/core/java/android/net/NetworkProvider.java
+++ b/core/java/android/net/NetworkProvider.java
@@ -106,10 +106,12 @@
     }
 
     // TODO: consider adding a register() method so ConnectivityManager does not need to call this.
+    /** @hide */
     public @Nullable Messenger getMessenger() {
         return mMessenger;
     }
 
+    /** @hide */
     public @NonNull String getName() {
         return mName;
     }
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 9ca1c33..fab906b 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -157,20 +157,20 @@
             Preconditions.checkNotNull(executor);
             Preconditions.checkNotNull(callback);
 
-            boolean validScreenshotFd = screenshotFd != null;
+            boolean isScreenshotRequested = screenshotFd != null;
             if (screenshotFd == null) {
                 // Binder needs a valid File Descriptor to be passed
                 screenshotFd = ParcelFileDescriptor.open(new File("/dev/null"),
                         ParcelFileDescriptor.MODE_READ_ONLY);
             }
             DumpstateListener dsListener = new DumpstateListener(executor, callback,
-                    validScreenshotFd);
+                    isScreenshotRequested);
             // Note: mBinder can get callingUid from the binder transaction.
             mBinder.startBugreport(-1 /* callingUid */,
                     mContext.getOpPackageName(),
                     bugreportFd.getFileDescriptor(),
                     screenshotFd.getFileDescriptor(),
-                    params.getMode(), dsListener);
+                    params.getMode(), dsListener, isScreenshotRequested);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (FileNotFoundException e) {
@@ -225,13 +225,13 @@
     private final class DumpstateListener extends IDumpstateListener.Stub {
         private final Executor mExecutor;
         private final BugreportCallback mCallback;
-        private final boolean mValidScreenshotFd;
+        private final boolean mIsScreenshotRequested;
 
         DumpstateListener(Executor executor, BugreportCallback callback,
-                boolean validScreenshotFd) {
+                boolean isScreenshotRequested) {
             mExecutor = executor;
             mCallback = callback;
-            mValidScreenshotFd = validScreenshotFd;
+            mIsScreenshotRequested = isScreenshotRequested;
         }
 
         @Override
@@ -272,7 +272,7 @@
 
         @Override
         public void onScreenshotTaken(boolean success) throws RemoteException {
-            if (!mValidScreenshotFd) {
+            if (!mIsScreenshotRequested) {
                 return;
             }
 
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index f2fb5b2..09ccb72 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -23,6 +23,7 @@
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
+import android.compat.Compatibility;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
@@ -88,6 +89,16 @@
     private static final File DIR_APEX_ROOT = getDirectory(ENV_APEX_ROOT,
             "/apex");
 
+    /**
+     * See definition in com.android.providers.media.LocalCallingIdentity
+     */
+    private static final long DEFAULT_SCOPED_STORAGE = 149924527L;
+
+    /**
+     * See definition in com.android.providers.media.LocalCallingIdentity
+     */
+    private static final long FORCE_ENABLE_SCOPED_STORAGE = 132649864L;
+
     @UnsupportedAppUsage
     private static UserEnvironment sCurrentUser;
     private static boolean sUserRequired;
@@ -1191,12 +1202,13 @@
     }
 
     /**
-     * Returns whether the primary shared/external storage media is a legacy
-     * view that includes files not owned by the app.
+     * Returns whether the shared/external storage media is a
+     * legacy view that includes files not owned by the app.
      * <p>
      * This value may be different from the value requested by
      * {@code requestLegacyExternalStorage} in the app's manifest, since an app
-     * may inherit its legacy state based on when it was first installed.
+     * may inherit its legacy state based on when it was first installed, target sdk and other
+     * factors.
      * <p>
      * Non-legacy apps can continue to discover and read media belonging to
      * other apps via {@link android.provider.MediaStore}.
@@ -1207,18 +1219,19 @@
     }
 
     /**
-     * Returns whether the shared/external storage media at the given path is a
+     * Returns whether the shared/external storage media is a
      * legacy view that includes files not owned by the app.
      * <p>
      * This value may be different from the value requested by
      * {@code requestLegacyExternalStorage} in the app's manifest, since an app
-     * may inherit its legacy state based on when it was first installed.
+     * may inherit its legacy state based on when it was first installed, target sdk and other
+     * factors.
      * <p>
      * Non-legacy apps can continue to discover and read media belonging to
      * other apps via {@link android.provider.MediaStore}.
      *
      * @throws IllegalArgumentException if the path is not a valid storage
-     *             device.
+     * device.
      */
     public static boolean isExternalStorageLegacy(@NonNull File path) {
         final Context context = AppGlobals.getInitialApplication();
@@ -1232,24 +1245,23 @@
             return false;
         }
 
-        if (packageManager.checkPermission(Manifest.permission.WRITE_MEDIA_STORAGE,
-                context.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
+        // TODO(b/150672994): Compat flags do not override instant app and isolated process's
+        //  behavior.
+        boolean defaultScopedStorage = Compatibility.isChangeEnabled(DEFAULT_SCOPED_STORAGE);
+        boolean forceEnableScopedStorage = Compatibility.isChangeEnabled(
+                FORCE_ENABLE_SCOPED_STORAGE);
+        // if Scoped Storage is strictly enforced, the app does *not* have legacy storage access
+        // Note: does not require packagename/uid as this is directly called from an app process
+        if (isScopedStorageEnforced(defaultScopedStorage, forceEnableScopedStorage)) {
+            return false;
+        }
+        // if Scoped Storage is strictly disabled, the app has legacy storage access
+        // Note: does not require packagename/uid as this is directly called from an app process
+        if (isScopedStorageDisabled(defaultScopedStorage, forceEnableScopedStorage)) {
             return true;
         }
 
-        if (packageManager.checkPermission(Manifest.permission.INSTALL_PACKAGES,
-                context.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
-            return true;
-        }
         final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
-        final String[] packagesForUid = packageManager.getPackagesForUid(uid);
-        for (String packageName : packagesForUid) {
-            if (appOps.checkOpNoThrow(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
-                    uid, packageName) == AppOpsManager.MODE_ALLOWED) {
-                return true;
-            }
-        }
-
         return appOps.checkOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE,
                 uid, context.getOpPackageName()) == AppOpsManager.MODE_ALLOWED;
     }
@@ -1298,6 +1310,16 @@
         }
     }
 
+    private static boolean isScopedStorageEnforced(boolean defaultScopedStorage,
+            boolean forceEnableScopedStorage) {
+        return defaultScopedStorage && forceEnableScopedStorage;
+    }
+
+    private static boolean isScopedStorageDisabled(boolean defaultScopedStorage,
+            boolean forceEnableScopedStorage) {
+        return !defaultScopedStorage && !forceEnableScopedStorage;
+    }
+
     static File getDirectory(String variableName, String defaultPath) {
         String path = System.getenv(variableName);
         return path == null ? new File(defaultPath) : new File(path);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index d7af1b9..a557bd9 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -607,6 +607,7 @@
      *                             started.
      * @param pkgDataInfoMap Map from related package names to private data directory
      *                       volume UUID and inode number.
+     * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
      * @param zygoteArgs Additional arguments to supply to the zygote process.
      * @return An object that describes the result of the attempt to start the process.
      * @throws RuntimeException on fatal start failure
@@ -630,12 +631,13 @@
                                            @Nullable long[] disabledCompatChanges,
                                            @Nullable Map<String, Pair<String, Long>>
                                                    pkgDataInfoMap,
+                                           boolean bindMountAppStorageDirs,
                                            @Nullable String[] zygoteArgs) {
         return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
                     zygotePolicyFlags, isTopApp, disabledCompatChanges,
-                    pkgDataInfoMap, zygoteArgs);
+                    pkgDataInfoMap, bindMountAppStorageDirs, zygoteArgs);
     }
 
     /** @hide */
@@ -659,7 +661,7 @@
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
                     /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, /*isTopApp=*/ false,
-                disabledCompatChanges, /* pkgDataInfoMap */ null, zygoteArgs);
+                disabledCompatChanges, /* pkgDataInfoMap */ null, false, zygoteArgs);
     }
 
     /**
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 2d218f4..1992f1d 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -963,7 +963,7 @@
                 PRIMITIVE_QUICK_RISE,
                 PRIMITIVE_SLOW_RISE,
                 PRIMITIVE_QUICK_FALL,
-                PRIMITIVE_LIGHT_TICK,
+                PRIMITIVE_TICK,
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface Primitive {}
@@ -981,10 +981,14 @@
          * A haptic effect that simulates downwards movement with gravity. Often
          * followed by extra energy of hitting and reverberation to augment
          * physicality.
+         *
+         * @hide Not confident enough to expose publicly yet
          */
         public static final int PRIMITIVE_THUD = 2;
         /**
          * A haptic effect that simulates spinning momentum.
+         *
+         * @hide Not confident enough to expose publicly yet
          */
         public static final int PRIMITIVE_SPIN = 3;
         /**
@@ -1003,7 +1007,8 @@
          * This very short effect should produce a light crisp sensation intended
          * to be used repetitively for dynamic feedback.
          */
-        public static final int PRIMITIVE_LIGHT_TICK = 7;
+        // Internally this maps to the HAL constant CompositePrimitive::LIGHT_TICK
+        public static final int PRIMITIVE_TICK = 7;
 
 
         private ArrayList<PrimitiveEffect> mEffects = new ArrayList<>();
@@ -1081,7 +1086,7 @@
          *
          */
         static int checkPrimitive(int primitiveId) {
-            Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_LIGHT_TICK,
+            Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_TICK,
                     "primitiveId");
             return primitiveId;
         }
@@ -1108,8 +1113,8 @@
                     return "PRIMITIVE_SLOW_RISE";
                 case PRIMITIVE_QUICK_FALL:
                     return "PRIMITIVE_QUICK_FALL";
-                case PRIMITIVE_LIGHT_TICK:
-                    return "PRIMITIVE_LIGHT_TICK";
+                case PRIMITIVE_TICK:
+                    return "PRIMITIVE_TICK";
 
                 default:
                     return Integer.toString(id);
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 34cec06..5f3f14f 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -333,6 +333,7 @@
      *                             started.
      * @param pkgDataInfoMap Map from related package names to private data directory
      *                       volume UUID and inode number.
+     * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
      *
      * @param zygoteArgs Additional arguments to supply to the Zygote process.
      * @return An object that describes the result of the attempt to start the process.
@@ -354,6 +355,7 @@
                                                   @Nullable long[] disabledCompatChanges,
                                                   @Nullable Map<String, Pair<String, Long>>
                                                           pkgDataInfoMap,
+                                                  boolean bindMountAppStorageDirs,
                                                   @Nullable String[] zygoteArgs) {
         // TODO (chriswailes): Is there a better place to check this value?
         if (fetchUsapPoolEnabledPropWithMinInterval()) {
@@ -365,7 +367,7 @@
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
                     packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges,
-                    pkgDataInfoMap, zygoteArgs);
+                    pkgDataInfoMap, bindMountAppStorageDirs, zygoteArgs);
         } catch (ZygoteStartFailedEx ex) {
             Log.e(LOG_TAG,
                     "Starting VM process through Zygote failed");
@@ -606,6 +608,7 @@
      * @param disabledCompatChanges a list of disabled compat changes for the process being started.
      * @param pkgDataInfoMap Map from related package names to private data directory volume UUID
      *                       and inode number.
+     * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
      * @param extraArgs Additional arguments to supply to the zygote process.
      * @return An object that describes the result of the attempt to start the process.
      * @throws ZygoteStartFailedEx if process start failed for any reason
@@ -628,6 +631,7 @@
                                                       @Nullable long[] disabledCompatChanges,
                                                       @Nullable Map<String, Pair<String, Long>>
                                                               pkgDataInfoMap,
+                                                      boolean bindMountAppStorageDirs,
                                                       @Nullable String[] extraArgs)
                                                       throws ZygoteStartFailedEx {
         ArrayList<String> argsForZygote = new ArrayList<>();
@@ -725,6 +729,10 @@
             argsForZygote.add(sb.toString());
         }
 
+        if (bindMountAppStorageDirs) {
+            argsForZygote.add(Zygote.BIND_MOUNT_APP_STORAGE_DIRS);
+        }
+
         if (disabledCompatChanges != null && disabledCompatChanges.length > 0) {
             StringBuilder sb = new StringBuilder();
             sb.append("--disabled-compat-changes=");
@@ -1282,7 +1290,9 @@
                     abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
                     true /* startChildZygote */, null /* packageName */,
                     ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS /* zygotePolicyFlags */, false /* isTopApp */,
-                    null /* disabledCompatChanges */, null /* pkgDataInfoMap */, extraArgs);
+                    null /* disabledCompatChanges */, null /* pkgDataInfoMap */,
+                    /* bindMountAppStorageDirs */ false, extraArgs);
+
         } catch (ZygoteStartFailedEx ex) {
             throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
         }
diff --git a/core/java/android/os/incremental/IncrementalNewFileParams.aidl b/core/java/android/os/incremental/IncrementalNewFileParams.aidl
index 182732c..8faf158 100644
--- a/core/java/android/os/incremental/IncrementalNewFileParams.aidl
+++ b/core/java/android/os/incremental/IncrementalNewFileParams.aidl
@@ -16,8 +16,6 @@
 
 package android.os.incremental;
 
-import android.os.incremental.IncrementalSignature;
-
 /**
  * All the parameters to create a new file on IncFS
  * FileId is a 16 byte-long identifier.
@@ -27,5 +25,5 @@
     long size;
     byte[] fileId;
     byte[] metadata;
-    @nullable IncrementalSignature signature;
+    @nullable byte[] signature;
 }
diff --git a/core/java/android/os/incremental/IncrementalSignature.aidl b/core/java/android/os/incremental/IncrementalSignature.aidl
deleted file mode 100644
index 729e8e5..0000000
--- a/core/java/android/os/incremental/IncrementalSignature.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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 android.os.incremental;
-
-/** {@hide} */
-parcelable IncrementalSignature {
-    /*
-     * Stable AIDL doesn't support constants, but here's the possible values
-     *   const int HASH_ALGO_NONE = 0;
-     *   const int HASH_ALGO_SHA256 = 1;
-    */
-
-    int hashAlgorithm = 0;
-    byte[] rootHash;
-    byte[] additionalData;
-    byte[] signature;
-}
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index bf31bc2..7092751 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -20,8 +20,6 @@
 import android.annotation.Nullable;
 import android.os.RemoteException;
 
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -180,11 +178,12 @@
             if (id == null && metadata == null) {
                 throw new IOException("File ID and metadata cannot both be null");
             }
+            validateV4Signature(v4signatureBytes);
             final IncrementalNewFileParams params = new IncrementalNewFileParams();
             params.size = size;
             params.metadata = (metadata == null ? new byte[0] : metadata);
             params.fileId = idToBytes(id);
-            params.signature = parseV4Signature(v4signatureBytes);
+            params.signature = v4signatureBytes;
             int res = mService.makeFile(mId, path, params);
             if (res != 0) {
                 throw new IOException("makeFile() failed with errno " + -res);
@@ -415,27 +414,23 @@
         return new UUID(msb, lsb);
     }
 
-    private static final int INCFS_HASH_SHA256 = 1;
     private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256
     private static final int INCFS_MAX_ADD_DATA_SIZE = 128;
 
     /**
      * Deserialize and validate v4 signature bytes.
      */
-    private static IncrementalSignature parseV4Signature(@Nullable byte[] v4signatureBytes)
+    private static void validateV4Signature(@Nullable byte[] v4signatureBytes)
             throws IOException {
         if (v4signatureBytes == null || v4signatureBytes.length == 0) {
-            return null;
+            return;
         }
 
         final V4Signature signature;
-        try (DataInputStream input = new DataInputStream(
-                new ByteArrayInputStream(v4signatureBytes))) {
-            try {
-                signature = V4Signature.readFrom(input);
-            } catch (IOException e) {
-                throw new IOException("Failed to read v4 signature:", e);
-            }
+        try {
+            signature = V4Signature.readFrom(v4signatureBytes);
+        } catch (IOException e) {
+            throw new IOException("Failed to read v4 signature:", e);
         }
 
         if (!signature.isVersionSupported()) {
@@ -443,25 +438,27 @@
                     + " is not supported");
         }
 
-        final byte[] rootHash = signature.verityRootHash;
-        final byte[] additionalData = signature.v3Digest;
-        final byte[] pkcs7Signature = signature.pkcs7SignatureBlock;
+        final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray(
+                signature.hashingInfo);
+        final V4Signature.SigningInfo signingInfo = V4Signature.SigningInfo.fromByteArray(
+                signature.signingInfo);
 
-        if (rootHash.length != INCFS_MAX_HASH_SIZE) {
-            throw new IOException("rootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
+        if (hashingInfo.hashAlgorithm != V4Signature.HASHING_ALGORITHM_SHA256) {
+            throw new IOException("Unsupported hashAlgorithm: " + hashingInfo.hashAlgorithm);
         }
-        if (additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
+        if (hashingInfo.log2BlockSize != V4Signature.LOG2_BLOCK_SIZE_4096_BYTES) {
+            throw new IOException("Unsupported log2BlockSize: " + hashingInfo.log2BlockSize);
+        }
+        if (hashingInfo.salt != null && hashingInfo.salt.length > 0) {
+            throw new IOException("Unsupported salt: " + hashingInfo.salt);
+        }
+        if (hashingInfo.rawRootHash.length != INCFS_MAX_HASH_SIZE) {
+            throw new IOException("rawRootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
+        }
+        if (signingInfo.additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
             throw new IOException(
                     "additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes");
         }
-
-        IncrementalSignature result = new IncrementalSignature();
-        result.hashAlgorithm = INCFS_HASH_SHA256;
-        result.rootHash = rootHash;
-        result.additionalData = additionalData;
-        result.signature = pkcs7Signature;
-
-        return result;
     }
 
     /**
diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java
index 6d334f5..71f931d 100644
--- a/core/java/android/os/incremental/V4Signature.java
+++ b/core/java/android/os/incremental/V4Signature.java
@@ -20,9 +20,12 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
+import java.io.EOFException;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 
 /**
  * V4 signature fields.
@@ -31,30 +34,95 @@
  */
 public class V4Signature {
     public static final String EXT = ".idsig";
-    public static final int SUPPORTED_VERSION = 1;
+    public static final int SUPPORTED_VERSION = 2;
 
-    public final int version;
-    public final byte[] verityRootHash;
-    public final byte[] v3Digest;
-    public final byte[] pkcs7SignatureBlock;
+    public static final int HASHING_ALGORITHM_SHA256 = 1;
+    public static final byte LOG2_BLOCK_SIZE_4096_BYTES = 12;
+
+    /**
+     * IncFS hashing data.
+     */
+    public static class HashingInfo {
+        public final int hashAlgorithm; // only 1 == SHA256 supported
+        public final byte log2BlockSize; // only 12 (block size 4096) supported now
+        public final byte[] salt; // used exactly as in fs-verity, 32 bytes max
+        public final byte[] rawRootHash; // salted digest of the first Merkle tree page
+
+        HashingInfo(int hashAlgorithm, byte log2BlockSize, byte[] salt, byte[] rawRootHash) {
+            this.hashAlgorithm = hashAlgorithm;
+            this.log2BlockSize = log2BlockSize;
+            this.salt = salt;
+            this.rawRootHash = rawRootHash;
+        }
+
+        /**
+         * Constructs HashingInfo from byte array.
+         */
+        public static HashingInfo fromByteArray(byte[] bytes) throws IOException {
+            ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+            final int hashAlgorithm = buffer.getInt();
+            final byte log2BlockSize = buffer.get();
+            byte[] salt = readBytes(buffer);
+            byte[] rawRootHash = readBytes(buffer);
+            return new HashingInfo(hashAlgorithm, log2BlockSize, salt, rawRootHash);
+        }
+    }
+
+    /**
+     * V4 signature data.
+     */
+    public static class SigningInfo {
+        public final byte[] v3Digest;  // used to match with the corresponding APK
+        public final byte[] certificate; // ASN.1 DER form
+        public final byte[] additionalData; // a free-form binary data blob
+        public final byte[] publicKey; // ASN.1 DER, must match the certificate
+        public final int signatureAlgorithmId; // see the APK v2 doc for the list
+        public final byte[] signature;
+
+        SigningInfo(byte[] v3Digest, byte[] certificate, byte[] additionalData,
+                byte[] publicKey, int signatureAlgorithmId, byte[] signature) {
+            this.v3Digest = v3Digest;
+            this.certificate = certificate;
+            this.additionalData = additionalData;
+            this.publicKey = publicKey;
+            this.signatureAlgorithmId = signatureAlgorithmId;
+            this.signature = signature;
+        }
+
+        /**
+         * Constructs SigningInfo from byte array.
+         */
+        public static SigningInfo fromByteArray(byte[] bytes) throws IOException {
+            ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+            byte[] v3Digest = readBytes(buffer);
+            byte[] certificate = readBytes(buffer);
+            byte[] additionalData = readBytes(buffer);
+            byte[] publicKey = readBytes(buffer);
+            int signatureAlgorithmId = buffer.getInt();
+            byte[] signature = readBytes(buffer);
+            return new SigningInfo(v3Digest, certificate, additionalData, publicKey,
+                    signatureAlgorithmId, signature);
+        }
+    }
+
+    public final int version; // Always 2 for now.
+    public final byte[] hashingInfo;
+    public final byte[] signingInfo; // Passed as-is to the kernel. Can be retrieved later.
 
     /**
      * Construct a V4Signature from .idsig file.
      */
     public static V4Signature readFrom(ParcelFileDescriptor pfd) throws IOException {
-        final ParcelFileDescriptor dupedFd = pfd.dup();
-        final ParcelFileDescriptor.AutoCloseInputStream fdInputStream =
-                new ParcelFileDescriptor.AutoCloseInputStream(dupedFd);
-        try (DataInputStream stream = new DataInputStream(fdInputStream)) {
+        try (InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd.dup())) {
             return readFrom(stream);
         }
     }
 
     /**
-     * Construct a V4Signature from .idsig file.
+     * Construct a V4Signature from a byte array.
      */
     public static V4Signature readFrom(byte[] bytes) throws IOException {
-        try (DataInputStream stream = new DataInputStream(new ByteArrayInputStream(bytes))) {
+        try (InputStream stream = new ByteArrayInputStream(bytes)) {
             return readFrom(stream);
         }
     }
@@ -63,51 +131,131 @@
      * Store the V4Signature to a byte-array.
      */
     public byte[] toByteArray() {
-        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
-            try (DataOutputStream steam = new DataOutputStream(byteArrayOutputStream)) {
-                this.writeTo(steam);
-                steam.flush();
-            }
-            return byteArrayOutputStream.toByteArray();
+        try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
+            this.writeTo(stream);
+            return stream.toByteArray();
         } catch (IOException e) {
             return null;
         }
     }
 
-    boolean isVersionSupported() {
+    /**
+     * Combines necessary data to a signed data blob.
+     * The blob can be validated against signingInfo.signature.
+     *
+     * @param fileSize - size of the signed file (APK)
+     */
+    public static byte[] getSigningData(long fileSize, HashingInfo hashingInfo,
+            SigningInfo signingInfo) {
+        final int size =
+                4/*size*/ + 8/*fileSize*/ + 4/*hash_algorithm*/ + 1/*log2_blocksize*/ + bytesSize(
+                        hashingInfo.salt) + bytesSize(hashingInfo.rawRootHash) + bytesSize(
+                        signingInfo.v3Digest) + bytesSize(signingInfo.certificate) + bytesSize(
+                        signingInfo.additionalData);
+        ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
+        buffer.putInt(size);
+        buffer.putLong(fileSize);
+        buffer.putInt(hashingInfo.hashAlgorithm);
+        buffer.put(hashingInfo.log2BlockSize);
+        writeBytes(buffer, hashingInfo.salt);
+        writeBytes(buffer, hashingInfo.rawRootHash);
+        writeBytes(buffer, signingInfo.v3Digest);
+        writeBytes(buffer, signingInfo.certificate);
+        writeBytes(buffer, signingInfo.additionalData);
+        return buffer.array();
+    }
+
+    public boolean isVersionSupported() {
         return this.version == SUPPORTED_VERSION;
     }
 
-    static V4Signature readFrom(DataInputStream stream) throws IOException {
-        final int version = stream.readInt();
-        byte[] verityRootHash = readBytes(stream);
-        byte[] v3Digest = readBytes(stream);
-        byte[] pkcs7SignatureBlock = readBytes(stream);
-        return new V4Signature(version, verityRootHash, v3Digest, pkcs7SignatureBlock);
-    }
-
-    V4Signature(int version, byte[] verityRootHash, byte[] v3Digest, byte[] pkcs7SignatureBlock) {
+    private V4Signature(int version, byte[] hashingInfo, byte[] signingInfo) {
         this.version = version;
-        this.verityRootHash = verityRootHash;
-        this.v3Digest = v3Digest;
-        this.pkcs7SignatureBlock = pkcs7SignatureBlock;
+        this.hashingInfo = hashingInfo;
+        this.signingInfo = signingInfo;
     }
 
-    void writeTo(DataOutputStream stream) throws IOException {
-        stream.writeInt(this.version);
-        writeBytes(stream, this.verityRootHash);
-        writeBytes(stream, this.v3Digest);
-        writeBytes(stream, this.pkcs7SignatureBlock);
+    private static V4Signature readFrom(InputStream stream) throws IOException {
+        final int version = readIntLE(stream);
+        final byte[] hashingInfo = readBytes(stream);
+        final byte[] signingInfo = readBytes(stream);
+        return new V4Signature(version, hashingInfo, signingInfo);
     }
 
-    private static byte[] readBytes(DataInputStream stream) throws IOException {
-        byte[] result = new byte[stream.readInt()];
-        stream.read(result);
-        return result;
+    private void writeTo(OutputStream stream) throws IOException {
+        writeIntLE(stream, this.version);
+        writeBytes(stream, this.hashingInfo);
+        writeBytes(stream, this.signingInfo);
     }
 
-    private static void writeBytes(DataOutputStream stream, byte[] bytes) throws IOException {
-        stream.writeInt(bytes.length);
+    // Utility methods.
+    private static int bytesSize(byte[] bytes) {
+        return 4/*length*/ + (bytes == null ? 0 : bytes.length);
+    }
+
+    private static void readFully(InputStream stream, byte[] buffer) throws IOException {
+        int len = buffer.length;
+        int n = 0;
+        while (n < len) {
+            int count = stream.read(buffer, n, len - n);
+            if (count < 0) {
+                throw new EOFException();
+            }
+            n += count;
+        }
+    }
+
+    private static int readIntLE(InputStream stream) throws IOException {
+        final byte[] buffer = new byte[4];
+        readFully(stream, buffer);
+        return ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
+    }
+
+    private static void writeIntLE(OutputStream stream, int v) throws IOException {
+        final byte[] buffer = ByteBuffer.wrap(new byte[4]).order(ByteOrder.LITTLE_ENDIAN).putInt(
+                v).array();
+        stream.write(buffer);
+    }
+
+    private static byte[] readBytes(InputStream stream) throws IOException {
+        try {
+            final int size = readIntLE(stream);
+            final byte[] bytes = new byte[size];
+            readFully(stream, bytes);
+            return bytes;
+        } catch (EOFException ignored) {
+            return null;
+        }
+    }
+
+    private static byte[] readBytes(ByteBuffer buffer) throws IOException {
+        if (buffer.remaining() < 4) {
+            throw new EOFException();
+        }
+        final int size = buffer.getInt();
+        if (buffer.remaining() < size) {
+            throw new EOFException();
+        }
+        final byte[] bytes = new byte[size];
+        buffer.get(bytes);
+        return bytes;
+    }
+
+    private static void writeBytes(OutputStream stream, byte[] bytes) throws IOException {
+        if (bytes == null) {
+            writeIntLE(stream, 0);
+            return;
+        }
+        writeIntLE(stream, bytes.length);
         stream.write(bytes);
     }
+
+    private static void writeBytes(ByteBuffer buffer, byte[] bytes) {
+        if (bytes == null) {
+            buffer.putInt(0);
+            return;
+        }
+        buffer.putInt(bytes.length);
+        buffer.put(bytes);
+    }
 }
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index a2def7f..f43a252 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -100,10 +100,11 @@
     }
 
     /**
-     * Check if fuse is running in target user, if it's running then setup its obb directories.
-     * TODO: System server should store a list of active pids that obb is not mounted and use it.
+     * Create storage directories if it does not exist.
+     * Return true if the directories were setup correctly, otherwise false.
      */
-    public abstract void prepareObbDirs(int userId, Set<String> packageList, String processName);
+    public abstract boolean prepareStorageDirs(int userId, Set<String> packageList,
+            String processName);
 
     /**
      * Add a listener to listen to reset event in StorageManagerService.
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 40acb7b..09df72c 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -110,4 +110,8 @@
     List<String> getAutoRevokeExemptionRequestedPackages(int userId);
 
     List<String> getAutoRevokeExemptionGrantedPackages(int userId);
+
+    boolean setAutoRevokeWhitelisted(String packageName, boolean whitelisted, int userId);
+
+    boolean isAutoRevokeWhitelisted(String packageName, int userId);
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ce9b449..d8679b2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8497,7 +8497,6 @@
          *
          * @hide
          */
-        @SystemApi
         public static final String CARRIER_APPS_HANDLED = "carrier_apps_handled";
 
         /**
diff --git a/core/java/android/service/autofill/InlineSuggestionRenderService.java b/core/java/android/service/autofill/InlineSuggestionRenderService.java
index b6cc62d..f0a72c5 100644
--- a/core/java/android/service/autofill/InlineSuggestionRenderService.java
+++ b/core/java/android/service/autofill/InlineSuggestionRenderService.java
@@ -94,7 +94,7 @@
 
             final SurfaceControlViewHost host = new SurfaceControlViewHost(this, getDisplay(),
                     hostInputToken);
-            host.addView(suggestionRoot, lp);
+            host.setView(suggestionRoot, lp);
             suggestionRoot.setOnClickListener((v) -> {
                 try {
                     callback.onClick();
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 0cd96b8..c52b02b 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -55,7 +55,6 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
-import android.util.Slog;
 import android.widget.RemoteViews;
 
 import com.android.internal.annotations.GuardedBy;
@@ -1570,6 +1569,7 @@
         private boolean mVisuallyInterruptive;
         private boolean mIsConversation;
         private ShortcutInfo mShortcutInfo;
+        private boolean mIsBubble;
 
         private static final int PARCEL_VERSION = 2;
 
@@ -1604,6 +1604,7 @@
             out.writeBoolean(mVisuallyInterruptive);
             out.writeBoolean(mIsConversation);
             out.writeParcelable(mShortcutInfo, flags);
+            out.writeBoolean(mIsBubble);
         }
 
         /** @hide */
@@ -1639,6 +1640,7 @@
             mVisuallyInterruptive = in.readBoolean();
             mIsConversation = in.readBoolean();
             mShortcutInfo = in.readParcelable(cl);
+            mIsBubble = in.readBoolean();
         }
 
 
@@ -1844,6 +1846,14 @@
         }
 
         /**
+         * Returns whether this notification is actively a bubble.
+         * @hide
+         */
+        public boolean isBubble() {
+            return mIsBubble;
+        }
+
+        /**
          * @hide
          */
         public @Nullable ShortcutInfo getShortcutInfo() {
@@ -1862,7 +1872,8 @@
                 int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
                 boolean noisy, ArrayList<Notification.Action> smartActions,
                 ArrayList<CharSequence> smartReplies, boolean canBubble,
-                boolean visuallyInterruptive, boolean isConversation, ShortcutInfo shortcutInfo) {
+                boolean visuallyInterruptive, boolean isConversation, ShortcutInfo shortcutInfo,
+                boolean isBubble) {
             mKey = key;
             mRank = rank;
             mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1886,6 +1897,7 @@
             mVisuallyInterruptive = visuallyInterruptive;
             mIsConversation = isConversation;
             mShortcutInfo = shortcutInfo;
+            mIsBubble = isBubble;
         }
 
         /**
@@ -1913,7 +1925,8 @@
                     other.mCanBubble,
                     other.mVisuallyInterruptive,
                     other.mIsConversation,
-                    other.mShortcutInfo);
+                    other.mShortcutInfo,
+                    other.mIsBubble);
         }
 
         /**
@@ -1970,7 +1983,8 @@
                     && Objects.equals(mIsConversation, other.mIsConversation)
                     // Shortcutinfo doesn't have equals either; use id
                     &&  Objects.equals((mShortcutInfo == null ? 0 : mShortcutInfo.getId()),
-                    (other.mShortcutInfo == null ? 0 : other.mShortcutInfo.getId()));
+                    (other.mShortcutInfo == null ? 0 : other.mShortcutInfo.getId()))
+                    && Objects.equals(mIsBubble, other.mIsBubble);
         }
     }
 
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java b/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
index d2494a5..ae67068 100644
--- a/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
@@ -24,8 +24,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import java.util.List;
-
 /**
  * Handles response from the {@link QuickAccessWalletService} for {@link GetWalletCardsRequest}
  *
@@ -56,7 +54,6 @@
      *                 presented as the selected card.
      */
     public void onSuccess(@NonNull GetWalletCardsResponse response) {
-        Log.i(TAG, "onSuccess");
         if (isValidResponse(response)) {
             mHandler.post(() -> onSuccessInternal(response));
         } else {
@@ -78,7 +75,6 @@
     }
 
     private void onSuccessInternal(GetWalletCardsResponse response) {
-        Log.i(TAG, "onSuccessInternal");
         if (mCalled) {
             Log.w(TAG, "already called");
             return;
@@ -86,7 +82,6 @@
         mCalled = true;
         try {
             mCallback.onGetWalletCardsSuccess(response);
-            Log.i(TAG, "onSuccessInternal: returned response");
         } catch (RemoteException e) {
             Log.w(TAG, "Error returning wallet cards", e);
         }
@@ -106,29 +101,53 @@
     }
 
     private boolean isValidResponse(@NonNull GetWalletCardsResponse response) {
-        return response != null
-                && response.getWalletCards() != null
-                && response.getSelectedIndex() >= 0
-                && (response.getWalletCards().isEmpty() // selectedIndex may be 0 when list is empty
-                || response.getSelectedIndex() < response.getWalletCards().size())
-                && response.getWalletCards().size() < mRequest.getMaxCards()
-                && areValidCards(response.getWalletCards());
-    }
-
-    private boolean areValidCards(List<WalletCard> walletCards) {
-        for (WalletCard walletCard : walletCards) {
-            if (walletCard == null
-                    || walletCard.getCardId() == null
-                    || walletCard.getCardImage() == null
-                    || TextUtils.isEmpty(walletCard.getContentDescription())
-                    || walletCard.getPendingIntent() == null) {
+        if (response == null) {
+            Log.w(TAG, "Invalid response: response is null");
+            return false;
+        }
+        if (response.getWalletCards() == null) {
+            Log.w(TAG, "Invalid response: walletCards is null");
+            return false;
+        }
+        if (response.getSelectedIndex() < 0) {
+            Log.w(TAG, "Invalid response: selectedIndex is negative");
+            return false;
+        }
+        if (!response.getWalletCards().isEmpty()
+                && response.getSelectedIndex() >= response.getWalletCards().size()) {
+            Log.w(TAG, "Invalid response: selectedIndex out of bounds");
+            return false;
+        }
+        if (response.getWalletCards().size() > mRequest.getMaxCards()) {
+            Log.w(TAG, "Invalid response: too many cards");
+            return false;
+        }
+        for (WalletCard walletCard : response.getWalletCards()) {
+            if (walletCard == null) {
+                Log.w(TAG, "Invalid response: card is null");
+                return false;
+            }
+            if (walletCard.getCardId() == null) {
+                Log.w(TAG, "Invalid response: cardId is null");
                 return false;
             }
             Icon cardImage = walletCard.getCardImage();
+            if (cardImage == null) {
+                Log.w(TAG, "Invalid response: cardImage is null");
+                return false;
+            }
             if (cardImage.getType() == Icon.TYPE_BITMAP
-                    && walletCard.getCardImage().getBitmap().getConfig()
-                    != Bitmap.Config.HARDWARE) {
-                Log.w(TAG, "WalletCard bitmaps should be hardware bitmaps");
+                    && cardImage.getBitmap().getConfig() != Bitmap.Config.HARDWARE) {
+                Log.w(TAG, "Invalid response: cardImage bitmaps must be hardware bitmaps");
+                return false;
+            }
+            if (TextUtils.isEmpty(walletCard.getContentDescription())) {
+                Log.w(TAG, "Invalid response: contentDescription is null");
+                return false;
+            }
+            if (walletCard.getPendingIntent() == null) {
+                Log.w(TAG, "Invalid response: pendingIntent is null");
+                return false;
             }
         }
         return true;
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
index be9ab11..4f5b139 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
@@ -16,19 +16,23 @@
 
 package android.service.quickaccesswallet;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.content.Context;
 import android.content.Intent;
 
+import java.io.Closeable;
+import java.util.concurrent.Executor;
+
 /**
  * Facilitates accessing cards from the {@link QuickAccessWalletService}.
  *
  * @hide
  */
 @TestApi
-public interface QuickAccessWalletClient {
+public interface QuickAccessWalletClient extends Closeable {
 
     /**
      * Create a client for accessing wallet cards from the {@link QuickAccessWalletService}. If the
@@ -92,6 +96,14 @@
             @NonNull OnWalletCardsRetrievedCallback callback);
 
     /**
+     * Get wallet cards from the {@link QuickAccessWalletService}.
+     */
+    void getWalletCards(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull GetWalletCardsRequest request,
+            @NonNull OnWalletCardsRetrievedCallback callback);
+
+    /**
      * Callback for getWalletCards
      */
     interface OnWalletCardsRetrievedCallback {
@@ -111,12 +123,19 @@
     void notifyWalletDismissed();
 
     /**
-     * Unregister event listener.
+     * Register an event listener.
      */
     void addWalletServiceEventListener(@NonNull WalletServiceEventListener listener);
 
     /**
-     * Unregister event listener
+     * Register an event listener.
+     */
+    void addWalletServiceEventListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull WalletServiceEventListener listener);
+
+    /**
+     * Unregister an event listener
      */
     void removeWalletServiceEventListener(@NonNull WalletServiceEventListener listener);
 
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
index 37a8703..e4dd082 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
@@ -18,6 +18,7 @@
 
 import static android.service.quickaccesswallet.QuickAccessWalletService.SERVICE_INTERFACE;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -36,16 +37,19 @@
 
 import com.android.internal.widget.LockPatternUtils;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.Map;
 import java.util.Queue;
 import java.util.UUID;
+import java.util.concurrent.Executor;
 
 /**
  * Implements {@link QuickAccessWalletClient}. The client connects, performs requests, waits for
- * responses, and disconnects automatically after a short period of time. The client may
+ * responses, and disconnects automatically one minute after the last call is performed.
+ *
  * @hide
  */
 public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, ServiceConnection {
@@ -78,15 +82,14 @@
 
     @Override
     public boolean isWalletServiceAvailable() {
-        boolean available = mServiceInfo != null;
-        Log.i(TAG, "isWalletServiceAvailable: " + available);
-        return available;
+        return mServiceInfo != null;
     }
 
     @Override
     public boolean isWalletFeatureAvailable() {
         int currentUser = ActivityManager.getCurrentUser();
-        return checkUserSetupComplete()
+        return currentUser == UserHandle.USER_SYSTEM
+                && checkUserSetupComplete()
                 && checkSecureSetting(Settings.Secure.GLOBAL_ACTIONS_PANEL_ENABLED)
                 && !new LockPatternUtils(mContext).isUserInLockdown(currentUser);
     }
@@ -101,23 +104,29 @@
     public void getWalletCards(
             @NonNull GetWalletCardsRequest request,
             @NonNull OnWalletCardsRetrievedCallback callback) {
+        getWalletCards(mContext.getMainExecutor(), request, callback);
+    }
 
-        Log.i(TAG, "getWalletCards");
-
+    @Override
+    public void getWalletCards(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull GetWalletCardsRequest request,
+            @NonNull OnWalletCardsRetrievedCallback callback) {
         if (!isWalletServiceAvailable()) {
-            callback.onWalletCardRetrievalError(new GetWalletCardsError(null, null));
+            executor.execute(
+                    () -> callback.onWalletCardRetrievalError(new GetWalletCardsError(null, null)));
             return;
         }
 
         BaseCallbacks serviceCallback = new BaseCallbacks() {
             @Override
             public void onGetWalletCardsSuccess(GetWalletCardsResponse response) {
-                mHandler.post(() -> callback.onWalletCardsRetrieved(response));
+                executor.execute(() -> callback.onWalletCardsRetrieved(response));
             }
 
             @Override
             public void onGetWalletCardsFailure(GetWalletCardsError error) {
-                mHandler.post(() -> callback.onWalletCardRetrievalError(error));
+                executor.execute(() -> callback.onWalletCardRetrievalError(error));
             }
         };
 
@@ -132,11 +141,11 @@
                 serviceCallback.onGetWalletCardsFailure(new GetWalletCardsError(null, null));
             }
         });
+
     }
 
     @Override
     public void selectWalletCard(@NonNull SelectWalletCardRequest request) {
-        Log.i(TAG, "selectWalletCard");
         if (!isWalletServiceAvailable()) {
             return;
         }
@@ -153,7 +162,6 @@
         if (!isWalletServiceAvailable()) {
             return;
         }
-        Log.i(TAG, "notifyWalletDismissed");
         executeApiCall(new ApiCaller("onWalletDismissed") {
             @Override
             public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
@@ -164,15 +172,20 @@
 
     @Override
     public void addWalletServiceEventListener(WalletServiceEventListener listener) {
+        addWalletServiceEventListener(mContext.getMainExecutor(), listener);
+    }
+
+    @Override
+    public void addWalletServiceEventListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull WalletServiceEventListener listener) {
         if (!isWalletServiceAvailable()) {
             return;
         }
-        Log.i(TAG, "registerWalletServiceEventListener");
         BaseCallbacks callback = new BaseCallbacks() {
             @Override
             public void onWalletServiceEvent(WalletServiceEvent event) {
-                Log.i(TAG, "onWalletServiceEvent");
-                mHandler.post(() -> listener.onWalletServiceEvent(event));
+                executor.execute(() -> listener.onWalletServiceEvent(event));
             }
         };
 
@@ -193,7 +206,6 @@
         if (!isWalletServiceAvailable()) {
             return;
         }
-        Log.i(TAG, "unregisterWalletServiceEventListener");
         executeApiCall(new ApiCaller("unregisterListener") {
             @Override
             public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
@@ -209,8 +221,12 @@
     }
 
     @Override
+    public void close() throws IOException {
+        disconnect();
+    }
+
+    @Override
     public void disconnect() {
-        Log.i(TAG, "disconnect");
         mHandler.post(() -> disconnectInternal(true));
     }
 
@@ -241,18 +257,15 @@
     }
 
     private void connect() {
-        Log.i(TAG, "connect");
         mHandler.post(this::connectInternal);
     }
 
     private void connectInternal() {
-        Log.i(TAG, "connectInternal");
         if (mServiceInfo == null) {
             Log.w(TAG, "Wallet service unavailable");
             return;
         }
         if (mIsConnected) {
-            Log.w(TAG, "already connected");
             return;
         }
         mIsConnected = true;
@@ -264,23 +277,14 @@
     }
 
     private void onConnectedInternal(IQuickAccessWalletService service) {
-        Log.i(TAG, "onConnectedInternal");
         if (!mIsConnected) {
             Log.w(TAG, "onConnectInternal but connection closed");
             mService = null;
             return;
         }
         mService = service;
-        Log.i(TAG, "onConnectedInternal success: request queue size " + mRequestQueue.size());
         for (ApiCaller apiCaller : new ArrayList<>(mRequestQueue)) {
-            try {
-                apiCaller.performApiCall(mService);
-            } catch (RemoteException e) {
-                Log.e(TAG, "onConnectedInternal error", e);
-                apiCaller.onApiError();
-                disconnect();
-                break;
-            }
+            performApiCallInternal(apiCaller, mService);
             mRequestQueue.remove(apiCaller);
         }
     }
@@ -290,7 +294,6 @@
      * posting a new delayed message.
      */
     private void resetServiceConnectionTimeout() {
-        Log.i(TAG, "resetServiceConnectionTimeout");
         mHandler.removeMessages(MSG_TIMEOUT_SERVICE);
         mHandler.postDelayed(
                 () -> disconnectInternal(true),
@@ -299,13 +302,11 @@
     }
 
     private void disconnectInternal(boolean clearEventListeners) {
-        Log.i(TAG, "disconnectInternal: " + clearEventListeners);
         if (!mIsConnected) {
             Log.w(TAG, "already disconnected");
             return;
         }
         if (clearEventListeners && !mEventListeners.isEmpty()) {
-            Log.i(TAG, "disconnectInternal: clear event listeners");
             for (WalletServiceEventListener listener : mEventListeners.keySet()) {
                 removeWalletServiceEventListener(listener);
             }
@@ -320,29 +321,33 @@
     }
 
     private void executeApiCall(ApiCaller apiCaller) {
-        Log.i(TAG, "execute: " + apiCaller.mDesc);
         mHandler.post(() -> executeInternal(apiCaller));
     }
 
-    private void executeInternal(ApiCaller apiCall) {
-        Log.i(TAG, "executeInternal: " + apiCall.mDesc);
+    private void executeInternal(ApiCaller apiCaller) {
         if (mIsConnected && mService != null) {
-            try {
-                apiCall.performApiCall(mService);
-                Log.i(TAG, "executeInternal success: " + apiCall.mDesc);
-                resetServiceConnectionTimeout();
-            } catch (RemoteException e) {
-                Log.w(TAG, "executeInternal error: " + apiCall.mDesc, e);
-                apiCall.onApiError();
-                disconnect();
-            }
+            performApiCallInternal(apiCaller, mService);
         } else {
-            Log.i(TAG, "executeInternal: queued" + apiCall.mDesc);
-            mRequestQueue.add(apiCall);
+            mRequestQueue.add(apiCaller);
             connect();
         }
     }
 
+    private void performApiCallInternal(ApiCaller apiCaller, IQuickAccessWalletService service) {
+        if (service == null) {
+            apiCaller.onApiError();
+            return;
+        }
+        try {
+            apiCaller.performApiCall(service);
+            resetServiceConnectionTimeout();
+        } catch (RemoteException e) {
+            Log.w(TAG, "executeInternal error: " + apiCaller.mDesc, e);
+            apiCaller.onApiError();
+            disconnect();
+        }
+    }
+
     private abstract static class ApiCaller {
         private final String mDesc;
 
@@ -350,7 +355,8 @@
             this.mDesc = desc;
         }
 
-        abstract void performApiCall(IQuickAccessWalletService service) throws RemoteException;
+        abstract void performApiCall(IQuickAccessWalletService service)
+                throws RemoteException;
 
         void onApiError() {
             Log.w(TAG, "api error: " + mDesc);
@@ -359,7 +365,6 @@
 
     @Override // ServiceConnection
     public void onServiceConnected(ComponentName name, IBinder binder) {
-        Log.i(TAG, "onServiceConnected: " + name);
         IQuickAccessWalletService service = IQuickAccessWalletService.Stub.asInterface(binder);
         mHandler.post(() -> onConnectedInternal(service));
     }
@@ -367,19 +372,16 @@
     @Override // ServiceConnection
     public void onServiceDisconnected(ComponentName name) {
         // Do not disconnect, as we may later be re-connected
-        Log.w(TAG, "onServiceDisconnected");
     }
 
     @Override // ServiceConnection
     public void onBindingDied(ComponentName name) {
         // This is a recoverable error but the client will need to reconnect.
-        Log.w(TAG, "onBindingDied");
         disconnect();
     }
 
     @Override // ServiceConnection
     public void onNullBinding(ComponentName name) {
-        Log.w(TAG, "onNullBinding");
         disconnect();
     }
 
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
index 23173a8..31e51bb 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -66,13 +66,11 @@
     static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) {
         ComponentName defaultPaymentApp = getDefaultPaymentApp(context);
         if (defaultPaymentApp == null) {
-            Log.d(TAG, "create: default payment app not set");
             return null;
         }
 
         ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultPaymentApp.getPackageName());
         if (serviceInfo == null) {
-            Log.d(TAG, "create: unable to resolve service intent");
             return null;
         }
 
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index e6ae0ab..b09d2e9 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -180,7 +180,7 @@
          *                           GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
          *                           GetWalletCardsRequest#getCardHeightPx()}. If the card image
          *                           does not have these dimensions, it may appear distorted when it
-         *                           is scaled to fit these dimensions on screen. Bitmaps should be
+         *                           is scaled to fit these dimensions on screen. Bitmaps must be
          *                           of type {@link android.graphics.Bitmap.Config#HARDWARE} for
          *                           performance reasons.
          * @param contentDescription The content description of the card image. This field is
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 6b87a10..cfbe393 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -71,7 +71,7 @@
         // introduced in R and will be removed in the next release (b/148367230).
         DEFAULT_FLAGS.put(SETTINGS_DO_NOT_RESTORE_PRESERVED, "false");
 
-        DEFAULT_FLAGS.put("settings_tether_all_in_one", "true");
+        DEFAULT_FLAGS.put("settings_tether_all_in_one", "false");
         DEFAULT_FLAGS.put(SETTINGS_SCHEDULES_FLAG, "false");
         DEFAULT_FLAGS.put("settings_contextual_home2", "false");
     }
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
index abd04cc..79eb9f6 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
@@ -16,6 +16,8 @@
 
 package android.util.apk;
 
+import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
 import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
 import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
@@ -211,6 +213,12 @@
                     verityDigest, apk.length(), signatureInfo);
         }
 
+        if (contentDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA512)) {
+            result.digest = contentDigests.get(CONTENT_DIGEST_CHUNKED_SHA512);
+        } else if (contentDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA256)) {
+            result.digest = contentDigests.get(CONTENT_DIGEST_CHUNKED_SHA256);
+        }
+
         return result;
     }
 
@@ -568,6 +576,7 @@
         public final VerifiedProofOfRotation por;
 
         public byte[] verityRootHash;
+        public byte[] digest;
 
         public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
             this.certs = certs;
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java
index b6b8089..8c240d9 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java
@@ -16,13 +16,32 @@
 
 package android.util.apk;
 
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;
+
 import android.os.incremental.IncrementalManager;
+import android.os.incremental.V4Signature;
+import android.util.Pair;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
 import java.security.cert.Certificate;
-
-import sun.security.pkcs.PKCS7;
-import sun.security.pkcs.ParsingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
 
 /**
  * APK Signature Scheme v4 verifier.
@@ -30,24 +49,118 @@
  * @hide for internal use only.
  */
 public class ApkSignatureSchemeV4Verifier {
+    /**
+     * Extracts and verifies APK Signature Scheme v4 signatures of the provided APK and returns the
+     * certificates associated with each signer.
+     */
+    public static VerifiedSigner extractCertificates(String apkFile)
+            throws SignatureNotFoundException, SecurityException {
+        final File apk = new File(apkFile);
+        final byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature(
+                apk.getAbsolutePath());
+        if (signatureBytes == null || signatureBytes.length == 0) {
+            throw new SignatureNotFoundException("Failed to obtain signature bytes from IncFS.");
+        }
+
+        final V4Signature signature;
+        final V4Signature.HashingInfo hashingInfo;
+        final V4Signature.SigningInfo signingInfo;
+        try {
+            signature = V4Signature.readFrom(signatureBytes);
+
+            if (!signature.isVersionSupported()) {
+                throw new SecurityException(
+                        "v4 signature version " + signature.version + " is not supported");
+            }
+
+            hashingInfo = V4Signature.HashingInfo.fromByteArray(signature.hashingInfo);
+            signingInfo = V4Signature.SigningInfo.fromByteArray(signature.signingInfo);
+        } catch (IOException e) {
+            throw new SignatureNotFoundException("Failed to read V4 signature.", e);
+        }
+
+        final byte[] signedData = V4Signature.getSigningData(apk.length(), hashingInfo,
+                signingInfo);
+
+        return verifySigner(signingInfo, signedData);
+    }
+
+    private static VerifiedSigner verifySigner(V4Signature.SigningInfo signingInfo,
+            final byte[] signedData) throws SecurityException {
+        if (!isSupportedSignatureAlgorithm(signingInfo.signatureAlgorithmId)) {
+            throw new SecurityException("No supported signatures found");
+        }
+
+        final int signatureAlgorithmId = signingInfo.signatureAlgorithmId;
+        final byte[] signatureBytes = signingInfo.signature;
+        final byte[] publicKeyBytes = signingInfo.publicKey;
+        final byte[] encodedCert = signingInfo.certificate;
+
+        String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(signatureAlgorithmId);
+        Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
+                getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithmId);
+        String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
+        AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
+        boolean sigVerified;
+        try {
+            PublicKey publicKey =
+                    KeyFactory.getInstance(keyAlgorithm)
+                            .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
+            Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
+            sig.initVerify(publicKey);
+            if (jcaSignatureAlgorithmParams != null) {
+                sig.setParameter(jcaSignatureAlgorithmParams);
+            }
+            sig.update(signedData);
+            sigVerified = sig.verify(signatureBytes);
+        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
+                | InvalidAlgorithmParameterException | SignatureException e) {
+            throw new SecurityException(
+                    "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
+        }
+        if (!sigVerified) {
+            throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
+        }
+
+        // Signature over signedData has verified.
+        CertificateFactory certFactory;
+        try {
+            certFactory = CertificateFactory.getInstance("X.509");
+        } catch (CertificateException e) {
+            throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+        }
+
+        X509Certificate certificate;
+        try {
+            certificate = (X509Certificate)
+                    certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
+        } catch (CertificateException e) {
+            throw new SecurityException("Failed to decode certificate", e);
+        }
+        certificate = new VerbatimX509Certificate(certificate, encodedCert);
+
+        byte[] certificatePublicKeyBytes = certificate.getPublicKey().getEncoded();
+        if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
+            throw new SecurityException(
+                    "Public key mismatch between certificate and signature record");
+        }
+
+        return new VerifiedSigner(new Certificate[]{certificate}, signingInfo.v3Digest);
+    }
 
     /**
-     * Extracts APK Signature Scheme v4 signatures of the provided APK and returns the certificates
-     * associated with each signer.
+     * Verified APK Signature Scheme v4 signer, including V3 digest.
+     *
+     * @hide for internal use only.
      */
-    public static Certificate[] extractCertificates(String apkFile)
-            throws SignatureNotFoundException, SecurityException {
-        final byte[] rawSignature = IncrementalManager.unsafeGetFileSignature(
-                new File(apkFile).getAbsolutePath());
-        if (rawSignature == null || rawSignature.length == 0) {
-            throw new SignatureNotFoundException("Failed to obtain raw signature from IncFS.");
+    public static class VerifiedSigner {
+        public final Certificate[] certs;
+        public byte[] v3Digest;
+
+        public VerifiedSigner(Certificate[] certs, byte[] v3Digest) {
+            this.certs = certs;
+            this.v3Digest = v3Digest;
         }
 
-        try {
-            PKCS7 pkcs7 = new PKCS7(rawSignature);
-            return pkcs7.getCertificates();
-        } catch (ParsingException e) {
-            throw new SecurityException("Failed to parse signature and extract certificates", e);
-        }
     }
 }
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index f325c21..c1cee48 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -168,7 +168,7 @@
     /**
      * Verifies the provided APK using V4 schema.
      *
-     * @param verifyFull whether to verify all contents of this APK or just collect certificates.
+     * @param verifyFull whether to verify (V4 vs V3) or just collect certificates.
      * @return the certificates associated with each signer.
      * @throws SignatureNotFoundException if there are no V4 signatures in the APK
      * @throws PackageParserException     if there was a problem collecting certificates
@@ -178,30 +178,34 @@
             throws SignatureNotFoundException, PackageParserException {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4");
         try {
-            Certificate[] certs = ApkSignatureSchemeV4Verifier.extractCertificates(apkPath);
-            Certificate[][] signerCerts = new Certificate[][]{certs};
+            ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner =
+                    ApkSignatureSchemeV4Verifier.extractCertificates(apkPath);
+            Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
             Signature[] signerSigs = convertToSignatures(signerCerts);
 
             if (verifyFull) {
-                // v4 is an add-on and requires v2/v3 signature to validate against its certificates
-                final PackageParser.SigningDetails nonstreaming = verifyV3AndBelowSignatures(
-                        apkPath, minSignatureSchemeVersion, false);
-                if (nonstreaming.signatureSchemeVersion <= SignatureSchemeVersion.JAR) {
+                // v4 is an add-on and requires v3 signature to validate against its certificates
+                ApkSignatureSchemeV3Verifier.VerifiedSigner nonstreaming =
+                        ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath);
+                Certificate[][] nonstreamingCerts = new Certificate[][]{nonstreaming.certs};
+                Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts);
+
+                if (nonstreamingSigs.length != signerSigs.length) {
                     throw new SecurityException(
-                            "V4 signing block can only be verified along with V2 and above.");
-                }
-                if (nonstreaming.signatures.length == 0
-                        || nonstreaming.signatures.length != signerSigs.length) {
-                    throw new SecurityException("Invalid number of signatures in "
-                            + nonstreaming.signatureSchemeVersion);
+                            "Invalid number of certificates: " + nonstreaming.certs.length);
                 }
 
                 for (int i = 0, size = signerSigs.length; i < size; ++i) {
-                    if (!nonstreaming.signatures[i].equals(signerSigs[i])) {
-                        throw new SecurityException("V4 signature certificate does not match "
-                                + nonstreaming.signatureSchemeVersion);
+                    if (!nonstreamingSigs[i].equals(signerSigs[i])) {
+                        throw new SecurityException("V4 signature certificate does not match V3");
                     }
                 }
+
+                // TODO(b/151240006): add support for v2 digest and make it mandatory.
+                if (!ArrayUtils.isEmpty(vSigner.v3Digest) && !ArrayUtils.equals(vSigner.v3Digest,
+                        nonstreaming.digest, vSigner.v3Digest.length)) {
+                    throw new SecurityException("V3 digest in V4 signature does not match V3");
+                }
             }
 
             return new PackageParser.SigningDetails(signerSigs,
diff --git a/core/java/android/util/apk/SourceStampVerifier.java b/core/java/android/util/apk/SourceStampVerifier.java
index 70e4a51..a7ae32d 100644
--- a/core/java/android/util/apk/SourceStampVerifier.java
+++ b/core/java/android/util/apk/SourceStampVerifier.java
@@ -24,6 +24,7 @@
 import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
 
 import android.util.Pair;
+import android.util.Slog;
 import android.util.jar.StrictJarFile;
 
 import libcore.io.IoUtils;
@@ -43,6 +44,7 @@
 import java.security.PublicKey;
 import java.security.Signature;
 import java.security.SignatureException;
+import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
@@ -68,6 +70,8 @@
  */
 public abstract class SourceStampVerifier {
 
+    private static final String TAG = "SourceStampVerifier";
+
     private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
     private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
     private static final int SOURCE_STAMP_BLOCK_ID = 0x2b09189e;
@@ -78,30 +82,51 @@
     /** Hidden constructor to prevent instantiation. */
     private SourceStampVerifier() {}
 
-    /** Verifies SourceStamp present in the provided APK. */
-    public static SourceStampVerificationResult verify(String apkFile) {
-        try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
-            return verify(apk);
-        } catch (IOException e) {
-            // Any exception in reading the APK returns a non-present SourceStamp outcome
-            // without affecting the outcome of any of the other signature schemes.
-            return SourceStampVerificationResult.notPresent();
+    /** Verifies SourceStamp present in a list of APKs. */
+    public static SourceStampVerificationResult verify(List<String> apkFiles) {
+        Certificate stampCertificate = null;
+        for (String apkFile : apkFiles) {
+            SourceStampVerificationResult sourceStampVerificationResult = verify(apkFile);
+            if (!sourceStampVerificationResult.isPresent()
+                    || !sourceStampVerificationResult.isVerified()) {
+                return sourceStampVerificationResult;
+            }
+            if (stampCertificate != null
+                    && !stampCertificate.equals(sourceStampVerificationResult.getCertificate())) {
+                return SourceStampVerificationResult.notVerified();
+            }
+            stampCertificate = sourceStampVerificationResult.getCertificate();
         }
+        return SourceStampVerificationResult.verified(stampCertificate);
     }
 
-    private static SourceStampVerificationResult verify(RandomAccessFile apk) {
-        byte[] sourceStampCertificateDigest;
-        try {
-            sourceStampCertificateDigest = getSourceStampCertificateDigest(apk);
+    /** Verifies SourceStamp present in the provided APK. */
+    public static SourceStampVerificationResult verify(String apkFile) {
+        StrictJarFile apkJar = null;
+        try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+            apkJar =
+                    new StrictJarFile(
+                            apkFile,
+                            /* verify= */ false,
+                            /* signatureSchemeRollbackProtectionsEnforced= */ false);
+            byte[] sourceStampCertificateDigest = getSourceStampCertificateDigest(apkJar);
             if (sourceStampCertificateDigest == null) {
                 // SourceStamp certificate hash file not found, which means that there is not
                 // SourceStamp present.
                 return SourceStampVerificationResult.notPresent();
             }
+            return verify(apk, sourceStampCertificateDigest);
         } catch (IOException e) {
+            // Any exception in reading the APK returns a non-present SourceStamp outcome
+            // without affecting the outcome of any of the other signature schemes.
             return SourceStampVerificationResult.notPresent();
+        } finally {
+            closeApkJar(apkJar);
         }
+    }
 
+    private static SourceStampVerificationResult verify(
+            RandomAccessFile apk, byte[] sourceStampCertificateDigest) {
         try {
             SignatureInfo signatureInfo =
                     ApkSigningBlockUtils.findSignature(apk, SOURCE_STAMP_BLOCK_ID);
@@ -264,22 +289,17 @@
         return apkContentDigests;
     }
 
-    private static byte[] getSourceStampCertificateDigest(RandomAccessFile apk) throws IOException {
-        StrictJarFile apkJar =
-                new StrictJarFile(
-                        apk.getFD(),
-                        /* verify= */ false,
-                        /* signatureSchemeRollbackProtectionsEnforced= */ false);
-        ZipEntry zipEntry = apkJar.findEntry(SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME);
-        if (zipEntry == null) {
-            // SourceStamp certificate hash file not found, which means that there is not
-            // SourceStamp present.
-            return null;
-        }
+    private static byte[] getSourceStampCertificateDigest(StrictJarFile apkJar) throws IOException {
         InputStream inputStream = null;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
         try {
+            ZipEntry zipEntry = apkJar.findEntry(SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME);
+            if (zipEntry == null) {
+                // SourceStamp certificate hash file not found, which means that there is not
+                // SourceStamp present.
+                return null;
+            }
             inputStream = apkJar.getInputStream(zipEntry);
+            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
 
             // Trying to read the certificate digest, which should be less than 1024 bytes.
             byte[] buffer = new byte[1024];
@@ -308,4 +328,15 @@
         }
         return result.array();
     }
+
+    private static void closeApkJar(StrictJarFile apkJar) {
+        try {
+            if (apkJar == null) {
+                return;
+            }
+            apkJar.close();
+        } catch (IOException e) {
+            Slog.e(TAG, "Could not close APK jar", e);
+        }
+    }
 }
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 88e7f2e..123b9db 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -53,7 +53,6 @@
 import android.view.animation.PathInterpolator;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 
 import java.io.PrintWriter;
@@ -518,14 +517,13 @@
             PendingControlRequest pendingRequest = mPendingImeControlRequest;
             mPendingImeControlRequest = null;
             mHandler.removeCallbacks(mPendingControlTimeout);
-            CancellationSignal cancellationSignal = controlAnimationUnchecked(
-                    pendingRequest.types,
+            controlAnimationUnchecked(
+                    pendingRequest.types, pendingRequest.cancellationSignal,
                     pendingRequest.listener, mFrame,
                     true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator,
                     false /* fade */, pendingRequest.animationType,
                     pendingRequest.layoutInsetsDuringAnimation,
                     pendingRequest.useInsetsAnimationThread);
-            pendingRequest.cancellationSignal.setOnCancelListener(cancellationSignal::cancel);
             return;
         }
 
@@ -571,24 +569,26 @@
     }
 
     @Override
-    public CancellationSignal controlWindowInsetsAnimation(@InsetsType int types, long durationMs,
+    public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
             @Nullable Interpolator interpolator,
+            @Nullable CancellationSignal cancellationSignal,
             @NonNull WindowInsetsAnimationControlListener listener) {
-        return controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs,
-                interpolator, ANIMATION_TYPE_USER);
+        controlWindowInsetsAnimation(types, cancellationSignal, listener,
+                false /* fromIme */, durationMillis, interpolator, ANIMATION_TYPE_USER);
     }
 
-    private CancellationSignal controlWindowInsetsAnimation(@InsetsType int types,
-            WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs,
-            @Nullable Interpolator interpolator, @AnimationType int animationType) {
+    private void controlWindowInsetsAnimation(@InsetsType int types,
+            @Nullable CancellationSignal cancellationSignal,
+            WindowInsetsAnimationControlListener listener,
+            boolean fromIme, long durationMs, @Nullable Interpolator interpolator,
+            @AnimationType int animationType) {
         if (!checkDisplayFramesForControlling()) {
             listener.onCancelled();
-            CancellationSignal cancellationSignal = new CancellationSignal();
-            cancellationSignal.cancel();
-            return cancellationSignal;
+            return;
         }
-        return controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, interpolator,
-                false /* fade */, animationType, getLayoutInsetsDuringAnimationMode(types),
+        controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
+                interpolator, false /* fade */, animationType,
+                getLayoutInsetsDuringAnimationMode(types),
                 false /* useInsetsAnimationThread */);
     }
 
@@ -599,18 +599,17 @@
         return mState.getDisplayFrame().equals(mFrame);
     }
 
-    private CancellationSignal controlAnimationUnchecked(@InsetsType int types,
+    private void controlAnimationUnchecked(@InsetsType int types,
+            @Nullable CancellationSignal cancellationSignal,
             WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
             long durationMs, Interpolator interpolator, boolean fade,
             @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
             boolean useInsetsAnimationThread) {
-        CancellationSignal cancellationSignal = new CancellationSignal();
         if (types == 0) {
             // nothing to animate.
             listener.onCancelled();
-            cancellationSignal.cancel();
-            return cancellationSignal;
+            return;
         }
         cancelExistingControllers(types);
         mLastStartedAnimTypes |= types;
@@ -631,18 +630,19 @@
                     useInsetsAnimationThread);
             mPendingImeControlRequest = request;
             mHandler.postDelayed(mPendingControlTimeout, PENDING_CONTROL_TIMEOUT_MS);
-            cancellationSignal.setOnCancelListener(() -> {
-                if (mPendingImeControlRequest == request) {
-                    abortPendingImeControlRequest();
-                }
-            });
-            return cancellationSignal;
+            if (cancellationSignal != null) {
+                cancellationSignal.setOnCancelListener(() -> {
+                    if (mPendingImeControlRequest == request) {
+                        abortPendingImeControlRequest();
+                    }
+                });
+            }
+            return;
         }
 
         if (typesReady == 0) {
             listener.onCancelled();
-            cancellationSignal.cancel();
-            return cancellationSignal;
+            return;
         }
 
 
@@ -654,13 +654,14 @@
                         frame, mState, listener, typesReady, this, durationMs, interpolator, fade,
                         animationType);
         mRunningAnimations.add(new RunningAnimation(runner, animationType));
-        cancellationSignal.setOnCancelListener(runner::cancel);
+        if (cancellationSignal != null) {
+            cancellationSignal.setOnCancelListener(runner::cancel);
+        }
         if (layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) {
             showDirectly(types);
         } else {
             hideDirectly(types, false /* animationFinished */, animationType);
         }
-        return cancellationSignal;
     }
 
     /**
@@ -922,7 +923,8 @@
         // Show/hide animations always need to be relative to the display frame, in order that shown
         // and hidden state insets are correct.
         controlAnimationUnchecked(
-                types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(),
+                types, null /* cancellationSignal */, listener, mState.getDisplayFrame(), fromIme,
+                listener.getDurationMs(),
                 INTERPOLATOR, true /* fade */, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
                 show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
                         : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java
index 7f36418..e8d9bb5 100644
--- a/core/java/android/view/PendingInsetsController.java
+++ b/core/java/android/view/PendingInsetsController.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.CancellationSignal;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.animation.Interpolator;
@@ -60,21 +62,6 @@
     }
 
     @Override
-    public CancellationSignal controlWindowInsetsAnimation(int types, long durationMillis,
-            Interpolator interpolator,
-            WindowInsetsAnimationControlListener listener) {
-        if (mReplayedInsetsController != null) {
-            return mReplayedInsetsController.controlWindowInsetsAnimation(types, durationMillis,
-                    interpolator, listener);
-        } else {
-            listener.onCancelled();
-            CancellationSignal cancellationSignal = new CancellationSignal();
-            cancellationSignal.cancel();
-            return cancellationSignal;
-        }
-    }
-
-    @Override
     public void setSystemBarsAppearance(int appearance, int mask) {
         if (mReplayedInsetsController != null) {
             mReplayedInsetsController.setSystemBarsAppearance(appearance, mask);
@@ -176,6 +163,19 @@
         mReplayedInsetsController = null;
     }
 
+    @Override
+    public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
+            @Nullable Interpolator interpolator,
+            CancellationSignal cancellationSignal,
+            @NonNull WindowInsetsAnimationControlListener listener) {
+        if (mReplayedInsetsController != null) {
+            mReplayedInsetsController.controlWindowInsetsAnimation(types, durationMillis,
+                    interpolator, cancellationSignal, listener);
+        } else {
+            listener.onCancelled();
+        }
+    }
+
     private interface PendingRequest {
         void replay(InsetsController controller);
     }
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index a3b3f1f..41a3847 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -26,6 +26,8 @@
 import android.os.Parcelable;
 import android.view.accessibility.IAccessibilityEmbeddedConnection;
 
+import java.util.Objects;
+
 /**
  * Utility class for adding a View hierarchy to a {@link SurfaceControl}. The View hierarchy
  * will render in to a root SurfaceControl, and receive input based on the SurfaceControl's
@@ -159,7 +161,8 @@
      * @hide
      */
     @TestApi
-    public void addView(@NonNull View view, WindowManager.LayoutParams attrs) {
+    public void setView(@NonNull View view, @NonNull WindowManager.LayoutParams attrs) {
+        Objects.requireNonNull(view);
         mViewRoot.setView(view, attrs, null);
     }
 
@@ -172,11 +175,18 @@
      * @param width The width to layout the View within, in pixels.
      * @param height The height to layout the View within, in pixels.
      */
-    public void addView(@NonNull View view, int width, int height) {
+    public void setView(@NonNull View view, int width, int height) {
         final WindowManager.LayoutParams lp =
                 new WindowManager.LayoutParams(width, height,
                         WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT);
-        addView(view, lp);
+        setView(view, lp);
+    }
+
+    /**
+     * @return The view passed to setView, or null if none has been passed.
+     */
+    public @Nullable View getView() {
+        return mViewRoot.getView();
     }
 
     /**
diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java
index 701bd31..faaf920 100644
--- a/core/java/android/view/WindowInsetsAnimationControlListener.java
+++ b/core/java/android/view/WindowInsetsAnimationControlListener.java
@@ -16,7 +16,6 @@
 
 package android.view;
 
-import android.annotation.Hide;
 import android.annotation.NonNull;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.inputmethod.EditorInfo;
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index 2ad557e..0282eca 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -156,16 +156,17 @@
      *                     calculate {@link WindowInsetsAnimation#getInterpolatedFraction()}.
      * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
      *                 windows are ready to be controlled, among other callbacks.
-     * @return A cancellation signal that the caller can use to cancel the request to obtain
-     *         control, or once they have control, to cancel the control.
+     * @param cancellationSignal A cancellation signal that the caller can use to cancel the
+     *                           request to obtain control, or once they have control, to cancel the
+     *                           control.
      * @see WindowInsetsAnimation#getFraction()
      * @see WindowInsetsAnimation#getInterpolatedFraction()
      * @see WindowInsetsAnimation#getInterpolator()
      * @see WindowInsetsAnimation#getDurationMillis()
      */
-    @NonNull
-    CancellationSignal controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
+    void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
             @Nullable Interpolator interpolator,
+            @Nullable CancellationSignal cancellationSignal,
             @NonNull WindowInsetsAnimationControlListener listener);
 
     /**
diff --git a/core/java/android/view/inline/InlinePresentationSpec.java b/core/java/android/view/inline/InlinePresentationSpec.java
index 3cc04b8..3788e2b 100644
--- a/core/java/android/view/inline/InlinePresentationSpec.java
+++ b/core/java/android/view/inline/InlinePresentationSpec.java
@@ -58,7 +58,7 @@
 
 
 
-    // Code below generated by codegen v1.0.14.
+    // Code below generated by codegen v1.0.15.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -104,8 +104,8 @@
     }
 
     /**
-     * The extras encoding the UI style information. Defaults to null in which case the default
-     * system UI style will be used.
+     * The extras encoding the UI style information. Defaults to {@code null} in which case the
+     * default system UI style will be used.
      */
     @DataClass.Generated.Member
     public @Nullable Bundle getStyle() {
@@ -244,11 +244,11 @@
         }
 
         /**
-         * The extras encoding the UI style information. Defaults to null in which case the default
-         * system UI style will be used.
+         * The extras encoding the UI style information. Defaults to {@code null} in which case the
+         * default system UI style will be used.
          */
         @DataClass.Generated.Member
-        public @NonNull Builder setStyle(@Nullable Bundle value) {
+        public @NonNull Builder setStyle(@NonNull Bundle value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x4;
             mStyle = value;
@@ -279,8 +279,8 @@
     }
 
     @DataClass.Generated(
-            time = 1582078731418L,
-            codegenVersion = "1.0.14",
+            time = 1584067238741L,
+            codegenVersion = "1.0.15",
             sourceFile = "frameworks/base/core/java/android/view/inline/InlinePresentationSpec.java",
             inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.Nullable android.os.Bundle mStyle\nprivate static  android.os.Bundle defaultStyle()\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 8e8c7df..2945a86 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -72,7 +72,6 @@
     /**
      * The extras state propagated from the IME to pass extra data.
      */
-    @DataClass.MaySetToNull
     private @Nullable Bundle mExtras;
 
     /**
@@ -81,7 +80,6 @@
      *
      * @hide
      */
-    @DataClass.MaySetToNull
     private @Nullable IBinder mHostInputToken;
 
     /**
@@ -498,7 +496,7 @@
          * The extras state propagated from the IME to pass extra data.
          */
         @DataClass.Generated.Member
-        public @NonNull Builder setExtras(@Nullable Bundle value) {
+        public @NonNull Builder setExtras(@NonNull Bundle value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x10;
             mExtras = value;
@@ -513,7 +511,7 @@
          */
         @DataClass.Generated.Member
         @Override
-        @NonNull Builder setHostInputToken(@Nullable IBinder value) {
+        @NonNull Builder setHostInputToken(@NonNull IBinder value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x20;
             mHostInputToken = value;
@@ -578,10 +576,10 @@
     }
 
     @DataClass.Generated(
-            time = 1583975428858L,
+            time = 1584067152935L,
             codegenVersion = "1.0.15",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
-            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @com.android.internal.util.DataClass.MaySetToNull @android.annotation.Nullable android.os.Bundle mExtras\nprivate @com.android.internal.util.DataClass.MaySetToNull @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\npublic  void setHostInputToken(android.os.IBinder)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.Nullable android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.Nullable android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\npublic  void setHostInputToken(android.os.IBinder)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.Nullable android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 816612f..16d9ed3 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -4589,7 +4589,7 @@
         private float mTouchOffsetY;
         // Where the touch position should be on the handle to ensure a maximum cursor visibility.
         // This is the distance in pixels from the top of the handle view.
-        private float mIdealVerticalOffset;
+        private final float mIdealVerticalOffset;
         // Parent's (TextView) previous position in window
         private int mLastParentX, mLastParentY;
         // Parent's (TextView) previous position on screen
@@ -4638,8 +4638,18 @@
 
             final int handleHeight = getPreferredHeight();
             mTouchOffsetY = -0.3f * handleHeight;
-            mIdealVerticalOffset = 0.7f * handleHeight;
-            mIdealFingerToCursorOffset = (int)(mIdealVerticalOffset - mTouchOffsetY);
+            final int distance = AppGlobals.getIntCoreSetting(
+                    WidgetFlags.KEY_FINGER_TO_CURSOR_DISTANCE,
+                    WidgetFlags.FINGER_TO_CURSOR_DISTANCE_DEFAULT);
+            if (distance < 0 || distance > 100) {
+                mIdealVerticalOffset = 0.7f * handleHeight;
+                mIdealFingerToCursorOffset = (int)(mIdealVerticalOffset - mTouchOffsetY);
+            } else {
+                mIdealFingerToCursorOffset = (int) TypedValue.applyDimension(
+                        TypedValue.COMPLEX_UNIT_DIP, distance,
+                        mTextView.getContext().getResources().getDisplayMetrics());
+                mIdealVerticalOffset = mIdealFingerToCursorOffset + mTouchOffsetY;
+            }
         }
 
         public float getIdealVerticalOffset() {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 01a0e9b..7f6c0d2 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -2818,7 +2818,7 @@
 
     /**
      * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
-     * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
+     * costly to set PendingIntents on the individual items, and is hence not recommended. Instead
      * this method should be used to set a single PendingIntent template on the collection, and
      * individual items can differentiate their on-click behavior using
      * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
@@ -2834,7 +2834,7 @@
 
     /**
      * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
-     * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
+     * costly to set PendingIntents on the individual items, and is hence not recommended. Instead
      * a single PendingIntent template can be set on the collection, see {@link
      * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click
      * action of a given item can be distinguished by setting a fillInIntent on that item. The
@@ -3960,7 +3960,7 @@
 
         /**
          * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is
-         * very costly to set PendingIntents on the individual items, and is hence not permitted.
+         * very costly to set PendingIntents on the individual items, and is hence not recommended.
          * Instead a single PendingIntent template can be set on the collection, see {@link
          * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click
          * action of a given item can be distinguished by setting a fillInIntent on that item. The
diff --git a/core/java/android/widget/WidgetFlags.java b/core/java/android/widget/WidgetFlags.java
index bce5497..09ab5aa 100644
--- a/core/java/android/widget/WidgetFlags.java
+++ b/core/java/android/widget/WidgetFlags.java
@@ -41,6 +41,25 @@
     public static final boolean ENABLE_CURSOR_DRAG_FROM_ANYWHERE_DEFAULT = true;
 
     /**
+     * The flag of finger-to-cursor distance in DP for cursor dragging.
+     * The value unit is DP and the range is {0..100}. If the value is out of range, the legacy
+     * value, which is based on handle size, will be used.
+     */
+    public static final String FINGER_TO_CURSOR_DISTANCE =
+            "CursorControlFeature__finger_to_cursor_distance";
+
+    /**
+     * The key used in app core settings for the flag {@link #FINGER_TO_CURSOR_DISTANCE}.
+     */
+    public static final String KEY_FINGER_TO_CURSOR_DISTANCE =
+            "widget__finger_to_cursor_distance";
+
+    /**
+     * Default value for the flag {@link #FINGER_TO_CURSOR_DISTANCE}.
+     */
+    public static final int FINGER_TO_CURSOR_DISTANCE_DEFAULT = -1;
+
+    /**
      * Whether additional gestures should be enabled for the insertion cursor handle (e.g.
      * long-press or double-tap on the handle to trigger selection).
      */
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index fa567f2..ec2653f 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.AsyncTask;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.stats.devicepolicy.DevicePolicyEnums;
@@ -90,7 +91,9 @@
 
             @Override
             public void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle) {
-                userManager.requestQuietModeEnabled(enabled, workProfileUserHandle);
+                AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
+                    userManager.requestQuietModeEnabled(enabled, workProfileUserHandle);
+                });
                 mIsWaitingToEnableWorkProfile = true;
             }
         };
@@ -284,7 +287,7 @@
     }
 
     private int userHandleToPageIndex(UserHandle userHandle) {
-        if (userHandle == getPersonalListAdapter().mResolverListController.getUserHandle()) {
+        if (userHandle.equals(getPersonalListAdapter().mResolverListController.getUserHandle())) {
             return PROFILE_PERSONAL;
         } else {
             return PROFILE_WORK;
@@ -293,7 +296,7 @@
 
     private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) {
         UserHandle listUserHandle = activeListAdapter.getUserHandle();
-        if (listUserHandle == mWorkProfileUserHandle
+        if (listUserHandle.equals(mWorkProfileUserHandle)
                 && mInjector.isQuietModeEnabled(mWorkProfileUserHandle)) {
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.RESOLVER_EMPTY_STATE_WORK_APPS_DISABLED)
@@ -311,7 +314,7 @@
         if (UserHandle.myUserId() != listUserHandle.getIdentifier()) {
             if (!mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(),
                     UserHandle.myUserId(), listUserHandle.getIdentifier())) {
-                if (listUserHandle == mPersonalProfileUserHandle) {
+                if (listUserHandle.equals(mPersonalProfileUserHandle)) {
                     DevicePolicyEventLogger.createEvent(
                                 DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL)
                             .setStrings(getMetricsCategory())
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 188041e..3696c83 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2140,7 +2140,7 @@
             return null;
         }
 
-        if (getPersonalProfileUserHandle() == userHandle) {
+        if (getPersonalProfileUserHandle().equals(userHandle)) {
             if (mPersonalAppPredictor != null) {
                 return mPersonalAppPredictor;
             }
@@ -2166,7 +2166,7 @@
                         .getSystemService(AppPredictionManager.class);
         AppPredictor appPredictionSession = appPredictionManager.createAppPredictionSession(
                 appPredictionContext);
-        if (getPersonalProfileUserHandle() == userHandle) {
+        if (getPersonalProfileUserHandle().equals(userHandle)) {
             mPersonalAppPredictor = appPredictionSession;
         } else {
             mWorkAppPredictor = appPredictionSession;
@@ -2566,7 +2566,7 @@
 
         ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
         if (chooserListAdapter.getUserHandle()
-                == mChooserMultiProfilePagerAdapter.getCurrentUserHandle()) {
+                .equals(mChooserMultiProfilePagerAdapter.getCurrentUserHandle())) {
             mChooserMultiProfilePagerAdapter.getActiveAdapterView()
                     .setAdapter(mChooserMultiProfilePagerAdapter.getCurrentRootAdapter());
             mChooserMultiProfilePagerAdapter
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index 2167b1e..c6a00f3 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -93,10 +93,10 @@
     @Override
     @Nullable
     ChooserListAdapter getListAdapterForUserHandle(UserHandle userHandle) {
-        if (getActiveListAdapter().getUserHandle() == userHandle) {
+        if (getActiveListAdapter().getUserHandle().equals(userHandle)) {
             return getActiveListAdapter();
         } else if (getInactiveListAdapter() != null
-                && getInactiveListAdapter().getUserHandle() == userHandle) {
+                && getInactiveListAdapter().getUserHandle().equals(userHandle)) {
             return getInactiveListAdapter();
         }
         return null;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index ec371d9..086a718 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1242,12 +1242,12 @@
     }
 
     private void maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle) {
-        if (!hasWorkProfile() || currentUserHandle == getUser()) {
+        if (!hasWorkProfile() || currentUserHandle.equals(getUser())) {
             return;
         }
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED)
-                .setBoolean(currentUserHandle == getPersonalProfileUserHandle())
+                .setBoolean(currentUserHandle.equals(getPersonalProfileUserHandle()))
                 .setStrings(getMetricsCategory(),
                         cti instanceof ChooserTargetInfo ? "direct_share" : "other_target")
                 .write();
@@ -1486,7 +1486,8 @@
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET)
-                .setBoolean(activeListAdapter.getUserHandle() == getPersonalProfileUserHandle())
+                .setBoolean(activeListAdapter.getUserHandle()
+                        .equals(getPersonalProfileUserHandle()))
                 .setStrings(getMetricsCategory())
                 .write();
         safelyStartActivity(activeProfileTarget);
@@ -1778,7 +1779,7 @@
     @Override // ResolverListCommunicator
     public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
         if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) {
-            if (listAdapter.getUserHandle() == getWorkProfileUserHandle()
+            if (listAdapter.getUserHandle().equals(getWorkProfileUserHandle())
                     && mMultiProfilePagerAdapter.isWaitingToEnableWorkProfile()) {
                 // We have just turned on the work profile and entered the pass code to start it,
                 // now we are waiting to receive the ACTION_USER_UNLOCKED broadcast. There is no
@@ -1819,7 +1820,7 @@
                     mMultiProfilePagerAdapter.markWorkProfileEnabledBroadcastReceived();
                 }
                 if (mMultiProfilePagerAdapter.getCurrentUserHandle()
-                        == getWorkProfileUserHandle()) {
+                        .equals(getWorkProfileUserHandle())) {
                     mMultiProfilePagerAdapter.rebuildActiveTab(true);
                 } else {
                     mMultiProfilePagerAdapter.clearInactiveProfileCache();
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index 0440f5e..578f6ae 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -103,10 +103,10 @@
     @Override
     @Nullable
     ResolverListAdapter getListAdapterForUserHandle(UserHandle userHandle) {
-        if (getActiveListAdapter().getUserHandle() == userHandle) {
+        if (getActiveListAdapter().getUserHandle().equals(userHandle)) {
             return getActiveListAdapter();
         } else if (getInactiveListAdapter() != null
-                && getInactiveListAdapter().getUserHandle() == userHandle) {
+                && getInactiveListAdapter().getUserHandle().equals(userHandle)) {
             return getInactiveListAdapter();
         }
         return null;
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 633aa2c..ff03f1a 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -205,6 +205,9 @@
     /** List of packages with the same uid, and its app data info: volume uuid and inode. */
     public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map";
 
+    /** Bind mount app storage dirs to lower fs not via fuse */
+    public static final String BIND_MOUNT_APP_STORAGE_DIRS = "--bind-mount-storage-dirs";
+
     /**
      * An extraArg passed when a zygote process is forking a child-zygote, specifying a name
      * in the abstract socket namespace. This socket name is what the new child zygote
@@ -310,6 +313,7 @@
      * @param isTopApp true if the process is for top (high priority) application.
      * @param pkgDataInfoList A list that stores related packages and its app data
      * info: volume uuid and inode.
+     * @param bindMountAppStorageDirs  True if the zygote needs to mount storage dirs.
      *
      * @return 0 if this is the child, pid of the child
      * if this is the parent, or -1 on error.
@@ -317,13 +321,13 @@
     static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
             int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
-            boolean isTopApp, String[] pkgDataInfoList) {
+            boolean isTopApp, String[] pkgDataInfoList, boolean bindMountAppStorageDirs) {
         ZygoteHooks.preFork();
 
         int pid = nativeForkAndSpecialize(
                 uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
                 fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
-                pkgDataInfoList);
+                pkgDataInfoList, bindMountAppStorageDirs);
         if (pid == 0) {
             // Note that this event ends at the end of handleChildProc,
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
@@ -339,7 +343,8 @@
     private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
             int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
             int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
-            String appDataDir, boolean isTopApp, String[] pkgDataInfoList);
+            String appDataDir, boolean isTopApp, String[] pkgDataInfoList,
+            boolean bindMountAppStorageDirs);
 
     /**
      * Specialize an unspecialized app process.  The current VM must have been started
@@ -366,14 +371,15 @@
      * volume uuid and CE dir inode. For example, pkgDataInfoList = [app_a_pkg_name,
      * app_a_data_volume_uuid, app_a_ce_inode, app_b_pkg_name, app_b_data_volume_uuid,
      * app_b_ce_inode, ...];
+     * @param bindMountAppStorageDirs  True if the zygote needs to mount storage dirs.
      */
     private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, int mountExternal, String seInfo, String niceName,
             boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
-            String[] pkgDataInfoList) {
+            String[] pkgDataInfoList, boolean bindMountAppStorageDirs) {
         nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
                 niceName, startChildZygote, instructionSet, appDataDir, isTopApp,
-                pkgDataInfoList);
+                pkgDataInfoList, bindMountAppStorageDirs);
 
         // Note that this event ends at the end of handleChildProc.
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
@@ -393,7 +399,7 @@
     private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids,
             int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
             boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
-            String[] pkgDataInfoList);
+            String[] pkgDataInfoList, boolean bindMountAppStorageDirs);
 
     /**
      * Called to do any initialization before starting an application.
@@ -718,7 +724,7 @@
                                  args.mRuntimeFlags, rlimits, args.mMountExternal,
                                  args.mSeInfo, args.mNiceName, args.mStartChildZygote,
                                  args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
-                                 args.mPkgDataInfoList);
+                                 args.mPkgDataInfoList, args.mBindMountAppStorageDirs);
 
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index 37f570b..1a63765 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -227,6 +227,11 @@
     String[] mPkgDataInfoList;
 
     /**
+     * @see Zygote#BIND_MOUNT_APP_STORAGE_DIRS
+     */
+    boolean mBindMountAppStorageDirs;
+
+    /**
      * Constructs instance and parses args
      *
      * @param args zygote command-line args
@@ -447,6 +452,8 @@
                 }
             } else if (arg.startsWith(Zygote.PKG_DATA_INFO_MAP)) {
                 mPkgDataInfoList = getAssignmentList(arg);
+            } else if (arg.equals(Zygote.BIND_MOUNT_APP_STORAGE_DIRS)) {
+                mBindMountAppStorageDirs = true;
             } else {
                 break;
             }
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 4949811..bc8dfd4 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -258,7 +258,7 @@
                 parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
                 parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
                 parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mIsTopApp,
-                parsedArgs.mPkgDataInfoList);
+                parsedArgs.mPkgDataInfoList, parsedArgs.mBindMountAppStorageDirs);
 
         try {
             if (pid == 0) {
diff --git a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
new file mode 100644
index 0000000..ebfea450
--- /dev/null
+++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
@@ -0,0 +1,78 @@
+/*
+ * 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.policy;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+/**
+ * @hide
+ */
+public class GestureNavigationSettingsObserver extends ContentObserver {
+    private Context mContext;
+    private Runnable mOnChangeRunnable;
+
+    public GestureNavigationSettingsObserver(Handler handler, Context context,
+            Runnable onChangeRunnable) {
+        super(handler);
+        mContext = context;
+        mOnChangeRunnable = onChangeRunnable;
+    }
+
+    public void register() {
+        ContentResolver r = mContext.getContentResolver();
+        r.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT),
+                false, this, UserHandle.USER_ALL);
+        r.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT),
+                false, this, UserHandle.USER_ALL);
+    }
+
+    public void unregister() {
+        mContext.getContentResolver().unregisterContentObserver(this);
+    }
+
+    @Override
+    public void onChange(boolean selfChange) {
+        super.onChange(selfChange);
+        if (mOnChangeRunnable != null) {
+            mOnChangeRunnable.run();
+        }
+    }
+
+    public int getLeftSensitivity(Resources userRes) {
+        return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT);
+    }
+
+    public int getRightSensitivity(Resources userRes) {
+        return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT);
+    }
+
+    private int getSensitivity(Resources userRes, String side) {
+        final int inset = userRes.getDimensionPixelSize(
+                com.android.internal.R.dimen.config_backGestureInset);
+        final float scale = Settings.Secure.getFloatForUser(
+                mContext.getContentResolver(), side, 1.0f, UserHandle.USER_CURRENT);
+        return (int) (inset * scale);
+    }
+}
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
index 8446bbd..e4a4408 100755
--- a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
+++ b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
@@ -200,8 +200,9 @@
         try {
             return doInvoke();
         } finally {
-            if (isRecycleOnUse()) doRecycle();
-            if (!isRecycled()) {
+            if (isRecycleOnUse()) {
+                doRecycle();
+            } else if (!isRecycled()) {
                 int argsSize = ArrayUtils.size(mArgs);
                 for (int i = 0; i < argsSize; i++) {
                     popArg(i);
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 07be113..73da600 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -122,6 +122,11 @@
     private CharSequence mFallbackChatName;
     private CharSequence mFallbackGroupChatName;
     private CharSequence mConversationTitle;
+    private int mNotificationHeaderExpandedPadding;
+    private View mConversationHeader;
+    private View mContentContainer;
+    private boolean mExpandable = true;
+    private int mContentMarginEnd;
 
     public ConversationLayout(@NonNull Context context) {
         super(context);
@@ -166,12 +171,18 @@
         });
         mConversationText = findViewById(R.id.conversation_text);
         mExpandButtonContainer = findViewById(R.id.expand_button_container);
+        mConversationHeader = findViewById(R.id.conversation_header);
+        mContentContainer = findViewById(R.id.notification_action_list_margin_target);
         mExpandButtonAndContentContainer = findViewById(R.id.expand_button_and_content_container);
         mExpandButton = findViewById(R.id.expand_button);
         mExpandButtonExpandedTopMargin = getResources().getDimensionPixelSize(
                 R.dimen.conversation_expand_button_top_margin_expanded);
         mExpandButtonExpandedSize = getResources().getDimensionPixelSize(
                 R.dimen.conversation_expand_button_expanded_size);
+        mNotificationHeaderExpandedPadding = getResources().getDimensionPixelSize(
+                R.dimen.conversation_header_expanded_padding_end);
+        mContentMarginEnd = getResources().getDimensionPixelSize(
+                R.dimen.notification_content_margin_end);
         mBadgedSideMargins = getResources().getDimensionPixelSize(
                 R.dimen.conversation_badge_side_margin);
         mIconSizeBadged = getResources().getDimensionPixelSize(
@@ -207,6 +218,7 @@
         mIsCollapsed = isCollapsed;
         mMessagingLinearLayout.setMaxDisplayedLines(isCollapsed ? 1 : Integer.MAX_VALUE);
         updateExpandButton();
+        updateContentPaddings();
     }
 
     @RemotableViewMethod
@@ -652,6 +664,7 @@
                 mAddedGroups.add(newGroup);
             }
             newGroup.setDisplayImagesAtEnd(mIsCollapsed);
+            newGroup.setIsInConversation(true);
             newGroup.setLayoutColor(mLayoutColor);
             newGroup.setTextColors(mSenderTextColor, mMessageTextColor);
             Person sender = senders.get(groupIndex);
@@ -851,10 +864,39 @@
         mExpandButton.setLayoutParams(layoutParams);
 
         mExpandButtonContainer.setContentDescription(mContext.getText(contentDescriptionId));
+    }
 
+    private void updateContentPaddings() {
+
+        // 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;
+        int contentPaddingEnd;
+        if (!mExpandable) {
+            headerPaddingEnd = 0;
+            contentPaddingEnd = mContentMarginEnd;
+        } else if (mIsCollapsed) {
+            headerPaddingEnd = 0;
+            contentPaddingEnd = 0;
+        } else {
+            headerPaddingEnd = mNotificationHeaderExpandedPadding;
+            contentPaddingEnd = mContentMarginEnd;
+        }
+        mConversationHeader.setPaddingRelative(
+                mConversationHeader.getPaddingStart(),
+                mConversationHeader.getPaddingTop(),
+                headerPaddingEnd,
+                mConversationHeader.getPaddingBottom());
+
+        mContentContainer.setPaddingRelative(
+                mContentContainer.getPaddingStart(),
+                mContentContainer.getPaddingTop(),
+                contentPaddingEnd,
+                mContentContainer.getPaddingBottom());
     }
 
     public void updateExpandability(boolean expandable, @Nullable OnClickListener onClickListener) {
+        mExpandable = expandable;
         if (expandable) {
             mExpandButtonContainer.setVisibility(VISIBLE);
             mExpandButtonContainer.setOnClickListener(onClickListener);
@@ -862,5 +904,6 @@
             // TODO: handle content paddings to end of layout
             mExpandButtonContainer.setVisibility(GONE);
         }
+        updateContentPaddings();
     }
 }
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 9977903..c68da97 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -85,6 +85,10 @@
     private int mSenderTextPaddingSingleLine;
     private boolean mIsFirstGroupInLayout = true;
     private boolean mCanHideSenderIfFirst;
+    private boolean mIsInConversation = true;
+    private ViewGroup mMessagingIconContainer;
+    private int mConversationContentStart;
+    private int mNonConversationMarginEnd;
 
     public MessagingGroup(@NonNull Context context) {
         super(context);
@@ -112,6 +116,7 @@
         mAvatarView = findViewById(R.id.message_icon);
         mImageContainer = findViewById(R.id.messaging_group_icon_container);
         mSendingSpinner = findViewById(R.id.messaging_group_sending_progress);
+        mMessagingIconContainer = findViewById(R.id.message_icon_container);
         mContentContainer = findViewById(R.id.messaging_group_content_container);
         mSendingSpinnerContainer = findViewById(R.id.messaging_group_sending_progress_container);
         DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
@@ -119,6 +124,10 @@
         mDisplaySize.y = displayMetrics.heightPixels;
         mSenderTextPaddingSingleLine = getResources().getDimensionPixelSize(
                 R.dimen.messaging_group_singleline_sender_padding_end);
+        mConversationContentStart = getResources().getDimensionPixelSize(
+                R.dimen.conversation_content_start);
+        mNonConversationMarginEnd = getResources().getDimensionPixelSize(
+                R.dimen.messaging_layout_margin_end);
     }
 
     public void updateClipRect() {
@@ -644,4 +653,21 @@
     public boolean isSingleLine() {
         return mSingleLine;
     }
+
+    /**
+     * Set this group to be displayed in a conversation and adjust the visual appearance
+     *
+     * @param isInConversation is this in a conversation
+     */
+    public void setIsInConversation(boolean isInConversation) {
+        if (mIsInConversation != isInConversation) {
+            mIsInConversation = isInConversation;
+            MarginLayoutParams layoutParams =
+                    (MarginLayoutParams) mMessagingIconContainer.getLayoutParams();
+            layoutParams.width = mIsInConversation ? mConversationContentStart
+                    : ViewPager.LayoutParams.WRAP_CONTENT;
+            layoutParams.setMarginEnd(mIsInConversation ? 0 : mNonConversationMarginEnd);
+            mMessagingIconContainer.setLayoutParams(layoutParams);
+        }
+    }
 }
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 503f3f1..3fb5d43 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -448,6 +448,7 @@
                 mAddedGroups.add(newGroup);
             }
             newGroup.setDisplayImagesAtEnd(mDisplayImagesAtEnd);
+            newGroup.setIsInConversation(false);
             newGroup.setLayoutColor(mLayoutColor);
             newGroup.setTextColors(mSenderTextColor, mMessageTextColor);
             Person sender = senders.get(groupIndex);
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index dafb377..ea3c0fa 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1567,23 +1567,6 @@
   auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
   const userid_t user_id = multiuser_get_user_id(uid);
 
-  // If FUSE is not ready for this user, skip it
-  // TODO(148772775): Pass primary volume name from zygote argument to here
-  std::string tmp = GetProperty("vold.fuse_running_users", "");
-  std::istringstream fuse_running_users(tmp);
-  bool user_found = false;
-  std::string s;
-  std::string user_id_str = std::to_string(user_id);
-  while (!user_found && std::getline(fuse_running_users, s, ',')) {
-    if (user_id_str == s) {
-      user_found = true;
-    }
-  }
-  if (!user_found) {
-    ALOGI("User %d is not running fuse yet, fuse_running_users=%s", user_id, tmp.c_str());
-    return;
-  }
-
   // Fuse is ready, so we can start using fuse path.
   int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0;
 
@@ -1611,7 +1594,7 @@
                              jstring managed_nice_name, bool is_system_server,
                              bool is_child_zygote, jstring managed_instruction_set,
                              jstring managed_app_data_dir, bool is_top_app,
-                             jobjectArray pkg_data_info_list) {
+                             jobjectArray pkg_data_info_list, bool mount_storage_dirs) {
   const char* process_name = is_system_server ? "system_server" : "zygote";
   auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
   auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
@@ -1650,10 +1633,7 @@
     isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
     isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
   }
-
-  if ((mount_external != MOUNT_EXTERNAL_INSTALLER) &&
-        GetBoolProperty(kPropFuse, false) &&
-        GetBoolProperty(ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) {
+  if ((mount_external != MOUNT_EXTERNAL_INSTALLER) && mount_storage_dirs) {
     BindMountStorageDirs(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
   }
 
@@ -2023,7 +2003,7 @@
         jint mount_external, jstring se_info, jstring nice_name,
         jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
         jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
-        jobjectArray pkg_data_info_list) {
+        jobjectArray pkg_data_info_list, jboolean mount_storage_dirs) {
     jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
 
     if (UNLIKELY(managed_fds_to_close == nullptr)) {
@@ -2060,7 +2040,8 @@
                        capabilities, capabilities,
                        mount_external, se_info, nice_name, false,
                        is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
-                       is_top_app == JNI_TRUE, pkg_data_info_list);
+                       is_top_app == JNI_TRUE, pkg_data_info_list,
+                       mount_storage_dirs == JNI_TRUE);
     }
     return pid;
 }
@@ -2095,7 +2076,7 @@
                        permitted_capabilities, effective_capabilities,
                        MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
                        false, nullptr, nullptr, /* is_top_app= */ false,
-                       /* pkg_data_info_list */ nullptr);
+                       /* pkg_data_info_list */ nullptr, false);
   } else if (pid > 0) {
       // The zygote process checks whether the child process has died or not.
       ALOGI("System server process %d has been created", pid);
@@ -2225,14 +2206,15 @@
     jint runtime_flags, jobjectArray rlimits,
     jint mount_external, jstring se_info, jstring nice_name,
     jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
-    jobjectArray pkg_data_info_list) {
+    jobjectArray pkg_data_info_list, jboolean mount_storage_dirs) {
   jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
 
   SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
                    capabilities, capabilities,
                    mount_external, se_info, nice_name, false,
                    is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
-                   is_top_app == JNI_TRUE, pkg_data_info_list);
+                   is_top_app == JNI_TRUE, pkg_data_info_list,
+                   mount_storage_dirs == JNI_TRUE);
 }
 
 /**
@@ -2426,7 +2408,7 @@
 static const JNINativeMethod gMethods[] = {
         {"nativeForkAndSpecialize",
          "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/"
-         "String;Z[Ljava/lang/String;)I",
+         "String;Z[Ljava/lang/String;Z)I",
          (void*)com_android_internal_os_Zygote_nativeForkAndSpecialize},
         {"nativeForkSystemServer", "(II[II[[IJJ)I",
          (void*)com_android_internal_os_Zygote_nativeForkSystemServer},
@@ -2439,7 +2421,7 @@
         {"nativeForkUsap", "(II[IZ)I", (void*)com_android_internal_os_Zygote_nativeForkUsap},
         {"nativeSpecializeAppProcess",
          "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/"
-         "String;Z[Ljava/lang/String;)V",
+         "String;Z[Ljava/lang/String;Z)V",
          (void*)com_android_internal_os_Zygote_nativeSpecializeAppProcess},
         {"nativeInitNativeState", "(Z)V",
          (void*)com_android_internal_os_Zygote_nativeInitNativeState},
diff --git a/core/proto/android/app/enums.proto b/core/proto/android/app/enums.proto
index 9abe923..42437d5 100644
--- a/core/proto/android/app/enums.proto
+++ b/core/proto/android/app/enums.proto
@@ -104,3 +104,108 @@
     PROCESS_STATE_NONEXISTENT = 1019;
 }
 
+// AppOpsManager.java - operation ids for logging
+enum AppOpEnum {
+    APP_OP_NONE = -1;
+    APP_OP_COARSE_LOCATION = 0;
+    APP_OP_FINE_LOCATION = 1;
+    APP_OP_GPS = 2;
+    APP_OP_VIBRATE = 3;
+    APP_OP_READ_CONTACTS = 4;
+    APP_OP_WRITE_CONTACTS = 5;
+    APP_OP_READ_CALL_LOG = 6;
+    APP_OP_WRITE_CALL_LOG = 7;
+    APP_OP_READ_CALENDAR = 8;
+    APP_OP_WRITE_CALENDAR = 9;
+    APP_OP_WIFI_SCAN = 10;
+    APP_OP_POST_NOTIFICATION = 11;
+    APP_OP_NEIGHBORING_CELLS = 12;
+    APP_OP_CALL_PHONE = 13;
+    APP_OP_READ_SMS = 14;
+    APP_OP_WRITE_SMS = 15;
+    APP_OP_RECEIVE_SMS = 16;
+    APP_OP_RECEIVE_EMERGENCY_SMS = 17;
+    APP_OP_RECEIVE_MMS = 18;
+    APP_OP_RECEIVE_WAP_PUSH = 19;
+    APP_OP_SEND_SMS = 20;
+    APP_OP_READ_ICC_SMS = 21;
+    APP_OP_WRITE_ICC_SMS = 22;
+    APP_OP_WRITE_SETTINGS = 23;
+    APP_OP_SYSTEM_ALERT_WINDOW = 24;
+    APP_OP_ACCESS_NOTIFICATIONS = 25;
+    APP_OP_CAMERA = 26;
+    APP_OP_RECORD_AUDIO = 27;
+    APP_OP_PLAY_AUDIO = 28;
+    APP_OP_READ_CLIPBOARD = 29;
+    APP_OP_WRITE_CLIPBOARD = 30;
+    APP_OP_TAKE_MEDIA_BUTTONS = 31;
+    APP_OP_TAKE_AUDIO_FOCUS = 32;
+    APP_OP_AUDIO_MASTER_VOLUME = 33;
+    APP_OP_AUDIO_VOICE_VOLUME = 34;
+    APP_OP_AUDIO_RING_VOLUME = 35;
+    APP_OP_AUDIO_MEDIA_VOLUME = 36;
+    APP_OP_AUDIO_ALARM_VOLUME = 37;
+    APP_OP_AUDIO_NOTIFICATION_VOLUME = 38;
+    APP_OP_AUDIO_BLUETOOTH_VOLUME = 39;
+    APP_OP_WAKE_LOCK = 40;
+    APP_OP_MONITOR_LOCATION = 41;
+    APP_OP_MONITOR_HIGH_POWER_LOCATION = 42;
+    APP_OP_GET_USAGE_STATS = 43;
+    APP_OP_MUTE_MICROPHONE = 44;
+    APP_OP_TOAST_WINDOW = 45;
+    APP_OP_PROJECT_MEDIA = 46;
+    APP_OP_ACTIVATE_VPN = 47;
+    APP_OP_WRITE_WALLPAPER = 48;
+    APP_OP_ASSIST_STRUCTURE = 49;
+    APP_OP_ASSIST_SCREENSHOT = 50;
+    APP_OP_READ_PHONE_STATE = 51;
+    APP_OP_ADD_VOICEMAIL = 52;
+    APP_OP_USE_SIP = 53;
+    APP_OP_PROCESS_OUTGOING_CALLS = 54;
+    APP_OP_USE_FINGERPRINT = 55;
+    APP_OP_BODY_SENSORS = 56;
+    APP_OP_READ_CELL_BROADCASTS = 57;
+    APP_OP_MOCK_LOCATION = 58;
+    APP_OP_READ_EXTERNAL_STORAGE = 59;
+    APP_OP_WRITE_EXTERNAL_STORAGE = 60;
+    APP_OP_TURN_SCREEN_ON = 61;
+    APP_OP_GET_ACCOUNTS = 62;
+    APP_OP_RUN_IN_BACKGROUND = 63;
+    APP_OP_AUDIO_ACCESSIBILITY_VOLUME = 64;
+    APP_OP_READ_PHONE_NUMBERS = 65;
+    APP_OP_REQUEST_INSTALL_PACKAGES = 66;
+    APP_OP_PICTURE_IN_PICTURE = 67;
+    APP_OP_INSTANT_APP_START_FOREGROUND = 68;
+    APP_OP_ANSWER_PHONE_CALLS = 69;
+    APP_OP_RUN_ANY_IN_BACKGROUND = 70;
+    APP_OP_CHANGE_WIFI_STATE = 71;
+    APP_OP_REQUEST_DELETE_PACKAGES = 72;
+    APP_OP_BIND_ACCESSIBILITY_SERVICE = 73;
+    APP_OP_ACCEPT_HANDOVER = 74;
+    APP_OP_MANAGE_IPSEC_TUNNELS = 75;
+    APP_OP_START_FOREGROUND = 76;
+    APP_OP_BLUETOOTH_SCAN = 77;
+    APP_OP_USE_BIOMETRIC = 78;
+    APP_OP_ACTIVITY_RECOGNITION = 79;
+    APP_OP_SMS_FINANCIAL_TRANSACTIONS = 80;
+    APP_OP_READ_MEDIA_AUDIO = 81;
+    APP_OP_WRITE_MEDIA_AUDIO = 82;
+    APP_OP_READ_MEDIA_VIDEO = 83;
+    APP_OP_WRITE_MEDIA_VIDEO = 84;
+    APP_OP_READ_MEDIA_IMAGES = 85;
+    APP_OP_WRITE_MEDIA_IMAGES = 86;
+    APP_OP_LEGACY_STORAGE = 87;
+    APP_OP_ACCESS_ACCESSIBILITY = 88;
+    APP_OP_READ_DEVICE_IDENTIFIERS = 89;
+    APP_OP_ACCESS_MEDIA_LOCATION = 90;
+    APP_OP_QUERY_ALL_PACKAGES = 91;
+    APP_OP_MANAGE_EXTERNAL_STORAGE = 92;
+    APP_OP_INTERACT_ACROSS_PROFILES = 93;
+    APP_OP_ACTIVATE_PLATFORM_VPN = 94;
+    APP_OP_LOADER_USAGE_STATS = 95;
+    APP_OP_ACCESS_CALL_AUDIO = 96;
+    APP_OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED = 97;
+    APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER = 98;
+}
+
+
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 3d8108d..684a292 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -153,7 +153,7 @@
   CROSS_PROFILE_APPS_START_ACTIVITY_AS_USER = 126;
   SET_AUTO_TIME = 127;
   SET_AUTO_TIME_ZONE = 128;
-  SET_PACKAGES_PROTECTED = 129;
+  SET_USER_CONTROL_DISABLED_PACKAGES = 129;
   SET_FACTORY_RESET_PROTECTION = 130;
   SET_COMMON_CRITERIA_MODE = 131;
   ALLOW_MODIFICATION_OF_ADMIN_CONFIGURED_NETWORKS = 132;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4001def..7a3ec95 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3756,6 +3756,12 @@
     <permission android:name="android.permission.WHITELIST_RESTRICTED_PERMISSIONS"
                 android:protectionLevel="signature|installer" />
 
+    <!-- @SystemApi Allows an application to an exempt an app from having its permission be
+        auto-revoked when unused for an extended period of time.
+        @hide -->
+    <permission android:name="android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS"
+        android:protectionLevel="signature|installer" />
+
     <!-- @hide Allows an application to observe permission changes. -->
     <permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS"
         android:protectionLevel="signature|privileged" />
diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml
index dc52e97..2348dee 100644
--- a/core/res/res/layout/notification_template_material_conversation.xml
+++ b/core/res/res/layout/notification_template_material_conversation.xml
@@ -93,9 +93,10 @@
             android:clipChildren="false"
             android:clipToPadding="false">
             <!--TODO: move this into a separate layout and share logic with the header to bring back app opps etc-->
-            <FrameLayout
+            <com.android.internal.widget.RemeasuringLinearLayout
                 android:id="@+id/notification_action_list_margin_target"
                 android:layout_width="0dp"
+                android:orientation="vertical"
                 android:layout_height="wrap_content"
                 android:layout_weight="1">
 
@@ -163,12 +164,12 @@
                     android:id="@+id/notification_messaging"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:layout_marginTop="40dp"
+                    android:layout_marginTop="2dp"
                     android:spacing="@dimen/notification_messaging_spacing"
                     android:clipToPadding="false"
                     android:clipChildren="false"
                     />
-            </FrameLayout>
+            </com.android.internal.widget.RemeasuringLinearLayout>
             <!-- Unread Count -->
             <!-- <TextView /> -->
 
diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml
index 15146c0..3188861 100644
--- a/core/res/res/layout/notification_template_messaging_group.xml
+++ b/core/res/res/layout/notification_template_messaging_group.xml
@@ -21,8 +21,9 @@
     android:layout_height="wrap_content"
     android:orientation="horizontal" >
     <FrameLayout
+        android:id="@+id/message_icon_container"
         android:layout_width="@dimen/conversation_content_start"
-        android:layout_height="wrap_content">         <!--TODO: make sure to make this padding dynamic-->
+        android:layout_height="wrap_content">
         <ImageView
             android:layout_gravity="top|center_horizontal"
             android:id="@+id/message_icon"
diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml
index 619b392..176f289 100644
--- a/core/res/res/layout/resolver_empty_states.xml
+++ b/core/res/res/layout/resolver_empty_states.xml
@@ -38,7 +38,7 @@
         android:layout_height="wrap_content"
         android:fontFamily="@string/config_headlineFontFamilyMedium"
         android:textColor="@color/resolver_empty_state_text"
-        android:textSize="18sp"
+        android:textSize="14sp"
         android:layout_centerHorizontal="true" />
     <TextView
         android:id="@+id/resolver_empty_state_subtitle"
@@ -47,7 +47,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textColor="@color/resolver_empty_state_text"
-        android:textSize="14sp"
+        android:textSize="12sp"
         android:gravity="center_horizontal"
         android:layout_centerHorizontal="true" />
     <Button
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6f468e0..a2aa492 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4421,4 +4421,8 @@
 
     <!-- The max scale for the wallpaper when it's zoomed in -->
     <item name="config_wallpaperMaxScale" format="float" type="dimen">1</item>
+
+    <!-- Package name that will receive an explicit manifest broadcast for
+         android.os.action.POWER_SAVE_MODE_CHANGED. -->
+    <string name="config_powerSaveModeChangedListenerPackage" translatable="false"></string>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index e3fe982..15ef09c 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -699,6 +699,12 @@
     <!-- margin on the top when the icon is centered for group conversations -->
     <dimen name="conversation_icon_margin_top_centered">5dp</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>
+
+    <!-- margin at the end of messaging group icons when not conversations -->
+    <dimen name="messaging_layout_margin_end">12dp</dimen>
+
     <!-- Padding between text and sender when singleline -->
     <dimen name="messaging_group_singleline_sender_padding_end">4dp</dimen>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 49a0f17..4c0dd8d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3615,6 +3615,7 @@
   <java-symbol type="bool" name="config_batterySaverStickyBehaviourDisabled" />
   <java-symbol type="integer" name="config_dynamicPowerSavingsDefaultDisableThreshold" />
   <java-symbol type="string" name="config_batterySaverScheduleProvider" />
+  <java-symbol type="string" name="config_powerSaveModeChangedListenerPackage" />
 
   <!-- For car devices -->
   <java-symbol type="string" name="car_loading_profile" />
@@ -3890,6 +3891,7 @@
   <java-symbol type="id" name="conversation_face_pile_top" />
   <java-symbol type="id" name="conversation_face_pile" />
   <java-symbol type="id" name="conversation_text" />
+  <java-symbol type="id" name="message_icon_container" />
   <java-symbol type="dimen" name="conversation_expand_button_top_margin_expanded" />
   <java-symbol type="dimen" name="conversation_expand_button_expanded_size" />
   <java-symbol type="dimen" name="messaging_group_singleline_sender_padding_end" />
@@ -3897,6 +3899,9 @@
   <java-symbol type="dimen" name="conversation_icon_size_badged" />
   <java-symbol type="dimen" name="conversation_icon_size_centered" />
   <java-symbol type="dimen" name="conversation_icon_margin_top_centered" />
+  <java-symbol type="dimen" name="conversation_content_start" />
+  <java-symbol type="dimen" name="messaging_layout_margin_end" />
+  <java-symbol type="dimen" name="conversation_header_expanded_padding_end" />
   <java-symbol type="layout" name="notification_template_material_conversation" />
   <java-symbol type="layout" name="conversation_face_pile_layout" />
 
diff --git a/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java b/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java
index 44f6407..37b2817 100644
--- a/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java
+++ b/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java
@@ -37,6 +37,8 @@
 import java.io.InputStream;
 import java.nio.file.Files;
 import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
@@ -129,6 +131,48 @@
         assertNull(result.getCertificate());
     }
 
+    @Test
+    public void testSourceStamp_multiApk_validStamps() throws Exception {
+        File testApk1 = getApk("SourceStampVerifierTest/valid-stamp.apk");
+        File testApk2 = getApk("SourceStampVerifierTest/valid-stamp.apk");
+        ZipFile apkZipFile = new ZipFile(testApk1);
+        ZipEntry stampCertZipEntry = apkZipFile.getEntry("stamp-cert-sha256");
+        int size = (int) stampCertZipEntry.getSize();
+        byte[] expectedStampCertHash = new byte[size];
+        try (InputStream inputStream = apkZipFile.getInputStream(stampCertZipEntry)) {
+            inputStream.read(expectedStampCertHash);
+        }
+        List<String> apkFiles = new ArrayList<>();
+        apkFiles.add(testApk1.getAbsolutePath());
+        apkFiles.add(testApk2.getAbsolutePath());
+
+        SourceStampVerificationResult result =
+                SourceStampVerifier.verify(apkFiles);
+
+        assertTrue(result.isPresent());
+        assertTrue(result.isVerified());
+        assertNotNull(result.getCertificate());
+        byte[] actualStampCertHash =
+                MessageDigest.getInstance("SHA-256").digest(result.getCertificate().getEncoded());
+        assertArrayEquals(expectedStampCertHash, actualStampCertHash);
+    }
+
+    @Test
+    public void testSourceStamp_multiApk_invalidStamps() throws Exception {
+        File testApk1 = getApk("SourceStampVerifierTest/valid-stamp.apk");
+        File testApk2 = getApk("SourceStampVerifierTest/stamp-apk-hash-mismatch.apk");
+        List<String> apkFiles = new ArrayList<>();
+        apkFiles.add(testApk1.getAbsolutePath());
+        apkFiles.add(testApk2.getAbsolutePath());
+
+        SourceStampVerificationResult result =
+                SourceStampVerifier.verify(apkFiles);
+
+        assertTrue(result.isPresent());
+        assertFalse(result.isVerified());
+        assertNull(result.getCertificate());
+    }
+
     private File getApk(String apkPath) throws IOException {
         File testApk = File.createTempFile("SourceStampApk", ".apk");
         try (InputStream inputStream = mContext.getAssets().open(apkPath)) {
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index f1ab8dd..42ab2e7 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -58,12 +58,12 @@
 import android.view.test.InsetsModeSession;
 import android.widget.TextView;
 
-import com.android.server.testutils.OffsettableClock;
-import com.android.server.testutils.TestHandler;
-
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -74,7 +74,6 @@
 import org.mockito.Mockito;
 
 import java.util.concurrent.CountDownLatch;
-import java.util.function.Supplier;
 
 /**
  * Tests for {@link InsetsController}.
@@ -199,7 +198,7 @@
             WindowInsetsAnimationControlListener mockListener =
                     mock(WindowInsetsAnimationControlListener.class);
             mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */,
-                    new LinearInterpolator(), mockListener);
+                    new LinearInterpolator(), new CancellationSignal(), mockListener);
 
             // Ready gets deferred until next predraw
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -219,7 +218,7 @@
         WindowInsetsAnimationControlListener controlListener =
                 mock(WindowInsetsAnimationControlListener.class);
         mController.controlWindowInsetsAnimation(0, 0 /* durationMs */, new LinearInterpolator(),
-                controlListener);
+                new CancellationSignal(), controlListener);
         mController.addOnControllableInsetsChangedListener(
                 (controller, typeMask) -> assertEquals(0, typeMask));
         verify(controlListener).onCancelled();
@@ -498,7 +497,7 @@
             WindowInsetsAnimationControlListener mockListener =
                     mock(WindowInsetsAnimationControlListener.class);
             mController.controlWindowInsetsAnimation(statusBars(), 0 /* durationMs */,
-                    new LinearInterpolator(), mockListener);
+                    new LinearInterpolator(), new CancellationSignal(), mockListener);
 
             ArgumentCaptor<WindowInsetsAnimationController> controllerCaptor =
                     ArgumentCaptor.forClass(WindowInsetsAnimationController.class);
@@ -523,9 +522,10 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener mockListener =
                     mock(WindowInsetsAnimationControlListener.class);
-            CancellationSignal cancellationSignal = mController.controlWindowInsetsAnimation(
+            CancellationSignal cancellationSignal = new CancellationSignal();
+            mController.controlWindowInsetsAnimation(
                     statusBars(), 0 /* durationMs */,
-                    new LinearInterpolator(), mockListener);
+                    new LinearInterpolator(), cancellationSignal, mockListener);
 
             // Ready gets deferred until next predraw
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -548,7 +548,8 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener listener =
                     mock(WindowInsetsAnimationControlListener.class);
-            mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(), listener);
+            mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(),
+                    null /* cancellationSignal */, listener);
 
             // Ready gets deferred until next predraw
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -572,7 +573,8 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener listener =
                     mock(WindowInsetsAnimationControlListener.class);
-            mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(), listener);
+            mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(),
+                    null /* cancellationSignal */, listener);
 
             // Ready gets deferred until next predraw
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -592,7 +594,8 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener listener =
                     mock(WindowInsetsAnimationControlListener.class);
-            mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(), listener);
+            mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(),
+                    null /* cancellationSignal */, listener);
 
             // Ready gets deferred until next predraw
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -613,8 +616,10 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener listener =
                     mock(WindowInsetsAnimationControlListener.class);
-            mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(), listener)
-                    .cancel();
+            CancellationSignal cancellationSignal = new CancellationSignal();
+            mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(),
+                    cancellationSignal, listener);
+            cancellationSignal.cancel();
 
             verify(listener).onCancelled();
 
diff --git a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
index 9797178..33f859e 100644
--- a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
@@ -20,8 +20,9 @@
 import static android.view.WindowInsets.Type.systemBars;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -35,12 +36,12 @@
 import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
 import android.view.animation.LinearInterpolator;
 
+import androidx.test.runner.AndroidJUnit4;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import androidx.test.runner.AndroidJUnit4;
-
 /**
  * Tests for {@link PendingInsetsControllerTest}.
  *
@@ -95,10 +96,11 @@
     public void testControl() {
         WindowInsetsAnimationControlListener listener =
                 mock(WindowInsetsAnimationControlListener.class);
-        CancellationSignal signal = mPendingInsetsController.controlWindowInsetsAnimation(
-                systemBars(), 0, new LinearInterpolator(), listener);
+        CancellationSignal cancellationSignal = new CancellationSignal();
+        mPendingInsetsController.controlWindowInsetsAnimation(
+                systemBars(), 0, new LinearInterpolator(), cancellationSignal, listener);
         verify(listener).onCancelled();
-        assertTrue(signal.isCanceled());
+        assertFalse(cancellationSignal.isCanceled());
     }
 
     @Test
@@ -106,10 +108,11 @@
         WindowInsetsAnimationControlListener listener =
                 mock(WindowInsetsAnimationControlListener.class);
         mPendingInsetsController.replayAndAttach(mReplayedController);
-        mPendingInsetsController.controlWindowInsetsAnimation(
-                systemBars(), 0L, new LinearInterpolator(), listener);
+        CancellationSignal cancellationSignal = new CancellationSignal();
+        mPendingInsetsController.controlWindowInsetsAnimation(systemBars(), 0L,
+                new LinearInterpolator(), cancellationSignal, listener);
         verify(mReplayedController).controlWindowInsetsAnimation(eq(systemBars()), eq(0L), any(),
-                eq(listener));
+                eq(cancellationSignal), eq(listener));
     }
 
     @Test
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 487a6e9..5466ac8 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -62,6 +62,10 @@
         <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
     </privapp-permissions>
 
+    <privapp-permissions package="com.android.launcher3">
+        <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.location.fused">
         <permission name="android.permission.INSTALL_LOCATION_PROVIDER"/>
         <permission name="android.permission.UPDATE_DEVICE_STATS"/>
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 8e39dfa..fb95e98 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -466,10 +466,6 @@
             return;
         }
 
-        List<RoutingSessionInfo> sessionInfos;
-        synchronized (mSessionLock) {
-            sessionInfos = new ArrayList<>(mSessionInfo.values());
-        }
         try {
             mRemoteCallback.updateState(mProviderInfo);
         } catch (RemoteException ex) {
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 37e2ab5..bde45d7 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -58,7 +58,7 @@
 // TODO: Add method names at the beginning of log messages. (e.g. updateControllerOnHandler)
 //       Not only MediaRouter2, but also to service / manager / provider.
 // TODO: ensure thread-safe and document it
-public class MediaRouter2 {
+public final class MediaRouter2 {
     private static final String TAG = "MR2";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final Object sRouterLock = new Object();
@@ -93,9 +93,9 @@
     MediaRouter2Stub mStub;
 
     @GuardedBy("sRouterLock")
-    private Map<String, RoutingController> mRoutingControllers = new ArrayMap<>();
+    private final Map<String, RoutingController> mRoutingControllers = new ArrayMap<>();
 
-    private AtomicInteger mControllerCreationRequestCnt = new AtomicInteger(1);
+    private final AtomicInteger mControllerCreationRequestCnt = new AtomicInteger(1);
 
     final Handler mHandler;
     @GuardedBy("sRouterLock")
@@ -419,8 +419,7 @@
 
         controller.release();
 
-        final int requestId;
-        requestId = mControllerCreationRequestCnt.getAndIncrement();
+        final int requestId = mControllerCreationRequestCnt.getAndIncrement();
 
         ControllerCreationRequest request =
                 new ControllerCreationRequest(requestId, controller, route);
@@ -610,10 +609,16 @@
         }
 
         if (sessionInfo != null) {
-            RoutingController newController = new RoutingController(sessionInfo);
-            synchronized (sRouterLock) {
-                mRoutingControllers.put(newController.getId(), newController);
+            RoutingController newController;
+            if (sessionInfo.isSystemSession()) {
+                newController = getSystemController();
+            } else {
+                newController = new RoutingController(sessionInfo);
+                synchronized (sRouterLock) {
+                    mRoutingControllers.put(newController.getId(), newController);
+                }
             }
+            //TODO: Determine oldController properly when transfer is launched by Output Switcher.
             notifyTransferred(matchingRequest != null ? matchingRequest.mController :
                     getSystemController(), newController);
         }
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 11c9fe1..88bcd6a 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -50,7 +50,7 @@
  * A class that monitors and controls media routing of other apps.
  * @hide
  */
-public class MediaRouter2Manager {
+public final class MediaRouter2Manager {
     private static final String TAG = "MR2Manager";
     private static final Object sLock = new Object();
 
@@ -61,7 +61,7 @@
 
     final String mPackageName;
 
-    private Context mContext;
+    private final Context mContext;
     @GuardedBy("sLock")
     private Client mClient;
     private final IMediaRouterService mMediaRouterService;
@@ -74,7 +74,7 @@
     @NonNull
     final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>();
 
-    private AtomicInteger mNextRequestId = new AtomicInteger(1);
+    private final AtomicInteger mNextRequestId = new AtomicInteger(1);
 
     /**
      * Gets an instance of media router manager that controls media route of other applications.
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
index 7b85fa8..382cc85 100644
--- a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
@@ -17,6 +17,7 @@
 package android.media.tv.tuner.frontend;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -213,13 +214,24 @@
     /**
      * Builder for {@link AnalogFrontendSettings}.
      */
-    public static class Builder extends FrontendSettings.Builder<Builder> {
+    public static class Builder {
+        private int mFrequency;
         private int mSignalType;
         private int mSifStandard;
 
         private Builder() {}
 
         /**
+         * Sets frequency in Hz.
+         */
+        @NonNull
+        @IntRange(from = 1)
+        public Builder setFrequency(int frequency) {
+            mFrequency = frequency;
+            return this;
+        }
+
+        /**
          * Sets analog signal type.
          */
         @NonNull
@@ -244,10 +256,5 @@
         public AnalogFrontendSettings build() {
             return new AnalogFrontendSettings(mFrequency, mSignalType, mSifStandard);
         }
-
-        @Override
-        Builder self() {
-            return this;
-        }
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
index b40ab00..1394716 100644
--- a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
@@ -17,6 +17,7 @@
 package android.media.tv.tuner.frontend;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -325,7 +326,8 @@
     /**
      * Builder for {@link Atsc3FrontendSettings}.
      */
-    public static class Builder extends FrontendSettings.Builder<Builder> {
+    public static class Builder {
+        private int mFrequency;
         private int mBandwidth;
         private int mDemodOutputFormat;
         private Atsc3PlpSettings[] mPlpSettings;
@@ -334,6 +336,16 @@
         }
 
         /**
+         * Sets frequency in Hz.
+         */
+        @NonNull
+        @IntRange(from = 1)
+        public Builder setFrequency(int frequency) {
+            mFrequency = frequency;
+            return this;
+        }
+
+        /**
          * Sets bandwidth.
          */
         @NonNull
@@ -366,11 +378,6 @@
             return new Atsc3FrontendSettings(
                 mFrequency, mBandwidth, mDemodOutputFormat, mPlpSettings);
         }
-
-        @Override
-        Builder self() {
-            return this;
-        }
     }
 
     @Override
diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
index fc82a1c..53352f0 100644
--- a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
@@ -17,6 +17,7 @@
 package android.media.tv.tuner.frontend;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -91,13 +92,24 @@
     /**
      * Builder for {@link AtscFrontendSettings}.
      */
-    public static class Builder extends FrontendSettings.Builder<Builder> {
+    public static class Builder {
+        private int mFrequency;
         private int mModulation;
 
         private Builder() {
         }
 
         /**
+         * Sets frequency in Hz.
+         */
+        @NonNull
+        @IntRange(from = 1)
+        public Builder setFrequency(int frequency) {
+            mFrequency = frequency;
+            return this;
+        }
+
+        /**
          * Sets Modulation.
          */
         @NonNull
@@ -113,11 +125,6 @@
         public AtscFrontendSettings build() {
             return new AtscFrontendSettings(mFrequency, mModulation);
         }
-
-        @Override
-        Builder self() {
-            return this;
-        }
     }
 
     @Override
diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
index 705d520..6d58570 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
@@ -17,6 +17,7 @@
 package android.media.tv.tuner.frontend;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -144,17 +145,17 @@
 
 
     private final int mModulation;
-    private final long mFec;
+    private final long mInnerFec;
     private final int mSymbolRate;
     private final int mOuterFec;
     private final int mAnnex;
     private final int mSpectralInversion;
 
-    private DvbcFrontendSettings(int frequency, int modulation, long fec, int symbolRate,
+    private DvbcFrontendSettings(int frequency, int modulation, long innerFec, int symbolRate,
             int outerFec, int annex, int spectralInversion) {
         super(frequency);
         mModulation = modulation;
-        mFec = fec;
+        mInnerFec = innerFec;
         mSymbolRate = symbolRate;
         mOuterFec = outerFec;
         mAnnex = annex;
@@ -172,8 +173,8 @@
      * Gets Inner Forward Error Correction.
      */
     @InnerFec
-    public long getFec() {
-        return mFec;
+    public long getInnerFec() {
+        return mInnerFec;
     }
     /**
      * Gets Symbol Rate in symbols per second.
@@ -218,9 +219,10 @@
     /**
      * Builder for {@link DvbcFrontendSettings}.
      */
-    public static class Builder extends FrontendSettings.Builder<Builder> {
+    public static class Builder {
+        private int mFrequency;
         private int mModulation;
-        private long mFec;
+        private long mInnerFec;
         private int mSymbolRate;
         private int mOuterFec;
         private int mAnnex;
@@ -230,6 +232,16 @@
         }
 
         /**
+         * Sets frequency in Hz.
+         */
+        @NonNull
+        @IntRange(from = 1)
+        public Builder setFrequency(int frequency) {
+            mFrequency = frequency;
+            return this;
+        }
+
+        /**
          * Sets Modulation.
          */
         @NonNull
@@ -241,8 +253,8 @@
          * Sets Inner Forward Error Correction.
          */
         @NonNull
-        public Builder setFec(@InnerFec long fec) {
-            mFec = fec;
+        public Builder setInnerFec(@InnerFec long fec) {
+            mInnerFec = fec;
             return this;
         }
         /**
@@ -283,13 +295,8 @@
          */
         @NonNull
         public DvbcFrontendSettings build() {
-            return new DvbcFrontendSettings(mFrequency, mModulation, mFec, mSymbolRate, mOuterFec,
-                    mAnnex, mSpectralInversion);
-        }
-
-        @Override
-        Builder self() {
-            return this;
+            return new DvbcFrontendSettings(mFrequency, mModulation, mInnerFec, mSymbolRate,
+                mOuterFec, mAnnex, mSpectralInversion);
         }
     }
 
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
index 4a4fed5..9c45dd1 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
@@ -17,6 +17,7 @@
 package android.media.tv.tuner.frontend;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -303,7 +304,8 @@
     /**
      * Builder for {@link DvbsFrontendSettings}.
      */
-    public static class Builder extends FrontendSettings.Builder<Builder> {
+    public static class Builder {
+        private int mFrequency;
         private int mModulation;
         private DvbsCodeRate mCodeRate;
         private int mSymbolRate;
@@ -317,6 +319,16 @@
         }
 
         /**
+         * Sets frequency in Hz.
+         */
+        @NonNull
+        @IntRange(from = 1)
+        public Builder setFrequency(int frequency) {
+            mFrequency = frequency;
+            return this;
+        }
+
+        /**
          * Sets Modulation.
          */
         @NonNull
@@ -389,11 +401,6 @@
             return new DvbsFrontendSettings(mFrequency, mModulation, mCodeRate, mSymbolRate,
                     mRolloff, mPilot, mInputStreamId, mStandard, mVcmMode);
         }
-
-        @Override
-        Builder self() {
-            return this;
-        }
     }
 
     @Override
diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
index 1510b2d..4accabb 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
@@ -17,6 +17,7 @@
 package android.media.tv.tuner.frontend;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -506,7 +507,8 @@
     /**
      * Builder for {@link DvbtFrontendSettings}.
      */
-    public static class Builder extends FrontendSettings.Builder<Builder> {
+    public static class Builder {
+        private int mFrequency;
         private int mTransmissionMode;
         private int mBandwidth;
         private int mConstellation;
@@ -525,6 +527,16 @@
         }
 
         /**
+         * Sets frequency in Hz.
+         */
+        @NonNull
+        @IntRange(from = 1)
+        public Builder setFrequency(int frequency) {
+            mFrequency = frequency;
+            return this;
+        }
+
+        /**
          * Sets Transmission Mode.
          */
         @NonNull
@@ -638,11 +650,6 @@
                     mConstellation, mHierarchy, mHpCodeRate, mLpCodeRate, mGuardInterval,
                     mIsHighPriority, mStandard, mIsMiso, mPlpMode, mPlpId, mPlpGroupId);
         }
-
-        @Override
-        Builder self() {
-            return this;
-        }
     }
 
     @Override
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
index 9071526..2f2fa97 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
@@ -17,9 +17,7 @@
 package android.media.tv.tuner.frontend;
 
 import android.annotation.IntDef;
-import android.annotation.IntRange;
 import android.annotation.LongDef;
-import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.hardware.tv.tuner.V1_0.Constants;
 
@@ -265,27 +263,4 @@
     public int getFrequency() {
         return mFrequency;
     }
-
-    /**
-     * Builder for {@link FrontendSettings}.
-     *
-     * @param <T> The subclass to be built.
-     */
-    public abstract static class Builder<T extends Builder<T>> {
-        /* package */ int mFrequency;
-
-        /* package */ Builder() {}
-
-        /**
-         * Sets frequency in Hz.
-         */
-        @NonNull
-        @IntRange(from = 1)
-        public T setFrequency(int frequency) {
-            mFrequency = frequency;
-            return self();
-        }
-
-        /* package */ abstract T self();
-    }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index 63de0334..ea91ee9 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -304,7 +304,7 @@
      *  and ETSI EN 302 307-2 V1.1.1.
      */
     @FrontendSettings.InnerFec
-    public long getFec() {
+    public long getInnerFec() {
         if (mInnerFec == null) {
             throw new IllegalStateException();
         }
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
index 9b0e533..f385379 100644
--- a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
@@ -17,6 +17,7 @@
 package android.media.tv.tuner.frontend;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -224,7 +225,8 @@
     /**
      * Builder for {@link Isdbs3FrontendSettings}.
      */
-    public static class Builder extends FrontendSettings.Builder<Builder> {
+    public static class Builder {
+        private int mFrequency;
         private int mStreamId;
         private int mStreamIdType;
         private int mModulation;
@@ -236,6 +238,16 @@
         }
 
         /**
+         * Sets frequency in Hz.
+         */
+        @NonNull
+        @IntRange(from = 1)
+        public Builder setFrequency(int frequency) {
+            mFrequency = frequency;
+            return this;
+        }
+
+        /**
          * Sets Stream ID.
          */
         @NonNull
@@ -292,11 +304,6 @@
             return new Isdbs3FrontendSettings(mFrequency, mStreamId, mStreamIdType, mModulation,
                     mCodeRate, mSymbolRate, mRolloff);
         }
-
-        @Override
-        Builder self() {
-            return this;
-        }
     }
 
     @Override
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
index 14c08b1..4667b9b 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
@@ -17,6 +17,7 @@
 package android.media.tv.tuner.frontend;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -208,7 +209,8 @@
     /**
      * Builder for {@link IsdbsFrontendSettings}.
      */
-    public static class Builder extends FrontendSettings.Builder<Builder> {
+    public static class Builder {
+        private int mFrequency;
         private int mStreamId;
         private int mStreamIdType;
         private int mModulation;
@@ -220,6 +222,16 @@
         }
 
         /**
+         * Sets frequency in Hz.
+         */
+        @NonNull
+        @IntRange(from = 1)
+        public Builder setFrequency(int frequency) {
+            mFrequency = frequency;
+            return this;
+        }
+
+        /**
          * Sets Stream ID.
          */
         @NonNull
@@ -276,11 +288,6 @@
             return new IsdbsFrontendSettings(mFrequency, mStreamId, mStreamIdType, mModulation,
                     mCodeRate, mSymbolRate, mRolloff);
         }
-
-        @Override
-        Builder self() {
-            return this;
-        }
     }
 
     @Override
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
index de3c80d..b607623 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
@@ -17,6 +17,7 @@
 package android.media.tv.tuner.frontend;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -202,7 +203,8 @@
     /**
      * Builder for {@link IsdbtFrontendSettings}.
      */
-    public static class Builder extends FrontendSettings.Builder<Builder> {
+    public static class Builder {
+        private int mFrequency;
         private int mModulation;
         private int mBandwidth;
         private int mMode;
@@ -214,6 +216,16 @@
         }
 
         /**
+         * Sets frequency in Hz.
+         */
+        @NonNull
+        @IntRange(from = 1)
+        public Builder setFrequency(int frequency) {
+            mFrequency = frequency;
+            return this;
+        }
+
+        /**
          * Sets Modulation.
          */
         @NonNull
@@ -270,11 +282,6 @@
             return new IsdbtFrontendSettings(mFrequency, mModulation, mBandwidth, mMode, mCodeRate,
                     mGuardInterval, mServiceAreaId);
         }
-
-        @Override
-        Builder self() {
-            return this;
-        }
     }
 
     @Override
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index 524b6c2..9dddcd4 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -60,11 +60,6 @@
     private static final String TAG = "TunerResourceManager";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    public static final int INVALID_FRONTEND_ID = -1;
-    public static final int INVALID_CAS_SESSION_RESOURCE_ID = -1;
-    public static final int INVALID_LNB_ID = -1;
-    public static final int INVALID_TV_INPUT_DEVICE_ID = -1;
-    public static final int INVALID_TV_INPUT_PORT_ID = -1;
     public static final int INVALID_RESOURCE_HANDLE = -1;
 
     private final ITunerResourceManager mService;
diff --git a/media/jni/audioeffect/Visualizer.cpp b/media/jni/audioeffect/Visualizer.cpp
index 83f3b6e..efeb335 100644
--- a/media/jni/audioeffect/Visualizer.cpp
+++ b/media/jni/audioeffect/Visualizer.cpp
@@ -120,8 +120,9 @@
     }
 
     if (mCaptureThread != 0) {
+        sp<CaptureThread> t = mCaptureThread;
         mCaptureLock.unlock();
-        mCaptureThread->requestExitAndWait();
+        t->requestExitAndWait();
         mCaptureLock.lock();
     }
 
diff --git a/media/tests/MediaRouter/AndroidManifest.xml b/media/tests/MediaRouter/AndroidManifest.xml
index 9546500..d9806f3 100644
--- a/media/tests/MediaRouter/AndroidManifest.xml
+++ b/media/tests/MediaRouter/AndroidManifest.xml
@@ -19,7 +19,7 @@
 
     <application android:label="@string/app_name">
         <uses-library android:name="android.test.runner" />
-        <service android:name=".SampleMediaRoute2ProviderService"
+        <service android:name=".StubMediaRoute2ProviderService"
                  android:exported="true">
             <intent-filter>
                 <action android:name="android.media.MediaRoute2ProviderService" />
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 230b9e4..a97baaf 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -22,16 +22,16 @@
 import static android.media.MediaRoute2ProviderService.REASON_REJECTED;
 import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE;
 
-import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.FEATURE_SAMPLE;
-import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.FEATURE_SPECIAL;
-import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.ROUTE_ID1;
-import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.ROUTE_ID2;
-import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.ROUTE_ID5_TO_TRANSFER_TO;
-import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.ROUTE_ID_FIXED_VOLUME;
-import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.ROUTE_ID_SPECIAL_FEATURE;
-import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.ROUTE_ID_VARIABLE_VOLUME;
-import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.ROUTE_NAME2;
-import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.VOLUME_MAX;
+import static com.android.mediaroutertest.StubMediaRoute2ProviderService.FEATURE_SAMPLE;
+import static com.android.mediaroutertest.StubMediaRoute2ProviderService.FEATURE_SPECIAL;
+import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID1;
+import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID2;
+import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID5_TO_TRANSFER_TO;
+import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_FIXED_VOLUME;
+import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_SPECIAL_FEATURE;
+import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_VARIABLE_VOLUME;
+import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_NAME2;
+import static com.android.mediaroutertest.StubMediaRoute2ProviderService.VOLUME_MAX;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -115,7 +115,7 @@
         // unregister callbacks
         clearCallbacks();
 
-        SampleMediaRoute2ProviderService instance = SampleMediaRoute2ProviderService.getInstance();
+        StubMediaRoute2ProviderService instance = StubMediaRoute2ProviderService.getInstance();
         if (instance != null) {
             instance.setProxy(null);
         }
@@ -161,8 +161,8 @@
 
         MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2);
 
-        SampleMediaRoute2ProviderService sInstance =
-                SampleMediaRoute2ProviderService.getInstance();
+        StubMediaRoute2ProviderService sInstance =
+                StubMediaRoute2ProviderService.getInstance();
         assertNotNull(sInstance);
         sInstance.removeRoute(ROUTE_ID2);
         assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -413,12 +413,12 @@
         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
         MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
 
-        SampleMediaRoute2ProviderService instance = SampleMediaRoute2ProviderService.getInstance();
+        StubMediaRoute2ProviderService instance = StubMediaRoute2ProviderService.getInstance();
         assertNotNull(instance);
 
         final List<Long> requestIds = new ArrayList<>();
         final CountDownLatch onSetRouteVolumeLatch = new CountDownLatch(1);
-        instance.setProxy(new SampleMediaRoute2ProviderService.Proxy() {
+        instance.setProxy(new StubMediaRoute2ProviderService.Proxy() {
             @Override
             public void onSetRouteVolume(String routeId, int volume, long requestId) {
                 requestIds.add(requestId);
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
similarity index 98%
rename from media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
rename to media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
index f05d8ad..6d46ba5 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
@@ -36,7 +36,7 @@
 
 import javax.annotation.concurrent.GuardedBy;
 
-public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService {
+public class StubMediaRoute2ProviderService extends MediaRoute2ProviderService {
     private static final String TAG = "SampleMR2ProviderSvc";
     private static final Object sLock = new Object();
 
@@ -74,7 +74,7 @@
     private int mNextSessionId = 1000;
 
     @GuardedBy("sLock")
-    private static SampleMediaRoute2ProviderService sInstance;
+    private static StubMediaRoute2ProviderService sInstance;
     private Proxy mProxy;
 
     private void initializeRoutes() {
@@ -127,7 +127,7 @@
         mRoutes.put(variableVolumeRoute.getId(), variableVolumeRoute);
     }
 
-    public static SampleMediaRoute2ProviderService getInstance() {
+    public static StubMediaRoute2ProviderService getInstance() {
         synchronized (sLock) {
             return sInstance;
         }
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
index b63162b..3ee92bd7 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -238,10 +238,12 @@
         }
 
         buildNavBarContent();
-        // If the UI was rebuilt (day/night change) while the keyguard was up we need to
-        // correctly respect that state.
+        // If the UI was rebuilt (day/night change or user change) while the keyguard was up we need
+        // to correctly respect that state.
         if (mKeyguardStateControllerLazy.get().isShowing()) {
             mCarNavigationBarController.showAllKeyguardButtons(isDeviceSetupForUser());
+        } else {
+            mCarNavigationBarController.hideAllKeyguardButtons(isDeviceSetupForUser());
         }
 
         // Upon restarting the Navigation Bar, CarFacetButtonController should immediately apply the
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
index c04e47f..f2748b8 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
@@ -46,8 +46,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import dagger.Lazy;
-
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
@@ -68,12 +66,8 @@
     @Mock
     private ButtonSelectionStateListener mButtonSelectionStateListener;
     @Mock
-    private Lazy<KeyguardStateController> mKeyguardStateControllerLazy;
-    @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
-    private Lazy<NavigationBarController> mNavigationBarControllerLazy;
-    @Mock
     private NavigationBarController mNavigationBarController;
     @Mock
     private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
@@ -89,13 +83,11 @@
         mCarNavigationBar = new CarNavigationBar(mContext, mCarNavigationBarController,
                 mWindowManager, mDeviceProvisionedController, new CommandQueue(mContext),
                 mAutoHideController, mButtonSelectionStateListener, mHandler,
-                mKeyguardStateControllerLazy, mNavigationBarControllerLazy,
+                () -> mKeyguardStateController, () -> mNavigationBarController,
                 mSuperStatusBarViewFactory, mButtonSelectionStateController);
         StatusBarWindowView statusBarWindowView = (StatusBarWindowView) LayoutInflater.from(
                 mContext).inflate(R.layout.super_status_bar, /* root= */ null);
         when(mSuperStatusBarViewFactory.getStatusBarWindowView()).thenReturn(statusBarWindowView);
-        when(mNavigationBarControllerLazy.get()).thenReturn(mNavigationBarController);
-        when(mKeyguardStateControllerLazy.get()).thenReturn(mKeyguardStateController);
         when(mKeyguardStateController.isShowing()).thenReturn(false);
         mDependency.injectMockDependency(WindowManager.class);
         // Needed to inflate top navigation bar.
@@ -119,4 +111,44 @@
 
         verify(mButtonSelectionStateListener).onTaskStackChanged();
     }
+
+    @Test
+    public void restartNavBars_newUserNotSetupWithKeyguardShowing_showsKeyguardButtons() {
+        ArgumentCaptor<CarDeviceProvisionedController.DeviceProvisionedListener>
+                deviceProvisionedCallbackCaptor = ArgumentCaptor.forClass(
+                CarDeviceProvisionedController.DeviceProvisionedListener.class);
+        when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
+        mCarNavigationBar.start();
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        // switching the currentUserSetup value to force restart the navbars.
+        when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(false);
+        verify(mDeviceProvisionedController).addCallback(deviceProvisionedCallbackCaptor.capture());
+
+        deviceProvisionedCallbackCaptor.getValue().onUserSwitched();
+        waitForIdleSync(mHandler);
+
+        verify(mCarNavigationBarController).showAllKeyguardButtons(false);
+    }
+
+    @Test
+    public void restartNavbars_newUserIsSetupWithKeyguardHidden_hidesKeyguardButtons() {
+        ArgumentCaptor<CarDeviceProvisionedController.DeviceProvisionedListener>
+                deviceProvisionedCallbackCaptor = ArgumentCaptor.forClass(
+                CarDeviceProvisionedController.DeviceProvisionedListener.class);
+        when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
+        mCarNavigationBar.start();
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        // switching the currentUserSetup value to force restart the navbars.
+        when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(false);
+        verify(mDeviceProvisionedController).addCallback(deviceProvisionedCallbackCaptor.capture());
+        deviceProvisionedCallbackCaptor.getValue().onUserSwitched();
+        waitForIdleSync(mHandler);
+        when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+        deviceProvisionedCallbackCaptor.getValue().onUserSetupChanged();
+        waitForIdleSync(mHandler);
+
+        verify(mCarNavigationBarController).hideAllKeyguardButtons(true);
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 18eb187..3c78560 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -218,7 +218,7 @@
     }
 
     public boolean supportsHighQualityAudio(BluetoothDevice device) {
-        BluetoothDevice bluetoothDevice = (device == null) ? device : mService.getActiveDevice();
+        BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
         if (bluetoothDevice == null) {
             return false;
         }
@@ -227,7 +227,7 @@
     }
 
     public boolean isHighQualityAudioEnabled(BluetoothDevice device) {
-        BluetoothDevice bluetoothDevice = (device == null) ? device : mService.getActiveDevice();
+        BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
         if (bluetoothDevice == null) {
             return false;
         }
@@ -253,7 +253,7 @@
     }
 
     public void setHighQualityAudioEnabled(BluetoothDevice device, boolean enabled) {
-        BluetoothDevice bluetoothDevice = (device == null) ? device : mService.getActiveDevice();
+        BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
         if (bluetoothDevice == null) {
             return;
         }
@@ -272,7 +272,7 @@
     }
 
     public String getHighQualityAudioOptionLabel(BluetoothDevice device) {
-        BluetoothDevice bluetoothDevice = (device == null) ? device : mService.getActiveDevice();
+        BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
         int unknownCodecId = R.string.bluetooth_profile_a2dp_high_quality_unknown_codec;
         if (bluetoothDevice == null || !supportsHighQualityAudio(device)
                 || getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 4a0d16c..44b481d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -49,11 +49,13 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({MediaDeviceState.STATE_CONNECTED,
             MediaDeviceState.STATE_CONNECTING,
-            MediaDeviceState.STATE_DISCONNECTED})
+            MediaDeviceState.STATE_DISCONNECTED,
+            MediaDeviceState.STATE_CONNECTING_FAILED})
     public @interface MediaDeviceState {
-        int STATE_CONNECTED = 1;
-        int STATE_CONNECTING = 2;
-        int STATE_DISCONNECTED = 3;
+        int STATE_CONNECTED = 0;
+        int STATE_CONNECTING = 1;
+        int STATE_DISCONNECTED = 2;
+        int STATE_CONNECTING_FAILED = 3;
     }
 
     private final Collection<DeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
@@ -126,6 +128,7 @@
             final CachedBluetoothDevice cachedDevice =
                     ((BluetoothMediaDevice) device).getCachedDevice();
             if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) {
+                device.setState(MediaDeviceState.STATE_CONNECTING);
                 cachedDevice.connect();
                 return;
             }
@@ -140,6 +143,7 @@
             mCurrentConnectedDevice.disconnect();
         }
 
+        device.setState(MediaDeviceState.STATE_CONNECTING);
         if (TextUtils.isEmpty(mPackageName)) {
             mInfoMediaManager.connectDeviceWithoutPackageName(device);
         } else {
@@ -421,6 +425,7 @@
             MediaDevice connectDevice = getMediaDeviceById(mMediaDevices, id);
             connectDevice = connectDevice != null
                     ? connectDevice : updateCurrentConnectedDevice();
+            connectDevice.setState(MediaDeviceState.STATE_CONNECTED);
 
             if (connectDevice == mCurrentConnectedDevice) {
                 Log.d(TAG, "onConnectedDeviceChanged() this device all ready connected!");
@@ -438,6 +443,11 @@
 
         @Override
         public void onRequestFailed(int reason) {
+            for (MediaDevice device : mMediaDevices) {
+                if (device.getState() == MediaDeviceState.STATE_CONNECTING) {
+                    device.setState(MediaDeviceState.STATE_CONNECTING_FAILED);
+                }
+            }
             dispatchOnRequestFailed(reason);
         }
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 33c3d7e..39e6a12 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -51,6 +51,7 @@
     int mType;
 
     private int mConnectedRecord;
+    private int mState;
 
     protected final Context mContext;
     protected final MediaRoute2Info mRouteInfo;
@@ -200,6 +201,22 @@
     }
 
     /**
+     * Set current device's state
+     */
+    public void setState(@LocalMediaManager.MediaDeviceState int state) {
+        mState = state;
+    }
+
+    /**
+     * Get current device's state
+     *
+     * @return state of device
+     */
+    public @LocalMediaManager.MediaDeviceState int getState() {
+        return mState;
+    }
+
+    /**
      * Rules:
      * 1. If there is one of the connected devices identified as a carkit, this carkit will
      * be always on the top of the device list. Rule 2 and Rule 3 can’t overrule this rule.
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 18d8f14..f825ec5 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
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.media;
 
+import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -27,6 +29,8 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
 
 import com.android.settingslib.bluetooth.A2dpProfile;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -53,10 +57,13 @@
 @Config(shadows = {ShadowBluetoothAdapter.class})
 public class LocalMediaManagerTest {
 
+    private static final String TEST_DEVICE_NAME_1 = "device_name_1";
+    private static final String TEST_DEVICE_NAME_2 = "device_name_2";
     private static final String TEST_DEVICE_ID_1 = "device_id_1";
     private static final String TEST_DEVICE_ID_2 = "device_id_2";
     private static final String TEST_DEVICE_ID_3 = "device_id_3";
     private static final String TEST_CURRENT_DEVICE_ID = "currentDevice_id";
+    private static final String TEST_PACKAGE_NAME = "com.test.playmusic";
 
     @Mock
     private BluetoothMediaManager mBluetoothMediaManager;
@@ -72,10 +79,18 @@
     private A2dpProfile mA2dpProfile;
     @Mock
     private LocalBluetoothProfileManager mLocalProfileManager;
+    @Mock
+    private MediaRouter2Manager mMediaRouter2Manager;
+    @Mock
+    private MediaRoute2Info mRouteInfo1;
+    @Mock
+    private MediaRoute2Info mRouteInfo2;
 
     private Context mContext;
     private LocalMediaManager mLocalMediaManager;
     private ShadowBluetoothAdapter mShadowBluetoothAdapter;
+    private InfoMediaDevice mInfoMediaDevice1;
+    private InfoMediaDevice mInfoMediaDevice2;
 
     @Before
     public void setUp() {
@@ -84,11 +99,18 @@
         final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
         mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
         mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices);
-
+        when(mRouteInfo1.getName()).thenReturn(TEST_DEVICE_NAME_1);
+        when(mRouteInfo1.getId()).thenReturn(TEST_DEVICE_ID_1);
+        when(mRouteInfo2.getName()).thenReturn(TEST_DEVICE_NAME_2);
+        when(mRouteInfo2.getId()).thenReturn(TEST_DEVICE_ID_2);
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
         when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
         when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
 
+        mInfoMediaDevice1 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo1,
+                TEST_PACKAGE_NAME);
+        mInfoMediaDevice2 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo2,
+                TEST_PACKAGE_NAME);
         mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
                 mInfoMediaManager, "com.test.packagename");
         mLocalMediaManager.mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -123,6 +145,32 @@
     }
 
     @Test
+    public void connectDevice_deviceNotEqualCurrentConnectedDevice_isConnectingState() {
+        mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice1);
+        mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice2);
+        mLocalMediaManager.mCurrentConnectedDevice = mInfoMediaDevice1;
+
+        mLocalMediaManager.registerCallback(mCallback);
+        mLocalMediaManager.connectDevice(mInfoMediaDevice2);
+
+        assertThat(mInfoMediaDevice2.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
+                .STATE_CONNECTING);
+    }
+
+    @Test
+    public void connectDevice_deviceEqualCurrentConnectedDevice_notConnectingState() {
+        mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice1);
+        mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice2);
+        mLocalMediaManager.mCurrentConnectedDevice = mInfoMediaDevice1;
+
+        mLocalMediaManager.registerCallback(mCallback);
+        mLocalMediaManager.connectDevice(mInfoMediaDevice1);
+
+        assertThat(mInfoMediaDevice1.getState()).isNotEqualTo(LocalMediaManager.MediaDeviceState
+                .STATE_CONNECTING);
+    }
+
+    @Test
     public void connectDevice_bluetoothDeviceNotConnected_connectBluetoothDevice() {
         final MediaDevice device = mock(BluetoothMediaDevice.class);
         final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
@@ -388,6 +436,21 @@
     }
 
     @Test
+    public void onConnectedDeviceChanged_isConnectedState() {
+        mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice1);
+        mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice2);
+        mInfoMediaDevice1.setState(LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
+
+        assertThat(mInfoMediaDevice1.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
+                .STATE_DISCONNECTED);
+        mLocalMediaManager.registerCallback(mCallback);
+        mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_1);
+
+        assertThat(mInfoMediaDevice1.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
+                .STATE_CONNECTED);
+    }
+
+    @Test
     public void onDeviceAttributesChanged_shouldDispatchDeviceListUpdate() {
         mLocalMediaManager.registerCallback(mCallback);
 
@@ -397,6 +460,26 @@
     }
 
     @Test
+    public void onRequestFailed_checkDevicesState() {
+        mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice1);
+        mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice2);
+        mInfoMediaDevice1.setState(LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
+        mInfoMediaDevice2.setState(LocalMediaManager.MediaDeviceState.STATE_CONNECTED);
+
+        assertThat(mInfoMediaDevice1.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
+                .STATE_CONNECTING);
+        assertThat(mInfoMediaDevice2.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
+                .STATE_CONNECTED);
+        mLocalMediaManager.registerCallback(mCallback);
+        mLocalMediaManager.mMediaDeviceCallback.onRequestFailed(REASON_UNKNOWN_ERROR);
+
+        assertThat(mInfoMediaDevice1.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
+                .STATE_CONNECTING_FAILED);
+        assertThat(mInfoMediaDevice2.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
+                .STATE_CONNECTED);
+    }
+
+    @Test
     public void getActiveMediaDevice_checkList() {
         final List<MediaDevice> devices = new ArrayList<>();
         final MediaDevice device1 = mock(MediaDevice.class);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index 3f29b72..4b08387 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -448,4 +448,23 @@
         assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo(
                 mContext.getResources().getString(R.string.unknown));
     }
+
+    @Test
+    public void setState_verifyGetState() {
+        mInfoMediaDevice1.setState(LocalMediaManager.MediaDeviceState.STATE_CONNECTED);
+        assertThat(mInfoMediaDevice1.getState()).isEqualTo(
+                LocalMediaManager.MediaDeviceState.STATE_CONNECTED);
+
+        mInfoMediaDevice1.setState(LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
+        assertThat(mInfoMediaDevice1.getState()).isEqualTo(
+                LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
+
+        mInfoMediaDevice1.setState(LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
+        assertThat(mInfoMediaDevice1.getState()).isEqualTo(
+                LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
+
+        mInfoMediaDevice1.setState(LocalMediaManager.MediaDeviceState.STATE_CONNECTING_FAILED);
+        assertThat(mInfoMediaDevice1.getState()).isEqualTo(
+                LocalMediaManager.MediaDeviceState.STATE_CONNECTING_FAILED);
+    }
 }
diff --git a/packages/SystemUI/res/drawable/ic_bubble_overflow_button.xml b/packages/SystemUI/res/drawable/ic_bubble_overflow_button.xml
index 64b57c5..3acebc1 100644
--- a/packages/SystemUI/res/drawable/ic_bubble_overflow_button.xml
+++ b/packages/SystemUI/res/drawable/ic_bubble_overflow_button.xml
@@ -14,10 +14,11 @@
 limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:viewportHeight="24"
-    android:viewportWidth="24"
-    android:height="52dp"
-    android:width="52dp">
-    <path android:fillColor="#1A73E8"
-          android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
-</vector>
\ No newline at end of file
+  android:viewportWidth="24"
+  android:viewportHeight="24"
+  android:width="24dp"
+  android:height="24dp">
+  <path
+      android:fillColor="#1A73E8"
+      android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+</vector>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index c8b0d99..82224df 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -529,4 +529,7 @@
     <!--  Flag to turn on the rendering of the above path or not  -->
     <bool name="config_enableDisplayCutoutProtection">false</bool>
 
+    <!-- Respect drawable/rounded.xml intrinsic size for multiple radius corner path customization -->
+    <bool name="config_roundedCornerMultipleRadius">false</bool>
+
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 4885ade..07bd3a0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -78,6 +78,9 @@
     public static final int SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1 << 11;
     // Winscope tracing is enabled
     public static final int SYSUI_STATE_TRACING_ENABLED = 1 << 12;
+    // The Assistant gesture should be constrained. It is up to the launcher implementation to
+    // decide how to constrain it
+    public static final int SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1 << 13;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -92,7 +95,8 @@
             SYSUI_STATE_OVERVIEW_DISABLED,
             SYSUI_STATE_HOME_DISABLED,
             SYSUI_STATE_SEARCH_DISABLED,
-            SYSUI_STATE_TRACING_ENABLED
+            SYSUI_STATE_TRACING_ENABLED,
+            SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED
     })
     public @interface SystemUiStateFlags {}
 
@@ -112,6 +116,8 @@
         str.add((flags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0 ? "a11y_click" : "");
         str.add((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0 ? "a11y_long_click" : "");
         str.add((flags & SYSUI_STATE_TRACING_ENABLED) != 0 ? "tracing" : "");
+        str.add((flags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0
+                ? "asst_gesture_constrain" : "");
         return str.toString();
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
index d33c653..29100ef 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
@@ -90,7 +90,7 @@
             view.setTranslationX((surfaceControl.getWidth() - scale * viewSize.getWidth()) / 2);
             view.setTranslationY((surfaceControl.getHeight() - scale * viewSize.getHeight()) / 2);
 
-            mSurfaceControlViewHost.addView(view, layoutParams);
+            mSurfaceControlViewHost.setView(view, layoutParams);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 537a812..a8a3cae 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -50,6 +50,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
+import android.graphics.drawable.VectorDrawable;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -126,8 +127,9 @@
     private WindowManager mWindowManager;
     private int mRotation;
     private SecureSetting mColorInversionSetting;
-    private boolean mPendingRotationChange;
     private Handler mHandler;
+    private boolean mPendingRotationChange;
+    private boolean mIsRoundedCornerMultipleRadius;
 
     private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback =
             new CameraAvailabilityListener.CameraTransitionCallback() {
@@ -194,6 +196,8 @@
         mRotation = mContext.getDisplay().getRotation();
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
+        mIsRoundedCornerMultipleRadius = mContext.getResources().getBoolean(
+                R.bool.config_roundedCornerMultipleRadius);
         updateRoundedCornerRadii();
         setupDecorations();
         setupCameraListener();
@@ -572,15 +576,22 @@
                 com.android.internal.R.dimen.rounded_corner_radius_top);
         final int newRoundedDefaultBottom = mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.rounded_corner_radius_bottom);
-
         final boolean roundedCornersChanged = mRoundedDefault != newRoundedDefault
                 || mRoundedDefaultBottom != newRoundedDefaultBottom
                 || mRoundedDefaultTop != newRoundedDefaultTop;
 
         if (roundedCornersChanged) {
-            mRoundedDefault = newRoundedDefault;
-            mRoundedDefaultTop = newRoundedDefaultTop;
-            mRoundedDefaultBottom = newRoundedDefaultBottom;
+            // If config_roundedCornerMultipleRadius set as true, ScreenDecorations respect the
+            // max(width, height) size of drawable/rounded.xml instead of rounded_corner_radius
+            if (mIsRoundedCornerMultipleRadius) {
+                final VectorDrawable d = (VectorDrawable) mContext.getDrawable(R.drawable.rounded);
+                mRoundedDefault = Math.max(d.getIntrinsicWidth(), d.getIntrinsicHeight());
+                mRoundedDefaultTop = mRoundedDefaultBottom = mRoundedDefault;
+            } else {
+                mRoundedDefault = newRoundedDefault;
+                mRoundedDefaultTop = newRoundedDefaultTop;
+                mRoundedDefaultBottom = newRoundedDefaultBottom;
+            }
             onTuningChanged(SIZE, null);
         }
     }
@@ -631,7 +642,8 @@
     }
 
     private boolean hasRoundedCorners() {
-        return mRoundedDefault > 0 || mRoundedDefaultBottom > 0 || mRoundedDefaultTop > 0;
+        return mRoundedDefault > 0 || mRoundedDefaultBottom > 0 || mRoundedDefaultTop > 0
+                || mIsRoundedCornerMultipleRadius;
     }
 
     private boolean shouldShowRoundedCorner(@BoundsPosition int pos) {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 5077e18..cc4ee89 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -1,6 +1,9 @@
 package com.android.systemui.assist;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -43,6 +46,7 @@
 import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.systemui.R;
 import com.android.systemui.assist.ui.DefaultUiController;
+import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -51,6 +55,8 @@
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
+import dagger.Lazy;
+
 /**
  * Class to manage everything related to assist in SystemUI.
  */
@@ -98,6 +104,9 @@
     public static final String INVOCATION_TYPE_KEY = "invocation_type";
     protected static final String ACTION_KEY = "action";
     protected static final String SHOW_ASSIST_HANDLES_ACTION = "show_assist_handles";
+    protected static final String SET_ASSIST_GESTURE_CONSTRAINED_ACTION =
+            "set_assist_gesture_constrained";
+    protected static final String CONSTRAINED_KEY = "should_constrain";
 
     public static final int INVOCATION_TYPE_GESTURE = 1;
     public static final int INVOCATION_TYPE_ACTIVE_EDGE = 2;
@@ -120,6 +129,7 @@
     private final PhoneStateMonitor mPhoneStateMonitor;
     private final AssistHandleBehaviorController mHandleController;
     private final UiController mUiController;
+    protected final Lazy<SysUiState> mSysUiState;
 
     private AssistOrbContainer mView;
     private final DeviceProvisionedController mDeviceProvisionedController;
@@ -185,7 +195,8 @@
             CommandQueue commandQueue,
             PhoneStateMonitor phoneStateMonitor,
             OverviewProxyService overviewProxyService,
-            ConfigurationController configurationController) {
+            ConfigurationController configurationController,
+            Lazy<SysUiState> sysUiState) {
         mContext = context;
         mDeviceProvisionedController = controller;
         mCommandQueue = commandQueue;
@@ -206,6 +217,8 @@
 
         mUiController = new DefaultUiController(mContext);
 
+        mSysUiState = sysUiState;
+
         overviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() {
             @Override
             public void onAssistantProgress(float progress) {
@@ -243,8 +256,16 @@
                         if (VERBOSE) {
                             Log.v(TAG, "UI hints received");
                         }
-                        if (SHOW_ASSIST_HANDLES_ACTION.equals(hints.getString(ACTION_KEY))) {
+
+                        String action = hints.getString(ACTION_KEY);
+                        if (SHOW_ASSIST_HANDLES_ACTION.equals(action)) {
                             requestAssistHandles();
+                        } else if (SET_ASSIST_GESTURE_CONSTRAINED_ACTION.equals(action)) {
+                            mSysUiState.get()
+                                    .setFlag(
+                                            SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
+                                            hints.getBoolean(CONSTRAINED_KEY, false))
+                                    .commitUpdate(DEFAULT_DISPLAY);
                         }
                     }
                 });
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 406e7ce..b39dd1a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -52,6 +52,7 @@
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.ZenModeConfig;
 import android.util.ArraySet;
@@ -146,13 +147,15 @@
     private BubbleData mBubbleData;
     @Nullable private BubbleStackView mStackView;
     private BubbleIconFactory mBubbleIconFactory;
-    private int mMaxBubbles;
 
     // Tracks the id of the current (foreground) user.
     private int mCurrentUserId;
     // Saves notification keys of active bubbles when users are switched.
     private final SparseSetArray<String> mSavedBubbleKeysPerUser;
 
+    // Used when ranking updates occur and we check if things should bubble / unbubble
+    private NotificationListenerService.Ranking mTmpRanking;
+
     // Saves notification keys of user created "fake" bubbles so that we can allow notifications
     // like these to bubble by default. Doesn't persist across reboots, not a long-term solution.
     private final HashSet<String> mUserCreatedBubbles;
@@ -338,7 +341,6 @@
 
         configurationController.addCallback(this /* configurationListener */);
 
-        mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered);
         mBubbleData = data;
         mBubbleData.setListener(mBubbleDataListener);
         mBubbleData.setSuppressionChangedListener(new NotificationSuppressionChangedListener() {
@@ -939,9 +941,29 @@
         }
     }
 
+    /**
+     * Called when NotificationListener has received adjusted notification rank and reapplied
+     * filtering and sorting. This is used to dismiss or create bubbles based on changes in
+     * permissions on the notification channel or the global setting.
+     *
+     * @param rankingMap the updated ranking map from NotificationListenerService
+     */
     private void onRankingUpdated(RankingMap rankingMap) {
-        // Forward to BubbleData to block any bubbles which should no longer be shown
-        mBubbleData.notificationRankingUpdated(rankingMap);
+        if (mTmpRanking == null) {
+            mTmpRanking = new NotificationListenerService.Ranking();
+        }
+        String[] orderedKeys = rankingMap.getOrderedKeys();
+        for (int i = 0; i < orderedKeys.length; i++) {
+            String key = orderedKeys[i];
+            NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key);
+            rankingMap.getRanking(key, mTmpRanking);
+            if (mBubbleData.hasBubbleWithKey(key) && !mTmpRanking.canBubble()) {
+                mBubbleData.notificationEntryRemoved(entry, BubbleController.DISMISS_BLOCKED);
+            } else if (entry != null && mTmpRanking.isBubble()) {
+                entry.setFlagBubble(true);
+                onEntryUpdated(entry);
+            }
+        }
     }
 
     @SuppressWarnings("FieldCanBeLocal")
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 4876c57..ff5e13c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -26,7 +26,6 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.service.notification.NotificationListenerService;
-import android.service.notification.NotificationListenerService.RankingMap;
 import android.util.Log;
 import android.util.Pair;
 
@@ -226,6 +225,14 @@
     Bubble getOrCreateBubble(NotificationEntry entry) {
         Bubble bubble = getBubbleWithKey(entry.getKey());
         if (bubble == null) {
+            for (int i = 0; i < mOverflowBubbles.size(); i++) {
+                Bubble b = mOverflowBubbles.get(i);
+                if (b.getKey().equals(entry.getKey())) {
+                    mOverflowBubbles.remove(b);
+                    mPendingBubbles.add(b);
+                    return b;
+                }
+            }
             // Check for it in pending
             for (int i = 0; i < mPendingBubbles.size(); i++) {
                 Bubble b = mPendingBubbles.get(i);
@@ -290,31 +297,6 @@
     }
 
     /**
-     * Called when NotificationListener has received adjusted notification rank and reapplied
-     * filtering and sorting. This is used to dismiss any bubbles which should no longer be shown
-     * due to changes in permissions on the notification channel or the global setting.
-     *
-     * @param rankingMap the updated ranking map from NotificationListenerService
-     */
-    public void notificationRankingUpdated(RankingMap rankingMap) {
-        if (mTmpRanking == null) {
-            mTmpRanking = new NotificationListenerService.Ranking();
-        }
-
-        String[] orderedKeys = rankingMap.getOrderedKeys();
-        for (int i = 0; i < orderedKeys.length; i++) {
-            String key = orderedKeys[i];
-            if (hasBubbleWithKey(key)) {
-                rankingMap.getRanking(key, mTmpRanking);
-                if (!mTmpRanking.canBubble()) {
-                    doRemove(key, BubbleController.DISMISS_BLOCKED);
-                }
-            }
-        }
-        dispatchPendingChanges();
-    }
-
-    /**
      * Adds a group key indicating that the summary for this group should be suppressed.
      *
      * @param groupKey the group key of the group whose summary should be suppressed.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 0778d5b..e666fb5 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -471,7 +471,8 @@
         mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
         int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
 
-        mStackAnimationController = new StackAnimationController(floatingContentCoordinator);
+        mStackAnimationController = new StackAnimationController(
+                floatingContentCoordinator, this::getBubbleCount);
 
         mExpandedAnimationController = new ExpandedAnimationController(
                 mDisplaySize, mExpandedViewPadding, res.getConfiguration().orientation);
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 3eaa90c..9b5dc31 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -409,11 +409,8 @@
         mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
 
         // Includes overflow button.
-        // TODO(b/148675523) this is a temporary work around; change back once we have proper fix.
-//        float totalGapWidth = getWidthForDisplayingBubbles() - (mExpandedViewPadding * 2)
-//                - (mBubblesMaxRendered + 1) * mBubbleSizePx;
-        float totalGapWidth = getAvailableScreenWidth(true /* includeStableInsets */)
-                - (mExpandedViewPadding * 2) - (mBubblesMaxRendered + 1) * mBubbleSizePx;
+        float totalGapWidth = getWidthForDisplayingBubbles() - (mExpandedViewPadding * 2)
+                - (mBubblesMaxRendered + 1) * mBubbleSizePx;
         mSpaceBetweenBubbles = totalGapWidth / mBubblesMaxRendered;
 
         // Ensure that all child views are at 1x scale, and visible, in case they were animating
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 86387f1..b63ba6f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -43,6 +43,7 @@
 import java.io.PrintWriter;
 import java.util.HashMap;
 import java.util.Set;
+import java.util.function.IntSupplier;
 
 /**
  * Animation controller for bubbles when they're in their stacked state. Stacked bubbles sit atop
@@ -241,9 +242,15 @@
         }
     };
 
+    /** Returns the number of 'real' bubbles (excluding the overflow bubble). */
+    private IntSupplier mBubbleCountSupplier;
+
     public StackAnimationController(
-            FloatingContentCoordinator floatingContentCoordinator) {
+            FloatingContentCoordinator floatingContentCoordinator,
+            IntSupplier bubbleCountSupplier) {
         mFloatingContentCoordinator = floatingContentCoordinator;
+        mBubbleCountSupplier = bubbleCountSupplier;
+
     }
 
     /**
@@ -669,6 +676,8 @@
                 new SpringAnimation(this, firstBubbleProperty)
                         .setSpring(spring)
                         .addEndListener((dynamicAnimation, b, v, v1) -> {
+                            mRestingStackPosition.set(mStackPosition);
+
                             if (after != null) {
                                 for (Runnable callback : after) {
                                     callback.run();
@@ -736,7 +745,7 @@
             return;
         }
 
-        if (mLayout.getChildCount() == 1) {
+        if (getBubbleCount() == 1) {
             // If this is the first child added, position the stack in its starting position.
             moveStackToStartPosition();
         } else if (isStackPositionSet() && mLayout.indexOfChild(child) == 0) {
@@ -758,7 +767,7 @@
                 .start();
 
         // If there are other bubbles, pull them into the correct position.
-        if (mLayout.getChildCount() > 0) {
+        if (getBubbleCount() > 0) {
             animationForChildAtIndex(0).translationX(mStackPosition.x).start();
         } else {
             // When all children are removed ensure stack position is sane
@@ -979,6 +988,11 @@
         return mMagnetizedStack;
     }
 
+    /** Returns the number of 'real' bubbles (excluding overflow). */
+    private int getBubbleCount() {
+        return mBubbleCountSupplier.getAsInt();
+    }
+
     /**
      * FloatProperty that uses {@link #moveFirstBubbleWithStackFollowing} to set the first bubble's
      * translation and animate the rest of the stack with it. A DynamicAnimation can animate this
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 6d16253..b99d765 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -116,16 +116,17 @@
 import javax.inject.Inject;
 
 /**
- * Helper to show the global actions dialog.  Each item is an {@link Action} that
- * may show depending on whether the keyguard is showing, and whether the device
- * is provisioned.
+ * Helper to show the global actions dialog.  Each item is an {@link Action} that may show depending
+ * on whether the keyguard is showing, and whether the device is provisioned.
  */
 public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
-        DialogInterface.OnShowListener, ConfigurationController.ConfigurationListener {
+        DialogInterface.OnShowListener,
+        ConfigurationController.ConfigurationListener,
+        GlobalActionsPanelPlugin.Callbacks {
 
-    static public final String SYSTEM_DIALOG_REASON_KEY = "reason";
-    static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
-    static public final String SYSTEM_DIALOG_REASON_DREAM = "dream";
+    public static final String SYSTEM_DIALOG_REASON_KEY = "reason";
+    public static final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
+    public static final String SYSTEM_DIALOG_REASON_DREAM = "dream";
 
     private static final String TAG = "GlobalActionsDialog";
 
@@ -271,8 +272,9 @@
             @Override
             public void onUnlockedChanged() {
                 if (mDialog != null && mDialog.mPanelController != null) {
-                    boolean locked = !keyguardStateController.canDismissLockScreen();
-                    mDialog.mPanelController.onDeviceLockStateChanged(locked);
+                    boolean unlocked = keyguardStateController.isUnlocked()
+                            || keyguardStateController.canDismissLockScreen();
+                    mDialog.mPanelController.onDeviceLockStateChanged(unlocked);
                 }
             }
         });
@@ -386,7 +388,7 @@
                 mItems.add(getSettingsAction());
             } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
                 if (Settings.Secure.getIntForUser(mContentResolver,
-                            Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, getCurrentUser().id) != 0
+                        Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, getCurrentUser().id) != 0
                         && shouldDisplayLockdown()) {
                     mItems.add(getLockdownAction());
                 }
@@ -420,26 +422,7 @@
 
         mAdapter = new MyAdapter();
 
-        GlobalActionsPanelPlugin.PanelViewController panelViewController =
-                mPanelPlugin != null
-                        ? mPanelPlugin.onPanelShown(
-                                new GlobalActionsPanelPlugin.Callbacks() {
-                                    @Override
-                                    public void dismissGlobalActionsMenu() {
-                                        dismissDialog();
-                                    }
-
-                                    @Override
-                                    public void startPendingIntentDismissingKeyguard(
-                                            PendingIntent intent) {
-                                        mActivityStarter
-                                                .startPendingIntentDismissingKeyguard(intent);
-                                    }
-                                },
-                                !mKeyguardStateController.isUnlocked())
-                        : null;
-
-        ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController,
+        ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, getWalletPanelViewController(),
                 mDepthController, mSysuiColorExtractor, mStatusBarService,
                 mNotificationShadeWindowController,
                 shouldShowControls() ? mControlsUiController : null, mBlurUtils);
@@ -477,6 +460,33 @@
         mConfigurationController.removeCallback(this);
     }
 
+    @Nullable
+    private GlobalActionsPanelPlugin.PanelViewController getWalletPanelViewController() {
+        if (mPanelPlugin == null) {
+            return null;
+        }
+        return mPanelPlugin.onPanelShown(this, !mKeyguardStateController.isUnlocked());
+    }
+
+    /**
+     * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is
+     * called when the quick access wallet requests dismissal.
+     */
+    @Override
+    public void dismissGlobalActionsMenu() {
+        dismissDialog();
+    }
+
+    /**
+     * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is
+     * called when the quick access wallet requests that an intent be started (with lock screen
+     * shown first if needed).
+     */
+    @Override
+    public void startPendingIntentDismissingKeyguard(PendingIntent pendingIntent) {
+        mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent);
+    }
+
     private final class PowerAction extends SinglePressAction implements LongPressAction {
         private PowerAction() {
             super(R.drawable.ic_lock_power_off,
@@ -919,7 +929,9 @@
         }
     }
 
-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     public void onDismiss(DialogInterface dialog) {
         if (mDialog == dialog) {
             mDialog = null;
@@ -935,17 +947,19 @@
         }
     }
 
-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     public void onShow(DialogInterface dialog) {
         mMetricsLogger.visible(MetricsEvent.POWER_MENU);
     }
 
     /**
-     * The adapter used for the list within the global actions dialog, taking
-     * into account whether the keyguard is showing via
-     * {@link com.android.systemui.globalactions.GlobalActionsDialog#mKeyguardShowing} and whether
-     * the device is provisioned
-     * via {@link com.android.systemui.globalactions.GlobalActionsDialog#mDeviceProvisioned}.
+     * The adapter used for the list within the global actions dialog, taking into account whether
+     * the keyguard is showing via
+     * {@link com.android.systemui.globalactions.GlobalActionsDialog#mKeyguardShowing}
+     * and whether the device is provisioned via
+     * {@link com.android.systemui.globalactions.GlobalActionsDialog#mDeviceProvisioned}.
      */
     public class MyAdapter extends MultiListAdapter {
         private int countItems(boolean separated) {
@@ -1076,8 +1090,7 @@
      */
     public interface Action {
         /**
-         * @return Text that will be announced when dialog is created.  null
-         * for none.
+         * @return Text that will be announced when dialog is created.  null for none.
          */
         CharSequence getLabelForAccessibility(Context context);
 
@@ -1086,15 +1099,13 @@
         void onPress();
 
         /**
-         * @return whether this action should appear in the dialog when the keygaurd
-         * is showing.
+         * @return whether this action should appear in the dialog when the keygaurd is showing.
          */
         boolean showDuringKeyguard();
 
         /**
-         * @return whether this action should appear in the dialog before the
-         * device is provisioned.onlongpress
-         *
+         * @return whether this action should appear in the dialog before the device is
+         * provisioned.onlongpress
          */
         boolean showBeforeProvisioning();
 
@@ -1113,8 +1124,7 @@
     }
 
     /**
-     * A single press action maintains no state, just responds to a press
-     * and takes an action.
+     * A single press action maintains no state, just responds to a press and takes an action.
      */
 
     private abstract class SinglePressAction implements Action {
@@ -1188,8 +1198,8 @@
     }
 
     /**
-     * A toggle action knows whether it is on or off, and displays an icon
-     * and status message accordingly.
+     * A toggle action knows whether it is on or off, and displays an icon and status message
+     * accordingly.
      */
     private static abstract class ToggleAction implements Action {
 
@@ -1239,8 +1249,7 @@
         }
 
         /**
-         * Override to make changes to resource IDs just before creating the
-         * View.
+         * Override to make changes to resource IDs just before creating the View.
          */
         void willCreate() {
 
@@ -1296,9 +1305,9 @@
         }
 
         /**
-         * Implementations may override this if their state can be in on of the intermediate
-         * states until some notification is received (e.g airplane mode is 'turning off' until
-         * we know the wireless connections are back online
+         * Implementations may override this if their state can be in on of the intermediate states
+         * until some notification is received (e.g airplane mode is 'turning off' until we know the
+         * wireless connections are back online
          *
          * @param buttonOn Whether the button was turned on or off
          */
@@ -1316,12 +1325,13 @@
     private class AirplaneModeAction extends ToggleAction {
         AirplaneModeAction() {
             super(
-                R.drawable.ic_lock_airplane_mode,
-                R.drawable.ic_lock_airplane_mode_off,
-                R.string.global_actions_toggle_airplane_mode,
-                R.string.global_actions_airplane_mode_on_status,
-                R.string.global_actions_airplane_mode_off_status);
+                    R.drawable.ic_lock_airplane_mode,
+                    R.drawable.ic_lock_airplane_mode_off,
+                    R.string.global_actions_toggle_airplane_mode,
+                    R.string.global_actions_airplane_mode_on_status,
+                    R.string.global_actions_airplane_mode_off_status);
         }
+
         void onToggle(boolean on) {
             if (mHasTelephony && TelephonyProperties.in_ecm_mode().orElse(false)) {
                 mIsWaitingForEcmExit = true;
@@ -1607,11 +1617,11 @@
             window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
             window.addFlags(
                     WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                    | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
-                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                            | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+                            | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                            | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                            | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
             window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
             window.getAttributes().setFitInsetsTypes(0 /* types */);
             setTitle(R.string.global_actions);
@@ -1755,7 +1765,8 @@
 
         /**
          * Updates background and system bars according to current GradientColors.
-         * @param colors Colors and hints to use.
+         *
+         * @param colors  Colors and hints to use.
          * @param animate Interpolates gradient if true, just sets otherwise.
          */
         private void updateColors(GradientColors colors, boolean animate) {
@@ -1921,8 +1932,8 @@
     }
 
     /**
-     * Determines whether or not the Global Actions menu should be forced to
-     * use the newer grid-style layout.
+     * Determines whether or not the Global Actions menu should be forced to use the newer
+     * grid-style layout.
      */
     private static boolean isForceGridEnabled(Context context) {
         return isPanelDebugModeEnabled(context);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 3d708a9..226ac16c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -91,8 +91,10 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.NavigationModeController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -374,6 +376,7 @@
 
     private boolean mLockLater;
     private boolean mShowHomeOverLockscreen;
+    private boolean mInGestureNavigationMode;
 
     private boolean mWakeAndUnlocking;
     private IKeyguardDrawnCallback mDrawnCallback;
@@ -720,7 +723,8 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor, DumpManager dumpManager,
             @UiBackground Executor uiBgExecutor, PowerManager powerManager,
             TrustManager trustManager,
-            DeviceConfigProxy deviceConfig) {
+            DeviceConfigProxy deviceConfig,
+            NavigationModeController navigationModeController) {
         super(context);
         mFalsingManager = falsingManager;
         mLockPatternUtils = lockPatternUtils;
@@ -742,6 +746,10 @@
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 mHandler::post,
                 mOnPropertiesChangedListener);
+        mInGestureNavigationMode =
+                QuickStepContract.isGesturalMode(navigationModeController.addListener(mode -> {
+                    mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
+                }));
     }
 
     public void userActivity() {
@@ -2013,7 +2021,7 @@
             // windows that appear on top, ever
             int flags = StatusBarManager.DISABLE_NONE;
             if (forceHideHomeRecentsButtons || isShowingAndNotOccluded()) {
-                if (!mShowHomeOverLockscreen) {
+                if (!mShowHomeOverLockscreen || !mInGestureNavigationMode) {
                     flags |= StatusBarManager.DISABLE_HOME;
                 }
                 flags |= StatusBarManager.DISABLE_RECENT;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 9be4786..7a63a5e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.phone.NavigationModeController;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -64,7 +65,8 @@
             PowerManager powerManager,
             TrustManager trustManager,
             @UiBackground Executor uiBgExecutor,
-            DeviceConfigProxy deviceConfig) {
+            DeviceConfigProxy deviceConfig,
+            NavigationModeController navigationModeController) {
         return new KeyguardViewMediator(
                 context,
                 falsingManager,
@@ -78,6 +80,7 @@
                 uiBgExecutor,
                 powerManager,
                 trustManager,
-                deviceConfig);
+                deviceConfig,
+                navigationModeController);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index fb348f4..88491b7 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -67,7 +67,7 @@
 
     private ComponentName mLastPipComponentName;
     private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
-    private Size mReentrySize = null;
+    private Size mReentrySize;
 
     private float mDefaultAspectRatio;
     private float mMinAspectRatio;
@@ -77,6 +77,7 @@
     private int mDefaultMinSize;
     private Point mScreenEdgeInsets;
     private int mCurrentMinSize;
+    private Size mOverrideMinimalSize;
 
     private boolean mIsImeShowing;
     private int mImeHeight;
@@ -226,11 +227,14 @@
     /**
      * @return {@link Rect} of the destination PiP window bounds.
      */
-    Rect getDestinationBounds(float aspectRatio, Rect bounds) {
+    Rect getDestinationBounds(float aspectRatio, Rect bounds, Size minimalSize) {
         final Rect destinationBounds;
         final Rect defaultBounds = getDefaultBounds(mReentrySnapFraction, mReentrySize);
         if (bounds == null) {
             destinationBounds = new Rect(defaultBounds);
+            if (mReentrySnapFraction == INVALID_SNAP_FRACTION && mReentrySize == null) {
+                mOverrideMinimalSize = minimalSize;
+            }
         } else {
             destinationBounds = new Rect(bounds);
         }
@@ -335,7 +339,6 @@
      */
     private void transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio,
             boolean useCurrentMinEdgeSize) {
-
         // Save the snap fraction and adjust the size based on the new aspect ratio.
         final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
                 getMovementBounds(stackBounds));
@@ -354,10 +357,38 @@
         final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
         final int top = (int) (stackBounds.centerY() - size.getHeight() / 2f);
         stackBounds.set(left, top, left + size.getWidth(), top + size.getHeight());
+        // apply the override minimal size if applicable, this minimal size is specified by app
+        if (mOverrideMinimalSize != null) {
+            transformBoundsToMinimalSize(stackBounds, aspectRatio, mOverrideMinimalSize);
+        }
         mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
     }
 
     /**
+     * Transforms a given bounds to meet the minimal size constraints.
+     * This function assumes the given {@param stackBounds} qualifies {@param aspectRatio}.
+     */
+    private void transformBoundsToMinimalSize(Rect stackBounds, float aspectRatio,
+            Size minimalSize) {
+        if (minimalSize == null) return;
+        final Size adjustedMinimalSize;
+        final float minimalSizeAspectRatio =
+                minimalSize.getWidth() / (float) minimalSize.getHeight();
+        if (minimalSizeAspectRatio > aspectRatio) {
+            // minimal size is wider, fixed the width and increase the height
+            adjustedMinimalSize = new Size(
+                    minimalSize.getWidth(), (int) (minimalSize.getWidth() / aspectRatio));
+        } else {
+            adjustedMinimalSize = new Size(
+                    (int) (minimalSize.getHeight() * aspectRatio), minimalSize.getHeight());
+        }
+        final Rect containerBounds = new Rect(stackBounds);
+        Gravity.apply(mDefaultStackGravity,
+                adjustedMinimalSize.getWidth(), adjustedMinimalSize.getHeight(),
+                containerBounds, stackBounds);
+    }
+
+    /**
      * @return the default bounds to show the PIP, if a {@param snapFraction} and {@param size} are
      * provided, then it will apply the default bounds to the provided snap fraction and size.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 4a4a638..4076c1b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -30,11 +30,13 @@
 import android.app.ITaskOrganizerController;
 import android.app.PictureInPictureParams;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Size;
 import android.view.ITaskOrganizer;
 import android.view.IWindowContainer;
 import android.view.SurfaceControl;
@@ -204,26 +206,12 @@
         mOneShotAnimationType = animationType;
     }
 
-    /**
-     * Callback to issue the final {@link WindowContainerTransaction} on end of movements.
-     * @param destinationBounds the final bounds.
-     */
-    public void onMotionMovementEnd(Rect destinationBounds) {
-        try {
-            mLastReportedBounds.set(destinationBounds);
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
-            wct.setBounds(mToken, destinationBounds);
-            mTaskOrganizerController.applyContainerTransaction(wct, null /* ITaskOrganizer */);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to apply window container transaction", e);
-        }
-    }
-
     @Override
     public void taskAppeared(ActivityManager.RunningTaskInfo info) {
         Objects.requireNonNull(info, "Requires RunningTaskInfo");
         final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                getAspectRatioOrDefault(info.pictureInPictureParams), null /* bounds */);
+                getAspectRatioOrDefault(info.pictureInPictureParams),
+                null /* bounds */, getMinimalSize(info.topActivityInfo));
         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
         mTaskInfo = info;
         mToken = mTaskInfo.token;
@@ -276,7 +264,8 @@
             return;
         }
         final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                getAspectRatioOrDefault(newParams), null /* bounds */);
+                getAspectRatioOrDefault(newParams),
+                null /* bounds */, getMinimalSize(info.topActivityInfo));
         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
         scheduleAnimateResizePip(destinationBounds, mEnterExitAnimationDuration, null);
     }
@@ -446,6 +435,14 @@
                 .start());
     }
 
+    private Size getMinimalSize(ActivityInfo activityInfo) {
+        if (activityInfo == null || activityInfo.windowLayout == null) {
+            return null;
+        }
+        final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout;
+        return new Size(windowLayout.minWidth, windowLayout.minHeight);
+    }
+
     private float getAspectRatioOrDefault(@Nullable PictureInPictureParams params) {
         return params == null
                 ? mPipBoundsHandler.getDefaultAspectRatio()
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 15a0088..449a2bc 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -426,7 +426,7 @@
         cancelAnimations();
 
         mAnimatedBoundsPhysicsAnimator
-                .withEndActions(() ->  mPipTaskOrganizer.onMotionMovementEnd(mAnimatedBounds))
+                .withEndActions(() ->  mPipTaskOrganizer.scheduleFinishResizePip(mAnimatedBounds))
                 .addUpdateListener(mResizePipUpdateListener)
                 .start();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index ea5ec05..d7eab3a 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -31,7 +31,6 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.provider.Settings;
-import android.util.Log;
 import android.util.Slog;
 import android.view.IWindowContainer;
 import android.view.LayoutInflater;
@@ -73,7 +72,7 @@
         DisplayController.OnDisplaysChangedListener {
     private static final String TAG = "Divider";
 
-    static final boolean DEBUG = true;
+    static final boolean DEBUG = false;
 
     static final int DEFAULT_APP_TRANSITION_DURATION = 336;
     static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
@@ -173,6 +172,9 @@
         @Nullable
         private ValueAnimator mAnimation = null;
 
+        private boolean mPaused = true;
+        private boolean mPausedTargetAdjusted = false;
+
         private boolean getSecondaryHasFocus(int displayId) {
             try {
                 IWindowContainer imeSplit = ActivityTaskManager.getTaskOrganizerController()
@@ -185,6 +187,14 @@
             return false;
         }
 
+        private void updateDimTargets() {
+            final boolean splitIsVisible = !mView.isHidden();
+            mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible)
+                    ? ADJUSTED_NONFOCUS_DIM : 0.f;
+            mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible)
+                    ? ADJUSTED_NONFOCUS_DIM : 0.f;
+        }
+
         @Override
         public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
                 boolean imeShouldShow, SurfaceControl.Transaction t) {
@@ -193,18 +203,31 @@
             }
             final boolean splitIsVisible = !mView.isHidden();
             mSecondaryHasFocus = getSecondaryHasFocus(displayId);
-            mTargetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus
+            final boolean targetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus
                     && !mSplitLayout.mDisplayLayout.isLandscape();
             mHiddenTop = hiddenTop;
             mShownTop = shownTop;
             mTargetShown = imeShouldShow;
             if (mLastAdjustTop < 0) {
                 mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop;
+            } else {
+                // Check for an "interruption" of an existing animation. In this case, we need to
+                // fake-flip the last-known state direction so that the animation completes in the
+                // other direction.
+                if (mTargetAdjusted != targetAdjusted && targetAdjusted == mAdjusted) {
+                    if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) {
+                        mAdjusted = mTargetAdjusted;
+                    }
+                }
             }
-            mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible)
-                    ? ADJUSTED_NONFOCUS_DIM : 0.f;
-            mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible)
-                    ? ADJUSTED_NONFOCUS_DIM : 0.f;
+            if (mPaused) {
+                mPausedTargetAdjusted = targetAdjusted;
+                if (DEBUG) Slog.d(TAG, " ime starting but paused " + dumpState());
+                return;
+            }
+            mTargetAdjusted = targetAdjusted;
+            updateDimTargets();
+            if (DEBUG) Slog.d(TAG, " ime starting. vis:" + splitIsVisible + "  " + dumpState());
             if (mAnimation != null || (mImeWasShown && imeShouldShow
                     && mTargetAdjusted != mAdjusted)) {
                 // We need to animate adjustment independently of the IME position, so
@@ -259,7 +282,7 @@
         @Override
         public void onImePositionChanged(int displayId, int imeTop,
                 SurfaceControl.Transaction t) {
-            if (mAnimation != null || !inSplitMode()) {
+            if (mAnimation != null || !inSplitMode() || mPaused) {
                 // Not synchronized with IME anymore, so return.
                 return;
             }
@@ -271,7 +294,7 @@
         @Override
         public void onImeEndPositioning(int displayId, boolean cancelled,
                 SurfaceControl.Transaction t) {
-            if (mAnimation != null || !inSplitMode()) {
+            if (mAnimation != null || !inSplitMode() || mPaused) {
                 // Not synchronized with IME anymore, so return.
                 return;
             }
@@ -279,7 +302,7 @@
         }
 
         private void onProgress(float progress, SurfaceControl.Transaction t) {
-            if (mTargetAdjusted != mAdjusted) {
+            if (mTargetAdjusted != mAdjusted && !mPaused) {
                 final float fraction = mTargetAdjusted ? progress : 1.f - progress;
                 mLastAdjustTop = (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop);
                 mSplitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop);
@@ -342,6 +365,52 @@
             });
             mAnimation.start();
         }
+
+        private String dumpState() {
+            return "top:" + mHiddenTop + "->" + mShownTop
+                    + " adj:" + mAdjusted + "->" + mTargetAdjusted + "(" + mLastAdjustTop + ")"
+                    + " shw:" + mImeWasShown + "->" + mTargetShown
+                    + " dims:" + mLastPrimaryDim + "," + mLastSecondaryDim
+                    + "->" + mTargetPrimaryDim + "," + mTargetSecondaryDim
+                    + " shf:" + mSecondaryHasFocus + " desync:" + (mAnimation != null)
+                    + " paus:" + mPaused + "[" + mPausedTargetAdjusted + "]";
+        }
+
+        /** Completely aborts/resets adjustment state */
+        public void pause(int displayId) {
+            if (DEBUG) Slog.d(TAG, "ime pause posting " + dumpState());
+            mHandler.post(() -> {
+                if (DEBUG) Slog.d(TAG, "ime pause run posted " + dumpState());
+                if (mPaused) {
+                    return;
+                }
+                mPaused = true;
+                mPausedTargetAdjusted = mTargetAdjusted;
+                mTargetAdjusted = false;
+                mTargetPrimaryDim = mTargetSecondaryDim = 0.f;
+                updateImeAdjustState();
+                startAsyncAnimation();
+            });
+        }
+
+        public void resume(int displayId) {
+            if (DEBUG) Slog.d(TAG, "ime resume posting " + dumpState());
+            mHandler.post(() -> {
+                if (DEBUG) Slog.d(TAG, "ime resume run posted " + dumpState());
+                if (!mPaused) {
+                    return;
+                }
+                mPaused = false;
+                mTargetAdjusted = mPausedTargetAdjusted;
+                updateDimTargets();
+                if ((mTargetAdjusted != mAdjusted) && !mMinimized && mView != null) {
+                    // End unminimize animations since they conflict with adjustment animations.
+                    mView.finishAnimations();
+                }
+                updateImeAdjustState();
+                startAsyncAnimation();
+            });
+        }
     }
     private final DividerImeController mImePositionProcessor = new DividerImeController();
 
@@ -493,6 +562,7 @@
     }
 
     void updateVisibility(final boolean visible) {
+        if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible);
         if (mVisible != visible) {
             mVisible = visible;
             mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
@@ -519,12 +589,21 @@
 
     /** Switch to minimized state if appropriate */
     public void setMinimized(final boolean minimized) {
+        if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible);
         mHandler.post(() -> {
+            if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible);
+            if (!mVisible) {
+                return;
+            }
             setHomeMinimized(minimized, mHomeStackResizable);
         });
     }
 
     private void setHomeMinimized(final boolean minimized, boolean homeStackResizable) {
+        if (DEBUG) {
+            Slog.d(TAG, "setHomeMinimized  min:" + mMinimized + "->" + minimized + " hrsz:"
+                    + mHomeStackResizable + "->" + homeStackResizable + " split:" + inSplitMode());
+        }
         WindowContainerTransaction wct = new WindowContainerTransaction();
         // Update minimized state
         if (mMinimized != minimized) {
@@ -544,7 +623,17 @@
 
         // Sync state to DividerView if it exists.
         if (mView != null) {
+            final int displayId = mView.getDisplay() != null
+                    ? mView.getDisplay().getDisplayId() : DEFAULT_DISPLAY;
+            // pause ime here (before updateMinimizedDockedStack)
+            if (mMinimized) {
+                mImePositionProcessor.pause(displayId);
+            }
             mView.setMinimizedDockStack(minimized, getAnimDuration(), homeStackResizable);
+            if (!mMinimized) {
+                // afterwards so it can end any animations started in view
+                mImePositionProcessor.resume(displayId);
+            }
         }
         updateTouchable();
         WindowManagerProxy.applyContainerTransaction(wct);
@@ -649,7 +738,7 @@
         if (!inSplitMode()) {
             // Wasn't in split-mode yet, so enter now.
             if (DEBUG) {
-                Log.d(TAG, " entering split mode with minimized=true");
+                Slog.d(TAG, " entering split mode with minimized=true");
             }
             updateVisibility(true /* visible */);
         }
@@ -660,7 +749,7 @@
         if (!inSplitMode()) {
             // Wasn't in split-mode, so enter now.
             if (DEBUG) {
-                Log.d(TAG, " enter split mode unminimized ");
+                Slog.d(TAG, " enter split mode unminimized ");
             }
             updateVisibility(true /* visible */);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 4114bb9..131f4e1 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -34,6 +34,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.util.AttributeSet;
+import android.util.Slog;
 import android.view.Choreographer;
 import android.view.Display;
 import android.view.InsetsState;
@@ -67,12 +68,15 @@
 import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 
+import java.util.function.Consumer;
+
 /**
  * Docked stack divider.
  */
 public class DividerView extends FrameLayout implements OnTouchListener,
         OnComputeInternalInsetsListener {
     private static final String TAG = "DividerView";
+    private static final boolean DEBUG = Divider.DEBUG;
 
     public interface DividerCallbacks {
         void onDraggingStart();
@@ -627,6 +631,7 @@
             cancelFlingAnimation();
             updateDockSide();
         }
+        if (DEBUG) Slog.d(TAG, "Getting fling " + position + "->" + snapTarget.position);
         final boolean taskPositionSameAtEnd = snapTarget.flag == SnapTarget.FLAG_NONE;
         ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
         anim.addUpdateListener(animation -> resizeStackDelayed((int) animation.getAnimatedValue(),
@@ -634,16 +639,21 @@
                         ? TASK_POSITION_SAME
                         : snapTarget.taskPosition,
                 snapTarget));
-        Runnable endAction = () -> {
+        Consumer<Boolean> endAction = cancelled -> {
+            if (DEBUG) Slog.d(TAG, "End Fling " + cancelled + " min:" + mIsInMinimizeInteraction);
+            final boolean wasMinimizeInteraction = mIsInMinimizeInteraction;
+            // Reset minimized divider position after unminimized state animation finishes.
+            if (!cancelled && !mDockedStackMinimized && mIsInMinimizeInteraction) {
+                mIsInMinimizeInteraction = false;
+            }
             boolean dismissed = commitSnapFlags(snapTarget);
             mWindowManagerProxy.setResizing(false);
             updateDockSide();
             mCurrentAnimator = null;
             mEntranceAnimationRunning = false;
             mExitAnimationRunning = false;
-            if (!dismissed) {
-                WindowManagerProxy.applyResizeSplits((mIsInMinimizeInteraction
-                        ? mSnapTargetBeforeMinimized : snapTarget).position, mSplitLayout);
+            if (!dismissed && !wasMinimizeInteraction) {
+                WindowManagerProxy.applyResizeSplits(snapTarget.position, mSplitLayout);
             }
             if (mCallback != null) {
                 mCallback.onDraggingEnd();
@@ -667,12 +677,6 @@
                 }
             }
         };
-        Runnable notCancelledEndAction = () -> {
-            // Reset minimized divider position after unminimized state animation finishes
-            if (!mDockedStackMinimized && mIsInMinimizeInteraction) {
-                mIsInMinimizeInteraction = false;
-            }
-        };
         anim.addListener(new AnimatorListenerAdapter() {
 
             private boolean mCancelled;
@@ -694,15 +698,11 @@
                     delay = mSfChoreographer.getSurfaceFlingerOffsetMs();
                 }
                 if (delay == 0) {
-                    if (!mCancelled) {
-                        notCancelledEndAction.run();
-                    }
-                    endAction.run();
+                    endAction.accept(mCancelled);
                 } else {
-                    if (!mCancelled) {
-                        mHandler.postDelayed(notCancelledEndAction, delay);
-                    }
-                    mHandler.postDelayed(endAction, delay);
+                    final Boolean cancelled = mCancelled;
+                    if (DEBUG) Slog.d(TAG, "Posting endFling " + cancelled + " d:" + delay + "ms");
+                    mHandler.postDelayed(() -> endAction.accept(cancelled), delay);
                 }
             }
         });
@@ -899,6 +899,7 @@
 
     public void setMinimizedDockStack(boolean minimized, long animDuration,
             boolean isHomeStackResizable) {
+        if (DEBUG) Slog.d(TAG, "setMinDock: " + mDockedStackMinimized + "->" + minimized);
         mHomeStackResizable = isHomeStackResizable;
         updateDockSide();
         if (!isHomeStackResizable) {
@@ -946,6 +947,15 @@
                 .start();
     }
 
+    // Needed to end any currently playing animations when they might compete with other anims
+    // (specifically, IME adjust animation immediately after leaving minimized). Someday maybe
+    // these can be unified, but not today.
+    void finishAnimations() {
+        if (mCurrentAnimator != null) {
+            mCurrentAnimator.end();
+        }
+    }
+
     public void setAdjustedForIme(boolean adjustedForIme, long animDuration) {
         if (mAdjustedForIme == adjustedForIme) {
             return;
@@ -1061,6 +1071,11 @@
         mDividerPositionX = dockedRect.right;
         mDividerPositionY = dockedRect.bottom;
 
+        if (DEBUG) {
+            Slog.d(TAG, "Resizing split surfaces: " + dockedRect + " " + dockedTaskRect
+                    + " " + otherRect + " " + otherTaskRect);
+        }
+
         t.setPosition(mTiles.mPrimarySurface, dockedTaskRect.left, dockedTaskRect.top);
         Rect crop = new Rect(dockedRect);
         crop.offsetTo(-Math.min(dockedTaskRect.left - dockedRect.left, 0),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
index 4597b16..b33424c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
@@ -33,6 +33,7 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry.OnSensitivityChangedListener;
 
 import java.util.List;
 
@@ -159,20 +160,30 @@
     }
 
     public void setEntry(NotificationEntry entry) {
-        if (entry != null) {
-            mShowingEntry = entry;
+        if (mShowingEntry != null) {
+            mShowingEntry.removeOnSensitivityChangedListener(mOnSensitivityChangedListener);
+        }
+        mShowingEntry = entry;
+
+        if (mShowingEntry != null) {
             CharSequence text = entry.headsUpStatusBarText;
             if (entry.isSensitive()) {
                 text = entry.headsUpStatusBarTextPublic;
             }
             mTextView.setText(text);
-            mShowingEntry.setOnSensitiveChangedListener(() -> setEntry(entry));
-        } else if (mShowingEntry != null){
-            mShowingEntry.setOnSensitiveChangedListener(null);
-            mShowingEntry = null;
+            mShowingEntry.addOnSensitivityChangedListener(mOnSensitivityChangedListener);
         }
     }
 
+    private final OnSensitivityChangedListener mOnSensitivityChangedListener = entry -> {
+        if (entry != mShowingEntry) {
+            throw new IllegalStateException("Got a sensitivity change for " + entry
+                    + " but mShowingEntry is " + mShowingEntry);
+        }
+        // Update the text
+        setEntry(entry);
+    };
+
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 047edd2..72d9d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -206,7 +206,8 @@
                     false,
                     false,
                     false,
-                    null
+                    null,
+                    false
             );
         }
         return ranking;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 87be739..d8fdf92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -70,6 +70,7 @@
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -262,11 +263,11 @@
         synchronized (mEntryManager) {
             NotificationEntry entry = mEntryManager
                     .getActiveNotificationUnfiltered(mMediaNotificationKey);
-            if (entry == null || entry.expandedIcon == null) {
+            if (entry == null || entry.getIcons().getShelfIcon() == null) {
                 return null;
             }
 
-            return entry.expandedIcon.getSourceIcon();
+            return entry.getIcons().getShelfIcon().getSourceIcon();
         }
     }
 
@@ -284,8 +285,7 @@
         boolean metaDataChanged = false;
 
         synchronized (mEntryManager) {
-            Set<NotificationEntry> allNotifications =
-                    mEntryManager.getPendingAndActiveNotifications();
+            Collection<NotificationEntry> allNotifications = mEntryManager.getAllNotifs();
 
             // Promote the media notification with a controller in 'playing' state, if any.
             NotificationEntry mediaNotification = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 1a8454c..d7f2ae4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -329,7 +329,7 @@
                     expandableRow.setAboveShelf(false);
                 }
                 if (notGoneIndex == 0) {
-                    StatusBarIconView icon = expandableRow.getEntry().expandedIcon;
+                    StatusBarIconView icon = expandableRow.getEntry().getIcons().getShelfIcon();
                     NotificationIconContainer.IconState iconState = getIconState(icon);
                     // The icon state might be null in rare cases where the notification is actually
                     // added to the layout, but not to the shelf. An example are replied messages,
@@ -432,7 +432,7 @@
             // if the shelf is clipped, lets make sure we also clip the icon
             maxTop = Math.max(maxTop, getTranslationY() + getClipTopAmount());
         }
-        StatusBarIconView icon = row.getEntry().expandedIcon;
+        StatusBarIconView icon = row.getEntry().getIcons().getShelfIcon();
         float shelfIconPosition = getTranslationY() + icon.getTop() + icon.getTranslationY();
         if (shelfIconPosition < maxTop && !mAmbientState.isFullyHidden()) {
             int top = (int) (maxTop - shelfIconPosition);
@@ -444,7 +444,7 @@
     }
 
     private void updateContinuousClipping(final ExpandableNotificationRow row) {
-        StatusBarIconView icon = row.getEntry().expandedIcon;
+        StatusBarIconView icon = row.getEntry().getIcons().getShelfIcon();
         boolean needsContinuousClipping = ViewState.isAnimatingY(icon) && !mAmbientState.isDozing();
         boolean isContinuousClipping = icon.getTag(TAG_CONTINUOUS_CLIPPING) != null;
         if (needsContinuousClipping && !isContinuousClipping) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 7f0479c..c6d84ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -29,6 +29,7 @@
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -56,9 +57,9 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -105,6 +106,10 @@
      */
     public static final int UNDEFINED_DISMISS_REASON = 0;
 
+    private final Set<NotificationEntry> mAllNotifications = new ArraySet<>();
+    private final Set<NotificationEntry> mReadOnlyAllNotifications =
+            Collections.unmodifiableSet(mAllNotifications);
+
     /** Pending notifications are ones awaiting inflation */
     @VisibleForTesting
     protected final HashMap<String, NotificationEntry> mPendingNotifications = new HashMap<>();
@@ -468,6 +473,8 @@
                     entry.removeRow();
                 }
 
+                mAllNotifications.remove(entry);
+
                 // Let's remove the children if this was a summary
                 handleGroupSummaryRemoved(key);
                 removeVisibleNotification(key);
@@ -548,6 +555,7 @@
                 notification,
                 ranking,
                 mFgsFeatureController.isForegroundServiceDismissalEnabled());
+        mAllNotifications.add(entry);
 
         mLeakDetector.trackInstance(entry);
 
@@ -709,15 +717,6 @@
     }
 
     /**
-     * @return all notifications we're currently aware of (both pending and active notifications)
-     */
-    public Set<NotificationEntry> getPendingAndActiveNotifications() {
-        Set<NotificationEntry> allNotifs = new HashSet<>(mPendingNotifications.values());
-        allNotifs.addAll(mSortedAndFiltered);
-        return allNotifs;
-    }
-
-    /**
      * Use this method to retrieve a notification entry that has been prepared for presentation.
      * Note that the notification may be filtered out and never shown to the user.
      *
@@ -842,7 +841,7 @@
 
     private void dumpEntry(PrintWriter pw, String indent, int i, NotificationEntry e) {
         pw.print(indent);
-        pw.println("  [" + i + "] key=" + e.getKey() + " icon=" + e.icon);
+        pw.println("  [" + i + "] key=" + e.getKey() + " icon=" + e.getIcons().getStatusBarIcon());
         StatusBarNotification n = e.getSbn();
         pw.print(indent);
         pw.println("      pkg=" + n.getPackageName() + " id=" + n.getId() + " importance="
@@ -861,6 +860,15 @@
         return mReadOnlyNotifications;
     }
 
+    /**
+     * Returns a collections containing ALL notifications we know about, including ones that are
+     * hidden or for other users. See {@link CommonNotifCollection#getAllNotifs()}.
+     */
+    @Override
+    public Collection<NotificationEntry> getAllNotifs() {
+        return mReadOnlyAllNotifications;
+    }
+
     /** @return A count of the active notifications */
     public int getActiveNotificationsCount() {
         return mReadOnlyNotifications.size();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 749e5e2..b90cfa8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -64,24 +64,34 @@
 import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent;
 import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
 import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CleanUpEntryEvent;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.EntryAddedEvent;
+import com.android.systemui.statusbar.notification.collection.notifcollection.EntryRemovedEvent;
+import com.android.systemui.statusbar.notification.collection.notifcollection.EntryUpdatedEvent;
+import com.android.systemui.statusbar.notification.collection.notifcollection.InitEntryEvent;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifEvent;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.collection.notifcollection.RankingAppliedEvent;
+import com.android.systemui.statusbar.notification.collection.notifcollection.RankingUpdatedEvent;
 import com.android.systemui.util.Assert;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 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.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Queue;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -124,6 +134,8 @@
     private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
     private final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>();
 
+    private Queue<NotifEvent> mEventQueue = new ArrayDeque<>();
+
     private boolean mAttached = false;
     private boolean mAmDispatchingToOtherCode;
 
@@ -160,8 +172,8 @@
         mBuildListener = buildListener;
     }
 
-    /** @see NotifPipeline#getActiveNotifs() */
-    Collection<NotificationEntry> getActiveNotifs() {
+    /** @see NotifPipeline#getAllNotifs() */
+    Collection<NotificationEntry> getAllNotifs() {
         Assert.isMainThread();
         return mReadOnlyNotificationSet;
     }
@@ -242,7 +254,7 @@
         }
 
         locallyDismissNotifications(entriesToLocallyDismiss);
-        rebuildList();
+        dispatchEventsAndRebuildList();
     }
 
     /**
@@ -251,8 +263,7 @@
     public void dismissNotification(
             NotificationEntry entry,
             @NonNull DismissedByUserStats stats) {
-        dismissNotifications(List.of(
-                new Pair<NotificationEntry, DismissedByUserStats>(entry, stats)));
+        dismissNotifications(List.of(new Pair<>(entry, stats)));
     }
 
     /**
@@ -268,7 +279,7 @@
             // system process is dead if we're here.
         }
 
-        final List<NotificationEntry> entries = new ArrayList(getActiveNotifs());
+        final List<NotificationEntry> entries = new ArrayList<>(getAllNotifs());
         for (int i = entries.size() - 1; i >= 0; i--) {
             NotificationEntry entry = entries.get(i);
             if (!shouldDismissOnClearAll(entry, userId)) {
@@ -283,7 +294,7 @@
         }
 
         locallyDismissNotifications(entries);
-        rebuildList();
+        dispatchEventsAndRebuildList();
     }
 
     /**
@@ -326,8 +337,9 @@
     private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
         Assert.isMainThread();
 
-        postNotification(sbn, requireRanking(rankingMap, sbn.getKey()), rankingMap);
-        rebuildList();
+        postNotification(sbn, requireRanking(rankingMap, sbn.getKey()));
+        applyRanking(rankingMap);
+        dispatchEventsAndRebuildList();
     }
 
     private void onNotificationGroupPosted(List<CoalescedEvent> batch) {
@@ -336,9 +348,9 @@
         mLogger.logNotifGroupPosted(batch.get(0).getSbn().getGroupKey(), batch.size());
 
         for (CoalescedEvent event : batch) {
-            postNotification(event.getSbn(), event.getRanking(), null);
+            postNotification(event.getSbn(), event.getRanking());
         }
-        rebuildList();
+        dispatchEventsAndRebuildList();
     }
 
     private void onNotificationRemoved(
@@ -354,55 +366,49 @@
             throw new IllegalStateException("No notification to remove with key " + sbn.getKey());
         }
         entry.mCancellationReason = reason;
-        applyRanking(rankingMap);
         tryRemoveNotification(entry);
-        rebuildList();
+        applyRanking(rankingMap);
+        dispatchEventsAndRebuildList();
     }
 
     private void onNotificationRankingUpdate(RankingMap rankingMap) {
         Assert.isMainThread();
+        mEventQueue.add(new RankingUpdatedEvent(rankingMap));
         applyRanking(rankingMap);
-        dispatchNotificationRankingUpdate(rankingMap);
-        rebuildList();
+        dispatchEventsAndRebuildList();
     }
 
     private void postNotification(
             StatusBarNotification sbn,
-            Ranking ranking,
-            @Nullable RankingMap rankingMap) {
+            Ranking ranking) {
         NotificationEntry entry = mNotificationSet.get(sbn.getKey());
 
         if (entry == null) {
             // A new notification!
-            mLogger.logNotifPosted(sbn.getKey());
-
             entry = new NotificationEntry(sbn, ranking);
             mNotificationSet.put(sbn.getKey(), entry);
-            dispatchOnEntryInit(entry);
 
-            if (rankingMap != null) {
-                applyRanking(rankingMap);
-            }
-
-            dispatchOnEntryAdded(entry);
+            mLogger.logNotifPosted(sbn.getKey());
+            mEventQueue.add(new InitEntryEvent(entry));
+            mEventQueue.add(new EntryAddedEvent(entry));
 
         } else {
             // Update to an existing entry
-            mLogger.logNotifUpdated(sbn.getKey());
 
             // Notification is updated so it is essentially re-added and thus alive again, so we
             // can reset its state.
+            // TODO: If a coalesced event ever gets here, it's possible to lose track of children,
+            //  since their rankings might have been updated earlier (and thus we may no longer
+            //  think a child is associated with this locally-dismissed entry).
             cancelLocalDismissal(entry);
             cancelLifetimeExtension(entry);
             cancelDismissInterception(entry);
             entry.mCancellationReason = REASON_NOT_CANCELED;
 
             entry.setSbn(sbn);
-            if (rankingMap != null) {
-                applyRanking(rankingMap);
-            }
 
-            dispatchOnEntryUpdated(entry);
+            mLogger.logNotifUpdated(sbn.getKey());
+            mEventQueue.add(new EntryUpdatedEvent(entry));
         }
     }
 
@@ -432,8 +438,8 @@
         if (!isLifetimeExtended(entry)) {
             mNotificationSet.remove(entry.getKey());
             cancelDismissInterception(entry);
-            dispatchOnEntryRemoved(entry, entry.mCancellationReason);
-            dispatchOnEntryCleanUp(entry);
+            mEventQueue.add(new EntryRemovedEvent(entry, entry.mCancellationReason));
+            mEventQueue.add(new CleanUpEntryEvent(entry));
             return true;
         } else {
             return false;
@@ -466,9 +472,16 @@
                 }
             }
         }
+        mEventQueue.add(new RankingAppliedEvent());
     }
 
-    private void rebuildList() {
+    private void dispatchEventsAndRebuildList() {
+        mAmDispatchingToOtherCode = true;
+        while (!mEventQueue.isEmpty()) {
+            mEventQueue.remove().dispatchTo(mNotifCollectionListeners);
+        }
+        mAmDispatchingToOtherCode = false;
+
         if (mBuildListener != null) {
             mBuildListener.onBuildList(mReadOnlyNotificationSet);
         }
@@ -491,7 +504,7 @@
 
         if (!isLifetimeExtended(entry)) {
             if (tryRemoveNotification(entry)) {
-                rebuildList();
+                dispatchEventsAndRebuildList();
             }
         }
     }
@@ -660,57 +673,9 @@
                 || entry.getSbn().getUser().getIdentifier() == userId;
     }
 
-    private void dispatchOnEntryInit(NotificationEntry entry) {
-        mAmDispatchingToOtherCode = true;
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onEntryInit(entry);
-        }
-        mAmDispatchingToOtherCode = false;
-    }
-
-    private void dispatchOnEntryAdded(NotificationEntry entry) {
-        mAmDispatchingToOtherCode = true;
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onEntryAdded(entry);
-        }
-        mAmDispatchingToOtherCode = false;
-    }
-
-    private void dispatchOnEntryUpdated(NotificationEntry entry) {
-        mAmDispatchingToOtherCode = true;
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onEntryUpdated(entry);
-        }
-        mAmDispatchingToOtherCode = false;
-    }
-
-    private void dispatchNotificationRankingUpdate(RankingMap map) {
-        mAmDispatchingToOtherCode = true;
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onRankingUpdate(map);
-        }
-        mAmDispatchingToOtherCode = false;
-    }
-
-    private void dispatchOnEntryRemoved(NotificationEntry entry, @CancellationReason int reason) {
-        mAmDispatchingToOtherCode = true;
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onEntryRemoved(entry, reason);
-        }
-        mAmDispatchingToOtherCode = false;
-    }
-
-    private void dispatchOnEntryCleanUp(NotificationEntry entry) {
-        mAmDispatchingToOtherCode = true;
-        for (NotifCollectionListener listener : mNotifCollectionListeners) {
-            listener.onEntryCleanUp(entry);
-        }
-        mAmDispatchingToOtherCode = false;
-    }
-
     @Override
     public void dump(@NonNull FileDescriptor fd, PrintWriter pw, @NonNull String[] args) {
-        final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
+        final List<NotificationEntry> entries = new ArrayList<>(getAllNotifs());
 
         pw.println("\t" + TAG + " unsorted/unfiltered notifications:");
         if (entries.size() == 0) {
@@ -754,6 +719,7 @@
     private static final String TAG = "NotifCollection";
 
     @IntDef(prefix = { "REASON_" }, value = {
+            REASON_NOT_CANCELED,
             REASON_UNKNOWN,
             REASON_CLICK,
             REASON_CANCEL_ALL,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
index 14903cd..17899e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -82,14 +82,14 @@
     }
 
     /**
-     * Returns the list of "active" notifications, i.e. the notifications that are currently posted
+     * Returns the list of all known notifications, i.e. the notifications that are currently posted
      * to the phone. In general, this tracks closely to the list maintained by NotificationManager,
      * but it can diverge slightly due to lifetime extenders.
      *
      * The returned collection is read-only, unsorted, unfiltered, and ungrouped.
      */
-    public Collection<NotificationEntry> getActiveNotifs() {
-        return mNotifCollection.getActiveNotifs();
+    public Collection<NotificationEntry> getAllNotifs() {
+        return mNotifCollection.getAllNotifs();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 3e9d8a4..7019b5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -29,8 +29,6 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
 
 import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
 import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
@@ -44,10 +42,7 @@
 import android.app.Person;
 import android.app.RemoteInputHistoryItem;
 import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.content.pm.LauncherApps.ShortcutQuery;
 import android.content.pm.ShortcutInfo;
-import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.SystemClock;
@@ -55,25 +50,20 @@
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageView;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.statusbar.InflationTask;
-import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.icon.IconPack;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
 import com.android.systemui.statusbar.notification.row.NotificationGuts;
@@ -81,8 +71,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
@@ -102,15 +90,11 @@
  * clean this up in the future.
  */
 public final class NotificationEntry extends ListEntry {
-    private static final String TAG = "NotificationEntry";
 
     private final String mKey;
     private StatusBarNotification mSbn;
     private Ranking mRanking;
 
-    private StatusBarIcon mSmallIcon;
-    private StatusBarIcon mPeopleAvatar;
-
     /*
      * Bookkeeping members
      */
@@ -142,10 +126,7 @@
     * TODO: Remove every member beneath this line if possible
     */
 
-    public StatusBarIconView icon;
-    public StatusBarIconView expandedIcon;
-    public StatusBarIconView centeredIcon;
-    public StatusBarIconView aodIcon;
+    private IconPack mIcons = IconPack.buildEmptyPack(null);
     private boolean interruption;
     public int targetSdk;
     private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
@@ -191,7 +172,8 @@
     private boolean hasSentReply;
 
     private boolean mSensitive = true;
-    private Runnable mOnSensitiveChangedListener;
+    private List<OnSensitivityChangedListener> mOnSensitivityChangedListeners = new ArrayList<>();
+
     private boolean mAutoHeadsUp;
     private boolean mPulseSupressed;
     private boolean mAllowFgsDismissal;
@@ -347,6 +329,15 @@
      * TODO: Remove as many of these as possible
      */
 
+    @NonNull
+    public IconPack getIcons() {
+        return mIcons;
+    }
+
+    public void setIcons(@NonNull IconPack icons) {
+        mIcons = icons;
+    }
+
     public void setInterruption() {
         interruption = true;
     }
@@ -464,239 +455,6 @@
                 || SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
     }
 
-    /**
-     * Create the icons for a notification
-     * @param context the context to create the icons with
-     * @param sbn the notification
-     * @throws InflationException Exception if required icons are not valid or specified
-     */
-    public void createIcons(Context context, StatusBarNotification sbn)
-            throws InflationException {
-        StatusBarIcon ic = getIcon(context, sbn, false /* redact */);
-
-        // Construct the icon.
-        icon = new StatusBarIconView(context,
-                sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
-        icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
-
-        // Construct the expanded icon.
-        expandedIcon = new StatusBarIconView(context,
-                sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
-        expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
-
-        // Construct the expanded icon.
-        aodIcon = new StatusBarIconView(context,
-                sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
-        aodIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
-        aodIcon.setIncreasedSize(true);
-
-        try {
-            setIcons(ic, Collections.singletonList(icon));
-            if (isSensitive()) {
-                ic = getIcon(context, sbn, true /* redact */);
-            }
-            setIcons(ic, Arrays.asList(expandedIcon, aodIcon));
-        } catch (InflationException e) {
-            icon = null;
-            expandedIcon = null;
-            centeredIcon = null;
-            aodIcon = null;
-            throw e;
-        }
-
-        expandedIcon.setVisibility(View.INVISIBLE);
-        expandedIcon.setOnVisibilityChangedListener(
-                newVisibility -> {
-                    if (row != null) {
-                        row.setIconsVisible(newVisibility != View.VISIBLE);
-                    }
-                });
-
-        // Construct the centered icon
-        if (mSbn.getNotification().isMediaNotification()) {
-            centeredIcon = new StatusBarIconView(context,
-                    sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
-            centeredIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
-            try {
-                setIcons(ic, Collections.singletonList(centeredIcon));
-            } catch (InflationException e) {
-                centeredIcon = null;
-                throw e;
-            }
-        }
-    }
-
-    /**
-     * Determines if this icon should be tinted based on the sensitivity of the icon, its context
-     * and the user's indicated sensitivity preference.
-     *
-     * @param ic The icon that should/should not be tinted.
-     * @return
-     */
-    private boolean shouldTintIcon(StatusBarIconView ic) {
-        boolean usedInSensitiveContext = (ic == expandedIcon || ic == aodIcon);
-        return !isImportantConversation() || (usedInSensitiveContext && isSensitive());
-    }
-
-
-    private void setIcons(StatusBarIcon ic, List<StatusBarIconView> icons)
-            throws InflationException {
-        for (StatusBarIconView icon: icons) {
-            if (icon == null) {
-              continue;
-            }
-            icon.setTintIcons(shouldTintIcon(icon));
-            if (!icon.set(ic)) {
-                throw new InflationException("Couldn't create icon" + ic);
-            }
-        }
-    }
-
-    private StatusBarIcon getIcon(Context context, StatusBarNotification sbn, boolean redact)
-            throws InflationException {
-        Notification n = sbn.getNotification();
-        final boolean showPeopleAvatar = isImportantConversation() && !redact;
-
-        // If cached, return corresponding cached values
-        if (showPeopleAvatar && mPeopleAvatar != null) {
-            return mPeopleAvatar;
-        } else if (!showPeopleAvatar && mSmallIcon != null) {
-            return mSmallIcon;
-        }
-
-        Icon icon = showPeopleAvatar ? createPeopleAvatar(context) : n.getSmallIcon();
-        if (icon == null) {
-            throw new InflationException("No icon in notification from " + sbn.getPackageName());
-        }
-
-        StatusBarIcon ic = new StatusBarIcon(
-                    sbn.getUser(),
-                    sbn.getPackageName(),
-                    icon,
-                    n.iconLevel,
-                    n.number,
-                    StatusBarIconView.contentDescForNotification(context, n));
-
-        // Cache if important conversation.
-        if (isImportantConversation()) {
-            if (showPeopleAvatar) {
-                mPeopleAvatar = ic;
-            } else {
-                mSmallIcon = ic;
-            }
-        }
-        return ic;
-    }
-
-    private Icon createPeopleAvatar(Context context) throws InflationException {
-        // Attempt to extract form shortcut.
-        String conversationId = getChannel().getConversationId();
-        ShortcutQuery query = new ShortcutQuery()
-                .setPackage(mSbn.getPackageName())
-                .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
-                .setShortcutIds(Collections.singletonList(conversationId));
-        List<ShortcutInfo> shortcuts = context.getSystemService(LauncherApps.class)
-                .getShortcuts(query, mSbn.getUser());
-        Icon ic = null;
-        if (shortcuts != null && !shortcuts.isEmpty()) {
-            ic = shortcuts.get(0).getIcon();
-        }
-
-        // Fall back to notification large icon if available
-        if (ic == null) {
-            ic = mSbn.getNotification().getLargeIcon();
-        }
-
-        // Fall back to extract from message
-        if (ic == null) {
-            Bundle extras = mSbn.getNotification().extras;
-            List<Message> messages = Message.getMessagesFromBundleArray(
-                    extras.getParcelableArray(Notification.EXTRA_MESSAGES));
-            Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
-
-            for (int i = messages.size() - 1; i >= 0; i--) {
-                Message message = messages.get(i);
-                Person sender = message.getSenderPerson();
-                if (sender != null && sender != user) {
-                    ic = message.getSenderPerson().getIcon();
-                    break;
-                }
-            }
-        }
-
-        // Revert to small icon if still not available
-        if (ic == null) {
-            ic = mSbn.getNotification().getSmallIcon();
-        }
-        if (ic == null) {
-            throw new InflationException("No icon in notification from " + mSbn.getPackageName());
-        }
-        return ic;
-    }
-
-    private void updateSensitiveIconState() {
-        try {
-            StatusBarIcon ic = getIcon(getRow().getContext(), mSbn, isSensitive());
-            setIcons(ic, Arrays.asList(expandedIcon, aodIcon));
-        } catch (InflationException e) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Unable to update icon", e);
-            }
-        }
-    }
-
-    public void setIconTag(int key, Object tag) {
-        if (icon != null) {
-            icon.setTag(key, tag);
-            expandedIcon.setTag(key, tag);
-        }
-
-        if (centeredIcon != null) {
-            centeredIcon.setTag(key, tag);
-        }
-
-        if (aodIcon != null) {
-            aodIcon.setTag(key, tag);
-        }
-    }
-
-    /**
-     * Update the notification icons.
-     *
-     * @param context the context to create the icons with.
-     * @param sbn the notification to read the icon from.
-     * @throws InflationException Exception if required icons are not valid or specified
-     */
-    public void updateIcons(Context context, StatusBarNotification sbn)
-            throws InflationException {
-        if (icon != null) {
-            // Update the icon
-            mSmallIcon = null;
-            mPeopleAvatar = null;
-
-            StatusBarIcon ic = getIcon(context, sbn, false /* redact */);
-
-            icon.setNotification(sbn);
-            expandedIcon.setNotification(sbn);
-            aodIcon.setNotification(sbn);
-            setIcons(ic, Arrays.asList(icon, expandedIcon));
-
-            if (isSensitive()) {
-                ic = getIcon(context, sbn, true /* redact */);
-            }
-            setIcons(ic, Collections.singletonList(aodIcon));
-
-            if (centeredIcon != null) {
-                centeredIcon.setNotification(sbn);
-                setIcons(ic, Collections.singletonList(centeredIcon));
-            }
-        }
-    }
-
-    private boolean isImportantConversation() {
-        return getChannel() != null && getChannel().isImportantConversation();
-    }
-
     public int getContrastedColor(Context context, boolean isLowPriority,
             int backgroundColor) {
         int rawColor = isLowPriority ? Notification.COLOR_DEFAULT :
@@ -1125,9 +883,8 @@
         getRow().setSensitive(sensitive, deviceSensitive);
         if (sensitive != mSensitive) {
             mSensitive = sensitive;
-            updateSensitiveIconState();
-            if (mOnSensitiveChangedListener != null) {
-                mOnSensitiveChangedListener.run();
+            for (int i = 0; i < mOnSensitivityChangedListeners.size(); i++) {
+                mOnSensitivityChangedListeners.get(i).onSensitivityChanged(this);
             }
         }
     }
@@ -1136,8 +893,14 @@
         return mSensitive;
     }
 
-    public void setOnSensitiveChangedListener(Runnable listener) {
-        mOnSensitiveChangedListener = listener;
+    /** Add a listener to be notified when the entry's sensitivity changes. */
+    public void addOnSensitivityChangedListener(OnSensitivityChangedListener listener) {
+        mOnSensitivityChangedListeners.add(listener);
+    }
+
+    /** Remove a listener that was registered above. */
+    public void removeOnSensitivityChangedListener(OnSensitivityChangedListener listener) {
+        mOnSensitivityChangedListeners.remove(listener);
     }
 
     public boolean isPulseSuppressed() {
@@ -1167,6 +930,12 @@
         }
     }
 
+    /** Listener interface for {@link #addOnSensitivityChangedListener} */
+    public interface OnSensitivityChangedListener {
+        /** Called when the sensitivity changes */
+        void onSensitivityChanged(@NonNull NotificationEntry entry);
+    }
+
     /** @see #getDismissState() */
     public enum DismissState {
         /** User has not dismissed this notif or its parent */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
index 98c45ff..596235c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
@@ -260,11 +260,14 @@
     private void applyRanking(RankingMap rankingMap) {
         for (CoalescedEvent event : mCoalescedEvents.values()) {
             Ranking ranking = new Ranking();
-            if (!rankingMap.getRanking(event.getKey(), ranking)) {
-                throw new IllegalStateException(
-                        "Ranking map doesn't contain key: " + event.getKey());
+            if (rankingMap.getRanking(event.getKey(), ranking)) {
+                event.setRanking(ranking);
+            } else {
+                // TODO: (b/148791039) We should crash if we are ever handed a ranking with
+                //  incomplete entries. Right now, there's a race condition in NotificationListener
+                //  that means this might occur when SystemUI is starting up.
+                mLogger.logMissingRanking(event.getKey());
             }
-            event.setRanking(ranking);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
index 6e8788d..d4d5b64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
@@ -57,6 +57,14 @@
             "Modification of notif $str1 triggered TIMEOUT emit of batched group $str2"
         })
     }
+
+    fun logMissingRanking(forKey: String) {
+        buffer.log(TAG, LogLevel.WARNING, {
+            str1 = forKey
+        }, {
+            "RankingMap is missing an entry for coalesced notification $str1"
+        })
+    }
 }
 
 private const val TAG = "GroupCoalescer"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 370de83..92426e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -126,7 +126,7 @@
                 mInterceptedDismissalEntries.remove(entry.getKey());
                 mOnEndDismissInterception.onEndDismissInterception(mDismissInterceptor, entry,
                         createDismissedByUserStats(entry));
-            } else if (mNotifPipeline.getActiveNotifs().contains(entry)) {
+            } else if (mNotifPipeline.getAllNotifs().contains(entry)) {
                 // Bubbles are hiding the notifications from the shade, but the bubble was
                 // deleted; therefore, the notification should be cancelled as if it were a user
                 // dismissal (this won't re-enter handleInterceptDimissal because Bubbles
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
index 854444f..b5b756d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
@@ -240,7 +240,7 @@
     }
 
     private NotificationEntry findNotificationEntryWithKey(String key) {
-        for (NotificationEntry entry : mNotifPipeline.getActiveNotifs()) {
+        for (NotificationEntry entry : mNotifPipeline.getAllNotifs()) {
             if (entry.getKey().equals(key)) {
                 return entry;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 4beeede..7237284 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification.collection.inflation;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
 import android.annotation.Nullable;
@@ -29,8 +28,6 @@
 import android.view.ViewGroup;
 
 import com.android.internal.util.NotificationMessagingUtil;
-import com.android.systemui.R;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -38,25 +35,22 @@
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.NotificationClicker;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.icon.IconManager;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
 import com.android.systemui.statusbar.notification.row.NotifBindPipeline;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
 import com.android.systemui.statusbar.notification.row.RowContentBindParams;
 import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Provider;
 import javax.inject.Singleton;
 
@@ -66,23 +60,23 @@
 
     private static final String TAG = "NotificationViewManager";
 
-    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
-
     private final Context mContext;
-    private final NotifBindPipeline mNotifBindPipeline;
-    private final RowContentBindStage mRowContentBindStage;
     private final NotificationMessagingUtil mMessagingUtil;
     private final NotificationRemoteInputManager mNotificationRemoteInputManager;
     private final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
+    private final NotifBindPipeline mNotifBindPipeline;
+    private final RowContentBindStage mRowContentBindStage;
+    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+    private final Provider<RowInflaterTask> mRowInflaterTaskProvider;
+    private final ExpandableNotificationRowComponent.Builder
+            mExpandableNotificationRowComponentBuilder;
+    private final IconManager mIconManager;
 
     private NotificationPresenter mPresenter;
     private NotificationListContainer mListContainer;
     private NotificationRowContentBinder.InflationCallback mInflationCallback;
     private BindRowCallback mBindRowCallback;
     private NotificationClicker mNotificationClicker;
-    private final Provider<RowInflaterTask> mRowInflaterTaskProvider;
-    private final ExpandableNotificationRowComponent.Builder
-            mExpandableNotificationRowComponentBuilder;
 
     @Inject
     public NotificationRowBinderImpl(
@@ -92,14 +86,10 @@
             NotificationLockscreenUserManager notificationLockscreenUserManager,
             NotifBindPipeline notifBindPipeline,
             RowContentBindStage rowContentBindStage,
-            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
-            KeyguardBypassController keyguardBypassController,
-            StatusBarStateController statusBarStateController,
-            NotificationGroupManager notificationGroupManager,
-            NotificationGutsManager notificationGutsManager,
             NotificationInterruptStateProvider notificationInterruptionStateProvider,
             Provider<RowInflaterTask> rowInflaterTaskProvider,
-            ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder) {
+            ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder,
+            IconManager iconManager) {
         mContext = context;
         mNotifBindPipeline = notifBindPipeline;
         mRowContentBindStage = rowContentBindStage;
@@ -109,6 +99,7 @@
         mNotificationInterruptStateProvider = notificationInterruptionStateProvider;
         mRowInflaterTaskProvider = rowInflaterTaskProvider;
         mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder;
+        mIconManager = iconManager;
     }
 
     /**
@@ -120,6 +111,8 @@
         mPresenter = presenter;
         mListContainer = listContainer;
         mBindRowCallback = bindRowCallback;
+
+        mIconManager.attach();
     }
 
     public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) {
@@ -142,12 +135,12 @@
 
         final StatusBarNotification sbn = entry.getSbn();
         if (entry.rowExists()) {
-            entry.updateIcons(mContext, sbn);
+            mIconManager.updateIcons(entry);
             entry.reset();
             updateNotification(entry, pmUser, sbn, entry.getRow());
             entry.getRowController().setOnDismissRunnable(onDismissRunnable);
         } else {
-            entry.createIcons(mContext, sbn);
+            mIconManager.createIcons(entry);
             mRowInflaterTaskProvider.get().inflate(mContext, parent, entry,
                     row -> {
                         // Setup the controller for the view.
@@ -227,8 +220,8 @@
         row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
                 && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
 
-        // TODO: should updates to the entry be happening somewhere else?
-        entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+        // TODO: should this be happening somewhere else?
+        mIconManager.updateIconTags(entry, entry.targetSdk);
 
         row.setOnActivatedListener(mPresenter);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
index 171816f..b4c2bb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
@@ -20,6 +20,8 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
+import java.util.Collection;
+
 /**
  * A notification collection that manages the list of {@link NotificationEntry}s that will be
  * rendered.
@@ -34,4 +36,13 @@
      * or deleted.
      */
     void addCollectionListener(NotifCollectionListener listener);
+
+    /**
+     * Returns the list of all known notifications, i.e. the notifications that are currently posted
+     * to the phone. In general, this tracks closely to the list maintained by NotificationManager,
+     * but it can diverge slightly due to lifetime extenders.
+     *
+     * The returned collection is read-only, unsorted, unfiltered, and ungrouped.
+     */
+    Collection<NotificationEntry> getAllNotifs();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
index b2c53da..0c0cded 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
@@ -70,8 +70,24 @@
     }
 
     /**
-     * Called whenever the RankingMap is updated by system server. By the time this listener is
-     * called, the Rankings of all entries will have been updated.
+     * Called whenever a ranking update is applied. During a ranking update, all active,
+     * non-lifetime-extended notification entries will have their ranking object updated.
+     *
+     * Ranking updates occur whenever a notification is added, updated, or removed, or when a
+     * standalone ranking is sent from the server.
+     */
+    default void onRankingApplied() {
+    }
+
+    /**
+     * Called whenever system server sends a standalone ranking update (i.e. one that isn't
+     * associated with a notification being added or removed).
+     *
+     * In general it is unsafe to depend on this method as rankings can change for other reasons.
+     * Instead, listen for {@link #onRankingApplied()}, which is called whenever ANY ranking update
+     * is applied, regardless of source.
+     *
+     * @deprecated Use {@link #onRankingApplied()} instead.
      */
     default void onRankingUpdate(NotificationListenerService.RankingMap rankingMap) {
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
new file mode 100644
index 0000000..2ef0368
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.notifcollection
+
+import android.service.notification.NotificationListenerService.RankingMap
+import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/**
+ * Set of classes that represent the various events that [NotifCollection] can dispatch to
+ * [NotifCollectionListener]s.
+ *
+ * These events build up in a queue and are periodically emitted in chunks by the collection.
+ */
+
+sealed class NotifEvent {
+    fun dispatchTo(listeners: List<NotifCollectionListener>) {
+        for (i in listeners.indices) {
+            dispatchToListener(listeners[i])
+        }
+    }
+
+    abstract fun dispatchToListener(listener: NotifCollectionListener)
+}
+
+data class InitEntryEvent(
+    val entry: NotificationEntry
+) : NotifEvent() {
+    override fun dispatchToListener(listener: NotifCollectionListener) {
+        listener.onEntryInit(entry)
+    }
+}
+
+data class EntryAddedEvent(
+    val entry: NotificationEntry
+) : NotifEvent() {
+    override fun dispatchToListener(listener: NotifCollectionListener) {
+        listener.onEntryAdded(entry)
+    }
+}
+
+data class EntryUpdatedEvent(
+    val entry: NotificationEntry
+) : NotifEvent() {
+    override fun dispatchToListener(listener: NotifCollectionListener) {
+        listener.onEntryUpdated(entry)
+    }
+}
+
+data class EntryRemovedEvent(
+    val entry: NotificationEntry,
+    val reason: Int
+) : NotifEvent() {
+    override fun dispatchToListener(listener: NotifCollectionListener) {
+        listener.onEntryRemoved(entry, reason)
+    }
+}
+
+data class CleanUpEntryEvent(
+    val entry: NotificationEntry
+) : NotifEvent() {
+    override fun dispatchToListener(listener: NotifCollectionListener) {
+        listener.onEntryCleanUp(entry)
+    }
+}
+
+data class RankingUpdatedEvent(
+    val rankingMap: RankingMap
+) : NotifEvent() {
+    override fun dispatchToListener(listener: NotifCollectionListener) {
+        listener.onRankingUpdate(rankingMap)
+    }
+}
+
+class RankingAppliedEvent() : NotifEvent() {
+    override fun dispatchToListener(listener: NotifCollectionListener) {
+        listener.onRankingApplied()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
new file mode 100644
index 0000000..afc123f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.icon
+
+import android.app.Notification
+import android.content.Context
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+/**
+ * Testable wrapper around Context.
+ */
+class IconBuilder @Inject constructor(
+    private val context: Context
+) {
+    fun createIconView(entry: NotificationEntry): StatusBarIconView {
+        return StatusBarIconView(
+                context,
+                "${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}",
+                entry.sbn)
+    }
+
+    fun getIconContentDescription(n: Notification): CharSequence {
+        return StatusBarIconView.contentDescForNotification(context, n)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
new file mode 100644
index 0000000..bb0fcaf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.icon
+
+import android.app.Notification
+import android.app.Person
+import android.content.pm.LauncherApps
+import android.graphics.drawable.Icon
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.widget.ImageView
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.R
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.InflationException
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import javax.inject.Inject
+
+/**
+ * Inflates and updates icons associated with notifications
+ *
+ * Notifications are represented by icons in a few different places -- in the status bar, in the
+ * notification shelf, in AOD, etc. This class is in charge of inflating the views that hold these
+ * icons and keeping the icon assets themselves up to date as notifications change.
+ *
+ * TODO: Much of this code was copied whole-sale in order to get it out of NotificationEntry.
+ *  Long-term, it should probably live somewhere in the content inflation pipeline.
+ */
+class IconManager @Inject constructor(
+    private val notifCollection: CommonNotifCollection,
+    private val launcherApps: LauncherApps,
+    private val iconBuilder: IconBuilder
+) {
+    fun attach() {
+        notifCollection.addCollectionListener(entryListener)
+    }
+
+    private val entryListener = object : NotifCollectionListener {
+        override fun onEntryInit(entry: NotificationEntry) {
+            entry.addOnSensitivityChangedListener(sensitivityListener)
+        }
+
+        override fun onEntryCleanUp(entry: NotificationEntry) {
+            entry.removeOnSensitivityChangedListener(sensitivityListener)
+        }
+
+        override fun onRankingApplied() {
+            // When the sensitivity changes OR when the isImportantConversation status changes,
+            // we need to update the icons
+            for (entry in notifCollection.allNotifs) {
+                val isImportant = isImportantConversation(entry)
+                if (entry.icons.areIconsAvailable &&
+                        isImportant != entry.icons.isImportantConversation) {
+                    updateIconsSafe(entry)
+                }
+                entry.icons.isImportantConversation = isImportant
+            }
+        }
+    }
+
+    private val sensitivityListener = NotificationEntry.OnSensitivityChangedListener {
+        entry -> updateIconsSafe(entry)
+    }
+
+    /**
+     * Inflate icon views for each icon variant and assign appropriate icons to them. Stores the
+     * result in [NotificationEntry.getIcons].
+     *
+     * @throws InflationException Exception if required icons are not valid or specified
+     */
+    @Throws(InflationException::class)
+    fun createIcons(entry: NotificationEntry) {
+        // Construct the status bar icon view.
+        val sbIcon = iconBuilder.createIconView(entry)
+        sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+
+        // Construct the shelf icon view.
+        val shelfIcon = iconBuilder.createIconView(entry)
+        shelfIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+
+        shelfIcon.visibility = View.INVISIBLE
+        // TODO: This doesn't belong here
+        shelfIcon.setOnVisibilityChangedListener { newVisibility: Int ->
+            if (entry.row != null) {
+                entry.row.setIconsVisible(newVisibility != View.VISIBLE)
+            }
+        }
+
+        // Construct the aod icon view.
+        val aodIcon = iconBuilder.createIconView(entry)
+        aodIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+        aodIcon.setIncreasedSize(true)
+
+        // Construct the centered icon view.
+        val centeredIcon = if (entry.sbn.notification.isMediaNotification) {
+            iconBuilder.createIconView(entry).apply {
+                scaleType = ImageView.ScaleType.CENTER_INSIDE
+            }
+        } else {
+            null
+        }
+
+        // Set the icon views' icons
+        val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
+
+        try {
+            setIcon(entry, normalIconDescriptor, sbIcon)
+            setIcon(entry, sensitiveIconDescriptor, shelfIcon)
+            setIcon(entry, sensitiveIconDescriptor, aodIcon)
+            if (centeredIcon != null) {
+                setIcon(entry, normalIconDescriptor, centeredIcon)
+            }
+            entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, centeredIcon, entry.icons)
+        } catch (e: InflationException) {
+            entry.icons = IconPack.buildEmptyPack(entry.icons)
+            throw e
+        }
+    }
+
+    /**
+     * Update the notification icons.
+     *
+     * @param entry the notification to read the icon from.
+     * @throws InflationException Exception if required icons are not valid or specified
+     */
+    @Throws(InflationException::class)
+    fun updateIcons(entry: NotificationEntry) {
+        if (!entry.icons.areIconsAvailable) {
+            return
+        }
+        entry.icons.smallIconDescriptor = null
+        entry.icons.peopleAvatarDescriptor = null
+
+        val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
+
+        entry.icons.statusBarIcon?.let {
+            it.notification = entry.sbn
+            setIcon(entry, normalIconDescriptor, it)
+        }
+
+        entry.icons.shelfIcon?.let {
+            it.notification = entry.sbn
+            setIcon(entry, normalIconDescriptor, it)
+        }
+
+        entry.icons.aodIcon?.let {
+            it.notification = entry.sbn
+            setIcon(entry, sensitiveIconDescriptor, it)
+        }
+
+        entry.icons.centeredIcon?.let {
+            it.notification = entry.sbn
+            setIcon(entry, sensitiveIconDescriptor, it)
+        }
+    }
+
+    /**
+     * Updates tags on the icon views to match the posting app's target SDK level
+     *
+     * Note that this method MUST be called after both [createIcons] and [updateIcons].
+     */
+    fun updateIconTags(entry: NotificationEntry, targetSdk: Int) {
+        setTagOnIconViews(
+                entry.icons,
+                R.id.icon_is_pre_L,
+                targetSdk < Build.VERSION_CODES.LOLLIPOP)
+    }
+
+    private fun updateIconsSafe(entry: NotificationEntry) {
+        try {
+            updateIcons(entry)
+        } catch (e: InflationException) {
+            // TODO This should mark the entire row as involved in an inflation error
+            Log.e(TAG, "Unable to update icon", e)
+        }
+    }
+
+    @Throws(InflationException::class)
+    private fun getIconDescriptors(
+        entry: NotificationEntry
+    ): Pair<StatusBarIcon, StatusBarIcon> {
+        val iconDescriptor = getIconDescriptor(entry, false /* redact */)
+        val sensitiveDescriptor = if (entry.isSensitive) {
+            getIconDescriptor(entry, true /* redact */)
+        } else {
+            iconDescriptor
+        }
+        return Pair(iconDescriptor, sensitiveDescriptor)
+    }
+
+    @Throws(InflationException::class)
+    private fun getIconDescriptor(
+        entry: NotificationEntry,
+        redact: Boolean
+    ): StatusBarIcon {
+        val n = entry.sbn.notification
+        val showPeopleAvatar = isImportantConversation(entry) && !redact
+
+        val peopleAvatarDescriptor = entry.icons.peopleAvatarDescriptor
+        val smallIconDescriptor = entry.icons.smallIconDescriptor
+
+        // If cached, return corresponding cached values
+        if (showPeopleAvatar && peopleAvatarDescriptor != null) {
+            return peopleAvatarDescriptor
+        } else if (!showPeopleAvatar && smallIconDescriptor != null) {
+            return smallIconDescriptor
+        }
+
+        val icon =
+                (if (showPeopleAvatar) {
+                    createPeopleAvatar(entry)
+                } else {
+                    n.smallIcon
+                }) ?: throw InflationException(
+                        "No icon in notification from " + entry.sbn.packageName)
+
+        val ic = StatusBarIcon(
+                entry.sbn.user,
+                entry.sbn.packageName,
+                icon,
+                n.iconLevel,
+                n.number,
+                iconBuilder.getIconContentDescription(n))
+
+        // Cache if important conversation.
+        if (isImportantConversation(entry)) {
+            if (showPeopleAvatar) {
+                entry.icons.peopleAvatarDescriptor = ic
+            } else {
+                entry.icons.smallIconDescriptor = ic
+            }
+        }
+
+        return ic
+    }
+
+    @Throws(InflationException::class)
+    private fun setIcon(
+        entry: NotificationEntry,
+        iconDescriptor: StatusBarIcon,
+        iconView: StatusBarIconView
+    ) {
+        iconView.setTintIcons(shouldTintIconView(entry, iconView))
+        if (!iconView.set(iconDescriptor)) {
+            throw InflationException("Couldn't create icon $iconDescriptor")
+        }
+    }
+
+    @Throws(InflationException::class)
+    private fun createPeopleAvatar(entry: NotificationEntry): Icon? {
+        // Attempt to extract form shortcut.
+        val conversationId = entry.ranking.channel.conversationId
+        val query = LauncherApps.ShortcutQuery()
+                .setPackage(entry.sbn.packageName)
+                .setQueryFlags(
+                        LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC
+                                or LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED)
+                .setShortcutIds(listOf(conversationId))
+        val shortcuts = launcherApps.getShortcuts(query, entry.sbn.user)
+        var ic: Icon? = null
+        if (shortcuts != null && shortcuts.isNotEmpty()) {
+            ic = shortcuts[0].icon
+        }
+
+        // Fall back to notification large icon if available
+        if (ic == null) {
+            ic = entry.sbn.notification.getLargeIcon()
+        }
+
+        // Fall back to extract from message
+        if (ic == null) {
+            val extras: Bundle = entry.sbn.notification.extras
+            val messages = Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+                    extras.getParcelableArray(Notification.EXTRA_MESSAGES))
+            val user = extras.getParcelable<Person>(Notification.EXTRA_MESSAGING_PERSON)
+            for (i in messages.indices.reversed()) {
+                val message = messages[i]
+                val sender = message.senderPerson
+                if (sender != null && sender !== user) {
+                    ic = message.senderPerson!!.icon
+                    break
+                }
+            }
+        }
+
+        // Revert to small icon if still not available
+        if (ic == null) {
+            ic = entry.sbn.notification.smallIcon
+        }
+        if (ic == null) {
+            throw InflationException("No icon in notification from " + entry.sbn.packageName)
+        }
+        return ic
+    }
+
+    /**
+     * Determines if this icon should be tinted based on the sensitivity of the icon, its context
+     * and the user's indicated sensitivity preference.
+     *
+     * @param iconView The icon that should/should not be tinted.
+     */
+    private fun shouldTintIconView(entry: NotificationEntry, iconView: StatusBarIconView): Boolean {
+        val usedInSensitiveContext =
+                iconView === entry.icons.shelfIcon || iconView === entry.icons.aodIcon
+        return !isImportantConversation(entry) || usedInSensitiveContext && entry.isSensitive
+    }
+
+    private fun isImportantConversation(entry: NotificationEntry): Boolean {
+        return entry.ranking.channel != null && entry.ranking.channel.isImportantConversation
+    }
+
+    private fun setTagOnIconViews(icons: IconPack, key: Int, tag: Any) {
+        icons.statusBarIcon?.setTag(key, tag)
+        icons.shelfIcon?.setTag(key, tag)
+        icons.aodIcon?.setTag(key, tag)
+        icons.centeredIcon?.setTag(key, tag)
+    }
+}
+
+private const val TAG = "IconManager"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
new file mode 100644
index 0000000..054e381
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.icon;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.statusbar.StatusBarIconView;
+
+/**
+ * Data class for storing icons associated with a notification
+ */
+public final class IconPack {
+
+    private final boolean mAreIconsAvailable;
+    @Nullable private final StatusBarIconView mStatusBarIcon;
+    @Nullable private final StatusBarIconView mShelfIcon;
+    @Nullable private final StatusBarIconView mAodIcon;
+    @Nullable private final StatusBarIconView mCenteredIcon;
+
+    @Nullable private StatusBarIcon mSmallIconDescriptor;
+    @Nullable private StatusBarIcon mPeopleAvatarDescriptor;
+
+    private boolean mIsImportantConversation;
+
+    /**
+     * Builds an empty instance of IconPack that doesn't have any icons (because either they
+     * haven't been inflated yet or there was an error while inflating them).
+     */
+    public static IconPack buildEmptyPack(@Nullable IconPack fromSource) {
+        return new IconPack(false, null, null, null, null, fromSource);
+    }
+
+    /**
+     * Builds an instance of an IconPack that contains successfully-inflated icons
+     */
+    public static IconPack buildPack(
+            @NonNull StatusBarIconView statusBarIcon,
+            @NonNull StatusBarIconView shelfIcon,
+            @NonNull StatusBarIconView aodIcon,
+            @Nullable StatusBarIconView centeredIcon,
+            @Nullable IconPack source) {
+        return new IconPack(true, statusBarIcon, shelfIcon, aodIcon, centeredIcon, source);
+    }
+
+    private IconPack(
+            boolean areIconsAvailable,
+            @Nullable StatusBarIconView statusBarIcon,
+            @Nullable StatusBarIconView shelfIcon,
+            @Nullable StatusBarIconView aodIcon,
+            @Nullable StatusBarIconView centeredIcon,
+            @Nullable IconPack source) {
+        mAreIconsAvailable = areIconsAvailable;
+        mStatusBarIcon = statusBarIcon;
+        mShelfIcon = shelfIcon;
+        mCenteredIcon = centeredIcon;
+        mAodIcon = aodIcon;
+        if (source != null) {
+            mIsImportantConversation = source.mIsImportantConversation;
+        }
+    }
+
+    /** The version of the notification icon that appears in the status bar. */
+    @Nullable
+    public StatusBarIconView getStatusBarIcon() {
+        return mStatusBarIcon;
+    }
+
+    /**
+     * The version of the icon that appears in the "shelf" at the bottom of the notification shade.
+     * In general, this icon also appears somewhere on the notification and is "sucked" into the
+     * shelf as the scrolls beyond it.
+     */
+    @Nullable
+    public StatusBarIconView getShelfIcon() {
+        return mShelfIcon;
+    }
+
+    @Nullable
+    public StatusBarIconView getCenteredIcon() {
+        return mCenteredIcon;
+    }
+
+    /** The version of the icon that's shown when pulsing (in AOD). */
+    @Nullable
+    public StatusBarIconView getAodIcon() {
+        return mAodIcon;
+    }
+
+    @Nullable
+    StatusBarIcon getSmallIconDescriptor() {
+        return mSmallIconDescriptor;
+    }
+
+    void setSmallIconDescriptor(@Nullable StatusBarIcon smallIconDescriptor) {
+        mSmallIconDescriptor = smallIconDescriptor;
+    }
+
+    @Nullable
+    StatusBarIcon getPeopleAvatarDescriptor() {
+        return mPeopleAvatarDescriptor;
+    }
+
+    void setPeopleAvatarDescriptor(@Nullable StatusBarIcon peopleAvatarDescriptor) {
+        mPeopleAvatarDescriptor = peopleAvatarDescriptor;
+    }
+
+    boolean isImportantConversation() {
+        return mIsImportantConversation;
+    }
+
+    void setImportantConversation(boolean importantConversation) {
+        mIsImportantConversation = importantConversation;
+    }
+
+    public boolean getAreIconsAvailable() {
+        return mAreIconsAvailable;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/DungeonRow.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/DungeonRow.kt
index 373457d..dbfa27f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/DungeonRow.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/DungeonRow.kt
@@ -37,7 +37,7 @@
         }
 
         (findViewById(R.id.icon) as StatusBarIconView).apply {
-            set(entry?.icon?.statusBarIcon)
+            set(entry?.icons?.statusBarIcon?.statusBarIcon)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index f61fe98..b1db5b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -579,7 +579,7 @@
 
     @VisibleForTesting
     void updateShelfIconColor() {
-        StatusBarIconView expandedIcon = mEntry.expandedIcon;
+        StatusBarIconView expandedIcon = mEntry.getIcons().getShelfIcon();
         boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L));
         boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon,
                 ContrastColorUtil.getInstance(mContext));
@@ -1290,7 +1290,7 @@
         setLongPressListener(null);
         mGroupParentWhenDismissed = mNotificationParent;
         mChildAfterViewWhenDismissed = null;
-        mEntry.icon.setDismissed();
+        mEntry.getIcons().getStatusBarIcon().setDismissed();
         if (isChildInGroup()) {
             List<ExpandableNotificationRow> notificationChildren =
                     mNotificationParent.getNotificationChildren();
@@ -1832,7 +1832,7 @@
                 mTranslateableViews.get(i).setTranslationX(0);
             }
             invalidateOutline();
-            getEntry().expandedIcon.setScrollX(0);
+            getEntry().getIcons().getShelfIcon().setScrollX(0);
         }
 
         if (mMenuRow != null) {
@@ -1912,7 +1912,7 @@
             // In order to keep the shelf in sync with this swiping, we're simply translating
             // it's icon by the same amount. The translation is already being used for the normal
             // positioning, so we can use the scrollX instead.
-            getEntry().expandedIcon.setScrollX((int) -translationX);
+            getEntry().getIcons().getShelfIcon().setScrollX((int) -translationX);
         }
 
         if (mMenuRow != null && mMenuRow.getMenuView() != null) {
@@ -2111,7 +2111,7 @@
 
     @Override
     public StatusBarIconView getShelfIcon() {
-        return getEntry().expandedIcon;
+        return getEntry().getIcons().getShelfIcon();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 0996ff2..14442e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -453,9 +453,10 @@
                         needsAnimation = false;
                     }
                     NotificationEntry entry = row.getEntry();
-                    StatusBarIconView icon = entry.icon;
-                    if (entry.centeredIcon != null && entry.centeredIcon.getParent() != null) {
-                        icon = entry.centeredIcon;
+                    StatusBarIconView icon = entry.getIcons().getStatusBarIcon();
+                    final StatusBarIconView centeredIcon = entry.getIcons().getCenteredIcon();
+                    if (centeredIcon != null && centeredIcon.getParent() != null) {
+                        icon = centeredIcon;
                     }
                     if (icon.getParent() != null) {
                         icon.getLocationOnScreen(mTmpLocation);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index 745843d..adca10f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -44,6 +44,7 @@
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
+import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.bubbles.BubbleController;
@@ -99,8 +100,10 @@
     private final Region mExcludeRegion = new Region();
     private final Region mUnrestrictedExcludeRegion = new Region();
 
-    // The edge width where touch down is allowed
-    private int mEdgeWidth;
+    // The left side edge width where touch down is allowed
+    private int mEdgeWidthLeft;
+    // The right side edge width where touch down is allowed
+    private int mEdgeWidthRight;
     // The bottom gesture area height
     private int mBottomGestureHeight;
     // The slop to distinguish between horizontal and vertical motion
@@ -127,6 +130,8 @@
     private int mRightInset;
     private int mSysUiFlags;
 
+    private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
+
     private final NavigationEdgeBackPlugin.BackCallback mBackCallback =
             new NavigationEdgeBackPlugin.BackCallback() {
                 @Override
@@ -174,13 +179,17 @@
         mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT,
                 ViewConfiguration.getLongPressTimeout());
 
+        mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
+                mContext.getMainThreadHandler(), mContext, () -> updateCurrentUserResources(res));
+
         updateCurrentUserResources(res);
         sysUiFlagContainer.addCallback(sysUiFlags -> mSysUiFlags = sysUiFlags);
     }
 
     public void updateCurrentUserResources(Resources res) {
-        mEdgeWidth = res.getDimensionPixelSize(
-                com.android.internal.R.dimen.config_backGestureInset);
+        mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
+        mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res);
+
         mBottomGestureHeight = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.navigation_bar_gesture_height);
     }
@@ -236,6 +245,7 @@
         }
 
         if (!mIsEnabled) {
+            mGestureNavigationSettingsObserver.unregister();
             mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
             mPluginManager.removePluginListener(this);
 
@@ -248,6 +258,7 @@
             }
 
         } else {
+            mGestureNavigationSettingsObserver.register();
             updateDisplaySize();
             mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
                     mContext.getMainThreadHandler());
@@ -321,7 +332,8 @@
 
     private boolean isWithinTouchRegion(int x, int y) {
         // Disallow if too far from the edge
-        if (x > mEdgeWidth + mLeftInset && x < (mDisplaySize.x - mEdgeWidth - mRightInset)) {
+        if (x > mEdgeWidthLeft + mLeftInset
+                && x < (mDisplaySize.x - mEdgeWidthRight - mRightInset)) {
             return false;
         }
 
@@ -364,7 +376,7 @@
         if (action == MotionEvent.ACTION_DOWN) {
             // Verify if this is in within the touch region and we aren't in immersive mode, and
             // either the bouncer is showing or the notification panel is hidden
-            mIsOnLeftEdge = ev.getX() <= mEdgeWidth + mLeftInset;
+            mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;
             mInRejectedExclusion = false;
             mAllowGesture = !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
                     && isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
@@ -461,7 +473,8 @@
         pw.println("  mExcludeRegion=" + mExcludeRegion);
         pw.println("  mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion);
         pw.println("  mIsAttached=" + mIsAttached);
-        pw.println("  mEdgeWidth=" + mEdgeWidth);
+        pw.println("  mEdgeWidthLeft=" + mEdgeWidthLeft);
+        pw.println("  mEdgeWidthRight=" + mEdgeWidthRight);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index c39ee3a..51c02c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -269,7 +269,7 @@
             }
             updateIsolatedIconLocation(false /* requireUpdate */);
             mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
-                    : newEntry.icon, animateIsolation);
+                    : newEntry.getIcons().getStatusBarIcon(), animateIsolation);
         }
     }
 
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 b119f0b..31266db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -551,14 +551,15 @@
     public void setWindowState(
             int displayId, @WindowType int window, @WindowVisibleState int state) {
         if (displayId == mDisplayId
-                && mNavigationBarView != null
                 && window == StatusBarManager.WINDOW_NAVIGATION_BAR
                 && mNavigationBarWindowState != state) {
             mNavigationBarWindowState = state;
+            updateSystemUiStateFlags(-1);
             if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state));
 
-            updateSystemUiStateFlags(-1);
-            mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
+            if (mNavigationBarView != null) {
+                mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
+            }
         }
     }
 
@@ -1219,6 +1220,7 @@
             @Override
             public void onViewDetachedFromWindow(View v) {
                 FragmentHostManager.removeAndDestroy(v);
+                navigationBarView.removeOnAttachStateChangeListener(this);
             }
         });
         context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
index 43ac4cf..d24ccf3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
@@ -28,6 +28,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.om.IOverlayManager;
+import android.content.om.OverlayInfo;
 import android.content.pm.PackageManager;
 import android.content.res.ApkAssets;
 import android.os.PatternMatcher;
@@ -172,6 +173,9 @@
     public void updateCurrentInteractionMode(boolean notify) {
         mCurrentUserContext = getCurrentUserContext();
         int mode = getCurrentInteractionMode(mCurrentUserContext);
+        if (mode == NAV_BAR_MODE_GESTURAL) {
+            switchToDefaultGestureNavOverlayIfNecessary();
+        }
         mUiBgExecutor.execute(() -> {
             Settings.Secure.putString(mCurrentUserContext.getContentResolver(),
                     Secure.NAVIGATION_MODE, String.valueOf(mode));
@@ -277,6 +281,34 @@
         }
     }
 
+    private void switchToDefaultGestureNavOverlayIfNecessary() {
+        final int userId = mCurrentUserContext.getUserId();
+        try {
+            final IOverlayManager om = mOverlayManager;
+            final OverlayInfo info = om.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, userId);
+            if (info != null && !info.isEnabled()) {
+                // Enable the default gesture nav overlay, and move the back gesture inset scale to
+                // Settings.Secure for left and right sensitivity.
+                final int curInset = mCurrentUserContext.getResources().getDimensionPixelSize(
+                        com.android.internal.R.dimen.config_backGestureInset);
+                om.setEnabledExclusiveInCategory(NAV_BAR_MODE_GESTURAL_OVERLAY, userId);
+                final int defInset = mCurrentUserContext.getResources().getDimensionPixelSize(
+                        com.android.internal.R.dimen.config_backGestureInset);
+
+                final float scale = defInset == 0 ? 1.0f : ((float) curInset) / defInset;
+                Settings.Secure.putFloat(mCurrentUserContext.getContentResolver(),
+                        Secure.BACK_GESTURE_INSET_SCALE_LEFT, scale);
+                Settings.Secure.putFloat(mCurrentUserContext.getContentResolver(),
+                        Secure.BACK_GESTURE_INSET_SCALE_RIGHT, scale);
+                if (DEBUG) {
+                    Log.v(TAG, "Moved back sensitivity for user " + userId + " to scale " + scale);
+                }
+            }
+        } catch (SecurityException | IllegalStateException | RemoteException e) {
+            Log.e(TAG, "Failed to switch to default gesture nav overlay for user " + userId);
+        }
+    }
+
     public void setModeOverlay(String overlayPkg, int userId) {
         mUiBgExecutor.execute(() -> {
             try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
deleted file mode 100644
index 5d8044f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import android.annotation.IntDef;
-import android.content.ComponentCallbacks;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.graphics.Point;
-import android.net.Uri;
-import android.os.Handler;
-import android.provider.Settings;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Coordinates with the prototype settings plugin app that uses Settings.Global to allow different
- * prototypes to run in the system. The class will handle communication changes from the settings
- * app and call back to listeners.
- */
-public class NavigationPrototypeController extends ContentObserver implements ComponentCallbacks {
-    private static final String HIDE_BACK_BUTTON_SETTING = "quickstepcontroller_hideback";
-    private static final String HIDE_HOME_BUTTON_SETTING = "quickstepcontroller_hidehome";
-    private static final String PROTOTYPE_ENABLED = "prototype_enabled";
-
-    public static final String EDGE_SENSITIVITY_WIDTH_SETTING =
-            "quickstepcontroller_edge_width_sensitivity";
-    private final String GESTURE_MATCH_SETTING = "quickstepcontroller_gesture_match_map";
-    public static final String NAV_COLOR_ADAPT_ENABLE_SETTING = "navbar_color_adapt_enable";
-    public static final String SHOW_HOME_HANDLE_SETTING = "quickstepcontroller_showhandle";
-    public static final String ENABLE_ASSISTANT_GESTURE = "ENABLE_ASSISTANT_GESTURE";
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK,
-            ACTION_QUICKSWITCH, ACTION_NOTHING, ACTION_ASSISTANT})
-    @interface GestureAction {}
-    static final int ACTION_DEFAULT = 0;
-    static final int ACTION_QUICKSTEP = 1;
-    static final int ACTION_QUICKSCRUB = 2;
-    static final int ACTION_BACK = 3;
-    static final int ACTION_QUICKSWITCH = 4;
-    static final int ACTION_NOTHING = 5;
-    static final int ACTION_ASSISTANT = 6;
-
-    private OnPrototypeChangedListener mListener;
-
-    /**
-     * Each index corresponds to a different action set in QuickStepController
-     * {@see updateSwipeLTRBackSetting}
-     */
-    private int[] mActionMap = new int[6];
-
-    private final Context mContext;
-
-    public NavigationPrototypeController(Context context) {
-        super(new Handler());
-        mContext = context;
-        updateSwipeLTRBackSetting();
-    }
-
-    public void setOnPrototypeChangedListener(OnPrototypeChangedListener listener) {
-        mListener = listener;
-    }
-
-    /**
-     * Observe all the settings to react to from prototype settings
-     */
-    public void register() {
-        registerObserver(HIDE_BACK_BUTTON_SETTING);
-        registerObserver(HIDE_HOME_BUTTON_SETTING);
-        registerObserver(GESTURE_MATCH_SETTING);
-        registerObserver(NAV_COLOR_ADAPT_ENABLE_SETTING);
-        registerObserver(SHOW_HOME_HANDLE_SETTING);
-        registerObserver(ENABLE_ASSISTANT_GESTURE);
-        mContext.registerComponentCallbacks(this);
-    }
-
-    /**
-     * Disable observing settings to react to from prototype settings
-     */
-    public void unregister() {
-        mContext.getContentResolver().unregisterContentObserver(this);
-        mContext.unregisterComponentCallbacks(this);
-    }
-
-    @Override
-    public void onChange(boolean selfChange, Uri uri) {
-        super.onChange(selfChange, uri);
-        if (!selfChange && mListener != null) {
-            final String path = uri.getPath();
-            if (path.endsWith(GESTURE_MATCH_SETTING)) {
-                // Get the settings gesture map corresponding to each action
-                // {@see updateSwipeLTRBackSetting}
-                updateSwipeLTRBackSetting();
-                mListener.onGestureRemap(mActionMap);
-            } else if (path.endsWith(HIDE_BACK_BUTTON_SETTING)) {
-                mListener.onBackButtonVisibilityChanged(
-                        !getGlobalBool(HIDE_BACK_BUTTON_SETTING, false));
-            } else if (path.endsWith(HIDE_HOME_BUTTON_SETTING)) {
-                mListener.onHomeButtonVisibilityChanged(!hideHomeButton());
-            } else if (path.endsWith(NAV_COLOR_ADAPT_ENABLE_SETTING)) {
-                mListener.onColorAdaptChanged(mContext.getDisplayId() == DEFAULT_DISPLAY);
-            } else if (path.endsWith(SHOW_HOME_HANDLE_SETTING)) {
-                mListener.onHomeHandleVisiblilityChanged(showHomeHandle());
-            } else if (path.endsWith(ENABLE_ASSISTANT_GESTURE)) {
-                mListener.onAssistantGestureEnabled(isAssistantGestureEnabled());
-            }
-        }
-    }
-
-    /**
-     * @return the width for edge swipe
-     */
-    public int getEdgeSensitivityWidth() {
-        return mContext.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.config_backGestureInset);
-    }
-
-    /**
-     * @return full screen height
-     */
-    public int getEdgeSensitivityHeight() {
-        final Point size = new Point();
-        mContext.getDisplay().getRealSize(size);
-        return size.y;
-    }
-
-    public boolean isEnabled() {
-        return getGlobalBool(PROTOTYPE_ENABLED, false);
-    }
-
-    /**
-     * Retrieve the action map to apply to the quick step controller
-     * @return an action map
-     */
-    int[] getGestureActionMap() {
-        return mActionMap;
-    }
-
-    /**
-     * @return if home button should be invisible
-     */
-    boolean hideHomeButton() {
-        return getGlobalBool(HIDE_HOME_BUTTON_SETTING, false /* default */);
-    }
-
-    boolean showHomeHandle() {
-        return getGlobalBool(SHOW_HOME_HANDLE_SETTING, false /* default */);
-    }
-
-    boolean isAssistantGestureEnabled() {
-        return getGlobalBool(ENABLE_ASSISTANT_GESTURE, false /* default */);
-    }
-
-
-    /**
-     * Since Settings.Global cannot pass arrays, use a string to represent each character as a
-     * gesture map to actions corresponding to {@see GestureAction}. The number is represented as:
-     * Number: [up] [down] [left] [right]
-     */
-    private void updateSwipeLTRBackSetting() {
-        String value = Settings.Global.getString(mContext.getContentResolver(),
-                GESTURE_MATCH_SETTING);
-        if (value != null) {
-            for (int i = 0; i < mActionMap.length; ++i) {
-                mActionMap[i] = Character.getNumericValue(value.charAt(i));
-            }
-        }
-    }
-
-    private boolean getGlobalBool(String name, boolean defaultVal) {
-        return Settings.Global.getInt(mContext.getContentResolver(), name, defaultVal ? 1 : 0) == 1;
-    }
-
-    private int getGlobalInt(String name, int defaultVal) {
-        return Settings.Global.getInt(mContext.getContentResolver(), name, defaultVal);
-    }
-
-    private void registerObserver(String name) {
-        mContext.getContentResolver()
-                .registerContentObserver(Settings.Global.getUriFor(name), false, this);
-    }
-
-    private static int convertDpToPixel(float dp) {
-        return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        if (mListener != null) {
-            mListener.onEdgeSensitivityChanged(getEdgeSensitivityWidth(),
-                    getEdgeSensitivityHeight());
-        }
-    }
-
-    @Override
-    public void onLowMemory() {
-    }
-
-    public interface OnPrototypeChangedListener {
-        void onGestureRemap(@GestureAction int[] actions);
-        void onBackButtonVisibilityChanged(boolean visible);
-        void onHomeButtonVisibilityChanged(boolean visible);
-        void onHomeHandleVisiblilityChanged(boolean visible);
-        void onColorAdaptChanged(boolean enabled);
-        void onEdgeSensitivityChanged(int width, int height);
-        void onAssistantGestureEnabled(boolean enabled);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index b09ccff..f58cce5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -253,8 +253,8 @@
             boolean hidePulsing, boolean onlyShowCenteredIcon) {
 
         final boolean isCenteredNotificationIcon = mCenteredIconView != null
-                && entry.centeredIcon != null
-                && Objects.equals(entry.centeredIcon, mCenteredIconView);
+                && entry.getIcons().getCenteredIcon() != null
+                && Objects.equals(entry.getIcons().getCenteredIcon(), mCenteredIconView);
         if (onlyShowCenteredIcon) {
             return isCenteredNotificationIcon;
         }
@@ -307,7 +307,7 @@
     }
 
     private void updateShelfIcons() {
-        updateIconsForLayout(entry -> entry.expandedIcon, mShelfIcons,
+        updateIconsForLayout(entry -> entry.getIcons().getShelfIcon(), mShelfIcons,
                 true /* showAmbient */,
                 true /* showLowPriority */,
                 false /* hideDismissed */,
@@ -319,7 +319,7 @@
     }
 
     public void updateStatusBarIcons() {
-        updateIconsForLayout(entry -> entry.icon, mNotificationIcons,
+        updateIconsForLayout(entry -> entry.getIcons().getStatusBarIcon(), mNotificationIcons,
                 false /* showAmbient */,
                 mShowLowPriority,
                 true /* hideDismissed */,
@@ -331,7 +331,7 @@
     }
 
     private void updateCenterIcon() {
-        updateIconsForLayout(entry -> entry.centeredIcon, mCenteredIcon,
+        updateIconsForLayout(entry -> entry.getIcons().getCenteredIcon(), mCenteredIcon,
                 false /* showAmbient */,
                 true /* showLowPriority */,
                 false /* hideDismissed */,
@@ -343,7 +343,7 @@
     }
 
     public void updateAodNotificationIcons() {
-        updateIconsForLayout(entry -> entry.aodIcon, mAodIcons,
+        updateIconsForLayout(entry -> entry.getIcons().getAodIcon(), mAodIcons,
                 false /* showAmbient */,
                 true /* showLowPriority */,
                 true /* hideDismissed */,
@@ -517,7 +517,7 @@
      * Shows the icon view given in the center.
      */
     public void showIconCentered(NotificationEntry entry) {
-        StatusBarIconView icon = entry == null ? null :  entry.centeredIcon;
+        StatusBarIconView icon = entry == null ? null : entry.getIcons().getCenteredIcon();
         if (!Objects.equals(mCenteredIconView, icon)) {
             mCenteredIconView = icon;
             updateNotificationIcons();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 1359f74..e25c14c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -40,6 +40,7 @@
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.util.leak.RotationUtils;
 
 import java.util.Objects;
 
@@ -47,7 +48,6 @@
     private static final String TAG = "PhoneStatusBarView";
     private static final boolean DEBUG = StatusBar.DEBUG;
     private static final boolean DEBUG_GESTURES = false;
-    private static final int NO_VALUE = Integer.MIN_VALUE;
     private final CommandQueue mCommandQueue;
 
     StatusBar mBar;
@@ -64,13 +64,14 @@
         }
     };
     private DarkReceiver mBattery;
-    private int mLastOrientation;
+    private int mRotationOrientation;
     @Nullable
     private View mCenterIconSpace;
     @Nullable
     private View mCutoutSpace;
     @Nullable
     private DisplayCutout mDisplayCutout;
+    private int mStatusBarHeight;
 
     /**
      * Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -107,7 +108,7 @@
         super.onAttachedToWindow();
         // Always have Battery meters in the status bar observe the dark/light modes.
         Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery);
-        if (updateOrientationAndCutout(getResources().getConfiguration().orientation)) {
+        if (updateOrientationAndCutout()) {
             updateLayoutForCutout();
         }
     }
@@ -124,7 +125,7 @@
         super.onConfigurationChanged(newConfig);
 
         // May trigger cutout space layout-ing
-        if (updateOrientationAndCutout(newConfig.orientation)) {
+        if (updateOrientationAndCutout()) {
             updateLayoutForCutout();
             requestLayout();
         }
@@ -132,7 +133,7 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        if (updateOrientationAndCutout(mLastOrientation)) {
+        if (updateOrientationAndCutout()) {
             updateLayoutForCutout();
             requestLayout();
         }
@@ -140,17 +141,14 @@
     }
 
     /**
-     *
-     * @param newOrientation may pass NO_VALUE for no change
      * @return boolean indicating if we need to update the cutout location / margins
      */
-    private boolean updateOrientationAndCutout(int newOrientation) {
+    private boolean updateOrientationAndCutout() {
         boolean changed = false;
-        if (newOrientation != NO_VALUE) {
-            if (mLastOrientation != newOrientation) {
-                changed = true;
-                mLastOrientation = newOrientation;
-            }
+        int newRotation = RotationUtils.getExactRotation(mContext);
+        if (newRotation != mRotationOrientation) {
+            changed = true;
+            mRotationOrientation = newRotation;
         }
 
         if (!Objects.equals(getRootWindowInsets().getDisplayCutout(), mDisplayCutout)) {
@@ -293,17 +291,16 @@
         final int waterfallTopInset =
                 mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top;
         ViewGroup.LayoutParams layoutParams = getLayoutParams();
-        layoutParams.height =
-                getResources().getDimensionPixelSize(R.dimen.status_bar_height) - waterfallTopInset;
+        mStatusBarHeight = getResources().getDimensionPixelSize(R.dimen.status_bar_height);
+        layoutParams.height = mStatusBarHeight - waterfallTopInset;
         setLayoutParams(layoutParams);
     }
 
     private void updateLayoutForCutout() {
         updateStatusBarHeight();
-        Pair<Integer, Integer> cornerCutoutMargins =
-                StatusBarWindowView.cornerCutoutMargins(mDisplayCutout, getDisplay());
-        updateCutoutLocation(cornerCutoutMargins);
-        updateSafeInsets(cornerCutoutMargins);
+        updateCutoutLocation(StatusBarWindowView.cornerCutoutMargins(mDisplayCutout, getDisplay()));
+        updateSafeInsets(StatusBarWindowView.statusBarCornerCutoutMargins(mDisplayCutout,
+                getDisplay(), mRotationOrientation, mStatusBarHeight));
     }
 
     private void updateCutoutLocation(Pair<Integer, Integer> cornerCutoutMargins) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 22bf513..760a6d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -33,6 +33,8 @@
 import android.view.WindowInsets;
 import android.widget.FrameLayout;
 
+import com.android.systemui.util.leak.RotationUtils;
+
 /**
  * Status bar view.
  */
@@ -52,17 +54,13 @@
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) {
         final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars());
-        mLeftInset = 0;
-        mRightInset = 0;
+        mLeftInset = insets.left;
+        mRightInset = insets.right;
         mTopInset = 0;
         DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
         if (displayCutout != null) {
             mTopInset = displayCutout.getWaterfallInsets().top;
-            mLeftInset = displayCutout.getSafeInsetLeft();
-            mRightInset = displayCutout.getSafeInsetRight();
         }
-        mLeftInset = Math.max(insets.left, mLeftInset);
-        mRightInset = Math.max(insets.right, mRightInset);
         applyMargins();
         return windowInsets;
     }
@@ -100,44 +98,33 @@
             return new Pair<>(roundedCornerContentPadding, roundedCornerContentPadding);
         }
 
-        // compute the padding needed for corner cutout.
-        final int leftMargin = cutout.getSafeInsetLeft();
-        final int rightMargin = cutout.getSafeInsetRight();
+        // padding needed for corner cutout.
         int leftCornerCutoutPadding = 0;
         int rightCornerCutoutPadding = 0;
         if (cornerCutoutPadding != null) {
-            if (cornerCutoutPadding.first > leftMargin) {
-                leftCornerCutoutPadding = cornerCutoutPadding.first - leftMargin;
-            }
-            if (cornerCutoutPadding.second > rightMargin) {
-                rightCornerCutoutPadding = cornerCutoutPadding.second - rightMargin;
-            }
-        }
-
-        // compute the padding needed for rounded corner
-        int leftRoundedCornerPadding = 0;
-        int rightRoundedCornerPadding = 0;
-        if (roundedCornerContentPadding > leftMargin) {
-            leftRoundedCornerPadding = roundedCornerContentPadding - leftMargin;
-        }
-        if (roundedCornerContentPadding > rightMargin) {
-            rightRoundedCornerPadding = roundedCornerContentPadding - rightMargin;
+            leftCornerCutoutPadding = cornerCutoutPadding.first;
+            rightCornerCutoutPadding = cornerCutoutPadding.second;
         }
 
         return new Pair<>(
-                Math.max(leftCornerCutoutPadding, leftRoundedCornerPadding),
-                Math.max(rightCornerCutoutPadding, rightRoundedCornerPadding));
+                Math.max(leftCornerCutoutPadding, roundedCornerContentPadding),
+                Math.max(rightCornerCutoutPadding, roundedCornerContentPadding));
     }
 
+
     /**
-     * Compute the corner cutout margins
-     *
-     * @param cutout
-     * @param display
-     * @return
+     * Compute the corner cutout margins in portrait mode
      */
     public static Pair<Integer, Integer> cornerCutoutMargins(DisplayCutout cutout,
             Display display) {
+        return statusBarCornerCutoutMargins(cutout, display, RotationUtils.ROTATION_NONE, 0);
+    }
+
+    /**
+     * Compute the corner cutout margins in the given orientation (exactRotation)
+     */
+    public static Pair<Integer, Integer> statusBarCornerCutoutMargins(DisplayCutout cutout,
+            Display display, int exactRotation, int statusBarHeight) {
         if (cutout == null) {
             return null;
         }
@@ -145,14 +132,33 @@
         display.getRealSize(size);
 
         Rect bounds = new Rect();
-        boundsFromDirection(cutout, Gravity.TOP, bounds);
+        switch (exactRotation) {
+            case RotationUtils.ROTATION_LANDSCAPE:
+                boundsFromDirection(cutout, Gravity.LEFT, bounds);
+                break;
+            case RotationUtils.ROTATION_SEASCAPE:
+                boundsFromDirection(cutout, Gravity.RIGHT, bounds);
+                break;
+            case RotationUtils.ROTATION_NONE:
+                boundsFromDirection(cutout, Gravity.TOP, bounds);
+                break;
+            case RotationUtils.ROTATION_UPSIDE_DOWN:
+                // we assume the cutout is always on top in portrait mode
+                return null;
+        }
+
+        if (statusBarHeight >= 0 && bounds.top > statusBarHeight) {
+            return null;
+        }
 
         if (bounds.left <= 0) {
             return new Pair<>(bounds.right, 0);
         }
+
         if (bounds.right >= size.x) {
             return new Pair<>(0, size.x - bounds.left);
         }
+
         return null;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
index 2f1b160..5411839 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
@@ -49,6 +49,8 @@
 public class DisplayImeController implements DisplayController.OnDisplaysChangedListener {
     private static final String TAG = "DisplayImeController";
 
+    private static final boolean DEBUG = false;
+
     public static final int ANIMATION_DURATION_SHOW_MS = 275;
     public static final int ANIMATION_DURATION_HIDE_MS = 340;
     public static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
@@ -209,6 +211,7 @@
             if ((types & WindowInsets.Type.ime()) == 0) {
                 return;
             }
+            if (DEBUG) Slog.d(TAG, "Got showInsets for ime");
             startAnimation(true /* show */);
         }
 
@@ -217,6 +220,7 @@
             if ((types & WindowInsets.Type.ime()) == 0) {
                 return;
             }
+            if (DEBUG) Slog.d(TAG, "Got hideInsets for ime");
             startAnimation(false /* show */);
         }
 
@@ -241,6 +245,11 @@
                 return;
             }
             mHandler.post(() -> {
+                if (DEBUG) {
+                    Slog.d(TAG, "Run startAnim  show:" + show + "  was:"
+                            + (mAnimationDirection == DIRECTION_SHOW ? "SHOW"
+                            : (mAnimationDirection == DIRECTION_HIDE ? "HIDE" : "NONE")));
+                }
                 if ((mAnimationDirection == DIRECTION_SHOW && show)
                         || (mAnimationDirection == DIRECTION_HIDE && !show)) {
                     return;
@@ -289,6 +298,11 @@
                     public void onAnimationStart(Animator animation) {
                         SurfaceControl.Transaction t = mTransactionPool.acquire();
                         t.setPosition(mImeSourceControl.getLeash(), x, startY);
+                        if (DEBUG) {
+                            Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:"
+                                    + imeTop(imeSource, hiddenY) + "->" + imeTop(imeSource, shownY)
+                                    + " showing:" + (mAnimationDirection == DIRECTION_SHOW));
+                        }
                         dispatchStartPositioning(mDisplayId, imeTop(imeSource, hiddenY),
                                 imeTop(imeSource, shownY), mAnimationDirection == DIRECTION_SHOW,
                                 t);
@@ -304,6 +318,7 @@
                     }
                     @Override
                     public void onAnimationEnd(Animator animation) {
+                        if (DEBUG) Slog.d(TAG, "onAnimationEnd " + mCancelled);
                         SurfaceControl.Transaction t = mTransactionPool.acquire();
                         if (!mCancelled) {
                             t.setPosition(mImeSourceControl.getLeash(), x, endY);
diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
index ccb8699..381ccdb 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
@@ -196,7 +196,7 @@
             final Display display = mDisplayController.getDisplay(mDisplayId);
             SurfaceControlViewHost viewRoot = new SurfaceControlViewHost(mContext, display, wwm);
             attrs.flags |= FLAG_HARDWARE_ACCELERATED;
-            viewRoot.addView(view, attrs);
+            viewRoot.setView(view, attrs);
             mViewRoots.put(view, viewRoot);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 1b34b3d..b6537bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -23,6 +23,8 @@
 
 import static com.android.systemui.ScreenDecorations.rectsToRegion;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -42,6 +44,7 @@
 import android.content.res.Configuration;
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.graphics.drawable.VectorDrawable;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.testing.AndroidTestingRunner;
@@ -142,6 +145,8 @@
                 com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
         mContext.getOrCreateTestableResources()
                 .addOverride(dimen.rounded_corner_content_padding, 0);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
 
         // no cutout
         doReturn(null).when(mScreenDecorations).getCutout();
@@ -161,6 +166,8 @@
                 com.android.internal.R.dimen.rounded_corner_radius, 20);
         mContext.getOrCreateTestableResources()
                 .addOverride(dimen.rounded_corner_content_padding, 20);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
 
         // no cutout
         doReturn(null).when(mScreenDecorations).getCutout();
@@ -182,6 +189,67 @@
     }
 
     @Test
+    public void testRoundingRadius_NoCutout() {
+        final int testRadius = 1;
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.dimen.rounded_corner_radius, testRadius);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.dimen.rounded_corner_radius_top, testRadius);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.dimen.rounded_corner_radius_bottom, testRadius);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+
+        // no cutout
+        doReturn(null).when(mScreenDecorations).getCutout();
+
+        mScreenDecorations.start();
+        // Size of corner view should same as rounded_corner_radius{_top|_bottom}
+        assertThat(mScreenDecorations.mRoundedDefault).isEqualTo(testRadius);
+        assertThat(mScreenDecorations.mRoundedDefaultTop).isEqualTo(testRadius);
+        assertThat(mScreenDecorations.mRoundedDefaultBottom).isEqualTo(testRadius);
+    }
+
+    @Test
+    public void testRoundingMultipleRadius_NoCutout() {
+        final VectorDrawable d = (VectorDrawable) mContext.getDrawable(R.drawable.rounded);
+        final int multipleRadiusSize = Math.max(d.getIntrinsicWidth(), d.getIntrinsicHeight());
+
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.dimen.rounded_corner_radius, 9999);
+        mContext.getOrCreateTestableResources()
+                .addOverride(dimen.rounded_corner_content_padding, 9999);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_roundedCornerMultipleRadius, true);
+
+        // no cutout
+        doReturn(null).when(mScreenDecorations).getCutout();
+
+        mScreenDecorations.start();
+        // Top and bottom windows are created for rounded corners.
+        verify(mWindowManager, times(1))
+                .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]), any());
+        verify(mWindowManager, times(1))
+                .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]), any());
+
+        // Left and right window should be null.
+        assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
+        assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
+
+        // One tunable.
+        verify(mTunerService, times(1)).addTunable(any(), any());
+
+        // Size of corner view should exactly match max(width, height) of R.drawable.rounded
+        assertThat(mScreenDecorations.mRoundedDefault).isEqualTo(multipleRadiusSize);
+        assertThat(mScreenDecorations.mRoundedDefaultTop).isEqualTo(multipleRadiusSize);
+        assertThat(mScreenDecorations.mRoundedDefaultBottom).isEqualTo(multipleRadiusSize);
+    }
+
+    @Test
     public void testNoRounding_CutoutShortEdge() {
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, true);
@@ -193,6 +261,8 @@
                 com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
         mContext.getOrCreateTestableResources()
                 .addOverride(dimen.rounded_corner_content_padding, 0);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
 
         // top cutout
         doReturn(new DisplayCutout(
@@ -227,6 +297,8 @@
                 com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
         mContext.getOrCreateTestableResources()
                 .addOverride(dimen.rounded_corner_content_padding, 0);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
 
         // left cutout
         doReturn(new DisplayCutout(
@@ -257,6 +329,8 @@
                 com.android.internal.R.dimen.rounded_corner_radius, 20);
         mContext.getOrCreateTestableResources()
                 .addOverride(dimen.rounded_corner_content_padding, 20);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
 
         // top cutout
         doReturn(new DisplayCutout(
@@ -288,6 +362,8 @@
                 com.android.internal.R.dimen.rounded_corner_radius, 20);
         mContext.getOrCreateTestableResources()
                 .addOverride(dimen.rounded_corner_content_padding, 20);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
 
         // left cutout
         doReturn(new DisplayCutout(
@@ -319,6 +395,8 @@
                 com.android.internal.R.dimen.rounded_corner_radius, 20);
         mContext.getOrCreateTestableResources()
                 .addOverride(dimen.rounded_corner_content_padding, 20);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
 
         // top and left cutout
         doReturn(new DisplayCutout(
@@ -355,6 +433,8 @@
                 com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
         mContext.getOrCreateTestableResources()
                 .addOverride(dimen.rounded_corner_content_padding, 0);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
 
         // Set to short edge cutout(top).
         doReturn(new DisplayCutout(
@@ -402,6 +482,8 @@
                 com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
         mContext.getOrCreateTestableResources()
                 .addOverride(dimen.rounded_corner_content_padding, 0);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
 
         // top cutout
         doReturn(new DisplayCutout(
@@ -440,6 +522,8 @@
                 com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.dimen.rounded_corner_radius, 20);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
 
         mScreenDecorations.start();
         assertEquals(mScreenDecorations.mRoundedDefault, 20);
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 f40fc94..866dfdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -268,13 +268,18 @@
         sendUpdatedEntryAtTime(mEntryB2, 5000);
         mBubbleData.setListener(mListener);
 
-        // Test
         sendUpdatedEntryAtTime(mEntryC1, 6000);
         verifyUpdateReceived();
-
-        // Verify
         assertBubbleRemoved(mBubbleA1, BubbleController.DISMISS_AGED);
-        assertThat(mBubbleData.getOverflowBubbles()).isEqualTo(ImmutableList.of(mBubbleA1));
+        assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
+
+        Bubble bubbleA1 = mBubbleData.getOrCreateBubble(mEntryA1);
+        bubbleA1.markUpdatedAt(7000L);
+        mBubbleData.notificationEntryUpdated(bubbleA1, false /* suppressFlyout*/,
+                true /* showInShade */);
+        verifyUpdateReceived();
+        assertBubbleRemoved(mBubbleA2, BubbleController.DISMISS_AGED);
+        assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
     }
 
     /**
@@ -931,6 +936,11 @@
         assertThat(update.expanded).named("expanded").isEqualTo(expected);
     }
 
+    private void assertOverflowChangedTo(ImmutableList<Bubble> bubbles) {
+        BubbleData.Update update = mUpdateCaptor.getValue();
+        assertThat(update.overflowBubbles).isEqualTo(bubbles);
+    }
+
 
     private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName) {
         return createBubbleEntry(userId, notifKey, packageName, 1000);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
index e3187cb9..b1ac022 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
@@ -43,6 +43,7 @@
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.IntSupplier;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -59,7 +60,13 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        mStackController = spy(new TestableStackController(mFloatingContentCoordinator));
+        mStackController = spy(new TestableStackController(
+                mFloatingContentCoordinator, new IntSupplier() {
+                    @Override
+                    public int getAsInt() {
+                        return mLayout.getChildCount();
+                    }
+                }));
         mLayout.setActiveController(mStackController);
         addOneMoreThanBubbleLimitBubbles();
         mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset);
@@ -295,8 +302,9 @@
      */
     private class TestableStackController extends StackAnimationController {
         TestableStackController(
-                FloatingContentCoordinator floatingContentCoordinator) {
-            super(floatingContentCoordinator);
+                FloatingContentCoordinator floatingContentCoordinator,
+                IntSupplier bubbleCountSupplier) {
+            super(floatingContentCoordinator, bubbleCountSupplier);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 8320b05..6871aad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -42,6 +42,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.phone.NavigationModeController;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -71,6 +72,7 @@
     private @Mock DumpManager mDumpManager;
     private @Mock PowerManager mPowerManager;
     private @Mock TrustManager mTrustManager;
+    private @Mock NavigationModeController mNavigationModeController;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -88,7 +90,7 @@
                 mContext, mFalsingManager, mLockPatternUtils, mBroadcastDispatcher,
                 mNotificationShadeWindowController, () -> mStatusBarKeyguardViewManager,
                 mDismissCallbackRegistry, mUpdateMonitor, mDumpManager, mUiBgExecutor,
-                mPowerManager, mTrustManager, mDeviceConfig);
+                mPowerManager, mTrustManager, mDeviceConfig, mNavigationModeController);
         mViewMediator.start();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
index b12db2b..0bf0f04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
@@ -24,6 +24,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
+import android.util.Size;
 import android.view.DisplayInfo;
 import android.view.Gravity;
 
@@ -51,6 +52,7 @@
     private static final float MIN_ASPECT_RATIO = 0.5f;
     private static final float MAX_ASPECT_RATIO = 2f;
     private static final Rect EMPTY_CURRENT_BOUNDS = null;
+    private static final Size EMPTY_MINIMAL_SIZE = null;
 
     private PipBoundsHandler mPipBoundsHandler;
     private DisplayInfo mDefaultDisplayInfo;
@@ -119,7 +121,7 @@
         };
         for (float aspectRatio : aspectRatios) {
             final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                    aspectRatio, EMPTY_CURRENT_BOUNDS);
+                    aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
             final float actualAspectRatio =
                     destinationBounds.width() / (destinationBounds.height() * 1f);
             assertEquals("Destination bounds matches the given aspect ratio",
@@ -135,7 +137,7 @@
         };
         for (float aspectRatio : invalidAspectRatios) {
             final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                    aspectRatio, EMPTY_CURRENT_BOUNDS);
+                    aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
             final float actualAspectRatio =
                     destinationBounds.width() / (destinationBounds.height() * 1f);
             assertEquals("Destination bounds fallbacks to default aspect ratio",
@@ -151,7 +153,7 @@
         currentBounds.right = (int) (currentBounds.height() * aspectRatio) + currentBounds.left;
 
         final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                aspectRatio, currentBounds);
+                aspectRatio, currentBounds, EMPTY_MINIMAL_SIZE);
 
         final float actualAspectRatio =
                 destinationBounds.width() / (destinationBounds.height() * 1f);
@@ -160,14 +162,58 @@
     }
 
     @Test
+    public void getDestinationBounds_withMinSize_returnMinBounds() {
+        final float[] aspectRatios = new float[] {
+                (MIN_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2,
+                DEFAULT_ASPECT_RATIO,
+                (MAX_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2
+        };
+        final Size[] minimalSizes = new Size[] {
+                new Size((int) (100 * aspectRatios[0]), 100),
+                new Size((int) (100 * aspectRatios[1]), 100),
+                new Size((int) (100 * aspectRatios[2]), 100)
+        };
+        for (int i = 0; i < aspectRatios.length; i++) {
+            final float aspectRatio = aspectRatios[i];
+            final Size minimalSize = minimalSizes[i];
+            final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
+                    aspectRatio, EMPTY_CURRENT_BOUNDS, minimalSize);
+            assertTrue("Destination bounds is no smaller than minimal requirement",
+                    (destinationBounds.width() == minimalSize.getWidth()
+                            && destinationBounds.height() >= minimalSize.getHeight())
+                            || (destinationBounds.height() == minimalSize.getHeight()
+                            && destinationBounds.width() >= minimalSize.getWidth()));
+            final float actualAspectRatio =
+                    destinationBounds.width() / (destinationBounds.height() * 1f);
+            assertEquals("Destination bounds matches the given aspect ratio",
+                    aspectRatio, actualAspectRatio, ASPECT_RATIO_ERROR_MARGIN);
+        }
+    }
+
+    @Test
+    public void getDestinationBounds_withCurrentBounds_ignoreMinBounds() {
+        final float aspectRatio = (DEFAULT_ASPECT_RATIO + MAX_ASPECT_RATIO) / 2;
+        final Rect currentBounds = new Rect(0, 0, 0, 100);
+        currentBounds.right = (int) (currentBounds.height() * aspectRatio) + currentBounds.left;
+        final Size minSize = new Size(currentBounds.width() / 2, currentBounds.height() / 2);
+
+        final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
+                aspectRatio, currentBounds, minSize);
+
+        assertTrue("Destination bounds ignores minimal size",
+                destinationBounds.width() > minSize.getWidth()
+                        && destinationBounds.height() > minSize.getHeight());
+    }
+
+    @Test
     public void setShelfHeight_offsetBounds() {
         final int shelfHeight = 100;
         final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         mPipBoundsHandler.setShelfHeight(true, shelfHeight);
         final Rect newPosition = mPipBoundsHandler.getDestinationBounds(
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         oldPosition.offset(0, -shelfHeight);
         assertBoundsWithMargin("offsetBounds by shelf", oldPosition, newPosition);
@@ -177,11 +223,11 @@
     public void onImeVisibilityChanged_offsetBounds() {
         final int imeHeight = 100;
         final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         mPipBoundsHandler.onImeVisibilityChanged(true, imeHeight);
         final Rect newPosition = mPipBoundsHandler.getDestinationBounds(
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         oldPosition.offset(0, -imeHeight);
         assertBoundsWithMargin("offsetBounds by IME", oldPosition, newPosition);
@@ -191,13 +237,13 @@
     public void onSaveReentryBounds_restoreLastPosition() {
         final ComponentName componentName = new ComponentName(mContext, "component1");
         final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         oldPosition.offset(0, -100);
         mPipBoundsHandler.onSaveReentryBounds(componentName, oldPosition);
 
         final Rect newPosition = mPipBoundsHandler.getDestinationBounds(
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         assertBoundsWithMargin("restoreLastPosition", oldPosition, newPosition);
     }
@@ -206,14 +252,14 @@
     public void onResetReentryBounds_useDefaultBounds() {
         final ComponentName componentName = new ComponentName(mContext, "component1");
         final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
         final Rect newBounds = new Rect(defaultBounds);
         newBounds.offset(0, -100);
         mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds);
 
         mPipBoundsHandler.onResetReentryBounds(componentName);
         final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         assertBoundsWithMargin("useDefaultBounds", defaultBounds, actualBounds);
     }
@@ -222,14 +268,14 @@
     public void onResetReentryBounds_componentMismatch_restoreLastPosition() {
         final ComponentName componentName = new ComponentName(mContext, "component1");
         final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
         final Rect newBounds = new Rect(defaultBounds);
         newBounds.offset(0, -100);
         mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds);
 
         mPipBoundsHandler.onResetReentryBounds(new ComponentName(mContext, "component2"));
         final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(
-                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         assertBoundsWithMargin("restoreLastPosition", newBounds, actualBounds);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
index fe8b89f..a58000d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -55,6 +55,7 @@
     private boolean mIsVisuallyInterruptive = false;
     private boolean mIsConversation = false;
     private ShortcutInfo mShortcutInfo = null;
+    private boolean mIsBubble = false;
 
     public RankingBuilder() {
     }
@@ -82,6 +83,7 @@
         mIsVisuallyInterruptive = ranking.visuallyInterruptive();
         mIsConversation = ranking.isConversation();
         mShortcutInfo = ranking.getShortcutInfo();
+        mIsBubble = ranking.isBubble();
     }
 
     public Ranking build() {
@@ -108,7 +110,8 @@
                 mCanBubble,
                 mIsVisuallyInterruptive,
                 mIsConversation,
-                mShortcutInfo);
+                mShortcutInfo,
+                mIsBubble);
         return ranking;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 312bb7f..972357e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -143,7 +143,7 @@
                     IMPORTANCE_DEFAULT,
                     null, null,
                     null, null, null, true, sentiment, false, -1, false, null, null, false, false,
-                    false, null);
+                    false, null, false);
             return true;
         }).when(mRankingMap).getRanking(eq(key), any(Ranking.class));
     }
@@ -162,7 +162,7 @@
                     null, null,
                     null, null, null, true,
                     Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
-                    false, smartActions, null, false, false, false, null);
+                    false, smartActions, null, false, false, false, null, false);
             return true;
         }).when(mRankingMap).getRanking(eq(key), any(Ranking.class));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index b7184be..82de4a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -34,16 +34,17 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static java.util.Collections.singletonList;
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.Nullable;
@@ -83,13 +84,13 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -123,6 +124,8 @@
     private NotifCollection mCollection;
     private BatchableNotificationHandler mNotifHandler;
 
+    private InOrder mListenerInOrder;
+
     private NoManSimulator mNoMan;
 
     @Before
@@ -133,6 +136,8 @@
         when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(true);
         when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(true);
 
+        mListenerInOrder = inOrder(mCollectionListener);
+
         mCollection = new NotifCollection(
                 mStatusBarService,
                 mock(DumpManager.class),
@@ -159,10 +164,12 @@
                         .setRank(4747));
 
         // THEN the listener is notified
-        verify(mCollectionListener).onEntryInit(mEntryCaptor.capture());
-        NotificationEntry entry = mEntryCaptor.getValue();
+        final NotificationEntry entry = mCollectionListener.getEntry(notif1.key);
 
-        verify(mCollectionListener).onEntryAdded(entry);
+        mListenerInOrder.verify(mCollectionListener).onEntryInit(entry);
+        mListenerInOrder.verify(mCollectionListener).onEntryAdded(entry);
+        mListenerInOrder.verify(mCollectionListener).onRankingApplied();
+
         assertEquals(notif1.key, entry.getKey());
         assertEquals(notif1.sbn, entry.getSbn());
         assertEquals(notif1.ranking, entry.getRanking());
@@ -215,12 +222,11 @@
         assertEquals(entry2.getRanking(), capturedUpdate.getRanking());
 
         // THEN onBuildList is called only once
-        verify(mBuildListener).onBuildList(mBuildListCaptor.capture());
-        assertEquals(new ArraySet<>(Arrays.asList(
-                capturedAdds.get(0),
-                capturedAdds.get(1),
-                capturedUpdate
-        )), new ArraySet<>(mBuildListCaptor.getValue()));
+        verifyBuiltList(
+                List.of(
+                        capturedAdds.get(0),
+                        capturedAdds.get(1),
+                        capturedUpdate));
     }
 
     @Test
@@ -234,9 +240,11 @@
                 .setRank(89));
 
         // THEN the listener is notified
-        verify(mCollectionListener).onEntryUpdated(mEntryCaptor.capture());
+        final NotificationEntry entry = mCollectionListener.getEntry(notif2.key);
 
-        NotificationEntry entry = mEntryCaptor.getValue();
+        mListenerInOrder.verify(mCollectionListener).onEntryUpdated(entry);
+        mListenerInOrder.verify(mCollectionListener).onRankingApplied();
+
         assertEquals(notif2.key, entry.getKey());
         assertEquals(notif2.sbn, entry.getSbn());
         assertEquals(notif2.ranking, entry.getRanking());
@@ -256,8 +264,10 @@
         mNoMan.retractNotif(notif.sbn, REASON_APP_CANCEL);
 
         // THEN the listener is notified
-        verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL);
-        verify(mCollectionListener).onEntryCleanUp(entry);
+        mListenerInOrder.verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL);
+        mListenerInOrder.verify(mCollectionListener).onEntryCleanUp(entry);
+        mListenerInOrder.verify(mCollectionListener).onRankingApplied();
+
         assertEquals(notif.sbn, entry.getSbn());
         assertEquals(notif.ranking, entry.getRanking());
     }
@@ -415,8 +425,8 @@
 
         // THEN the dismissed entry still appears in the notification set
         assertEquals(
-                new ArraySet<>(Collections.singletonList(entry1)),
-                new ArraySet<>(mCollection.getActiveNotifs()));
+                new ArraySet<>(singletonList(entry1)),
+                new ArraySet<>(mCollection.getAllNotifs()));
     }
 
     @Test
@@ -444,7 +454,7 @@
         mNoMan.retractNotif(notif2.sbn, REASON_CANCEL);
         assertEquals(
                 new ArraySet<>(List.of(entry1, entry2, entry3)),
-                new ArraySet<>(mCollection.getActiveNotifs()));
+                new ArraySet<>(mCollection.getAllNotifs()));
 
         // WHEN the summary is dismissed by the user
         mCollection.dismissNotification(entry1, defaultStats(entry1));
@@ -452,7 +462,7 @@
         // THEN the summary is removed, but both children stick around
         assertEquals(
                 new ArraySet<>(List.of(entry2, entry3)),
-                new ArraySet<>(mCollection.getActiveNotifs()));
+                new ArraySet<>(mCollection.getAllNotifs()));
         assertEquals(NOT_DISMISSED, entry2.getDismissState());
         assertEquals(NOT_DISMISSED, entry3.getDismissState());
     }
@@ -561,7 +571,7 @@
     }
 
     @Test
-    public void testEndDismissInterceptionUpdatesDismissInterceptors() throws RemoteException {
+    public void testEndDismissInterceptionUpdatesDismissInterceptors() {
         // GIVEN a collection with notifications with multiple dismiss interceptors
         mInterceptor1.shouldInterceptDismissal = true;
         mInterceptor2.shouldInterceptDismissal = true;
@@ -592,7 +602,7 @@
 
 
     @Test(expected = IllegalStateException.class)
-    public void testEndingDismissalOfNonInterceptedThrows() throws RemoteException {
+    public void testEndingDismissalOfNonInterceptedThrows() {
         // GIVEN a collection with notifications with a dismiss interceptor that hasn't been called
         mInterceptor1.shouldInterceptDismissal = false;
         mCollection.addNotificationDismissInterceptor(mInterceptor1);
@@ -820,7 +830,7 @@
         verify(mExtender3).shouldExtendLifetime(entry2, REASON_CLICK);
 
         // THEN the entry is not removed
-        assertTrue(mCollection.getActiveNotifs().contains(entry2));
+        assertTrue(mCollection.getAllNotifs().contains(entry2));
 
         // THEN the entry properly records all extenders that returned true
         assertEquals(Arrays.asList(mExtender1, mExtender2), entry2.mLifetimeExtenders);
@@ -841,7 +851,7 @@
 
         // GIVEN a notification gets lifetime-extended by one of them
         mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
-        assertTrue(mCollection.getActiveNotifs().contains(entry2));
+        assertTrue(mCollection.getAllNotifs().contains(entry2));
         clearInvocations(mExtender1, mExtender2, mExtender3);
 
         // WHEN the last active extender expires (but new ones become active)
@@ -856,7 +866,7 @@
         verify(mExtender3).shouldExtendLifetime(entry2, REASON_APP_CANCEL);
 
         // THEN the entry is not removed
-        assertTrue(mCollection.getActiveNotifs().contains(entry2));
+        assertTrue(mCollection.getAllNotifs().contains(entry2));
 
         // THEN the entry properly records all extenders that returned true
         assertEquals(Arrays.asList(mExtender1, mExtender3), entry2.mLifetimeExtenders);
@@ -878,7 +888,7 @@
 
         // GIVEN a notification gets lifetime-extended by a couple of them
         mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
-        assertTrue(mCollection.getActiveNotifs().contains(entry2));
+        assertTrue(mCollection.getAllNotifs().contains(entry2));
         clearInvocations(mExtender1, mExtender2, mExtender3);
 
         // WHEN one (but not all) of the extenders expires
@@ -886,7 +896,7 @@
         mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
 
         // THEN the entry is not removed
-        assertTrue(mCollection.getActiveNotifs().contains(entry2));
+        assertTrue(mCollection.getAllNotifs().contains(entry2));
 
         // THEN we don't re-query the extenders
         verify(mExtender1, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL);
@@ -894,7 +904,7 @@
         verify(mExtender3, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL);
 
         // THEN the entry properly records all extenders that returned true
-        assertEquals(Arrays.asList(mExtender1), entry2.mLifetimeExtenders);
+        assertEquals(singletonList(mExtender1), entry2.mLifetimeExtenders);
     }
 
     @Test
@@ -913,7 +923,7 @@
 
         // GIVEN a notification gets lifetime-extended by a couple of them
         mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
-        assertTrue(mCollection.getActiveNotifs().contains(entry2));
+        assertTrue(mCollection.getAllNotifs().contains(entry2));
         clearInvocations(mExtender1, mExtender2, mExtender3);
 
         // WHEN all of the active extenders expire
@@ -923,7 +933,7 @@
         mExtender1.callback.onEndLifetimeExtension(mExtender1, entry2);
 
         // THEN the entry removed
-        assertFalse(mCollection.getActiveNotifs().contains(entry2));
+        assertFalse(mCollection.getAllNotifs().contains(entry2));
         verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN);
     }
 
@@ -943,7 +953,7 @@
 
         // GIVEN a notification gets lifetime-extended by a couple of them
         mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
-        assertTrue(mCollection.getActiveNotifs().contains(entry2));
+        assertTrue(mCollection.getAllNotifs().contains(entry2));
         clearInvocations(mExtender1, mExtender2, mExtender3);
 
         // WHEN the notification is reposted
@@ -954,7 +964,7 @@
         verify(mExtender2).cancelLifetimeExtension(entry2);
 
         // THEN the notification is still present
-        assertTrue(mCollection.getActiveNotifs().contains(entry2));
+        assertTrue(mCollection.getAllNotifs().contains(entry2));
     }
 
     @Test(expected = IllegalStateException.class)
@@ -973,7 +983,7 @@
 
         // GIVEN a notification gets lifetime-extended by a couple of them
         mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
-        assertTrue(mCollection.getActiveNotifs().contains(entry2));
+        assertTrue(mCollection.getAllNotifs().contains(entry2));
         clearInvocations(mExtender1, mExtender2, mExtender3);
 
         // WHEN a lifetime extender makes a reentrant call during cancelLifetimeExtension()
@@ -1002,7 +1012,7 @@
 
         // GIVEN a notification gets lifetime-extended by a couple of them
         mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
-        assertTrue(mCollection.getActiveNotifs().contains(entry2));
+        assertTrue(mCollection.getAllNotifs().contains(entry2));
         clearInvocations(mExtender1, mExtender2, mExtender3);
 
         // WHEN the notification is reposted
@@ -1055,11 +1065,11 @@
 
         // WHEN both notifications are manually dismissed together
         mCollection.dismissNotifications(
-                List.of(new Pair(entry1, defaultStats(entry1)),
-                        new Pair(entry2, defaultStats(entry2))));
+                List.of(new Pair<>(entry1, defaultStats(entry1)),
+                        new Pair<>(entry2, defaultStats(entry2))));
 
         // THEN build list is only called one time
-        verify(mBuildListener).onBuildList(any(Collection.class));
+        verifyBuiltList(List.of(entry1, entry2));
     }
 
     @Test
@@ -1074,8 +1084,8 @@
         DismissedByUserStats stats1 = defaultStats(entry1);
         DismissedByUserStats stats2 = defaultStats(entry2);
         mCollection.dismissNotifications(
-                List.of(new Pair(entry1, defaultStats(entry1)),
-                        new Pair(entry2, defaultStats(entry2))));
+                List.of(new Pair<>(entry1, defaultStats(entry1)),
+                        new Pair<>(entry2, defaultStats(entry2))));
 
         // THEN we send the dismissals to system server
         verify(mStatusBarService).onNotificationClear(
@@ -1109,8 +1119,8 @@
 
         // WHEN both notifications are manually dismissed together
         mCollection.dismissNotifications(
-                List.of(new Pair(entry1, defaultStats(entry1)),
-                        new Pair(entry2, defaultStats(entry2))));
+                List.of(new Pair<>(entry1, defaultStats(entry1)),
+                        new Pair<>(entry2, defaultStats(entry2))));
 
         // THEN the entries are marked as dismissed
         assertEquals(DISMISSED, entry1.getDismissState());
@@ -1134,8 +1144,8 @@
 
         // WHEN both notifications are manually dismissed together
         mCollection.dismissNotifications(
-                List.of(new Pair(entry1, defaultStats(entry1)),
-                        new Pair(entry2, defaultStats(entry2))));
+                List.of(new Pair<>(entry1, defaultStats(entry1)),
+                        new Pair<>(entry2, defaultStats(entry2))));
 
         // THEN all interceptors get checked
         verify(mInterceptor1).shouldInterceptDismissal(entry1);
@@ -1162,7 +1172,7 @@
         mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
 
         // THEN build list is only called one time
-        verify(mBuildListener).onBuildList(any(Collection.class));
+        verifyBuiltList(List.of(entry1, entry2));
     }
 
     @Test
@@ -1271,13 +1281,18 @@
                 NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
     }
 
-    public CollectionEvent postNotif(NotificationEntryBuilder builder) {
+    private CollectionEvent postNotif(NotificationEntryBuilder builder) {
         clearInvocations(mCollectionListener);
         NotifEvent rawEvent = mNoMan.postNotif(builder);
         verify(mCollectionListener).onEntryAdded(mEntryCaptor.capture());
         return new CollectionEvent(rawEvent, requireNonNull(mEntryCaptor.getValue()));
     }
 
+    private void verifyBuiltList(Collection<NotificationEntry> list) {
+        verify(mBuildListener).onBuildList(mBuildListCaptor.capture());
+        assertEquals(new ArraySet<>(list), new ArraySet<>(mBuildListCaptor.getValue()));
+    }
+
     private static class RecordingCollectionListener implements NotifCollectionListener {
         private final Map<String, NotificationEntry> mLastSeenEntries = new ArrayMap<>();
 
@@ -1303,6 +1318,14 @@
         public void onEntryCleanUp(NotificationEntry entry) {
         }
 
+        @Override
+        public void onRankingApplied() {
+        }
+
+        @Override
+        public void onRankingUpdate(RankingMap rankingMap) {
+        }
+
         public NotificationEntry getEntry(String key) {
             if (!mLastSeenEntries.containsKey(key)) {
                 throw new RuntimeException("Key not found: " + key);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
index 67b1aad..407e1e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
@@ -223,7 +223,7 @@
                 .setPkg(TEST_PKG)
                 .setId(2)
                 .build();
-        when(mNotifPipeline.getActiveNotifs()).thenReturn(List.of(entry1, entry2, entry2Other));
+        when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry1, entry2, entry2Other));
 
         // GIVEN that entry2 is currently associated with a foreground service
         when(mForegroundServiceController.getStandardLayoutKey(0, TEST_PKG))
@@ -253,7 +253,7 @@
                 .setPkg(TEST_PKG)
                 .setId(2)
                 .build();
-        when(mNotifPipeline.getActiveNotifs()).thenReturn(List.of(entry));
+        when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry));
         when(mForegroundServiceController.getStandardLayoutKey(0, TEST_PKG))
                 .thenReturn(entry.getKey());
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index e960185..c356e0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -329,10 +329,10 @@
     @Test
     public void testIconScrollXAfterTranslationAndReset() throws Exception {
         mGroupRow.setTranslation(50);
-        assertEquals(50, -mGroupRow.getEntry().expandedIcon.getScrollX());
+        assertEquals(50, -mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX());
 
         mGroupRow.resetTranslation();
-        assertEquals(0, mGroupRow.getEntry().expandedIcon.getScrollX());
+        assertEquals(0, mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index a21a047..2e787ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
+import android.content.pm.LauncherApps;
 import android.os.Handler;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
@@ -61,6 +62,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.icon.IconBuilder;
+import com.android.systemui.statusbar.notification.icon.IconManager;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -245,14 +248,13 @@
                 mLockscreenUserManager,
                 pipeline,
                 stage,
-                true, /* allowLongPress */
-                mock(KeyguardBypassController.class),
-                mock(StatusBarStateController.class),
-                mGroupManager,
-                mGutsManager,
                 mNotificationInterruptionStateProvider,
                 RowInflaterTask::new,
-                mExpandableNotificationRowComponentBuilder);
+                mExpandableNotificationRowComponentBuilder,
+                new IconManager(
+                        mEntryManager,
+                        mock(LauncherApps.class),
+                        new IconBuilder(mContext)));
 
         mEntryManager.setUpWithPresenter(mPresenter);
         mEntryManager.addNotificationEntryListener(mEntryListener);
@@ -284,7 +286,8 @@
                 false,
                 false,
                 false,
-                null);
+                null,
+                false);
         mRankingMap = new NotificationListenerService.RankingMap(new Ranking[] {ranking});
 
         TestableLooper.get(this).processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 5a89fc4..dc3374b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -34,6 +34,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.LauncherApps;
 import android.graphics.drawable.Icon;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
@@ -53,6 +54,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.icon.IconBuilder;
+import com.android.systemui.statusbar.notification.icon.IconManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -93,6 +96,7 @@
     private final NotifBindPipeline mBindPipeline;
     private final NotifCollectionListener mBindPipelineEntryListener;
     private final RowContentBindStage mBindStage;
+    private final IconManager mIconManager;
     private StatusBarStateController mStatusBarStateController;
 
     public NotificationTestHelper(Context context, TestableDependency dependency) {
@@ -106,6 +110,10 @@
                 mock(KeyguardBypassController.class), mock(NotificationGroupManager.class),
                 mock(ConfigurationControllerImpl.class));
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
+        mIconManager = new IconManager(
+                mock(CommonNotifCollection.class),
+                mock(LauncherApps.class),
+                new IconBuilder(mContext));
 
         NotificationContentInflater contentBinder = new NotificationContentInflater(
                 mock(NotifRemoteViewCache.class),
@@ -377,7 +385,7 @@
                 .build();
 
         entry.setRow(row);
-        entry.createIcons(mContext, entry.getSbn());
+        mIconManager.createIcons(entry);
         row.setEntry(entry);
 
         mBindPipelineEntryListener.onEntryInit(entry);
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
index a18f5da..fd6f171 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
@@ -32,7 +32,7 @@
  * @hide
  */
 @SystemApi(client = MODULE_LIBRARIES)
-public class TetheringConstants {
+public final class TetheringConstants {
     /** An explicit private class to avoid exposing constructor.*/
     private TetheringConstants() { }
 
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index 4af5c53..04d6215 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -157,4 +157,49 @@
 
     <!-- ComponentName of the service used to run no ui tether provisioning. -->
     <string translatable="false" name="config_wifi_tether_enable">com.android.settings/.wifi.tether.TetherService</string>
+
+    <!-- Enable tethering notification -->
+    <!-- Icons for showing tether enable notification.
+         Each item should have two elements and be separated with ";".
+
+         The first element is downstream types which is one of tethering. This element has to be
+         made by WIFI, USB, BT, and OR'd with the others. Use "|" to combine multiple downstream
+         types and use "," to separate each combinations. Such as
+
+             USB|BT,WIFI|USB|BT
+
+         The second element is icon for the item. This element has to be composed by
+         <package name>:drawable/<resource name>. Such as
+
+             1. com.android.networkstack.tethering:drawable/stat_sys_tether_general
+             2. android:drawable/xxx
+
+         So the entire string of each item would be
+
+             USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general
+
+         NOTE: One config can be separated into two or more for readability. Such as
+
+               WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;android:drawable/xxx
+
+               can be separated into
+
+               WIFI|USB;android:drawable/xxx
+               WIFI|BT;android:drawable/xxx
+               USB|BT;android:drawable/xxx
+               WIFI|USB|BT;android:drawable/xxx
+
+         Notification will not show if the downstream type isn't listed in array.
+         Empty array means disable notifications. -->
+    <!-- In AOSP, hotspot is configured to no notification by default. Because status bar has showed
+         an icon on the right side already -->
+    <string-array translatable="false" name="tethering_notification_icons">
+        <item>USB;com.android.networkstack.tethering:drawable/stat_sys_tether_usb</item>
+        <item>BT;com.android.networkstack.tethering:drawable/stat_sys_tether_bluetooth</item>
+        <item>WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general</item>
+    </string-array>
+    <!-- String for tether enable notification title. -->
+    <string name="tethering_notification_title">@string/tethered_notification_title</string>
+    <!-- String for tether enable notification message. -->
+    <string name="tethering_notification_message">@string/tethered_notification_message</string>
 </resources>
diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml
index fe025c7..bbba3f3 100644
--- a/packages/Tethering/res/values/overlayable.xml
+++ b/packages/Tethering/res/values/overlayable.xml
@@ -16,6 +16,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
     <overlayable name="TetheringConfig">
         <policy type="product|system|vendor">
+            <!-- Params from config.xml that can be overlaid -->
             <item type="array" name="config_tether_usb_regexs"/>
             <item type="array" name="config_tether_ncm_regexs" />
             <item type="array" name="config_tether_wifi_regexs"/>
@@ -31,6 +32,45 @@
             <item type="string" name="config_mobile_hotspot_provision_response"/>
             <item type="integer" name="config_mobile_hotspot_provision_check_period"/>
             <item type="string" name="config_wifi_tether_enable"/>
+            <!-- Configuration values for TetheringNotificationUpdater -->
+            <!-- Icons for showing tether enable notification.
+            Each item should have two elements and be separated with ";".
+
+            The first element is downstream types which is one of tethering. This element has to be
+            made by WIFI, USB, BT, and OR'd with the others. Use "|" to combine multiple downstream
+            types and use "," to separate each combinations. Such as
+
+            USB|BT,WIFI|USB|BT
+
+            The second element is icon for the item. This element has to be composed by
+            <package name>:drawable/<resource name>. Such as
+
+            1. com.android.networkstack.tethering:drawable/stat_sys_tether_general
+            2. android:drawable/xxx
+
+            So the entire string of each item would be
+
+            USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general
+
+            NOTE: One config can be separated into two or more for readability. Such as
+
+                  WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;android:drawable/xxx
+
+                  can be separated into
+
+                  WIFI|USB;android:drawable/xxx
+                  WIFI|BT;android:drawable/xxx
+                  USB|BT;android:drawable/xxx
+                  WIFI|USB|BT;android:drawable/xxx
+
+            Notification will not show if the downstream type isn't listed in array.
+            Empty array means disable notifications. -->
+            <item type="array" name="tethering_notification_icons"/>
+            <!-- String for tether enable notification title. -->
+            <item type="string" name="tethering_notification_title"/>
+            <!-- String for tether enable notification message. -->
+            <item type="string" name="tethering_notification_message"/>
+            <!-- Params from config.xml that can be overlaid -->
         </policy>
     </overlayable>
 </resources>
diff --git a/packages/Tethering/res/values/strings.xml b/packages/Tethering/res/values/strings.xml
index 792bce9..ba98a66 100644
--- a/packages/Tethering/res/values/strings.xml
+++ b/packages/Tethering/res/values/strings.xml
@@ -15,19 +15,21 @@
 -->
 <resources>
     <!-- Shown when the device is tethered -->
-    <!-- Strings for tethered notification title [CHAR LIMIT=200] -->
+    <!-- String for tethered notification title [CHAR LIMIT=200] -->
     <string name="tethered_notification_title">Tethering or hotspot active</string>
-    <!-- Strings for tethered notification message [CHAR LIMIT=200] -->
+    <!-- String for tethered notification message [CHAR LIMIT=200] -->
     <string name="tethered_notification_message">Tap to set up.</string>
 
     <!-- This notification is shown when tethering has been disabled on a user's device.
     The device is managed by the user's employer. Tethering can't be turned on unless the
     IT administrator allows it. The noun "admin" is another reference for "IT administrator." -->
-    <!-- Strings for tether disabling notification title [CHAR LIMIT=200] -->
+    <!-- String for tether disabling notification title [CHAR LIMIT=200] -->
     <string name="disable_tether_notification_title">Tethering is disabled</string>
-    <!-- Strings for tether disabling notification message [CHAR LIMIT=200] -->
+    <!-- String for tether disabling notification message [CHAR LIMIT=200] -->
     <string name="disable_tether_notification_message">Contact your admin for details</string>
 
-    <!-- Strings for tether notification channel name [CHAR LIMIT=200] -->
+    <!-- This string should be consistent with the "Hotspot & tethering" text in the "Network and
+    Internet" settings page. That is currently the tether_settings_title_all string. -->
+    <!-- String for tether notification channel name [CHAR LIMIT=200] -->
     <string name="notification_channel_tethering_status">Hotspot &amp; tethering status</string>
 </resources>
\ No newline at end of file
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index f89da84..3d8dbab 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -59,10 +59,8 @@
 import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
+import static com.android.server.connectivity.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
+
 import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothPan;
@@ -72,7 +70,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.Resources;
 import android.hardware.usb.UsbManager;
 import android.net.ConnectivityManager;
 import android.net.EthernetManager;
@@ -128,7 +125,6 @@
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
-import com.android.networkstack.tethering.R;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -224,14 +220,13 @@
     private final ActiveDataSubIdListener mActiveDataSubIdListener;
     private final ConnectedClientsTracker mConnectedClientsTracker;
     private final TetheringThreadExecutor mExecutor;
+    private final TetheringNotificationUpdater mNotificationUpdater;
     private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
     // All the usage of mTetheringEventCallback should run in the same thread.
     private ITetheringEventCallback mTetheringEventCallback = null;
 
     private volatile TetheringConfiguration mConfig;
     private InterfaceSet mCurrentUpstreamIfaceSet;
-    private Notification.Builder mTetheredNotificationBuilder;
-    private int mLastNotificationId;
 
     private boolean mRndisEnabled;       // track the RNDIS function enabled state
     // True iff. WiFi tethering should be started when soft AP is ready.
@@ -255,6 +250,7 @@
         mContext = mDeps.getContext();
         mNetd = mDeps.getINetd(mContext);
         mLooper = mDeps.getTetheringLooper();
+        mNotificationUpdater = mDeps.getNotificationUpdater(mContext);
 
         mPublicSync = new Object();
 
@@ -738,13 +734,10 @@
         final ArrayList<String> erroredList = new ArrayList<>();
         final ArrayList<Integer> lastErrorList = new ArrayList<>();
 
-        boolean wifiTethered = false;
-        boolean usbTethered = false;
-        boolean bluetoothTethered = false;
-
         final TetheringConfiguration cfg = mConfig;
         mTetherStatesParcel = new TetherStatesParcel();
 
+        int downstreamTypesMask = DOWNSTREAM_NONE;
         synchronized (mPublicSync) {
             for (int i = 0; i < mTetherStates.size(); i++) {
                 TetherState tetherState = mTetherStates.valueAt(i);
@@ -758,11 +751,11 @@
                     localOnlyList.add(iface);
                 } else if (tetherState.lastState == IpServer.STATE_TETHERED) {
                     if (cfg.isUsb(iface)) {
-                        usbTethered = true;
+                        downstreamTypesMask |= (1 << TETHERING_USB);
                     } else if (cfg.isWifi(iface)) {
-                        wifiTethered = true;
+                        downstreamTypesMask |= (1 << TETHERING_WIFI);
                     } else if (cfg.isBluetooth(iface)) {
-                        bluetoothTethered = true;
+                        downstreamTypesMask |= (1 << TETHERING_BLUETOOTH);
                     }
                     tetherList.add(iface);
                 }
@@ -796,98 +789,7 @@
                     "error", TextUtils.join(",", erroredList)));
         }
 
-        if (usbTethered) {
-            if (wifiTethered || bluetoothTethered) {
-                showTetheredNotification(R.drawable.stat_sys_tether_general);
-            } else {
-                showTetheredNotification(R.drawable.stat_sys_tether_usb);
-            }
-        } else if (wifiTethered) {
-            if (bluetoothTethered) {
-                showTetheredNotification(R.drawable.stat_sys_tether_general);
-            } else {
-                /* We now have a status bar icon for WifiTethering, so drop the notification */
-                clearTetheredNotification();
-            }
-        } else if (bluetoothTethered) {
-            showTetheredNotification(R.drawable.stat_sys_tether_bluetooth);
-        } else {
-            clearTetheredNotification();
-        }
-    }
-
-    private void showTetheredNotification(int id) {
-        showTetheredNotification(id, true);
-    }
-
-    @VisibleForTesting
-    protected void showTetheredNotification(int id, boolean tetheringOn) {
-        NotificationManager notificationManager =
-                (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
-                        .getSystemService(Context.NOTIFICATION_SERVICE);
-        if (notificationManager == null) {
-            return;
-        }
-        final NotificationChannel channel = new NotificationChannel(
-                "TETHERING_STATUS",
-                mContext.getResources().getString(R.string.notification_channel_tethering_status),
-                NotificationManager.IMPORTANCE_LOW);
-        notificationManager.createNotificationChannel(channel);
-
-        if (mLastNotificationId != 0) {
-            if (mLastNotificationId == id) {
-                return;
-            }
-            notificationManager.cancel(null, mLastNotificationId);
-            mLastNotificationId = 0;
-        }
-
-        Intent intent = new Intent();
-        intent.setClassName("com.android.settings", "com.android.settings.TetherSettings");
-        intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
-
-        PendingIntent pi = PendingIntent.getActivity(
-                mContext.createContextAsUser(UserHandle.CURRENT, 0), 0, intent, 0, null);
-
-        Resources r = mContext.getResources();
-        final CharSequence title;
-        final CharSequence message;
-
-        if (tetheringOn) {
-            title = r.getText(R.string.tethered_notification_title);
-            message = r.getText(R.string.tethered_notification_message);
-        } else {
-            title = r.getText(R.string.disable_tether_notification_title);
-            message = r.getText(R.string.disable_tether_notification_message);
-        }
-
-        if (mTetheredNotificationBuilder == null) {
-            mTetheredNotificationBuilder = new Notification.Builder(mContext, channel.getId());
-            mTetheredNotificationBuilder.setWhen(0)
-                    .setOngoing(true)
-                    .setColor(mContext.getColor(
-                            android.R.color.system_notification_accent_color))
-                    .setVisibility(Notification.VISIBILITY_PUBLIC)
-                    .setCategory(Notification.CATEGORY_STATUS);
-        }
-        mTetheredNotificationBuilder.setSmallIcon(id)
-                .setContentTitle(title)
-                .setContentText(message)
-                .setContentIntent(pi);
-        mLastNotificationId = id;
-
-        notificationManager.notify(null, mLastNotificationId, mTetheredNotificationBuilder.build());
-    }
-
-    @VisibleForTesting
-    protected void clearTetheredNotification() {
-        NotificationManager notificationManager =
-                (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
-                        .getSystemService(Context.NOTIFICATION_SERVICE);
-        if (notificationManager != null && mLastNotificationId != 0) {
-            notificationManager.cancel(null, mLastNotificationId);
-            mLastNotificationId = 0;
-        }
+        mNotificationUpdater.onDownstreamChanged(downstreamTypesMask);
     }
 
     private class StateReceiver extends BroadcastReceiver {
@@ -1081,12 +983,10 @@
                 return;
             }
 
-            mWrapper.clearTetheredNotification();
+            // TODO: Add user restrictions notification.
             final boolean isTetheringActiveOnDevice = (mWrapper.getTetheredIfaces().length != 0);
 
             if (newlyDisallowed && isTetheringActiveOnDevice) {
-                mWrapper.showTetheredNotification(
-                        R.drawable.stat_sys_tether_general, false);
                 mWrapper.untetherAll();
                 // TODO(b/148139325): send tetheringSupported on restriction change
             }
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
index e019c3a..0330dad 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -26,6 +26,8 @@
 import android.os.IBinder;
 import android.os.Looper;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.util.StateMachine;
 
 import java.util.ArrayList;
@@ -102,6 +104,13 @@
     }
 
     /**
+     * Get a reference to the TetheringNotificationUpdater to be used by tethering.
+     */
+    public TetheringNotificationUpdater getNotificationUpdater(@NonNull final Context ctx) {
+        return new TetheringNotificationUpdater(ctx);
+    }
+
+    /**
      * Get tethering thread looper.
      */
     public abstract Looper getTetheringLooper();
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringNotificationUpdater.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringNotificationUpdater.java
new file mode 100644
index 0000000..b97f752
--- /dev/null
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringNotificationUpdater.java
@@ -0,0 +1,198 @@
+/*
+ * 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.connectivity.tethering;
+
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.annotation.ArrayRes;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.networkstack.tethering.R;
+
+/**
+ * A class to display tethering-related notifications.
+ *
+ * <p>This class is not thread safe, it is intended to be used only from the tethering handler
+ * thread. However the constructor is an exception, as it is called on another thread ;
+ * therefore for thread safety all members of this class MUST either be final or initialized
+ * to their default value (0, false or null).
+ *
+ * @hide
+ */
+public class TetheringNotificationUpdater {
+    private static final String TAG = TetheringNotificationUpdater.class.getSimpleName();
+    private static final String CHANNEL_ID = "TETHERING_STATUS";
+    private static final boolean NOTIFY_DONE = true;
+    private static final boolean NO_NOTIFY = false;
+    // Id to update and cancel tethering notification. Must be unique within the tethering app.
+    private static final int NOTIFY_ID = 20191115;
+    @VisibleForTesting
+    static final int NO_ICON_ID = 0;
+    @VisibleForTesting
+    static final int DOWNSTREAM_NONE = 0;
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+    private final NotificationChannel mChannel;
+    // Downstream type is one of ConnectivityManager.TETHERING_* constants, 0 1 or 2.
+    // This value has to be made 1 2 and 4, and OR'd with the others.
+    // WARNING : the constructor is called on a different thread. Thread safety therefore
+    // relies on this value being initialized to 0, and not any other value. If you need
+    // to change this, you will need to change the thread where the constructor is invoked,
+    // or to introduce synchronization.
+    private int mDownstreamTypesMask = DOWNSTREAM_NONE;
+
+    public TetheringNotificationUpdater(@NonNull final Context context) {
+        mContext = context;
+        mNotificationManager = (NotificationManager) context.createContextAsUser(UserHandle.ALL, 0)
+                .getSystemService(Context.NOTIFICATION_SERVICE);
+        mChannel = new NotificationChannel(
+                CHANNEL_ID,
+                context.getResources().getString(R.string.notification_channel_tethering_status),
+                NotificationManager.IMPORTANCE_LOW);
+        mNotificationManager.createNotificationChannel(mChannel);
+    }
+
+    /** Called when downstream has changed */
+    public void onDownstreamChanged(@IntRange(from = 0, to = 7) final int downstreamTypesMask) {
+        if (mDownstreamTypesMask == downstreamTypesMask) return;
+        mDownstreamTypesMask = downstreamTypesMask;
+        updateNotification();
+    }
+
+    private void updateNotification() {
+        final boolean tetheringInactive = mDownstreamTypesMask <= DOWNSTREAM_NONE;
+
+        if (tetheringInactive || setupNotification() == NO_NOTIFY) {
+            clearNotification();
+        }
+    }
+
+    private void clearNotification() {
+        mNotificationManager.cancel(null /* tag */, NOTIFY_ID);
+    }
+
+    /**
+     * Returns the downstream types mask which convert from given string.
+     *
+     * @param types This string has to be made by "WIFI", "USB", "BT", and OR'd with the others.
+     *
+     * @return downstream types mask value.
+     */
+    @IntRange(from = 0, to = 7)
+    private int getDownstreamTypesMask(@NonNull final String types) {
+        int downstreamTypesMask = DOWNSTREAM_NONE;
+        final String[] downstreams = types.split("\\|");
+        for (String downstream : downstreams) {
+            if ("USB".equals(downstream.trim())) {
+                downstreamTypesMask |= (1 << TETHERING_USB);
+            } else if ("WIFI".equals(downstream.trim())) {
+                downstreamTypesMask |= (1 << TETHERING_WIFI);
+            } else if ("BT".equals(downstream.trim())) {
+                downstreamTypesMask |= (1 << TETHERING_BLUETOOTH);
+            }
+        }
+        return downstreamTypesMask;
+    }
+
+    /**
+     * Returns the icons {@link android.util.SparseArray} which get from given string-array resource
+     * id.
+     *
+     * @param id String-array resource id
+     *
+     * @return {@link android.util.SparseArray} with downstream types and icon id info.
+     */
+    @NonNull
+    private SparseArray<Integer> getIcons(@ArrayRes int id) {
+        final Resources res = mContext.getResources();
+        final String[] array = res.getStringArray(id);
+        final SparseArray<Integer> icons = new SparseArray<>();
+        for (String config : array) {
+            if (TextUtils.isEmpty(config)) continue;
+
+            final String[] elements = config.split(";");
+            if (elements.length != 2) {
+                Log.wtf(TAG,
+                        "Unexpected format in Tethering notification configuration : " + config);
+                continue;
+            }
+
+            final String[] types = elements[0].split(",");
+            for (String type : types) {
+                int mask = getDownstreamTypesMask(type);
+                if (mask == DOWNSTREAM_NONE) continue;
+                icons.put(mask, res.getIdentifier(
+                        elements[1].trim(), null /* defType */, null /* defPackage */));
+            }
+        }
+        return icons;
+    }
+
+    private boolean setupNotification() {
+        final Resources res = mContext.getResources();
+        final SparseArray<Integer> downstreamIcons = getIcons(R.array.tethering_notification_icons);
+
+        final int iconId = downstreamIcons.get(mDownstreamTypesMask, NO_ICON_ID);
+        if (iconId == NO_ICON_ID) return NO_NOTIFY;
+
+        final String title = res.getString(R.string.tethering_notification_title);
+        final String message = res.getString(R.string.tethering_notification_message);
+
+        showNotification(iconId, title, message);
+        return NOTIFY_DONE;
+    }
+
+    private void showNotification(@DrawableRes final int iconId, @NonNull final String title,
+            @NonNull final String message) {
+        final Intent intent = new Intent(Settings.ACTION_TETHER_SETTINGS);
+        final PendingIntent pi = PendingIntent.getActivity(
+                mContext.createContextAsUser(UserHandle.CURRENT, 0),
+                0 /* requestCode */, intent, 0 /* flags */, null /* options */);
+        final Notification notification =
+                new Notification.Builder(mContext, mChannel.getId())
+                        .setSmallIcon(iconId)
+                        .setContentTitle(title)
+                        .setContentText(message)
+                        .setOngoing(true)
+                        .setColor(mContext.getColor(
+                                android.R.color.system_notification_accent_color))
+                        .setVisibility(Notification.VISIBILITY_PUBLIC)
+                        .setCategory(Notification.CATEGORY_STATUS)
+                        .setContentIntent(pi)
+                        .build();
+
+        mNotificationManager.notify(null /* tag */, NOTIFY_ID, notification);
+    }
+}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index af7ad66..820c852 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -46,6 +46,8 @@
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
+import static com.android.server.connectivity.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -53,7 +55,6 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.notNull;
-import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.any;
@@ -188,6 +189,7 @@
     @Mock private NetworkRequest mNetworkRequest;
     @Mock private ConnectivityManager mCm;
     @Mock private EthernetManager mEm;
+    @Mock private TetheringNotificationUpdater mNotificationUpdater;
 
     private final MockIpServerDependencies mIpServerDependencies =
             spy(new MockIpServerDependencies());
@@ -207,6 +209,7 @@
     private PhoneStateListener mPhoneStateListener;
     private InterfaceConfigurationParcel mInterfaceConfiguration;
 
+
     private class TestContext extends BroadcastInterceptingContext {
         TestContext(Context base) {
             super(base);
@@ -249,11 +252,6 @@
             if (TelephonyManager.class.equals(serviceClass)) return Context.TELEPHONY_SERVICE;
             return super.getSystemServiceName(serviceClass);
         }
-
-        @Override
-        public Context createContextAsUser(UserHandle user, int flags) {
-            return mContext;
-        }
     }
 
     public class MockIpServerDependencies extends IpServer.Dependencies {
@@ -315,12 +313,10 @@
     public class MockTetheringDependencies extends TetheringDependencies {
         StateMachine mUpstreamNetworkMonitorMasterSM;
         ArrayList<IpServer> mIpv6CoordinatorNotifyList;
-        int mIsTetheringSupportedCalls;
 
         public void reset() {
             mUpstreamNetworkMonitorMasterSM = null;
             mIpv6CoordinatorNotifyList = null;
-            mIsTetheringSupportedCalls = 0;
         }
 
         @Override
@@ -354,7 +350,6 @@
 
         @Override
         public boolean isTetheringSupported() {
-            mIsTetheringSupportedCalls++;
             return true;
         }
 
@@ -384,6 +379,11 @@
             // TODO: add test for bluetooth tethering.
             return null;
         }
+
+        @Override
+        public TetheringNotificationUpdater getNotificationUpdater(Context ctx) {
+            return mNotificationUpdater;
+        }
     }
 
     private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4,
@@ -472,7 +472,6 @@
         when(mOffloadHardwareInterface.getForwardedStats(any())).thenReturn(mForwardedStats);
 
         mServiceContext = new TestContext(mContext);
-        when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(null);
         mContentResolver = new MockContentResolver(mServiceContext);
         mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         mIntents = new Vector<>();
@@ -605,7 +604,8 @@
         // it creates a IpServer and sends out a broadcast indicating that the
         // interface is "available".
         if (emulateInterfaceStatusChanged) {
-            assertEquals(1, mTetheringDependencies.mIsTetheringSupportedCalls);
+            // There is 1 IpServer state change event: STATE_AVAILABLE
+            verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE);
             verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
             verify(mWifiManager).updateInterfaceIpState(
                     TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
@@ -689,9 +689,8 @@
         verifyNoMoreInteractions(mWifiManager);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
         verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
-        // This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
-        // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
-        assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls);
+        // There are 2 IpServer state change events: STATE_AVAILABLE -> STATE_LOCAL_ONLY
+        verify(mNotificationUpdater, times(2)).onDownstreamChanged(DOWNSTREAM_NONE);
 
         // Emulate externally-visible WifiManager effects, when hotspot mode
         // is being torn down.
@@ -917,7 +916,8 @@
         sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
         mLooper.dispatchAll();
 
-        assertEquals(1, mTetheringDependencies.mIsTetheringSupportedCalls);
+        // There is 1 IpServer state change event: STATE_AVAILABLE
+        verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
         verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
@@ -961,9 +961,9 @@
         // In tethering mode, in the default configuration, an explicit request
         // for a mobile network is also made.
         verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest();
-        // This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
-        // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
-        assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls);
+        // There are 2 IpServer state change events: STATE_AVAILABLE -> STATE_TETHERED
+        verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE);
+        verify(mNotificationUpdater, times(1)).onDownstreamChanged(eq(1 << TETHERING_WIFI));
 
         /////
         // We do not currently emulate any upstream being found.
@@ -1034,9 +1034,10 @@
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
         verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
-        // There are 3 state change event:
-        // AVAILABLE -> STATE_TETHERED -> STATE_AVAILABLE.
-        assertEquals(3, mTetheringDependencies.mIsTetheringSupportedCalls);
+        // There are 3 IpServer state change event:
+        //         STATE_AVAILABLE -> STATE_TETHERED -> STATE_AVAILABLE.
+        verify(mNotificationUpdater, times(2)).onDownstreamChanged(DOWNSTREAM_NONE);
+        verify(mNotificationUpdater, times(1)).onDownstreamChanged(eq(1 << TETHERING_WIFI));
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
         // This is called, but will throw.
         verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
@@ -1071,9 +1072,6 @@
         ural.onUserRestrictionsChanged();
 
         verify(mockTethering, times(expectedInteractionsWithShowNotification))
-                .showTetheredNotification(anyInt(), eq(false));
-
-        verify(mockTethering, times(expectedInteractionsWithShowNotification))
                 .untetherAll();
     }
 
@@ -1429,9 +1427,8 @@
         verifyNoMoreInteractions(mNetd);
         verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
         verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
-        // This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
-        // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
-        assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls);
+        // There are 2 IpServer state change events: STATE_AVAILABLE -> STATE_LOCAL_ONLY
+        verify(mNotificationUpdater, times(2)).onDownstreamChanged(DOWNSTREAM_NONE);
 
         assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastTetherError(TEST_P2P_IFNAME));
 
diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml
index 6f692c8..b9c5f1d 100644
--- a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml
@@ -16,6 +16,9 @@
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
+    <string translatable="false" name="config_mainBuiltInDisplayCutout"></string>
+    <string translatable="false" name="config_mainBuiltInDisplayCutoutRectApproximation"></string>
+
     <!-- Height of the status bar in portrait. The height should be
          Max((status bar content height + waterfall top size), top cutout size) -->
     <dimen name="status_bar_height_portrait">28dp</dimen>
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 5d699c0..5d97d21 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -1232,6 +1232,7 @@
      */
     public boolean computePartialInteractiveRegionForWindowLocked(int windowId,
             @NonNull Region outRegion) {
+        windowId = resolveParentWindowIdLocked(windowId);
         final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(windowId);
         if (observer != null) {
             return observer.computePartialInteractiveRegionForWindowLocked(windowId, outRegion);
@@ -1436,6 +1437,7 @@
      */
     @Nullable
     public WindowInfo findWindowInfoByIdLocked(int windowId) {
+        windowId = resolveParentWindowIdLocked(windowId);
         final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(windowId);
         if (observer != null) {
             return observer.findWindowInfoByIdLocked(windowId);
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
index 3376f2b..5de8171 100644
--- a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
+++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
@@ -62,7 +62,7 @@
  * side flow.
  *
  * <p>
- * This class is thread safe.
+ * This class should hold the same lock as {@link Session} as they call into each other.
  */
 final class InlineSuggestionSession {
 
@@ -105,12 +105,12 @@
     private boolean mImeInputViewStarted = false;
 
     InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal,
-            int userId, ComponentName componentName, Handler handler) {
+            int userId, ComponentName componentName, Handler handler, Object lock) {
         mInputMethodManagerInternal = inputMethodManagerInternal;
         mUserId = userId;
         mComponentName = componentName;
         mHandler = handler;
-        mLock = new Object();
+        mLock = lock;
         mImeStatusListener = new ImeStatusListener() {
             @Override
             public void onInputMethodStartInputView(AutofillId imeFieldId) {
@@ -261,29 +261,27 @@
             mHandler = handler;
             mTimeoutCallback = () -> {
                 Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest.");
-                synchronized (mLock) {
-                    completeIfNotLocked(null);
-                }
+                completeIfNot(null);
             };
             mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS);
         }
 
-        private void completeIfNotLocked(@Nullable ImeResponse response) {
-            if (mResponse.isDone()) {
-                return;
+        private void completeIfNot(@Nullable ImeResponse response) {
+            synchronized (mLock) {
+                if (mResponse.isDone()) {
+                    return;
+                }
+                mResponse.complete(response);
+                mRequestConsumer.accept(response == null ? null : response.mRequest);
+                mHandler.removeCallbacks(mTimeoutCallback);
             }
-            mResponse.complete(response);
-            mRequestConsumer.accept(response == null ? null : response.mRequest);
-            mHandler.removeCallbacks(mTimeoutCallback);
         }
 
         @BinderThread
         @Override
         public void onInlineSuggestionsUnsupported() throws RemoteException {
             if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
-            synchronized (mLock) {
-                completeIfNotLocked(null);
-            }
+            completeIfNot(null);
         }
 
         @BinderThread
@@ -302,13 +300,9 @@
                 mImeStatusListener.onInputMethodFinishInputView(imeFieldId);
             }
             if (request != null && callback != null) {
-                synchronized (mLock) {
-                    completeIfNotLocked(new ImeResponse(request, callback));
-                }
+                completeIfNot(new ImeResponse(request, callback));
             } else {
-                synchronized (mLock) {
-                    completeIfNotLocked(null);
-                }
+                completeIfNot(null);
             }
         }
 
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 5064663..de31118 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -756,7 +756,7 @@
         setClientLocked(client);
 
         mInlineSuggestionSession = new InlineSuggestionSession(inputMethodManagerInternal, userId,
-                componentName, handler);
+                componentName, handler, mLock);
 
         mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 942d563..9e19ad2 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -98,9 +98,9 @@
         "android.hardware.power-V1.0-java",
         "android.hardware.tv.cec-V1.0-java",
         "android.hardware.vibrator-java",
+        "android.net.ipsec.ike.stubs.module_libs_api",
         "app-compat-annotations",
         "framework-tethering-stubs-module_libs_api",
-        "ike-stubs",
     ],
 
     required: [
@@ -128,7 +128,6 @@
         "android.hidl.manager-V1.2-java",
         "dnsresolver_aidl_interface-V2-java",
         "netd_event_listener_interface-java",
-        "ike-stubs",
         "overlayable_policy_aidl-java",
     ],
 
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index dadcd4e..9fddafb 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -991,4 +991,9 @@
      * @param enabled true if visibility blocks should be logged
      */
     public abstract void setVisibilityLogging(String packageName, boolean enabled);
+
+    /**
+     * Returns if a package name is a valid system package.
+     */
+    public abstract boolean isSystemPackage(@NonNull String packageName);
 }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ecf1f13..3a3358c 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2083,9 +2083,9 @@
     }
 
     private void enforceNetworkFactoryPermission() {
-        mContext.enforceCallingOrSelfPermission(
+        enforceAnyPermissionOf(
                 android.Manifest.permission.NETWORK_FACTORY,
-                "ConnectivityService");
+                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
 
     private boolean checkSettingsPermission() {
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index 318a030..191a9bc 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -22,13 +22,9 @@
 import android.gsi.GsiProgress;
 import android.gsi.IGsiService;
 import android.gsi.IGsiServiceCallback;
-import android.gsi.IGsid;
 import android.os.Environment;
-import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.image.IDynamicSystemService;
@@ -42,9 +38,8 @@
  * DynamicSystemService implements IDynamicSystemService. It provides permission check before
  * passing requests to gsid
  */
-public class DynamicSystemService extends IDynamicSystemService.Stub implements DeathRecipient {
+public class DynamicSystemService extends IDynamicSystemService.Stub {
     private static final String TAG = "DynamicSystemService";
-    private static final String NO_SERVICE_ERROR = "no gsiservice";
     private static final int GSID_ROUGH_TIMEOUT_MS = 8192;
     private static final String PATH_DEFAULT = "/data/gsi/";
     private Context mContext;
@@ -55,57 +50,12 @@
         mContext = context;
     }
 
-    private static IGsiService connect(DeathRecipient recipient) throws RemoteException {
-        IBinder binder = ServiceManager.getService("gsiservice");
-        if (binder == null) {
-            return null;
-        }
-        /**
-         * The init will restart gsiservice if it crashed and the proxy object will need to be
-         * re-initialized in this case.
-         */
-        binder.linkToDeath(recipient, 0);
-
-        IGsid gsid = IGsid.Stub.asInterface(binder);
-        return gsid.getClient();
-    }
-
-    /** implements DeathRecipient */
-    @Override
-    public void binderDied() {
-        Slog.w(TAG, "gsiservice died; reconnecting");
-        synchronized (this) {
-            mGsiService = null;
-        }
-    }
-
     private IGsiService getGsiService() throws RemoteException {
         checkPermission();
-
-        if (!"running".equals(SystemProperties.get("init.svc.gsid"))) {
-            SystemProperties.set("ctl.start", "gsid");
+        if (mGsiService != null) {
+            return mGsiService;
         }
-
-        for (int sleepMs = 64; sleepMs <= (GSID_ROUGH_TIMEOUT_MS << 1); sleepMs <<= 1) {
-            synchronized (this) {
-                if (mGsiService == null) {
-                    mGsiService = connect(this);
-                }
-                if (mGsiService != null) {
-                    return mGsiService;
-                }
-            }
-
-            try {
-                Slog.d(TAG, "GsiService is not ready, wait for " + sleepMs + "ms");
-                Thread.sleep(sleepMs);
-            } catch (InterruptedException e) {
-                Slog.e(TAG, "Interrupted when waiting for GSID");
-                return null;
-            }
-        }
-
-        throw new RemoteException(NO_SERVICE_ERROR);
+        return IGsiService.Stub.asInterface(waitForService("gsiservice"));
     }
 
     private void checkPermission() {
@@ -133,6 +83,7 @@
     @Override
     public boolean startInstallation(String dsuSlot) throws RemoteException {
         IGsiService service = getGsiService();
+        mGsiService = service;
         // priority from high to low: sysprop -> sdcard -> /data
         String path = SystemProperties.get("os.aot.path");
         if (path.isEmpty()) {
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index b464422..52a1b5a 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -354,6 +354,10 @@
      */
     public void onPackageFailure(List<VersionedPackage> packages,
             @FailureReasons int failureReason) {
+        if (packages == null) {
+            Slog.w(TAG, "Could not resolve a list of failing packages");
+            return;
+        }
         mLongTaskHandler.post(() -> {
             synchronized (mLock) {
                 if (mAllObservers.isEmpty()) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index b7d050a..2c220d3 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -154,7 +154,6 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.server.SystemService.TargetUser;
 import com.android.server.pm.Installer;
 import com.android.server.storage.AppFuseBridge;
 import com.android.server.storage.StorageSessionController;
@@ -233,6 +232,8 @@
     private static final String FUSE_ENABLED = "fuse_enabled";
     private static final boolean DEFAULT_FUSE_ENABLED = true;
 
+    private final Set<Integer> mFuseMountedUser = new ArraySet<>();
+
     public static class Lifecycle extends SystemService {
         private StorageManagerService mStorageManagerService;
 
@@ -1497,6 +1498,9 @@
 
     @GuardedBy("mLock")
     private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) {
+        if (vol.type == VolumeInfo.TYPE_EMULATED && newState != VolumeInfo.STATE_MOUNTED) {
+            mFuseMountedUser.remove(vol.getMountUserId());
+        }
         // Remember that we saw this volume so we're ready to accept user
         // metadata, or so we can annoy them when a private volume is ejected
         if (!TextUtils.isEmpty(vol.fsUuid)) {
@@ -2075,39 +2079,86 @@
         mount(vol);
     }
 
+    private void remountAppStorageDirs(Map<Integer, String> pidPkgMap, int userId) {
+        for (Entry<Integer, String> entry : pidPkgMap.entrySet()) {
+            final int pid = entry.getKey();
+            final String packageName = entry.getValue();
+            Slog.i(TAG, "Remounting storage for pid: " + pid);
+            final String[] sharedPackages =
+                    mPmInternal.getSharedUserPackagesForPackage(packageName, userId);
+            final int uid = mPmInternal.getPackageUidInternal(packageName, 0, userId);
+            final String[] packages =
+                    sharedPackages.length != 0 ? sharedPackages : new String[]{packageName};
+            try {
+                mVold.remountAppStorageDirs(uid, pid, packages);
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        }
+    }
+
     private void mount(VolumeInfo vol) {
         try {
             // TODO(b/135341433): Remove paranoid logging when FUSE is stable
             Slog.i(TAG, "Mounting volume " + vol);
             mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
-                    @Override
-                    public boolean onVolumeChecking(FileDescriptor fd, String path,
-                            String internalPath) {
-                        vol.path = path;
-                        vol.internalPath = internalPath;
-                        ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd);
-                        try {
-                            mStorageSessionController.onVolumeMount(pfd, vol);
-                            return true;
-                        } catch (ExternalStorageServiceException e) {
-                            Slog.e(TAG, "Failed to mount volume " + vol, e);
+                @Override
+                public boolean onVolumeChecking(FileDescriptor fd, String path,
+                        String internalPath) {
+                    vol.path = path;
+                    vol.internalPath = internalPath;
+                    ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd);
+                    try {
+                        mStorageSessionController.onVolumeMount(pfd, vol);
+                        return true;
+                    } catch (ExternalStorageServiceException e) {
+                        Slog.e(TAG, "Failed to mount volume " + vol, e);
 
-                            int nextResetSeconds = REMOTE_TIMEOUT_SECONDS * 2;
-                            Slog.i(TAG, "Scheduling reset in " + nextResetSeconds + "s");
-                            mHandler.removeMessages(H_RESET);
-                            mHandler.sendMessageDelayed(mHandler.obtainMessage(H_RESET),
-                                    TimeUnit.SECONDS.toMillis(nextResetSeconds));
-                            return false;
-                        } finally {
-                            try {
-                                pfd.close();
-                            } catch (Exception e) {
-                                Slog.e(TAG, "Failed to close FUSE device fd", e);
-                            }
+                        int nextResetSeconds = REMOTE_TIMEOUT_SECONDS * 2;
+                        Slog.i(TAG, "Scheduling reset in " + nextResetSeconds + "s");
+                        mHandler.removeMessages(H_RESET);
+                        mHandler.sendMessageDelayed(mHandler.obtainMessage(H_RESET),
+                                TimeUnit.SECONDS.toMillis(nextResetSeconds));
+                        return false;
+                    } finally {
+                        try {
+                            pfd.close();
+                        } catch (Exception e) {
+                            Slog.e(TAG, "Failed to close FUSE device fd", e);
                         }
                     }
-                });
+                }
+            });
             Slog.i(TAG, "Mounted volume " + vol);
+            if (vol.type == VolumeInfo.TYPE_EMULATED) {
+                final int userId = vol.getMountUserId();
+                mFuseMountedUser.add(userId);
+                // Async remount app storage so it won't block the main thread.
+                new Thread(() -> {
+                    Map<Integer, String> pidPkgMap = null;
+                    // getProcessesWithPendingBindMounts() could fail when a new app process is
+                    // starting and it's not planning to mount storage dirs in zygote, but it's
+                    // rare, so we retry 5 times and hope we can get the result successfully.
+                    for (int i = 0; i < 5; i++) {
+                        try {
+                            pidPkgMap = LocalServices.getService(ActivityManagerInternal.class)
+                                    .getProcessesWithPendingBindMounts(vol.getMountUserId());
+                            break;
+                        } catch (IllegalStateException e) {
+                            Slog.i(TAG, "Some processes are starting, retry");
+                            // Wait 100ms and retry so hope the pending process is started.
+                            SystemClock.sleep(100);
+                        }
+                    }
+                    if (pidPkgMap != null) {
+                        remountAppStorageDirs(pidPkgMap, userId);
+                    } else {
+                        Slog.wtf(TAG, "Not able to getStorageNotOptimizedProcesses() after"
+                                + " 5 retries");
+                    }
+
+                }).start();
+            }
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -4309,7 +4360,8 @@
             pw.println();
             pw.println("mObbPathToStateMap:");
             pw.increaseIndent();
-            final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator();
+            final Iterator<Entry<String, ObbState>> maps =
+                    mObbPathToStateMap.entrySet().iterator();
             while (maps.hasNext()) {
                 final Entry<String, ObbState> e = maps.next();
                 pw.print(e.getKey());
@@ -4350,45 +4402,41 @@
         }
 
         /**
-         * Check if fuse is running in target user, if it's running then setup its obb directories.
-         * TODO: System server should store a list of active pids that obb is not mounted and use it.
+         * Check if fuse is running in target user, if it's running then setup its storage dirs.
+         * Return true if storage dirs are mounted.
          */
         @Override
-        public void prepareObbDirs(int userId, Set<String> packageList, String processName) {
-            String fuseRunningUsersList = SystemProperties.get("vold.fuse_running_users", "");
-            String[] fuseRunningUsers = fuseRunningUsersList.split(",");
-            boolean fuseReady = false;
-            String targetUserId = String.valueOf(userId);
-            for (String user : fuseRunningUsers) {
-                if (targetUserId.equals(user)) {
-                    fuseReady = true;
-                }
+        public boolean prepareStorageDirs(int userId, Set<String> packageList,
+                String processName) {
+            if (!mFuseMountedUser.contains(userId)) {
+                Slog.w(TAG, "User " + userId + " is not unlocked yet so skip mounting obb");
+                return false;
             }
-            if (fuseReady) {
-                try {
-                    final IVold vold = IVold.Stub.asInterface(
-                            ServiceManager.getServiceOrThrow("vold"));
-                    for (String pkg : packageList) {
-                        final String packageObbDir =
-                                String.format("/storage/emulated/%d/Android/obb/%s/", userId, pkg);
-                        final String packageDataDir =
-                                String.format("/storage/emulated/%d/Android/data/%s/",
-                                        userId, pkg);
+            try {
+                final IVold vold = IVold.Stub.asInterface(
+                        ServiceManager.getServiceOrThrow("vold"));
+                for (String pkg : packageList) {
+                    final String packageObbDir =
+                            String.format("/storage/emulated/%d/Android/obb/%s/", userId, pkg);
+                    final String packageDataDir =
+                            String.format("/storage/emulated/%d/Android/data/%s/",
+                                    userId, pkg);
 
-                        // Create package obb and data dir if it doesn't exist.
-                        File file = new File(packageObbDir);
-                        if (!file.exists()) {
-                            vold.setupAppDir(packageObbDir, mPmInternal.getPackage(pkg).getUid());
-                        }
-                        file = new File(packageDataDir);
-                        if (!file.exists()) {
-                            vold.setupAppDir(packageDataDir, mPmInternal.getPackage(pkg).getUid());
-                        }
+                    // Create package obb and data dir if it doesn't exist.
+                    File file = new File(packageObbDir);
+                    if (!file.exists()) {
+                        vold.setupAppDir(packageObbDir, mPmInternal.getPackage(pkg).getUid());
                     }
-                } catch (ServiceManager.ServiceNotFoundException | RemoteException e) {
-                    Slog.e(TAG, "Unable to create obb and data directories for " + processName, e);
+                    file = new File(packageDataDir);
+                    if (!file.exists()) {
+                        vold.setupAppDir(packageDataDir, mPmInternal.getPackage(pkg).getUid());
+                    }
                 }
+            } catch (ServiceManager.ServiceNotFoundException | RemoteException e) {
+                Slog.e(TAG, "Unable to create obb and data directories for " + processName,e);
+                return false;
             }
+            return true;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index cfc8b45..0ebb5bb 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -137,6 +137,10 @@
 
     private static final boolean SHOW_DUNGEON_NOTIFICATION = false;
 
+    //TODO: remove this when development is done.
+    private static final int DEBUG_FGS_ALLOW_WHILE_IN_USE = 0;
+    private static final int DEBUG_FGS_ENFORCE_TYPE = 1;
+
     // How long we wait for a service to finish executing.
     static final int SERVICE_TIMEOUT = 20*1000;
 
@@ -1701,7 +1705,7 @@
                     if (acceptances > 0 ||  rejections > 0) {
                         FrameworkStatsLog.write(
                                 FrameworkStatsLog.FOREGROUND_SERVICE_APP_OP_SESSION_ENDED,
-                                mProcessRecord.uid, opToEnum(op),
+                                mProcessRecord.uid, AppOpsManager.opToLoggingId(op),
                                 modeToEnum(mAppOpModes.get(op)),
                                 acceptances, rejections
                         );
@@ -1725,22 +1729,6 @@
         }
     }
 
-    /** Maps AppOp op value to atoms.proto enum. */
-    private static int opToEnum(int op) {
-        switch (op) {
-            case AppOpsManager.OP_COARSE_LOCATION: return FrameworkStatsLog
-                    .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_COARSE_LOCATION;
-            case AppOpsManager.OP_FINE_LOCATION: return FrameworkStatsLog
-                    .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_FINE_LOCATION;
-            case AppOpsManager.OP_CAMERA: return FrameworkStatsLog
-                    .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_CAMERA;
-            case AppOpsManager.OP_RECORD_AUDIO: return FrameworkStatsLog
-                    .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_RECORD_AUDIO;
-            default: return FrameworkStatsLog
-                    .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_NONE;
-        }
-    }
-
     private void cancelForegroundNotificationLocked(ServiceRecord r) {
         if (r.foregroundId != 0) {
             // First check to see if this app has any other active foreground services
@@ -4931,10 +4919,20 @@
                 if (!r.isForeground) {
                     continue;
                 }
-                if (!r.mAllowWhileInUsePermissionInFgs
-                        && r.mInfoDenyWhileInUsePermissionInFgs != null) {
-                    final String msg = r.mInfoDenyWhileInUsePermissionInFgs
-                            + " affected while-in-use permission:"
+                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);
                     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 f64272b..4485af1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17538,8 +17538,11 @@
 
     final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
         if (proc.thread != null && proc.baseProcessTracker != null) {
-            proc.baseProcessTracker.setState(
-                    proc.getReportedProcState(), memFactor, now, proc.pkgList.mPkgList);
+            final int procState = proc.getReportedProcState();
+            if (procState != PROCESS_STATE_NONEXISTENT) {
+                proc.baseProcessTracker.setState(
+                        procState, memFactor, now, proc.pkgList.mPkgList);
+            }
         }
     }
 
@@ -18714,6 +18717,11 @@
         }
 
         @Override
+        public Map<Integer, String> getProcessesWithPendingBindMounts(int userId) {
+            return mProcessList.getProcessesWithPendingBindMounts(userId);
+        }
+
+        @Override
         public boolean isSystemReady() {
             // no need to synchronize(this) just to read & return the value
             return mSystemReady;
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 48ceba9..8527ae9 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -115,6 +115,10 @@
                 DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.ENABLE_CURSOR_DRAG_FROM_ANYWHERE,
                 WidgetFlags.KEY_ENABLE_CURSOR_DRAG_FROM_ANYWHERE, boolean.class,
                 WidgetFlags.ENABLE_CURSOR_DRAG_FROM_ANYWHERE_DEFAULT));
+        sDeviceConfigEntries.add(new DeviceConfigEntry<Integer>(
+                DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.FINGER_TO_CURSOR_DISTANCE,
+                WidgetFlags.KEY_FINGER_TO_CURSOR_DISTANCE, int.class,
+                WidgetFlags.FINGER_TO_CURSOR_DISTANCE_DEFAULT));
         sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
                 DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.ENABLE_INSERTION_HANDLE_GESTURES,
                 WidgetFlags.KEY_ENABLE_INSERTION_HANDLE_GESTURES, boolean.class,
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index b584ea5..2b1534b 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -75,7 +75,6 @@
 import android.app.ApplicationExitInfo;
 import android.app.usage.UsageEvents;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledAfter;
 import android.content.Context;
 import android.content.pm.ServiceInfo;
@@ -149,12 +148,13 @@
      * capabilities.
      */
     @ChangeId
-    //TODO: change to @EnabledAfter when enforcing the feature.
-    @Disabled
+    @EnabledAfter(targetSdkVersion=android.os.Build.VERSION_CODES.Q)
     static final long CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID = 136219221L;
 
     //TODO: remove this when development is done.
     private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31;
+    private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 30;
+    private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 29;
 
     /**
      * For some direct access we need to power manager.
@@ -1513,10 +1513,12 @@
                     if (enabled) {
                         capabilityFromFGS |=
                                 (fgsType & FOREGROUND_SERVICE_TYPE_CAMERA)
-                                        != 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA : 0;
+                                        != 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA
+                                        : TEMP_PROCESS_CAPABILITY_FOREGROUND_CAMERA;
                         capabilityFromFGS |=
                                 (fgsType & FOREGROUND_SERVICE_TYPE_MICROPHONE)
-                                        != 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE : 0;
+                                        != 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
+                                        : TEMP_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
                     } else {
                         capabilityFromFGS |= PROCESS_CAPABILITY_FOREGROUND_CAMERA
                                 | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 0e9970e..57bd42b 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -99,6 +99,7 @@
 import android.system.Os;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.LongSparseArray;
 import android.util.Pair;
@@ -136,8 +137,11 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Activity manager code dealing with processes.
@@ -154,7 +158,8 @@
             "persist.sys.vold_app_data_isolation_enabled";
 
     // A device config to control the minimum target SDK to enable app data isolation
-    static final String ANDROID_APP_DATA_ISOLATION_MIN_SDK = "android_app_data_isolation_min_sdk";
+    static final String ANDROID_APP_DATA_ISOLATION_MIN_SDK =
+            "android_app_data_isolation_min_sdk";
 
     // The minimum time we allow between crashes, for us to consider this
     // application to be bad and stop and its services and reject broadcasts.
@@ -799,6 +804,31 @@
         }
     }
 
+    /**
+     * Get a map of pid and package name that process of that pid Android/data and Android/obb
+     * directory is not mounted to lowerfs to speed up access.
+     */
+    Map<Integer, String> getProcessesWithPendingBindMounts(int userId) {
+        final Map<Integer, String> pidPackageMap = new HashMap<>();
+        synchronized (mService) {
+            for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
+                final ProcessRecord record = mLruProcesses.get(i);
+                if (record.userId != userId || !record.bindMountPending) {
+                    continue;
+                }
+                final int pid = record.pid;
+                // It can happen when app process is starting, but zygote work is not done yet so
+                // system does not this pid record yet.
+                if (pid == 0) {
+                    throw new IllegalStateException("Pending process is not started yet,"
+                            + "retry later");
+                }
+                pidPackageMap.put(pid, record.info.packageName);
+            }
+            return pidPackageMap;
+        }
+    }
+
     private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
         // Scale buckets from avail memory: at 300MB we use the lowest values to
         // 700MB or more for the top values.
@@ -1680,6 +1710,7 @@
         if (app.pid > 0 && app.pid != ActivityManagerService.MY_PID) {
             checkSlow(startTime, "startProcess: removing from pids map");
             mService.removePidLocked(app);
+            app.bindMountPending = false;
             mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
             checkSlow(startTime, "startProcess: done removing from pids map");
             app.setPid(0);
@@ -2162,8 +2193,10 @@
             }
 
             final Map<String, Pair<String, Long>> pkgDataInfoMap;
+            boolean bindMountAppStorageDirs = false;
 
             if (shouldIsolateAppData(app)) {
+                bindMountAppStorageDirs = mVoldAppDataIsolationEnabled;
                 // Get all packages belongs to the same shared uid. sharedPackages is empty array
                 // if it doesn't have shared uid.
                 final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked();
@@ -2172,11 +2205,17 @@
                 pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, sharedPackages.length == 0
                         ? new String[]{app.info.packageName} : sharedPackages, uid);
 
+                int userId = UserHandle.getUserId(uid);
                 if (mVoldAppDataIsolationEnabled) {
                     StorageManagerInternal storageManagerInternal = LocalServices.getService(
-                                StorageManagerInternal.class);
-                    storageManagerInternal.prepareObbDirs(UserHandle.getUserId(uid),
-                            pkgDataInfoMap.keySet(), app.processName);
+                            StorageManagerInternal.class);
+                    if (!storageManagerInternal.prepareStorageDirs(userId, pkgDataInfoMap.keySet(),
+                            app.processName)) {
+                        // Cannot prepare Android/app and Android/obb directory,
+                        // so we won't mount it in zygote.
+                        app.bindMountPending = true;
+                        bindMountAppStorageDirs = false;
+                    }
                 }
             } else {
                 pkgDataInfoMap = null;
@@ -2197,7 +2236,7 @@
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                         app.info.dataDir, null, app.info.packageName,
                         /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
-                        app.mDisabledCompatChanges, pkgDataInfoMap,
+                        app.mDisabledCompatChanges, pkgDataInfoMap, bindMountAppStorageDirs,
                         new String[]{PROC_START_SEQ_IDENT + app.startSeq});
             } else {
                 startResult = Process.start(entryPoint,
@@ -2205,6 +2244,7 @@
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                         app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags,
                         isTopApp, app.mDisabledCompatChanges, pkgDataInfoMap,
+                        bindMountAppStorageDirs,
                         new String[]{PROC_START_SEQ_IDENT + app.startSeq});
             }
             checkSlow(startTime, "startProcess: returned from zygote!");
@@ -2663,6 +2703,7 @@
             int pid = app.pid;
             if (pid > 0) {
                 mService.removePidLocked(app);
+                app.bindMountPending = false;
                 mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
                 mService.mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
                 if (app.isolated) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index a78caac..e7f66bb 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -251,6 +251,7 @@
     int adjSourceProcState;     // Debugging: proc state of adjSource's process.
     Object adjTarget;           // Debugging: target component impacting oom_adj.
     Runnable crashHandler;      // Optional local handler to be invoked in the process crash.
+    boolean bindMountPending;   // True if Android/obb and Android/data need to be bind mount .
 
     // Cache of last retrieve memory info and uptime, to throttle how frequently
     // apps can requyest it.
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 310664e..2e2241f 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -41,6 +41,7 @@
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.OpEventProxyInfo;
 import static android.app.AppOpsManager.RestrictionBypass;
+import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM;
 import static android.app.AppOpsManager.UID_STATE_BACKGROUND;
@@ -77,10 +78,10 @@
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.AppOpsManager.AttributedOpEntry;
 import android.app.AppOpsManager.HistoricalOps;
 import android.app.AppOpsManager.Mode;
 import android.app.AppOpsManager.OpEntry;
-import android.app.AppOpsManager.AttributedOpEntry;
 import android.app.AppOpsManager.OpFlags;
 import android.app.AppOpsManagerInternal;
 import android.app.AppOpsManagerInternal.CheckOpsDelegate;
@@ -244,9 +245,15 @@
 
     private static final int MAX_UNFORWARED_OPS = 10;
     private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
+    private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000;
 
     //TODO: remove this when development is done.
     private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31;
+    private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 30;
+    private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 29;
+    private static final int DEBUG_FGS_ALLOW_WHILE_IN_USE = 0;
+    private static final int DEBUG_FGS_ENFORCE_TYPE = 1;
+
 
     final Context mContext;
     final AtomicFile mFile;
@@ -549,7 +556,7 @@
                                 // 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, mode);
+                                maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE);
                                 return MODE_IGNORED;
                             } else {
                                 return MODE_IGNORED;
@@ -557,15 +564,25 @@
                         case OP_CAMERA:
                             if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
                                 return MODE_ALLOWED;
+                            } else if ((capability & TEMP_PROCESS_CAPABILITY_FOREGROUND_CAMERA)
+                                    != 0) {
+                                // CHANGE TO MODE_IGNORED when enforce this feature.
+                                maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
+                                return MODE_ALLOWED;
                             } else {
-                                maybeShowWhileInUseDebugToast(op, mode);
+                                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 & TEMP_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE)
+                                    != 0) {
+                                // CHANGE TO MODE_IGNORED when enforce this feature.
+                                maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
+                                return MODE_ALLOWED;
                             } else {
-                                maybeShowWhileInUseDebugToast(op, mode);
+                                maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE);
                                 return MODE_IGNORED;
                             }
                         default:
@@ -580,15 +597,24 @@
                     case OP_CAMERA:
                         if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
                             return MODE_ALLOWED;
+                        } else if ((capability & TEMP_PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
+                            // CHANGE TO MODE_IGNORED when enforce this feature.
+                            maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
+                            return MODE_ALLOWED;
                         } else {
-                            maybeShowWhileInUseDebugToast(op, mode);
+                            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 & TEMP_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE)
+                                != 0) {
+                            // CHANGE TO MODE_IGNORED when enforce this feature.
+                            maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
+                            return MODE_ALLOWED;
                         } else {
-                            maybeShowWhileInUseDebugToast(op, mode);
+                            maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE);
                             return MODE_IGNORED;
                         }
                     default:
@@ -1657,17 +1683,14 @@
             }
         }, packageAddedFilter);
 
-        List<String> packageNames = getPackageNamesForSampling();
-        synchronized (this) {
-            resamplePackageAndAppOpLocked(packageNames);
-        }
-
-        AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+        mHandler.postDelayed(new Runnable() {
             @Override
             public void run() {
+                List<String> packageNames = getPackageNamesForSampling();
+                resamplePackageAndAppOpLocked(packageNames);
                 initializeRarelyUsedPackagesList(new ArraySet<>(packageNames));
             }
-        });
+        }, RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS);
 
         PackageManagerInternal packageManagerInternal = LocalServices.getService(
                 PackageManagerInternal.class);
@@ -3374,18 +3397,22 @@
         synchronized (this) {
             Op op = getOpLocked(code, uid, resolvedPackageName, attributionTag, bypass, true);
             if (op == null) {
+                Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
+                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
                 return;
             }
             final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
             if (attributedOp == null) {
+                Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
+                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
                 return;
             }
 
-            try {
+            if (attributedOp.isRunning()) {
                 attributedOp.finished(clientId);
-            } catch (IllegalStateException e) {
-                Slog.e(TAG, "Operation not started: uid=" + uid + " pkg="
-                        + packageName + " op=" + AppOpsManager.opToName(code), e);
+            } else {
+                Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
+                        + attributionTag + ") op=" + AppOpsManager.opToName(code));
             }
         }
     }
@@ -5614,7 +5641,7 @@
         int uid = Binder.getCallingUid();
         Objects.requireNonNull(packageName);
         synchronized (this) {
-            switchPackageIfRarelyUsedLocked(packageName);
+            switchPackageIfBootTimeOrRarelyUsedLocked(packageName);
             if (!packageName.equals(mSampledPackage)) {
                 return new MessageSamplingConfig(OP_NONE, 0,
                         Instant.now().plus(1, ChronoUnit.HOURS).toEpochMilli());
@@ -5644,7 +5671,7 @@
     private void reportRuntimeAppOpAccessMessageAsyncLocked(int uid,
             @NonNull String packageName, int opCode, @Nullable String attributionTag,
             @NonNull String message) {
-        switchPackageIfRarelyUsedLocked(packageName);
+        switchPackageIfBootTimeOrRarelyUsedLocked(packageName);
         if (!Objects.equals(mSampledPackage, packageName)) {
             return;
         }
@@ -5695,11 +5722,16 @@
 
     /**
      * Checks if package is in the list of rarely used package and starts watching the new package
-     * to collect incoming message.
+     * to collect incoming message or if collection is happening in first minutes since boot.
      * @param packageName
      */
-    private void switchPackageIfRarelyUsedLocked(@NonNull String packageName) {
-        if (mRarelyUsedPackages.contains(packageName)) {
+    private void switchPackageIfBootTimeOrRarelyUsedLocked(@NonNull String packageName) {
+        if (mSampledPackage == null) {
+            if (ThreadLocalRandom.current().nextFloat() < 0.1f) {
+                mSamplingStrategy = SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
+                resampleAppOpForPackageLocked(packageName);
+            }
+        } else if (mRarelyUsedPackages.contains(packageName)) {
             mRarelyUsedPackages.remove(packageName);
             if (ThreadLocalRandom.current().nextFloat() < 0.5f) {
                 mSamplingStrategy = SAMPLING_STRATEGY_RARELY_USED;
@@ -5761,6 +5793,10 @@
                             }
                         }
                         synchronized (this) {
+                            int numPkgs = mRarelyUsedPackages.size();
+                            for (int i = 0; i < numPkgs; i++) {
+                                candidates.add(mRarelyUsedPackages.valueAt(i));
+                            }
                             mRarelyUsedPackages = candidates;
                         }
                     }
diff --git a/services/core/java/com/android/server/hdmi/TEST_MAPPING b/services/core/java/com/android/server/hdmi/TEST_MAPPING
new file mode 100644
index 0000000..7245ec4
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/TEST_MAPPING
@@ -0,0 +1,34 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.hdmi"
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.hdmi"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index 6da0de1..44ab438 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -50,6 +50,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
@@ -69,8 +70,11 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.CertificateEncodingException;
@@ -85,6 +89,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /** Implementation of {@link AppIntegrityManagerService}. */
 public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
@@ -239,6 +245,11 @@
         return new ParceledListSlice<>(rules);
     }
 
+    @Override
+    public List<String> getWhitelistedRuleProviders() throws RemoteException {
+        return getAllowedRuleProviders();
+    }
+
     private void handleIntegrityVerification(Intent intent) {
         int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
 
@@ -467,8 +478,23 @@
         if (installationPath == null) {
             throw new IllegalArgumentException("Installation path is null, package not found");
         }
-        SourceStampVerificationResult sourceStampVerificationResult =
-                SourceStampVerifier.verify(installationPath.getAbsolutePath());
+
+        SourceStampVerificationResult sourceStampVerificationResult;
+        if (installationPath.isDirectory()) {
+            try (Stream<Path> filesList = Files.list(installationPath.toPath())) {
+                List<String> apkFiles =
+                        filesList
+                                .map(path -> path.toAbsolutePath().toString())
+                                .collect(Collectors.toList());
+                sourceStampVerificationResult = SourceStampVerifier.verify(apkFiles);
+            } catch (IOException e) {
+                throw new IllegalArgumentException("Could not read APK directory");
+            }
+        } else {
+            sourceStampVerificationResult =
+                    SourceStampVerifier.verify(installationPath.getAbsolutePath());
+        }
+
         appInstallMetadata.setIsStampPresent(sourceStampVerificationResult.isPresent());
         appInstallMetadata.setIsStampVerified(sourceStampVerificationResult.isVerified());
         // A verified stamp is set to be trusted.
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index aad7203..5c97e90 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -127,8 +127,10 @@
     @Override
     public void requestCreateSession(long requestId, String packageName, String routeId,
             Bundle sessionHints) {
-        // Handle it as an internal transfer.
+
         transferToRoute(requestId, SYSTEM_SESSION_ID, routeId);
+        mCallback.onSessionCreated(this, requestId, mSessionInfos.get(0));
+        //TODO: We should call after the session info is changed.
     }
 
     @Override
@@ -240,7 +242,6 @@
             builder.addTransferableRoute(route.getId());
         }
 
-
         RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build();
         if (Objects.equals(oldSessionInfo, newSessionInfo)) {
             return false;
@@ -261,6 +262,7 @@
         synchronized (mLock) {
             sessionInfo = mSessionInfos.get(0);
         }
+
         mCallback.onSessionUpdated(this, sessionInfo);
     }
 
diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java
index c9c8042..c96880c 100644
--- a/services/core/java/com/android/server/notification/BubbleExtractor.java
+++ b/services/core/java/com/android/server/notification/BubbleExtractor.java
@@ -15,9 +15,27 @@
 */
 package com.android.server.notification;
 
+import static android.app.Notification.CATEGORY_CALL;
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+
+import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING;
+import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Person;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayList;
+
 /**
  * Determines whether a bubble can be shown for this notification
  */
@@ -25,10 +43,15 @@
     private static final String TAG = "BubbleExtractor";
     private static final boolean DBG = false;
 
+    private BubbleChecker mBubbleChecker;
     private RankingConfig mConfig;
+    private ActivityManager mActivityManager;
+    private Context mContext;
 
-    public void initialize(Context ctx, NotificationUsageStats usageStats) {
+    public void initialize(Context context, NotificationUsageStats usageStats) {
         if (DBG) Slog.d(TAG, "Initializing  " + getClass().getSimpleName() + ".");
+        mContext = context;
+        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
     }
 
     public RankingReconsideration process(NotificationRecord record) {
@@ -41,6 +64,12 @@
             if (DBG) Slog.d(TAG, "missing config");
             return null;
         }
+
+        if (mBubbleChecker == null) {
+            if (DBG) Slog.d(TAG, "missing bubble checker");
+            return null;
+        }
+
         boolean appCanShowBubble =
                 mConfig.areBubblesAllowed(record.getSbn().getPackageName(), record.getSbn().getUid());
         if (!mConfig.bubblesEnabled() || !appCanShowBubble) {
@@ -52,7 +81,12 @@
                 record.setAllowBubble(appCanShowBubble);
             }
         }
-
+        final boolean applyFlag = mBubbleChecker.isNotificationAppropriateToBubble(record);
+        if (applyFlag) {
+            record.getNotification().flags |= FLAG_BUBBLE;
+        } else {
+            record.getNotification().flags &= ~FLAG_BUBBLE;
+        }
         return null;
     }
 
@@ -64,4 +98,185 @@
     @Override
     public void setZenHelper(ZenModeHelper helper) {
     }
+
+    /**
+     * Expected to be called after {@link #setConfig(RankingConfig)} has occurred.
+     */
+    void setShortcutHelper(ShortcutHelper helper) {
+        if (mConfig == null) {
+            if (DBG) Slog.d(TAG, "setting shortcut helper prior to setConfig");
+            return;
+        }
+        mBubbleChecker = new BubbleChecker(mContext, helper, mConfig, mActivityManager);
+    }
+
+    @VisibleForTesting
+    void setBubbleChecker(BubbleChecker checker) {
+        mBubbleChecker = checker;
+    }
+
+    /**
+     * Encapsulates special checks to see if a notification can be flagged as a bubble. This
+     * makes testing a bit easier.
+     */
+    public static class BubbleChecker {
+
+        private ActivityManager mActivityManager;
+        private RankingConfig mRankingConfig;
+        private Context mContext;
+        private ShortcutHelper mShortcutHelper;
+
+        BubbleChecker(Context context, ShortcutHelper helper, RankingConfig config,
+                ActivityManager activityManager) {
+            mContext = context;
+            mActivityManager = activityManager;
+            mShortcutHelper = helper;
+            mRankingConfig = config;
+        }
+
+        /**
+         * @return whether the provided notification record is allowed to be represented as a
+         * bubble, accounting for user choice & policy.
+         */
+        public boolean isNotificationAppropriateToBubble(NotificationRecord r) {
+            final String pkg = r.getSbn().getPackageName();
+            final int userId = r.getSbn().getUser().getIdentifier();
+            Notification notification = r.getNotification();
+            if (!canBubble(r, pkg, userId)) {
+                // no log: canBubble has its own
+                return false;
+            }
+
+            if (mActivityManager.isLowRamDevice()) {
+                logBubbleError(r.getKey(), "low ram device");
+                return false;
+            }
+
+            // At this point the bubble must fulfill communication policy
+
+            // Communication always needs a person
+            ArrayList<Person> peopleList = notification.extras != null
+                    ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)
+                    : null;
+            // Message style requires a person & it's not included in the list
+            boolean isMessageStyle = Notification.MessagingStyle.class.equals(
+                    notification.getNotificationStyle());
+            if (!isMessageStyle && (peopleList == null || peopleList.isEmpty())) {
+                logBubbleError(r.getKey(), "Must have a person and be "
+                        + "Notification.MessageStyle or Notification.CATEGORY_CALL");
+                return false;
+            }
+
+            // Communication is a message or a call
+            boolean isCall = CATEGORY_CALL.equals(notification.category);
+            boolean hasForegroundService = (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
+            if (hasForegroundService && !isCall) {
+                logBubbleError(r.getKey(),
+                        "foreground services must be Notification.CATEGORY_CALL to bubble");
+                return false;
+            }
+            if (isMessageStyle) {
+                return true;
+            } else if (isCall) {
+                if (hasForegroundService) {
+                    return true;
+                }
+                logBubbleError(r.getKey(), "calls require foreground service");
+                return false;
+            }
+            logBubbleError(r.getKey(), "Must be "
+                    + "Notification.MessageStyle or Notification.CATEGORY_CALL");
+            return false;
+        }
+
+        /**
+         * @return whether the user has enabled the provided notification to bubble, does not
+         * account for policy.
+         */
+        @VisibleForTesting
+        boolean canBubble(NotificationRecord r, String pkg, int userId) {
+            Notification notification = r.getNotification();
+            Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
+            if (metadata == null) {
+                // no log: no need to inform dev if they didn't attach bubble metadata
+                return false;
+            }
+            if (!mRankingConfig.bubblesEnabled()) {
+                logBubbleError(r.getKey(), "bubbles disabled for user: " + userId);
+                return false;
+            }
+            if (!mRankingConfig.areBubblesAllowed(pkg, userId)) {
+                logBubbleError(r.getKey(),
+                        "bubbles for package: " + pkg + " disabled for user: " + userId);
+                return false;
+            }
+            if (!r.getChannel().canBubble()) {
+                logBubbleError(r.getKey(),
+                        "bubbles for channel " + r.getChannel().getId() + " disabled");
+                return false;
+            }
+
+            String shortcutId = metadata.getShortcutId();
+            boolean shortcutValid = shortcutId != null
+                    && mShortcutHelper.hasValidShortcutInfo(shortcutId, pkg, r.getUser());
+            if (metadata.getBubbleIntent() == null && !shortcutValid) {
+                // Should have a shortcut if intent is null
+                logBubbleError(r.getKey(),
+                        "couldn't find valid shortcut for bubble with shortcutId: " + shortcutId);
+                return false;
+            }
+            if (shortcutValid) {
+                return true;
+            }
+            // no log: canLaunch method has the failure log
+            return canLaunchInActivityView(mContext, metadata.getBubbleIntent(), pkg);
+        }
+
+        /**
+         * Whether an intent is properly configured to display in an {@link
+         * android.app.ActivityView}.
+         *
+         * @param context       the context to use.
+         * @param pendingIntent the pending intent of the bubble.
+         * @param packageName   the notification package name for this bubble.
+         */
+        // Keep checks in sync with BubbleController#canLaunchInActivityView.
+        @VisibleForTesting
+        protected boolean canLaunchInActivityView(Context context, PendingIntent pendingIntent,
+                String packageName) {
+            if (pendingIntent == null) {
+                Slog.w(TAG, "Unable to create bubble -- no intent");
+                return false;
+            }
+
+            Intent intent = pendingIntent.getIntent();
+
+            ActivityInfo info = intent != null
+                    ? intent.resolveActivityInfo(context.getPackageManager(), 0)
+                    : null;
+            if (info == null) {
+                FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED,
+                        packageName,
+                        BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING);
+                Slog.w(TAG, "Unable to send as bubble -- couldn't find activity info for intent: "
+                        + intent);
+                return false;
+            }
+            if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
+                FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED,
+                        packageName,
+                        BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE);
+                Slog.w(TAG, "Unable to send as bubble -- activity is not resizable for intent: "
+                        + intent);
+                return false;
+            }
+            return true;
+        }
+
+        private void logBubbleError(String key, String failureMessage) {
+            if (DBG) {
+                Slog.w(TAG, "Bubble notification: " + key + " failed: " + failureMessage);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 69a5b35..20ad87a 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -17,7 +17,6 @@
 package com.android.server.notification;
 
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
-import static android.app.Notification.CATEGORY_CALL;
 import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
@@ -51,9 +50,6 @@
 import static android.content.Context.BIND_AUTO_CREATE;
 import static android.content.Context.BIND_FOREGROUND_SERVICE;
 import static android.content.Context.BIND_NOT_PERCEPTIBLE;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
 import static android.content.pm.PackageManager.FEATURE_TELEVISION;
 import static android.content.pm.PackageManager.MATCH_ALL;
@@ -93,8 +89,6 @@
 import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
-import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING;
-import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE;
 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;
@@ -131,8 +125,6 @@
 import android.app.NotificationManager;
 import android.app.NotificationManager.Policy;
 import android.app.PendingIntent;
-import android.app.Person;
-import android.app.RemoteInput;
 import android.app.StatsManager;
 import android.app.StatusBarManager;
 import android.app.UriGrantsManager;
@@ -152,7 +144,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.LauncherApps;
@@ -160,7 +151,6 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
-import android.content.pm.ShortcutInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -251,7 +241,6 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.TriPredicate;
@@ -296,7 +285,6 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map.Entry;
@@ -421,7 +409,7 @@
     private RoleObserver mRoleObserver;
     private UserManager mUm;
     private IPlatformCompat mPlatformCompat;
-    private LauncherApps mLauncherAppsService;
+    private ShortcutHelper mShortcutHelper;
 
     final IBinder mForegroundToken = new Binder();
     private WorkerHandler mHandler;
@@ -497,7 +485,8 @@
             "allow-secure-notifications-on-lockscreen";
     private static final String LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_VALUE = "value";
 
-    private RankingHelper mRankingHelper;
+    @VisibleForTesting
+    RankingHelper mRankingHelper;
     @VisibleForTesting
     PreferencesHelper mPreferencesHelper;
 
@@ -931,6 +920,8 @@
                         .setType(MetricsEvent.TYPE_ACTION)
                         .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, nv.rank)
                         .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_COUNT, nv.count));
+                mNotificationRecordLogger.log(
+                        NotificationRecordLogger.NotificationEvent.NOTIFICATION_CLICKED, r);
                 EventLogTags.writeNotificationClicked(key,
                         r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now),
                         nv.rank, nv.count);
@@ -970,7 +961,8 @@
                                 generatedByAssistant ? 1 : 0)
                         .addTaggedData(MetricsEvent.NOTIFICATION_LOCATION,
                                 nv.location.toMetricsEventEnum()));
-
+                mNotificationRecordLogger.log(
+                        NotificationRecordLogger.NotificationEvent.NOTIFICATION_ACTION_CLICKED, r);
                 EventLogTags.writeNotificationActionClicked(key, actionIndex,
                         r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now),
                         nv.rank, nv.count);
@@ -1004,6 +996,8 @@
         public void onPanelRevealed(boolean clearEffects, int items) {
             MetricsLogger.visible(getContext(), MetricsEvent.NOTIFICATION_PANEL);
             MetricsLogger.histogram(getContext(), "note_load", items);
+            mNotificationRecordLogger.log(
+                    NotificationRecordLogger.NotificationPanelEvent.NOTIFICATION_PANEL_OPEN);
             EventLogTags.writeNotificationPanelRevealed(items);
             if (clearEffects) {
                 clearEffects();
@@ -1014,6 +1008,8 @@
         @Override
         public void onPanelHidden() {
             MetricsLogger.hidden(getContext(), MetricsEvent.NOTIFICATION_PANEL);
+            mNotificationRecordLogger.log(
+                    NotificationRecordLogger.NotificationPanelEvent.NOTIFICATION_PANEL_CLOSE);
             EventLogTags.writeNotificationPanelHidden();
             mAssistants.onPanelHidden();
         }
@@ -1103,6 +1099,10 @@
                         MetricsLogger.action(r.getItemLogMaker()
                                 .setType(expanded ? MetricsEvent.TYPE_DETAIL
                                         : MetricsEvent.TYPE_COLLAPSE));
+                        mNotificationRecordLogger.log(
+                                NotificationRecordLogger.NotificationEvent.fromExpanded(expanded,
+                                        userAction),
+                                r);
                     }
                     if (expanded && userAction) {
                         r.recordExpanded();
@@ -1124,6 +1124,9 @@
                     mMetricsLogger.write(r.getLogMaker()
                             .setCategory(MetricsEvent.NOTIFICATION_DIRECT_REPLY_ACTION)
                             .setType(MetricsEvent.TYPE_ACTION));
+                    mNotificationRecordLogger.log(
+                            NotificationRecordLogger.NotificationEvent.NOTIFICATION_DIRECT_REPLIED,
+                            r);
                     reportUserInteraction(r);
                     mAssistants.notifyAssistantNotificationDirectReplyLocked(r.getSbn());
                 }
@@ -1166,6 +1169,9 @@
                                     MetricsEvent.NOTIFICATION_SMART_REPLY_MODIFIED_BEFORE_SENDING,
                                     modifiedBeforeSending ? 1 : 0);
                     mMetricsLogger.write(logMaker);
+                    mNotificationRecordLogger.log(
+                            NotificationRecordLogger.NotificationEvent.NOTIFICATION_SMART_REPLIED,
+                            r);
                     // Treat clicking on a smart reply as a user interaction.
                     reportUserInteraction(r);
                     mAssistants.notifyAssistantSuggestedReplySent(
@@ -1186,13 +1192,30 @@
 
         @Override
         public void onNotificationBubbleChanged(String key, boolean isBubble) {
+            String pkg;
+            synchronized (mNotificationLock) {
+                NotificationRecord r = mNotificationsByKey.get(key);
+                pkg = r != null && r.getSbn() != null ? r.getSbn().getPackageName() : null;
+            }
+            boolean isAppForeground = pkg != null
+                    && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
             synchronized (mNotificationLock) {
                 NotificationRecord r = mNotificationsByKey.get(key);
                 if (r != null) {
-                    final StatusBarNotification n = r.getSbn();
-                    final int callingUid = n.getUid();
-                    final String pkg = n.getPackageName();
-                    applyFlagBubble(r, pkg, callingUid, null /* oldEntry */, isBubble);
+                    if (!isBubble) {
+                        // This happens if the user has dismissed the bubble but the notification
+                        // is still active in the shade, enqueuing would create a bubble since
+                        // the notification is technically allowed. Flip the flag so that
+                        // apps querying noMan will know that their notification is not showing
+                        // as a bubble.
+                        r.getNotification().flags &= ~FLAG_BUBBLE;
+                    } else {
+                        // Enqueue will trigger resort & if the flag is allowed to be true it'll
+                        // be applied there.
+                        r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
+                        mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(),
+                                r, isAppForeground));
+                    }
                 }
             }
         }
@@ -1219,6 +1242,7 @@
                         flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
                     }
                     data.setFlags(flags);
+                    r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
                     mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r,
                             true /* isAppForeground */));
                 }
@@ -1310,6 +1334,9 @@
                             MetricsEvent.NOTIFICATION_SMART_REPLY_EDIT_BEFORE_SENDING,
                             r.getEditChoicesBeforeSending() ? 1 : 0);
             mMetricsLogger.write(logMaker);
+            mNotificationRecordLogger.log(
+                    NotificationRecordLogger.NotificationEvent.NOTIFICATION_SMART_REPLY_VISIBLE,
+                    r);
         }
     }
 
@@ -1595,80 +1622,6 @@
         }
     };
 
-    // Key: packageName Value: <shortcutId, notifId>
-    private HashMap<String, HashMap<String, String>> mActiveShortcutBubbles = new HashMap<>();
-
-    private boolean mLauncherAppsCallbackRegistered;
-
-    // Bubbles can be created based on a shortcut, we need to listen for changes to
-    // that shortcut so that we may update the bubble appropriately.
-    private final LauncherApps.Callback mLauncherAppsCallback = new LauncherApps.Callback() {
-        @Override
-        public void onPackageRemoved(String packageName, UserHandle user) {
-        }
-
-        @Override
-        public void onPackageAdded(String packageName, UserHandle user) {
-        }
-
-        @Override
-        public void onPackageChanged(String packageName, UserHandle user) {
-        }
-
-        @Override
-        public void onPackagesAvailable(String[] packageNames, UserHandle user,
-                boolean replacing) {
-        }
-
-        @Override
-        public void onPackagesUnavailable(String[] packageNames, UserHandle user,
-                boolean replacing) {
-        }
-
-        @Override
-        public void onShortcutsChanged(@NonNull String packageName,
-                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
-            HashMap<String, String> shortcutBubbles = mActiveShortcutBubbles.get(packageName);
-            boolean isAppForeground = packageName != null
-                    && mActivityManager.getPackageImportance(packageName) == IMPORTANCE_FOREGROUND;
-            ArrayList<String> bubbleKeysToRemove = new ArrayList<>();
-            if (shortcutBubbles != null) {
-                // If we can't find one of our bubbles in the shortcut list, that bubble needs
-                // to be removed.
-                for (String shortcutId : shortcutBubbles.keySet()) {
-                    boolean foundShortcut = false;
-                    for (int i = 0; i < shortcuts.size(); i++) {
-                        if (shortcuts.get(i).getId().equals(shortcutId)) {
-                            foundShortcut = true;
-                            break;
-                        }
-                    }
-                    if (!foundShortcut) {
-                        bubbleKeysToRemove.add(shortcutBubbles.get(shortcutId));
-                    }
-                }
-            }
-
-            // Do the removals
-            for (int i = 0; i < bubbleKeysToRemove.size(); i++) {
-                // update flag bubble
-                String bubbleKey = bubbleKeysToRemove.get(i);
-                synchronized (mNotificationLock) {
-                    NotificationRecord r = mNotificationsByKey.get(bubbleKey);
-                    if (r != null) {
-                        final StatusBarNotification n = r.getSbn();
-                        final int callingUid = n.getUid();
-                        final String pkg = n.getPackageName();
-                        applyFlagBubble(r, pkg, callingUid, null /* oldEntry */, isAppForeground);
-                        mHandler.post(new EnqueueNotificationRunnable(user.getIdentifier(), r,
-                                false /* isAppForeground */));
-                    }
-                }
-            }
-        }
-    };
-
-
     private final class SettingsObserver extends ContentObserver {
         private final Uri NOTIFICATION_BADGING_URI
                 = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING);
@@ -1763,8 +1716,8 @@
     }
 
     @VisibleForTesting
-    void setLauncherApps(LauncherApps launcherApps) {
-        mLauncherAppsService = launcherApps;
+    ShortcutHelper getShortcutHelper() {
+        return mShortcutHelper;
     }
 
     @VisibleForTesting
@@ -2314,8 +2267,13 @@
             mRoleObserver = new RoleObserver(getContext().getSystemService(RoleManager.class),
                     mPackageManager, getContext().getMainExecutor());
             mRoleObserver.init();
-            mLauncherAppsService =
+            LauncherApps launcherApps =
                     (LauncherApps) getContext().getSystemService(Context.LAUNCHER_APPS_SERVICE);
+            mShortcutHelper = new ShortcutHelper(launcherApps, mShortcutListener);
+            BubbleExtractor bubbsExtractor = mRankingHelper.findExtractor(BubbleExtractor.class);
+            if (bubbsExtractor != null) {
+                bubbsExtractor.setShortcutHelper(mShortcutHelper);
+            }
             registerNotificationPreferencesPullers();
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
             // This observer will force an update when observe is called, causing us to
@@ -3458,7 +3416,7 @@
             ArrayList<ConversationChannelWrapper> conversations =
                     mPreferencesHelper.getConversations(onlyImportant);
             for (ConversationChannelWrapper conversation : conversations) {
-                conversation.setShortcutInfo(getShortcutInfo(
+                conversation.setShortcutInfo(mShortcutHelper.getShortcutInfo(
                         conversation.getNotificationChannel().getConversationId(),
                         conversation.getPkg(),
                         UserHandle.of(UserHandle.getUserId(conversation.getUid()))));
@@ -3481,7 +3439,7 @@
             ArrayList<ConversationChannelWrapper> conversations =
                     mPreferencesHelper.getConversations(pkg, uid);
             for (ConversationChannelWrapper conversation : conversations) {
-                conversation.setShortcutInfo(getShortcutInfo(
+                conversation.setShortcutInfo(mShortcutHelper.getShortcutInfo(
                         conversation.getNotificationChannel().getConversationId(),
                         pkg,
                         UserHandle.of(UserHandle.getUserId(uid))));
@@ -5652,7 +5610,7 @@
             }
         }
 
-        r.setShortcutInfo(getShortcutInfo(notification.getShortcutId(), pkg, user));
+        r.setShortcutInfo(mShortcutHelper.getShortcutInfo(notification.getShortcutId(), pkg, user));
 
         if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
                 r.getSbn().getOverrideGroupKey() != null)) {
@@ -5780,16 +5738,12 @@
     }
 
     /**
-     * Updates the flags for this notification to reflect whether it is a bubble or not. Some
-     * bubble specific flags only work if the app is foreground, this will strip those flags
+     * Some bubble specific flags only work if the app is foreground, this will strip those flags
      * if the app wasn't foreground.
      */
-    private void updateNotificationBubbleFlags(NotificationRecord r, String pkg, int userId,
-            NotificationRecord oldRecord, boolean isAppForeground) {
-        Notification notification = r.getNotification();
-        applyFlagBubble(r, pkg, userId, oldRecord, true /* desiredFlag */);
-
+    private void updateNotificationBubbleFlags(NotificationRecord r, boolean isAppForeground) {
         // Remove any bubble specific flags that only work when foregrounded
+        Notification notification = r.getNotification();
         Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
         if (!isAppForeground && metadata != null) {
             int flags = metadata.getFlags();
@@ -5799,252 +5753,30 @@
         }
     }
 
-    /**
-     * Handles actually applying or removing {@link Notification#FLAG_BUBBLE}. Performs necessary
-     * checks for the provided record to see if it can actually be a bubble.
-     * Tracks shortcut based bubbles so that we can find out if they've changed or been removed.
-     */
-    private void applyFlagBubble(NotificationRecord r, String pkg, int userId,
-            NotificationRecord oldRecord, boolean desiredFlag) {
-        boolean applyFlag = desiredFlag
-                && isNotificationAppropriateToBubble(r, pkg, userId, oldRecord);
-        final String shortcutId = r.getNotification().getBubbleMetadata() != null
-                ? r.getNotification().getBubbleMetadata().getShortcutId()
-                : null;
-        if (applyFlag) {
-            if (shortcutId != null) {
-                // Must track shortcut based bubbles in case the shortcut is removed
-                HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
-                        r.getSbn().getPackageName());
-                if (packageBubbles == null) {
-                    packageBubbles = new HashMap<>();
+    private ShortcutHelper.ShortcutListener mShortcutListener =
+            new ShortcutHelper.ShortcutListener() {
+                @Override
+                public void onShortcutRemoved(String key) {
+                    String packageName;
+                    synchronized (mNotificationLock) {
+                        NotificationRecord r = mNotificationsByKey.get(key);
+                        packageName = r != null ? r.getSbn().getPackageName() : null;
+                    }
+                    boolean isAppForeground = packageName != null
+                            && mActivityManager.getPackageImportance(packageName)
+                            == IMPORTANCE_FOREGROUND;
+                    synchronized (mNotificationLock) {
+                        NotificationRecord r = mNotificationsByKey.get(key);
+                        if (r != null) {
+                            // Enqueue will trigger resort & flag is updated that way.
+                            r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
+                            mHandler.post(
+                                    new NotificationManagerService.EnqueueNotificationRunnable(
+                                            r.getUser().getIdentifier(), r, isAppForeground));
+                        }
+                    }
                 }
-                packageBubbles.put(shortcutId, r.getKey());
-                mActiveShortcutBubbles.put(r.getSbn().getPackageName(), packageBubbles);
-                if (!mLauncherAppsCallbackRegistered) {
-                    mLauncherAppsService.registerCallback(mLauncherAppsCallback, mHandler);
-                    mLauncherAppsCallbackRegistered = true;
-                }
-            }
-            r.getNotification().flags |= FLAG_BUBBLE;
-        } else {
-            if (shortcutId != null) {
-                // No longer track shortcut
-                HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
-                        r.getSbn().getPackageName());
-                if (packageBubbles != null) {
-                    packageBubbles.remove(shortcutId);
-                }
-                if (packageBubbles != null && packageBubbles.isEmpty()) {
-                    mActiveShortcutBubbles.remove(r.getSbn().getPackageName());
-                }
-                if (mLauncherAppsCallbackRegistered && mActiveShortcutBubbles.isEmpty()) {
-                    mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
-                    mLauncherAppsCallbackRegistered = false;
-                }
-            }
-            r.getNotification().flags &= ~FLAG_BUBBLE;
-        }
-    }
-
-    /**
-     * @return whether the provided notification record is allowed to be represented as a bubble,
-     * accounting for user choice & policy.
-     */
-    private boolean isNotificationAppropriateToBubble(NotificationRecord r, String pkg, int userId,
-            NotificationRecord oldRecord) {
-        Notification notification = r.getNotification();
-        if (!canBubble(r, pkg, userId)) {
-            // no log: canBubble has its own
-            return false;
-        }
-
-        if (mActivityManager.isLowRamDevice()) {
-            logBubbleError(r.getKey(), "low ram device");
-            return false;
-        }
-
-        if (oldRecord != null && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0) {
-            // This is an update to an active bubble
-            return true;
-        }
-
-        // At this point the bubble must fulfill communication policy
-
-        // Communication always needs a person
-        ArrayList<Person> peopleList = notification.extras != null
-                ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)
-                : null;
-        // Message style requires a person & it's not included in the list
-        boolean isMessageStyle = Notification.MessagingStyle.class.equals(
-                notification.getNotificationStyle());
-        if (!isMessageStyle && (peopleList == null || peopleList.isEmpty())) {
-            logBubbleError(r.getKey(), "Must have a person and be "
-                    + "Notification.MessageStyle or Notification.CATEGORY_CALL");
-            return false;
-        }
-
-        // Communication is a message or a call
-        boolean isCall = CATEGORY_CALL.equals(notification.category);
-        boolean hasForegroundService = (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
-        if (hasForegroundService && !isCall) {
-            logBubbleError(r.getKey(),
-                    "foreground services must be Notification.CATEGORY_CALL to bubble");
-            return false;
-        }
-        if (isMessageStyle) {
-            if (hasValidRemoteInput(notification)) {
-                return true;
-            }
-            logBubbleError(r.getKey(), "messages require valid remote input");
-            return false;
-        } else if (isCall) {
-            if (hasForegroundService) {
-                return true;
-            }
-            logBubbleError(r.getKey(), "calls require foreground service");
-            return false;
-        }
-        logBubbleError(r.getKey(), "Must be "
-                + "Notification.MessageStyle or Notification.CATEGORY_CALL");
-        return false;
-    }
-
-    /**
-     * @return whether the user has enabled the provided notification to bubble, does not account
-     * for policy.
-     */
-    private boolean canBubble(NotificationRecord r, String pkg, int userId) {
-        Notification notification = r.getNotification();
-        Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
-        if (metadata == null) {
-            // no log: no need to inform dev if they didn't attach bubble metadata
-            return false;
-        }
-        if (!mPreferencesHelper.bubblesEnabled()) {
-            logBubbleError(r.getKey(), "bubbles disabled for user: " + userId);
-            return false;
-        }
-        if (!mPreferencesHelper.areBubblesAllowed(pkg, userId)) {
-            logBubbleError(r.getKey(),
-                    "bubbles for package: " + pkg + " disabled for user: " + userId);
-            return false;
-        }
-        if (!r.getChannel().canBubble()) {
-            logBubbleError(r.getKey(),
-                    "bubbles for channel " + r.getChannel().getId() + " disabled");
-            return false;
-        }
-
-        String shortcutId = metadata.getShortcutId();
-        boolean shortcutValid = shortcutId != null
-                && hasValidShortcutInfo(shortcutId, pkg, r.getUser());
-        if (metadata.getBubbleIntent() == null && !shortcutValid) {
-            // Should have a shortcut if intent is null
-            logBubbleError(r.getKey(), "couldn't find shortcutId for bubble: " + shortcutId);
-            return false;
-        }
-        if (shortcutValid) {
-            return true;
-        }
-        // no log: canLaunch method has the failure log
-        return canLaunchInActivityView(getContext(), metadata.getBubbleIntent(), pkg);
-    }
-
-    private boolean hasValidRemoteInput(Notification n) {
-        // Also check for inline reply
-        Notification.Action[] actions = n.actions;
-        if (actions != null) {
-            // Get the remote inputs
-            for (int i = 0; i < actions.length; i++) {
-                Notification.Action action = actions[i];
-                RemoteInput[] inputs = action.getRemoteInputs();
-                if (inputs != null && inputs.length > 0) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private ShortcutInfo getShortcutInfo(String shortcutId, String packageName, UserHandle user) {
-        final long token = Binder.clearCallingIdentity();
-        try {
-            if (shortcutId == null || packageName == null || user == null) {
-                return null;
-            }
-            LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
-            if (packageName != null) {
-                query.setPackage(packageName);
-            }
-            if (shortcutId != null) {
-                query.setShortcutIds(Arrays.asList(shortcutId));
-            }
-            query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_CACHED);
-            List<ShortcutInfo> shortcuts = mLauncherAppsService.getShortcuts(query, user);
-            ShortcutInfo shortcutInfo = shortcuts != null && shortcuts.size() > 0
-                    ? shortcuts.get(0)
-                    : null;
-            return shortcutInfo;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    private boolean hasValidShortcutInfo(String shortcutId, String packageName, UserHandle user) {
-        ShortcutInfo shortcutInfo = getShortcutInfo(shortcutId, packageName, user);
-        return shortcutInfo != null && shortcutInfo.isLongLived();
-    }
-
-    private void logBubbleError(String key, String failureMessage) {
-        if (DBG) {
-            Log.w(TAG, "Bubble notification: " + key + " failed: " + failureMessage);
-        }
-    }
-    /**
-     * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
-     *
-     * @param context       the context to use.
-     * @param pendingIntent the pending intent of the bubble.
-     * @param packageName   the notification package name for this bubble.
-     */
-    // Keep checks in sync with BubbleController#canLaunchInActivityView.
-    @VisibleForTesting
-    protected boolean canLaunchInActivityView(Context context, PendingIntent pendingIntent,
-            String packageName) {
-        if (pendingIntent == null) {
-            Log.w(TAG, "Unable to create bubble -- no intent");
-            return false;
-        }
-
-        // Need escalated privileges to get the intent.
-        final long token = Binder.clearCallingIdentity();
-        Intent intent;
-        try {
-            intent = pendingIntent.getIntent();
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-
-        ActivityInfo info = intent != null
-                ? intent.resolveActivityInfo(context.getPackageManager(), 0)
-                : null;
-        if (info == null) {
-            FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
-                    BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING);
-            Log.w(TAG, "Unable to send as bubble -- couldn't find activity info for intent: "
-                    + intent);
-            return false;
-        }
-        if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
-            FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
-                    BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE);
-            Log.w(TAG, "Unable to send as bubble -- activity is not resizable for intent: "
-                    + intent);
-            return false;
-        }
-        return true;
-    }
+            };
 
     private void doChannelWarningToast(CharSequence toastText) {
         Binder.withCleanCallingIdentity(() -> {
@@ -6150,6 +5882,9 @@
                 MetricsLogger.action(r.getLogMaker()
                         .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
                         .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED));
+                mNotificationRecordLogger.log(
+                        NotificationRecordLogger.NotificationEvent.NOTIFICATION_NOT_POSTED_SNOOZED,
+                        r);
                 if (DBG) {
                     Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());
                 }
@@ -6279,6 +6014,8 @@
                             mDuration)
                     .addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
                             mSnoozeCriterionId == null ? 0 : 1));
+            mNotificationRecordLogger.log(
+                    NotificationRecordLogger.NotificationEvent.NOTIFICATION_SNOOZED, r);
             reportUserInteraction(r);
             boolean wasPosted = removeFromNotificationListsLocked(r);
             cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null);
@@ -6406,6 +6143,8 @@
                     cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
                             mSendDelete, childrenFlagChecker);
                     updateLightsLocked();
+                    mShortcutHelper.maybeListenForShortcutChangesForBubbles(r, true /* isRemoved */,
+                            mHandler);
                 } else {
                     // No notification was found, assume that it is snoozed and cancel it.
                     if (mReason != REASON_SNOOZED) {
@@ -6473,7 +6212,7 @@
                 final String tag = n.getTag();
 
                 // We need to fix the notification up a little for bubbles
-                updateNotificationBubbleFlags(r, pkg, callingUid, old, isAppForeground);
+                updateNotificationBubbleFlags(r, isAppForeground);
 
                 // Handle grouped notifications and bail out early if we
                 // can to avoid extracting signals.
@@ -6643,6 +6382,10 @@
                                 + n.getPackageName());
                     }
 
+                    mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
+                            false /* isRemoved */,
+                            mHandler);
+
                     maybeRecordInterruptionLocked(r);
 
                     // Log event to statsd
@@ -7402,6 +7145,7 @@
             int[] visibilities = new int[N];
             boolean[] showBadges = new boolean[N];
             boolean[] allowBubbles = new boolean[N];
+            boolean[] isBubble = new boolean[N];
             ArrayList<NotificationChannel> channelBefore = new ArrayList<>(N);
             ArrayList<String> groupKeyBefore = new ArrayList<>(N);
             ArrayList<ArrayList<String>> overridePeopleBefore = new ArrayList<>(N);
@@ -7417,6 +7161,7 @@
                 visibilities[i] = r.getPackageVisibilityOverride();
                 showBadges[i] = r.canShowBadge();
                 allowBubbles[i] = r.canBubble();
+                isBubble[i] = r.getNotification().isBubbleNotification();
                 channelBefore.add(r.getChannel());
                 groupKeyBefore.add(r.getGroupKey());
                 overridePeopleBefore.add(r.getPeopleOverride());
@@ -7435,6 +7180,7 @@
                         || visibilities[i] != r.getPackageVisibilityOverride()
                         || showBadges[i] != r.canShowBadge()
                         || allowBubbles[i] != r.canBubble()
+                        || isBubble[i] != r.getNotification().isBubbleNotification()
                         || !Objects.equals(channelBefore.get(i), r.getChannel())
                         || !Objects.equals(groupKeyBefore.get(i), r.getGroupKey())
                         || !Objects.equals(overridePeopleBefore.get(i), r.getPeopleOverride())
@@ -8597,7 +8343,8 @@
                     record.canBubble(),
                     record.isInterruptive(),
                     record.isConversation(),
-                    record.getShortcutInfo()
+                    record.getShortcutInfo(),
+                    record.getNotification().isBubbleNotification()
             );
             rankings.add(ranking);
         }
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index f4ee461..6c833f9 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -34,11 +34,14 @@
 import java.util.Objects;
 
 /**
- * Interface for writing NotificationReported atoms to statsd log.
+ * Interface for writing NotificationReported atoms to statsd log. Use NotificationRecordLoggerImpl
+ * in production.  Use NotificationRecordLoggerFake for testing.
  * @hide
  */
 public interface NotificationRecordLogger {
 
+    // The high-level interface used by clients.
+
     /**
      * May log a NotificationReported atom reflecting the posting or update of a notification.
      * @param r The new NotificationRecord. If null, no action is taken.
@@ -57,9 +60,11 @@
      * @param reason The reason the notification was canceled.
      * @param dismissalSurface The surface the notification was dismissed from.
      */
-    void logNotificationCancelled(@Nullable NotificationRecord r,
+    default void logNotificationCancelled(@Nullable NotificationRecord r,
             @NotificationListenerService.NotificationCancelReason int reason,
-            @NotificationStats.DismissalSurface int dismissalSurface);
+            @NotificationStats.DismissalSurface int dismissalSurface) {
+        log(NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface), r);
+    }
 
     /**
      * Logs a notification visibility change event using UiEventReported (event ids from the
@@ -67,7 +72,17 @@
      * @param r The NotificationRecord. If null, no action is taken.
      * @param visible True if the notification became visible.
      */
-    void logNotificationVisibility(@Nullable NotificationRecord r, boolean visible);
+    default void logNotificationVisibility(@Nullable NotificationRecord r, boolean visible) {
+        log(NotificationEvent.fromVisibility(visible), r);
+    }
+
+    // The UiEventReported logging methods are implemented in terms of this lower-level interface.
+
+    /** Logs a UiEventReported event for the given notification. */
+    void log(UiEventLogger.UiEventEnum event, NotificationRecord r);
+
+    /** Logs a UiEventReported event that is not associated with any notification. */
+    void log(UiEventLogger.UiEventEnum event);
 
     /**
      * The UiEvent enums that this class can log.
@@ -204,7 +219,30 @@
         @UiEvent(doc = "Notification became visible.")
         NOTIFICATION_OPEN(197),
         @UiEvent(doc = "Notification stopped being visible.")
-        NOTIFICATION_CLOSE(198);
+        NOTIFICATION_CLOSE(198),
+        @UiEvent(doc = "Notification was snoozed.")
+        NOTIFICATION_SNOOZED(317),
+        @UiEvent(doc = "Notification was not posted because its app is snoozed.")
+        NOTIFICATION_NOT_POSTED_SNOOZED(319),
+        @UiEvent(doc = "Notification was clicked.")
+        NOTIFICATION_CLICKED(320),
+        @UiEvent(doc = "Notification action was clicked.")
+        NOTIFICATION_ACTION_CLICKED(321),
+        @UiEvent(doc = "Notification detail was expanded due to non-user action.")
+        NOTIFICATION_DETAIL_OPEN_SYSTEM(327),
+        @UiEvent(doc = "Notification detail was collapsed due to non-user action.")
+        NOTIFICATION_DETAIL_CLOSE_SYSTEM(328),
+        @UiEvent(doc = "Notification detail was expanded due to user action.")
+        NOTIFICATION_DETAIL_OPEN_USER(329),
+        @UiEvent(doc = "Notification detail was collapsed due to user action.")
+        NOTIFICATION_DETAIL_CLOSE_USER(330),
+        @UiEvent(doc = "Notification direct reply action was used.")
+        NOTIFICATION_DIRECT_REPLIED(331),
+        @UiEvent(doc = "Notification smart reply action was used.")
+        NOTIFICATION_SMART_REPLIED(332),
+        @UiEvent(doc = "Notification smart reply action was visible.")
+        NOTIFICATION_SMART_REPLY_VISIBLE(333),
+        ;
 
         private final int mId;
         NotificationEvent(int id) {
@@ -217,7 +255,29 @@
         public static NotificationEvent fromVisibility(boolean visible) {
             return visible ? NOTIFICATION_OPEN : NOTIFICATION_CLOSE;
         }
+        public static NotificationEvent fromExpanded(boolean expanded, boolean userAction) {
+            if (userAction) {
+                return expanded ? NOTIFICATION_DETAIL_OPEN_USER : NOTIFICATION_DETAIL_CLOSE_USER;
+            }
+            return expanded ? NOTIFICATION_DETAIL_OPEN_SYSTEM : NOTIFICATION_DETAIL_CLOSE_SYSTEM;
+        }
     }
+
+    enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Notification panel became visible.")
+        NOTIFICATION_PANEL_OPEN(325),
+        @UiEvent(doc = "Notification panel stopped being visible.")
+        NOTIFICATION_PANEL_CLOSE(326);
+
+        private final int mId;
+        NotificationPanelEvent(int id) {
+            mId = id;
+        }
+        @Override public int getId() {
+            return mId;
+        }
+    }
+
     /**
      * A helper for extracting logging information from one or two NotificationRecords.
      */
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index 9fcac25..494ff31 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -65,20 +65,16 @@
     }
 
     @Override
-    public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) {
-        log(NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface), r);
-    }
-
-    @Override
-    public void logNotificationVisibility(NotificationRecord r, boolean visible) {
-        log(NotificationEvent.fromVisibility(visible), r);
-    }
-
-    void log(UiEventLogger.UiEventEnum event, NotificationRecord r) {
+    public void log(UiEventLogger.UiEventEnum event, NotificationRecord r) {
         if (r == null) {
             return;
         }
         mUiEventLogger.logWithInstanceId(event, r.getUid(), r.getSbn().getPackageName(),
                 r.getSbn().getInstanceId());
     }
+
+    @Override
+    public void log(UiEventLogger.UiEventEnum event) {
+        mUiEventLogger.log(event);
+    }
 }
diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java
new file mode 100644
index 0000000..7bbb3b1
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ShortcutHelper.java
@@ -0,0 +1,196 @@
+/*
+ * 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.notification;
+
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
+
+import android.annotation.NonNull;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Helper for querying shortcuts.
+ */
+class ShortcutHelper {
+
+    /**
+     * Listener to call when a shortcut we're tracking has been removed.
+     */
+    interface ShortcutListener {
+        void onShortcutRemoved(String key);
+    }
+
+    private LauncherApps mLauncherAppsService;
+    private ShortcutListener mShortcutListener;
+
+    // Key: packageName Value: <shortcutId, notifId>
+    private HashMap<String, HashMap<String, String>> mActiveShortcutBubbles = new HashMap<>();
+    private boolean mLauncherAppsCallbackRegistered;
+
+    // Bubbles can be created based on a shortcut, we need to listen for changes to
+    // that shortcut so that we may update the bubble appropriately.
+    private final LauncherApps.Callback mLauncherAppsCallback = new LauncherApps.Callback() {
+        @Override
+        public void onPackageRemoved(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackageAdded(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackageChanged(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackagesAvailable(String[] packageNames, UserHandle user,
+                boolean replacing) {
+        }
+
+        @Override
+        public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+                boolean replacing) {
+        }
+
+        @Override
+        public void onShortcutsChanged(@NonNull String packageName,
+                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
+            HashMap<String, String> shortcutBubbles = mActiveShortcutBubbles.get(packageName);
+            ArrayList<String> bubbleKeysToRemove = new ArrayList<>();
+            if (shortcutBubbles != null) {
+                // If we can't find one of our bubbles in the shortcut list, that bubble needs
+                // to be removed.
+                for (String shortcutId : shortcutBubbles.keySet()) {
+                    boolean foundShortcut = false;
+                    for (int i = 0; i < shortcuts.size(); i++) {
+                        if (shortcuts.get(i).getId().equals(shortcutId)) {
+                            foundShortcut = true;
+                            break;
+                        }
+                    }
+                    if (!foundShortcut) {
+                        bubbleKeysToRemove.add(shortcutBubbles.get(shortcutId));
+                    }
+                }
+            }
+
+            // Let NoMan know about the updates
+            for (int i = 0; i < bubbleKeysToRemove.size(); i++) {
+                // update flag bubble
+                String bubbleKey = bubbleKeysToRemove.get(i);
+                if (mShortcutListener != null) {
+                    mShortcutListener.onShortcutRemoved(bubbleKey);
+                }
+            }
+        }
+    };
+
+    ShortcutHelper(LauncherApps launcherApps, ShortcutListener listener) {
+        mLauncherAppsService = launcherApps;
+        mShortcutListener = listener;
+    }
+
+    @VisibleForTesting
+    void setLauncherApps(LauncherApps launcherApps) {
+        mLauncherAppsService = launcherApps;
+    }
+
+    ShortcutInfo getShortcutInfo(String shortcutId, String packageName, UserHandle user) {
+        if (mLauncherAppsService == null) {
+            return null;
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            if (shortcutId == null || packageName == null || user == null) {
+                return null;
+            }
+            LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
+            query.setPackage(packageName);
+            query.setShortcutIds(Arrays.asList(shortcutId));
+            query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_CACHED);
+            List<ShortcutInfo> shortcuts = mLauncherAppsService.getShortcuts(query, user);
+            return shortcuts != null && shortcuts.size() > 0
+                    ? shortcuts.get(0)
+                    : null;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    boolean hasValidShortcutInfo(String shortcutId, String packageName,
+            UserHandle user) {
+        ShortcutInfo shortcutInfo = getShortcutInfo(shortcutId, packageName, user);
+        return shortcutInfo != null && shortcutInfo.isLongLived();
+    }
+
+    /**
+     * Shortcut based bubbles require some extra work to listen for shortcut changes.
+     *
+     * @param r the notification record to check
+     * @param removedNotification true if this notification is being removed
+     * @param handler handler to register the callback with
+     */
+    void maybeListenForShortcutChangesForBubbles(NotificationRecord r, boolean removedNotification,
+            Handler handler) {
+        final String shortcutId = r.getNotification().getBubbleMetadata() != null
+                ? r.getNotification().getBubbleMetadata().getShortcutId()
+                : null;
+        if (shortcutId == null) {
+            return;
+        }
+        if (r.getNotification().isBubbleNotification() && !removedNotification) {
+            // Must track shortcut based bubbles in case the shortcut is removed
+            HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
+                    r.getSbn().getPackageName());
+            if (packageBubbles == null) {
+                packageBubbles = new HashMap<>();
+            }
+            packageBubbles.put(shortcutId, r.getKey());
+            mActiveShortcutBubbles.put(r.getSbn().getPackageName(), packageBubbles);
+            if (!mLauncherAppsCallbackRegistered) {
+                mLauncherAppsService.registerCallback(mLauncherAppsCallback, handler);
+                mLauncherAppsCallbackRegistered = true;
+            }
+        } else {
+            // No longer track shortcut
+            HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
+                    r.getSbn().getPackageName());
+            if (packageBubbles != null) {
+                packageBubbles.remove(shortcutId);
+            }
+            if (packageBubbles != null && packageBubbles.isEmpty()) {
+                mActiveShortcutBubbles.remove(r.getSbn().getPackageName());
+            }
+            if (mLauncherAppsCallbackRegistered && mActiveShortcutBubbles.isEmpty()) {
+                mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
+                mLauncherAppsCallbackRegistered = false;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 6c2d77c..8349632 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -66,7 +66,7 @@
     @RequiresPermission(android.Manifest.permission.DUMP)
     public void startBugreport(int callingUidUnused, String callingPackage,
             FileDescriptor bugreportFd, FileDescriptor screenshotFd,
-            int bugreportMode, IDumpstateListener listener) {
+            int bugreportMode, IDumpstateListener listener, boolean isScreenshotRequested) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "startBugreport");
         Objects.requireNonNull(callingPackage);
         Objects.requireNonNull(bugreportFd);
@@ -88,7 +88,7 @@
         }
         synchronized (mLock) {
             startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd,
-                    bugreportMode, listener);
+                    bugreportMode, listener, isScreenshotRequested);
         }
     }
 
@@ -145,7 +145,7 @@
     @GuardedBy("mLock")
     private void startBugreportLocked(int callingUid, String callingPackage,
             FileDescriptor bugreportFd, FileDescriptor screenshotFd,
-            int bugreportMode, IDumpstateListener listener) {
+            int bugreportMode, IDumpstateListener listener, boolean isScreenshotRequested) {
         if (isDumpstateBinderServiceRunningLocked()) {
             Slog.w(TAG, "'dumpstate' is already running. Cannot start a new bugreport"
                     + " while another one is currently in progress.");
@@ -165,7 +165,7 @@
         IDumpstateListener myListener = new DumpstateListener(listener, ds);
         try {
             ds.startBugreport(callingUid, callingPackage,
-                    bugreportFd, screenshotFd, bugreportMode, myListener);
+                    bugreportFd, screenshotFd, bugreportMode, myListener, isScreenshotRequested);
         } catch (RemoteException e) {
             // bugreportd service is already started now. We need to kill it to manage the
             // lifecycle correctly. If we don't subsequent callers will get
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 483f83e..28d7c13 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.app.AppOpsManager.MODE_DEFAULT;
 import static android.content.pm.DataLoaderType.INCREMENTAL;
 import static android.content.pm.DataLoaderType.STREAMING;
 import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
@@ -166,6 +167,8 @@
     private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission";
     private static final String TAG_WHITELISTED_RESTRICTED_PERMISSION =
             "whitelisted-restricted-permission";
+    private static final String TAG_AUTO_REVOKE_PERMISSIONS_MODE =
+            "auto-revoke-permissions-mode";
     private static final String ATTR_SESSION_ID = "sessionId";
     private static final String ATTR_USER_ID = "userId";
     private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
@@ -311,8 +314,41 @@
     @GuardedBy("mLock")
     private int mParentSessionId;
 
+    static class FileEntry {
+        private final int mIndex;
+        private final InstallationFile mFile;
+
+        FileEntry(int index, InstallationFile file) {
+            this.mIndex = index;
+            this.mFile = file;
+        }
+
+        int getIndex() {
+            return this.mIndex;
+        }
+
+        InstallationFile getFile() {
+            return this.mFile;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof FileEntry)) {
+                return false;
+            }
+            final FileEntry rhs = (FileEntry) obj;
+            return (mFile.getLocation() == rhs.mFile.getLocation()) && TextUtils.equals(
+                    mFile.getName(), rhs.mFile.getName());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mFile.getLocation(), mFile.getName());
+        }
+    }
+
     @GuardedBy("mLock")
-    private ArrayList<InstallationFile> mFiles = new ArrayList<>();
+    private ArraySet<FileEntry> mFiles = new ArraySet<>();
 
     @GuardedBy("mLock")
     private boolean mStagedSessionApplied;
@@ -516,8 +552,12 @@
         this.mParentSessionId = parentSessionId;
 
         if (files != null) {
-            for (InstallationFile file : files) {
-                mFiles.add(file);
+            for (int i = 0, size = files.length; i < size; ++i) {
+                InstallationFile file = files[i];
+                if (!mFiles.add(new FileEntry(i, file))) {
+                    throw new IllegalArgumentException(
+                            "Trying to add a duplicate installation file");
+                }
             }
         }
 
@@ -623,6 +663,7 @@
             }
             info.grantedRuntimePermissions = params.grantedRuntimePermissions;
             info.whitelistedRestrictedPermissions = params.whitelistedRestrictedPermissions;
+            info.autoRevokePermissionsMode = params.autoRevokePermissionsMode;
             info.installFlags = params.installFlags;
             info.isMultiPackage = params.isMultiPackage;
             info.isStaged = params.isStaged;
@@ -750,9 +791,19 @@
             return result;
         }
 
-        String[] result = new String[mFiles.size()];
-        for (int i = 0, size = mFiles.size(); i < size; ++i) {
-            result[i] = mFiles.get(i).getName();
+        InstallationFile[] files = getInstallationFilesLocked();
+        String[] result = new String[files.length];
+        for (int i = 0, size = files.length; i < size; ++i) {
+            result[i] = files[i].getName();
+        }
+        return result;
+    }
+
+    @GuardedBy("mLock")
+    private InstallationFile[] getInstallationFilesLocked() {
+        final InstallationFile[] result = new InstallationFile[mFiles.size()];
+        for (FileEntry fileEntry : mFiles) {
+            result[fileEntry.getIndex()] = fileEntry.getFile();
         }
         return result;
     }
@@ -2444,7 +2495,10 @@
             assertCallerIsOwnerOrRootLocked();
             assertPreparedAndNotSealedLocked("addFile");
 
-            mFiles.add(new InstallationFile(location, name, lengthBytes, metadata, signature));
+            if (!mFiles.add(new FileEntry(mFiles.size(),
+                    new InstallationFile(location, name, lengthBytes, metadata, signature)))) {
+                throw new IllegalArgumentException("File already added: " + name);
+            }
         }
     }
 
@@ -2462,7 +2516,10 @@
             assertCallerIsOwnerOrRootLocked();
             assertPreparedAndNotSealedLocked("removeFile");
 
-            mFiles.add(new InstallationFile(location, getRemoveMarkerName(name), -1, null, null));
+            if (!mFiles.add(new FileEntry(mFiles.size(),
+                    new InstallationFile(location, getRemoveMarkerName(name), -1, null, null)))) {
+                throw new IllegalArgumentException("File already removed: " + name);
+            }
         }
     }
 
@@ -2482,7 +2539,8 @@
         final List<InstallationFileParcel> addedFiles = new ArrayList<>();
         final List<String> removedFiles = new ArrayList<>();
 
-        for (InstallationFile file : mFiles) {
+        final InstallationFile[] files = getInstallationFilesLocked();
+        for (InstallationFile file : files) {
             if (sAddedFilter.accept(new File(this.stageDir, file.getName()))) {
                 addedFiles.add(file.getData());
                 continue;
@@ -2889,6 +2947,13 @@
         }
     }
 
+    private static void writeAutoRevokePermissionsMode(@NonNull XmlSerializer out, int mode)
+            throws IOException {
+        out.startTag(null, TAG_AUTO_REVOKE_PERMISSIONS_MODE);
+        writeIntAttribute(out, ATTR_MODE, mode);
+        out.endTag(null, TAG_AUTO_REVOKE_PERMISSIONS_MODE);
+    }
+
 
     private static File buildAppIconFile(int sessionId, @NonNull File sessionsDir) {
         return new File(sessionsDir, "app_icon." + sessionId + ".png");
@@ -2969,6 +3034,7 @@
             writeGrantedRuntimePermissionsLocked(out, params.grantedRuntimePermissions);
             writeWhitelistedRestrictedPermissionsLocked(out,
                     params.whitelistedRestrictedPermissions);
+            writeAutoRevokePermissionsMode(out, params.autoRevokePermissionsMode);
 
             // Persist app icon if changed since last written
             File appIconFile = buildAppIconFile(sessionId, sessionsDir);
@@ -2995,7 +3061,9 @@
                 writeIntAttribute(out, ATTR_SESSION_ID, childSessionId);
                 out.endTag(null, TAG_CHILD_SESSION);
             }
-            for (InstallationFile file : mFiles) {
+
+            final InstallationFile[] files = getInstallationFilesLocked();
+            for (InstallationFile file : getInstallationFilesLocked()) {
                 out.startTag(null, TAG_SESSION_FILE);
                 writeIntAttribute(out, ATTR_LOCATION, file.getLocation());
                 writeStringAttribute(out, ATTR_NAME, file.getName());
@@ -3112,6 +3180,7 @@
         // depth.
         List<String> grantedRuntimePermissions = new ArrayList<>();
         List<String> whitelistedRestrictedPermissions = new ArrayList<>();
+        int autoRevokePermissionsMode = MODE_DEFAULT;
         List<Integer> childSessionIds = new ArrayList<>();
         List<InstallationFile> files = new ArrayList<>();
         int outerDepth = in.getDepth();
@@ -3128,6 +3197,9 @@
                 whitelistedRestrictedPermissions.add(readStringAttribute(in, ATTR_NAME));
 
             }
+            if (TAG_AUTO_REVOKE_PERMISSIONS_MODE.equals(in.getName())) {
+                autoRevokePermissionsMode = readIntAttribute(in, ATTR_MODE);
+            }
             if (TAG_CHILD_SESSION.equals(in.getName())) {
                 childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID));
             }
@@ -3150,6 +3222,8 @@
             params.whitelistedRestrictedPermissions = whitelistedRestrictedPermissions;
         }
 
+        params.autoRevokePermissionsMode = autoRevokePermissionsMode;
+
         int[] childSessionIdsArray;
         if (childSessionIds.size() > 0) {
             childSessionIdsArray = new int[childSessionIds.size()];
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ca2e240..24ebd32 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -25,6 +25,7 @@
 import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.content.Intent.ACTION_MAIN;
 import static android.content.Intent.CATEGORY_DEFAULT;
@@ -1656,12 +1657,13 @@
                                     && parentRes.pkg != null)
                                 ? parentRes.pkg.getRequestedPermissions()
                                 : args.whitelistedRestrictedPermissions;
+                        int autoRevokePermissionsMode = args.autoRevokePermissionsMode;
 
                         // Handle the parent package
                         handlePackagePostInstall(parentRes, grantPermissions,
                                 killApp, virtualPreload, grantedPermissions,
-                                whitelistedRestrictedPermissions, didRestore,
-                                args.installSource.installerPackageName, args.observer,
+                                whitelistedRestrictedPermissions, autoRevokePermissionsMode,
+                                didRestore, args.installSource.installerPackageName, args.observer,
                                 args.mDataLoaderType);
 
                         // Handle the child packages
@@ -1671,7 +1673,8 @@
                             PackageInstalledInfo childRes = parentRes.addedChildPackages.valueAt(i);
                             handlePackagePostInstall(childRes, grantPermissions,
                                     killApp, virtualPreload, grantedPermissions,
-                                    whitelistedRestrictedPermissions, false /*didRestore*/,
+                                    whitelistedRestrictedPermissions, autoRevokePermissionsMode,
+                                    false /*didRestore*/,
                                     args.installSource.installerPackageName, args.observer,
                                     args.mDataLoaderType);
                         }
@@ -2000,6 +2003,7 @@
     private void handlePackagePostInstall(PackageInstalledInfo res, boolean grantPermissions,
             boolean killApp, boolean virtualPreload,
             String[] grantedPermissions, List<String> whitelistedRestrictedPermissions,
+            int autoRevokePermissionsMode,
             boolean launchedForRestore, String installerPackage,
             IPackageInstallObserver2 installObserver, int dataLoaderType) {
         final boolean succeeded = res.returnCode == PackageManager.INSTALL_SUCCEEDED;
@@ -2020,6 +2024,11 @@
                         Process.myUid(), FLAG_PERMISSION_WHITELIST_INSTALLER);
             }
 
+            if (autoRevokePermissionsMode == MODE_ALLOWED || autoRevokePermissionsMode == MODE_IGNORED) {
+                mPermissionManager.setAutoRevokeWhitelisted(res.pkg.getPackageName(),
+                        autoRevokePermissionsMode == MODE_IGNORED, UserHandle.myUserId());
+            }
+
             // Now that we successfully installed the package, grant runtime
             // permissions if requested before broadcasting the install. Also
             // for legacy apps in permission review mode we clear the permission
@@ -14297,6 +14306,7 @@
         final String packageAbiOverride;
         final String[] grantedRuntimePermissions;
         final List<String> whitelistedRestrictedPermissions;
+        final int autoRevokePermissionsMode;
         final VerificationInfo verificationInfo;
         final PackageParser.SigningDetails signingDetails;
         final int installReason;
@@ -14311,6 +14321,7 @@
                 int installFlags, InstallSource installSource, String volumeUuid,
                 VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride,
                 String[] grantedPermissions, List<String> whitelistedRestrictedPermissions,
+                int autoRevokePermissionsMode,
                 SigningDetails signingDetails, int installReason,
                 long requiredInstalledVersionCode, int dataLoaderType) {
             super(user);
@@ -14324,6 +14335,7 @@
             this.packageAbiOverride = packageAbiOverride;
             this.grantedRuntimePermissions = grantedPermissions;
             this.whitelistedRestrictedPermissions = whitelistedRestrictedPermissions;
+            this.autoRevokePermissionsMode = autoRevokePermissionsMode;
             this.signingDetails = signingDetails;
             this.installReason = installReason;
             this.requiredInstalledVersionCode = requiredInstalledVersionCode;
@@ -14360,6 +14372,7 @@
             packageAbiOverride = sessionParams.abiOverride;
             grantedRuntimePermissions = sessionParams.grantedRuntimePermissions;
             whitelistedRestrictedPermissions = sessionParams.whitelistedRestrictedPermissions;
+            autoRevokePermissionsMode = sessionParams.autoRevokePermissionsMode;
             signingDetails = activeInstallSession.getSigningDetails();
             requiredInstalledVersionCode = sessionParams.requiredInstalledVersionCode;
             forceQueryableOverride = sessionParams.forceQueryableOverride;
@@ -14728,9 +14741,8 @@
             verificationState.setRequiredVerifierUid(requiredUid);
             final int installerUid =
                     verificationInfo == null ? -1 : verificationInfo.installerUid;
-            if (!origin.existing && requiredUid != -1
-                    && isVerificationEnabled(
-                            pkgLite, verifierUser.getIdentifier(), installFlags, installerUid)) {
+            if (!origin.existing && isVerificationEnabled(pkgLite, verifierUser.getIdentifier(),
+                      installFlags, installerUid)) {
                 final Intent verification = new Intent(
                         Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
                 verification.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -14796,9 +14808,9 @@
                     }
                 }
 
-                final ComponentName requiredVerifierComponent = matchComponentForVerifier(
-                        mRequiredVerifierPackage, receivers);
                 if (mRequiredVerifierPackage != null) {
+                    final ComponentName requiredVerifierComponent = matchComponentForVerifier(
+                            mRequiredVerifierPackage, receivers);
                     /*
                      * Send the intent to the required verification agent,
                      * but only start the verification timeout after the
@@ -14956,6 +14968,7 @@
         final String abiOverride;
         final String[] installGrantPermissions;
         final List<String> whitelistedRestrictedPermissions;
+        final int autoRevokePermissionsMode;
         /** If non-null, drop an async trace when the install completes */
         final String traceMethod;
         final int traceCookie;
@@ -14975,6 +14988,7 @@
                 UserHandle user, String[] instructionSets,
                 String abiOverride, String[] installGrantPermissions,
                 List<String> whitelistedRestrictedPermissions,
+                int autoRevokePermissionsMode,
                 String traceMethod, int traceCookie, SigningDetails signingDetails,
                 int installReason, boolean forceQueryableOverride,
                 MultiPackageInstallParams multiPackageInstallParams, int dataLoaderType) {
@@ -14989,6 +15003,7 @@
             this.abiOverride = abiOverride;
             this.installGrantPermissions = installGrantPermissions;
             this.whitelistedRestrictedPermissions = whitelistedRestrictedPermissions;
+            this.autoRevokePermissionsMode = autoRevokePermissionsMode;
             this.traceMethod = traceMethod;
             this.traceCookie = traceCookie;
             this.signingDetails = signingDetails;
@@ -15004,6 +15019,7 @@
                     params.installSource, params.volumeUuid,
                     params.getUser(), null /*instructionSets*/, params.packageAbiOverride,
                     params.grantedRuntimePermissions, params.whitelistedRestrictedPermissions,
+                    params.autoRevokePermissionsMode,
                     params.traceMethod, params.traceCookie, params.signingDetails,
                     params.installReason, params.forceQueryableOverride,
                     params.mParentInstallParams, params.mDataLoaderType);
@@ -15095,7 +15111,7 @@
         /** Existing install */
         FileInstallArgs(String codePath, String resourcePath, String[] instructionSets) {
             super(OriginInfo.fromNothing(), null, null, 0, InstallSource.EMPTY,
-                    null, null, instructionSets, null, null, null, null, 0,
+                    null, null, instructionSets, null, null, null, MODE_DEFAULT, null, 0,
                     PackageParser.SigningDetails.UNKNOWN,
                     PackageManager.INSTALL_REASON_UNKNOWN, false, null /* parent */,
                     DataLoaderType.NONE);
@@ -22471,7 +22487,8 @@
         final InstallParams params = new InstallParams(origin, move, installObserver, installFlags,
                 installSource, volumeUuid, null /*verificationInfo*/, user,
                 packageAbiOverride, null /*grantedPermissions*/,
-                null /*whitelistedRestrictedPermissions*/, PackageParser.SigningDetails.UNKNOWN,
+                null /*whitelistedRestrictedPermissions*/, MODE_DEFAULT /* autoRevokePermissions */,
+                PackageParser.SigningDetails.UNKNOWN,
                 PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.VERSION_CODE_HIGHEST,
                 DataLoaderType.NONE);
         params.setTraceMethod("movePackage").setTraceCookie(System.identityHashCode(params));
@@ -24175,6 +24192,12 @@
             }
             mAppsFilter.getFeatureConfig().enableLogging(pkg.appId, enable);
         }
+
+        @Override
+        public boolean isSystemPackage(@NonNull String packageName) {
+            return packageName.equals(
+                    PackageManagerService.this.ensureSystemPackageName(packageName));
+        }
     }
 
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index be17dd8..8a9f1b3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3017,9 +3017,11 @@
 
     private int doAddFiles(int sessionId, ArrayList<String> args, long sessionSizeBytes,
             boolean isApex) throws RemoteException {
-        PackageInstaller.Session session = new PackageInstaller.Session(
-                mInterface.getPackageInstaller().openSession(sessionId));
+        PackageInstaller.Session session = null;
         try {
+            session = new PackageInstaller.Session(
+                    mInterface.getPackageInstaller().openSession(sessionId));
+
             // 1. Single file from stdin.
             if (args.isEmpty() || STDIN_PATH.equals(args.get(0))) {
                 final String name = "base." + (isApex ? "apex" : "apk");
@@ -3043,6 +3045,10 @@
                 }
             }
             return 0;
+        } catch (IllegalArgumentException e) {
+            getErrPrintWriter().println("Failed to add file(s), reason: " + e);
+            getOutPrintWriter().println("Failure [failed to add file(s)]");
+            return 1;
         } finally {
             IoUtils.closeQuietly(session);
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index df3c83a..1c2fcc1 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1300,14 +1300,15 @@
 
     @Override
     public boolean hasBadge(@UserIdInt int userId) {
-        checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "hasBadge");
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "hasBadge");
         final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
         return userTypeDetails != null && userTypeDetails.hasBadge();
     }
 
     @Override
     public @StringRes int getUserBadgeLabelResId(@UserIdInt int userId) {
-        checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserBadgeLabelResId");
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
+                "getUserBadgeLabelResId");
         final UserInfo userInfo = getUserInfoNoChecks(userId);
         final UserTypeDetails userTypeDetails = getUserTypeDetails(userInfo);
         if (userInfo == null || userTypeDetails == null || !userTypeDetails.hasBadge()) {
@@ -1320,7 +1321,8 @@
 
     @Override
     public @ColorRes int getUserBadgeColorResId(@UserIdInt int userId) {
-        checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserBadgeColorResId");
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
+                "getUserBadgeColorResId");
         final UserInfo userInfo = getUserInfoNoChecks(userId);
         final UserTypeDetails userTypeDetails = getUserTypeDetails(userInfo);
         if (userInfo == null || userTypeDetails == null || !userTypeDetails.hasBadge()) {
@@ -1333,7 +1335,7 @@
 
     @Override
     public @DrawableRes int getUserIconBadgeResId(@UserIdInt int userId) {
-        checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserIconBadgeResId");
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserIconBadgeResId");
         final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
         if (userTypeDetails == null || !userTypeDetails.hasBadge()) {
             Slog.e(LOG_TAG, "Requested icon badge for non-badged user " + userId);
@@ -1344,7 +1346,7 @@
 
     @Override
     public @DrawableRes int getUserBadgeResId(@UserIdInt int userId) {
-        checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserBadgeResId");
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserBadgeResId");
         final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
         if (userTypeDetails == null || !userTypeDetails.hasBadge()) {
             Slog.e(LOG_TAG, "Requested badge for non-badged user " + userId);
@@ -1355,7 +1357,7 @@
 
     @Override
     public @DrawableRes int getUserBadgeNoBackgroundResId(@UserIdInt int userId) {
-        checkManageOrInteractPermIfCallerInOtherProfileGroup(userId,
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
                 "getUserBadgeNoBackgroundResId");
         final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
         if (userTypeDetails == null || !userTypeDetails.hasBadge()) {
@@ -1367,7 +1369,7 @@
 
     @Override
     public boolean isProfile(@UserIdInt int userId) {
-        checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isProfile");
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isProfile");
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
             return userInfo != null && userInfo.isProfile();
@@ -1376,7 +1378,7 @@
 
     @Override
     public boolean isManagedProfile(@UserIdInt int userId) {
-        checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isManagedProfile");
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isManagedProfile");
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
             return userInfo != null && userInfo.isManagedProfile();
@@ -1385,19 +1387,20 @@
 
     @Override
     public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
-        checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserUnlockingOrUnlocked");
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
+                "isUserUnlockingOrUnlocked");
         return mLocalService.isUserUnlockingOrUnlocked(userId);
     }
 
     @Override
     public boolean isUserUnlocked(@UserIdInt int userId) {
-        checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserUnlocked");
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isUserUnlocked");
         return mLocalService.isUserUnlocked(userId);
     }
 
     @Override
     public boolean isUserRunning(@UserIdInt int userId) {
-        checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserRunning");
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isUserRunning");
         return mLocalService.isUserRunning(userId);
     }
 
@@ -1437,7 +1440,7 @@
         }
     }
 
-    private void checkManageOrInteractPermIfCallerInOtherProfileGroup(@UserIdInt int userId,
+    private void checkManageOrInteractPermissionIfCallerInOtherProfileGroup(@UserIdInt int userId,
             String name) {
         final int callingUserId = UserHandle.getCallingUserId();
         if (callingUserId == userId || isSameProfileGroupNoChecks(callingUserId, userId) ||
@@ -1466,11 +1469,7 @@
 
     @Override
     public boolean isPreCreated(@UserIdInt int userId) {
-        final int callingUserId = UserHandle.getCallingUserId();
-        if (callingUserId != userId && !hasManageUsersPermission()) {
-            throw new SecurityException("You need MANAGE_USERS permission to query if u=" + userId
-                    + " is pre-created");
-        }
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isPreCreated");
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
             return userInfo != null && userInfo.preCreated;
@@ -1826,7 +1825,7 @@
     /** @return a specific user restriction that's in effect currently. */
     @Override
     public boolean hasUserRestriction(String restrictionKey, @UserIdInt int userId) {
-        checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "hasUserRestriction");
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "hasUserRestriction");
         return mLocalService.hasUserRestriction(restrictionKey, userId);
     }
 
@@ -1951,7 +1950,7 @@
      */
     @Override
     public Bundle getUserRestrictions(@UserIdInt int userId) {
-        checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserRestrictions");
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserRestrictions");
         return UserRestrictionsUtils.clone(getEffectiveUserRestrictions(userId));
     }
 
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 5141191..46f121d 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -19,6 +19,8 @@
 import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY;
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
@@ -53,6 +55,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.AppOpsManager;
 import android.app.ApplicationPackageManager;
 import android.app.IActivityManager;
 import android.app.admin.DeviceAdminInfo;
@@ -217,6 +220,9 @@
     /** Default permission policy to provide proper behaviour out-of-the-box */
     private final DefaultPermissionGrantPolicy mDefaultPermissionGrantPolicy;
 
+    /** App ops manager */
+    private final AppOpsManager mAppOpsManager;
+
     /**
      * Built-in permissions. Read from system configuration files. Mapping is from
      * UID to permission name.
@@ -356,6 +362,7 @@
         mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
         mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
         mSettings = new PermissionSettings(mLock);
+        mAppOpsManager = context.getSystemService(AppOpsManager.class);
 
         mHandlerThread = new ServiceThread(TAG,
                 Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
@@ -1199,6 +1206,77 @@
     }
 
     @Override
+    public boolean setAutoRevokeWhitelisted(
+            @NonNull String packageName, boolean whitelisted, int userId) {
+        Objects.requireNonNull(packageName);
+
+        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
+        final int callingUid = Binder.getCallingUid();
+
+        if (!checkAutoRevokeAccess(pkg, callingUid)) {
+            return false;
+        }
+
+        if (mAppOpsManager
+                .checkOpNoThrow(AppOpsManager.OP_AUTO_REVOKE_MANAGED_BY_INSTALLER,
+                        callingUid, packageName)
+                != MODE_ALLOWED) {
+            // Whitelist user set - don't override
+            return false;
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mAppOpsManager.setMode(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
+                    callingUid, packageName,
+                    whitelisted ? MODE_IGNORED : MODE_ALLOWED);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return true;
+    }
+
+    private boolean checkAutoRevokeAccess(AndroidPackage pkg, int callingUid) {
+        if (pkg == null) {
+            return false;
+        }
+
+        final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission(
+                Manifest.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS)
+                == PackageManager.PERMISSION_GRANTED;
+        final boolean isCallerInstallerOnRecord =
+                mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);
+
+        if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
+            throw new SecurityException("Caller must either hold "
+                    + Manifest.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS
+                    + " or be the installer on record");
+        }
+        return true;
+    }
+
+    @Override
+    public boolean isAutoRevokeWhitelisted(@NonNull String packageName, int userId) {
+        Objects.requireNonNull(packageName);
+
+        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
+        final int callingUid = Binder.getCallingUid();
+
+        if (!checkAutoRevokeAccess(pkg, callingUid)) {
+            return false;
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return mAppOpsManager.checkOpNoThrow(
+                    AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, callingUid, packageName)
+                    == MODE_IGNORED;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
     public void grantRuntimePermission(String packageName, String permName, final int userId) {
         final int callingUid = Binder.getCallingUid();
         final boolean overridePolicy =
@@ -4377,6 +4455,12 @@
                     packageName, permissions, flags, userId);
         }
         @Override
+        public void setAutoRevokeWhitelisted(
+                @NonNull String packageName, boolean whitelisted, int userId) {
+            PermissionManagerService.this.setAutoRevokeWhitelisted(
+                    packageName, whitelisted, userId);
+        }
+        @Override
         public void updatePermissions(@NonNull String packageName, @Nullable AndroidPackage pkg) {
             PermissionManagerService.this
                     .updatePermissions(packageName, pkg, mDefaultPermissionCallback);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 32ef2ce..356d0ab 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -189,7 +189,9 @@
     /** Sets the whitelisted, restricted permissions for the given package. */
     public abstract void setWhitelistedRestrictedPermissions(
             @NonNull String packageName, @NonNull List<String> permissions,
-            @PackageManager.PermissionWhitelistFlags int flags, @NonNull int userId);
+            @PackageManager.PermissionWhitelistFlags int flags, int userId);
+    public abstract void setAutoRevokeWhitelisted(
+            @NonNull String packageName, boolean whitelisted, int userId);
 
     /**
      * Update permissions when a package changed.
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 64edacd..1e12565 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2139,8 +2139,10 @@
         }
 
         // check if user has enabled this operation. SecurityException will be thrown if this app
-        // has not been allowed by the user
-        final int mode = mAppOpsManager.noteOpNoThrow(outAppOp[0], callingUid, packageName);
+        // has not been allowed by the user. The reason to use "noteOp" (instead of checkOp) is to
+        // make sure the usage is logged.
+        final int mode = mAppOpsManager.noteOpNoThrow(outAppOp[0], callingUid, packageName,
+                null /* featureId */, "check-add");
         switch (mode) {
             case AppOpsManager.MODE_ALLOWED:
             case AppOpsManager.MODE_IGNORED:
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index beba106..8b6e9a4 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -16,11 +16,13 @@
 package com.android.server.power.batterysaver;
 
 import android.Manifest;
+import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManagerInternal;
 import android.hardware.power.V1_0.PowerHint;
 import android.os.BatteryManager;
 import android.os.BatterySaverPolicyConfig;
@@ -35,6 +37,7 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -49,6 +52,7 @@
 
 import java.util.ArrayList;
 import java.util.Objects;
+import java.util.Optional;
 
 /**
  * Responsible for battery saver mode transition logic.
@@ -112,6 +116,14 @@
      */
     private final Plugin[] mPlugins;
 
+    /**
+     * Package name that will receive an explicit manifest broadcast for
+     * {@link PowerManager#ACTION_POWER_SAVE_MODE_CHANGED}. It's {@code null} if it hasn't been
+     * retrieved yet.
+     */
+    @Nullable
+    private Optional<String> mPowerSaveModeChangedListenerPackage;
+
     public static final int REASON_PERCENTAGE_AUTOMATIC_ON = 0;
     public static final int REASON_PERCENTAGE_AUTOMATIC_OFF = 1;
     public static final int REASON_MANUAL_ON = 2;
@@ -494,6 +506,14 @@
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
 
+            // Send the broadcast to a manifest-registered receiver that is specified in the config.
+            if (getPowerSaveModeChangedListenerPackage().isPresent()) {
+                intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)
+                        .setPackage(getPowerSaveModeChangedListenerPackage().get())
+                        .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            }
+
             // Send internal version that requires signature permission.
             intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -508,6 +528,20 @@
         }
     }
 
+    private Optional<String> getPowerSaveModeChangedListenerPackage() {
+        if (mPowerSaveModeChangedListenerPackage == null) {
+            String configPowerSaveModeChangedListenerPackage =
+                    mContext.getString(R.string.config_powerSaveModeChangedListenerPackage);
+            mPowerSaveModeChangedListenerPackage =
+                    LocalServices
+                            .getService(PackageManagerInternal.class)
+                            .isSystemPackage(configPowerSaveModeChangedListenerPackage)
+                            ? Optional.of(configPowerSaveModeChangedListenerPackage)
+                            : Optional.empty();
+        }
+        return mPowerSaveModeChangedListenerPackage;
+    }
+
     private void updateBatterySavingStats() {
         final PowerManager pm = getPowerManager();
         if (pm == null) {
diff --git a/services/core/java/com/android/server/security/TEST_MAPPING b/services/core/java/com/android/server/security/TEST_MAPPING
new file mode 100644
index 0000000..9a5e90e
--- /dev/null
+++ b/services/core/java/com/android/server/security/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+  "presubmit": [
+    {
+      "name": "ApkVerityTest",
+      "file_patterns": ["VerityUtils\\.java"]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java
index 856a40f..2b793c8 100644
--- a/services/core/java/com/android/server/security/VerityUtils.java
+++ b/services/core/java/com/android/server/security/VerityUtils.java
@@ -81,22 +81,18 @@
 
     /** Returns whether the file has fs-verity enabled. */
     public static boolean hasFsverity(@NonNull String filePath) {
-        // NB: only measure but not check the actual measurement here. As long as this succeeds,
-        // the file is on readable if the measurement can be verified against a trusted key, and
-        // this is good enough for installed apps.
-        int errno = measureFsverityNative(filePath);
-        if (errno != 0) {
-            if (errno != OsConstants.ENODATA) {
-                Slog.e(TAG, "Failed to measure fs-verity, errno " + errno + ": " + filePath);
-            }
+        int retval = statxForFsverityNative(filePath);
+        if (retval < 0) {
+            Slog.e(TAG, "Failed to check whether fs-verity is enabled, errno " + -retval + ": "
+                    + filePath);
             return false;
         }
-        return true;
+        return (retval == 1);
     }
 
     private static native int enableFsverityNative(@NonNull String filePath,
             @NonNull byte[] pkcs7Signature);
-    private static native int measureFsverityNative(@NonNull String filePath);
+    private static native int statxForFsverityNative(@NonNull String filePath);
 
     /**
      * Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped.
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 7f7d668..fd275d8 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -2888,6 +2888,44 @@
             HistoricalOps histOps = ops.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS,
                     TimeUnit.MILLISECONDS);
             processHistoricalOps(histOps, atomTag, pulledData);
+
+            for (int uidIdx = 0; uidIdx < histOps.getUidCount(); uidIdx++) {
+                final HistoricalUidOps uidOps = histOps.getUidOpsAt(uidIdx);
+                final int uid = uidOps.getUid();
+                for (int pkgIdx = 0; pkgIdx < uidOps.getPackageCount(); pkgIdx++) {
+                    final HistoricalPackageOps packageOps = uidOps.getPackageOpsAt(pkgIdx);
+                    for (int opIdx = 0; opIdx < packageOps.getOpCount(); opIdx++) {
+                        final AppOpsManager.HistoricalOp op = packageOps.getOpAt(opIdx);
+
+                        StatsEvent.Builder e = StatsEvent.newBuilder();
+                        e.setAtomId(atomTag);
+                        e.writeInt(uid);
+                        e.writeString(packageOps.getPackageName());
+                        e.writeInt(op.getLoggingOpCode());
+                        e.writeLong(op.getForegroundAccessCount(OP_FLAGS_PULLED));
+                        e.writeLong(op.getBackgroundAccessCount(OP_FLAGS_PULLED));
+                        e.writeLong(op.getForegroundRejectCount(OP_FLAGS_PULLED));
+                        e.writeLong(op.getBackgroundRejectCount(OP_FLAGS_PULLED));
+                        e.writeLong(op.getForegroundAccessDuration(OP_FLAGS_PULLED));
+                        e.writeLong(op.getBackgroundAccessDuration(OP_FLAGS_PULLED));
+
+                        String perm = AppOpsManager.opToPermission(op.getOpCode());
+                        if (perm == null) {
+                            e.writeBoolean(false);
+                        } else {
+                            PermissionInfo permInfo;
+                            try {
+                                permInfo = mContext.getPackageManager().getPermissionInfo(perm, 0);
+                                e.writeBoolean(permInfo.getProtection() == PROTECTION_DANGEROUS);
+                            } catch (PackageManager.NameNotFoundException exception) {
+                                e.writeBoolean(false);
+                            }
+                        }
+
+                        pulledData.add(e.build());
+                    }
+                }
+            }
         } catch (Throwable t) {
             // TODO: catch exceptions at a more granular level
             Slog.e(TAG, "Could not read appops", t);
@@ -3006,11 +3044,7 @@
         if (atomTag == FrameworkStatsLog.ATTRIBUTED_APP_OPS) {
             e.writeString(attributionTag);
         }
-        if (atomTag == FrameworkStatsLog.ATTRIBUTED_APP_OPS) {
-            e.writeString(op.getOpName());
-        } else {
-            e.writeInt(op.getOpCode());
-        }
+        e.writeInt(op.getLoggingOpCode());
         e.writeLong(op.getForegroundAccessCount(OP_FLAGS_PULLED));
         e.writeLong(op.getBackgroundAccessCount(OP_FLAGS_PULLED));
         e.writeLong(op.getForegroundRejectCount(OP_FLAGS_PULLED));
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index b43d8b7..bd63b2d 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.tv.tunerresourcemanager;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -40,6 +41,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.SystemService;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -70,9 +73,31 @@
     private TvInputManager mManager;
     private UseCasePriorityHints mPriorityCongfig = new UseCasePriorityHints();
 
+    // An internal resource request count to help generate resource handle.
+    private int mResourceRequestCount = 0;
+
     // Used to synchronize the access to the service.
     private final Object mLock = new Object();
 
+    /**
+     * Tuner resource type to help generate resource handle
+     */
+    @IntDef({
+        TUNER_RESOURCE_TYPE_FRONTEND,
+        TUNER_RESOURCE_TYPE_DEMUX,
+        TUNER_RESOURCE_TYPE_DESCRAMBLER,
+        TUNER_RESOURCE_TYPE_LNB,
+        TUNER_RESOURCE_TYPE_CAS_SESSION,
+     })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TunerResourceType {}
+
+    public static final int TUNER_RESOURCE_TYPE_FRONTEND = 0;
+    public static final int TUNER_RESOURCE_TYPE_DEMUX = 1;
+    public static final int TUNER_RESOURCE_TYPE_DESCRAMBLER = 2;
+    public static final int TUNER_RESOURCE_TYPE_LNB = 3;
+    public static final int TUNER_RESOURCE_TYPE_CAS_SESSION = 4;
+
     public TunerResourceManagerService(@Nullable Context context) {
         super(context);
     }
@@ -96,7 +121,7 @@
         public void registerClientProfile(@NonNull ResourceClientProfile profile,
                 @NonNull IResourcesReclaimListener listener, @NonNull int[] clientId)
                 throws RemoteException {
-            enforceAccessPermission();
+            enforceTrmAccessPermission("registerClientProfile");
             if (profile == null) {
                 throw new RemoteException("ResourceClientProfile can't be null");
             }
@@ -120,7 +145,7 @@
 
         @Override
         public void unregisterClientProfile(int clientId) throws RemoteException {
-            enforceAccessPermission();
+            enforceTrmAccessPermission("unregisterClientProfile");
             synchronized (mLock) {
                 if (!checkClientExists(clientId)) {
                     Slog.e(TAG, "Unregistering non exists client:" + clientId);
@@ -132,7 +157,7 @@
 
         @Override
         public boolean updateClientPriority(int clientId, int priority, int niceValue) {
-            enforceAccessPermission();
+            enforceTrmAccessPermission("updateClientPriority");
             synchronized (mLock) {
                 return updateClientPriorityInternal(clientId, priority, niceValue);
             }
@@ -140,7 +165,7 @@
 
         @Override
         public void setFrontendInfoList(@NonNull TunerFrontendInfo[] infos) throws RemoteException {
-            enforceAccessPermission();
+            enforceTrmAccessPermission("setFrontendInfoList");
             if (infos == null) {
                 throw new RemoteException("TunerFrontendInfo can't be null");
             }
@@ -151,6 +176,7 @@
 
         @Override
         public void updateCasInfo(int casSystemId, int maxSessionNum) {
+            enforceTrmAccessPermission("updateCasInfo");
             if (DEBUG) {
                 Slog.d(TAG,
                         "updateCasInfo(casSystemId=" + casSystemId
@@ -160,6 +186,7 @@
 
         @Override
         public void setLnbInfoList(int[] lnbIds) {
+            enforceTrmAccessPermission("setLnbInfoList");
             if (DEBUG) {
                 for (int i = 0; i < lnbIds.length; i++) {
                     Slog.d(TAG, "updateLnbInfo(lnbId=" + lnbIds[i] + ")");
@@ -169,14 +196,15 @@
 
         @Override
         public boolean requestFrontend(@NonNull TunerFrontendRequest request,
-                @NonNull int[] frontendId) throws RemoteException {
-            enforceAccessPermission();
-            if (frontendId == null) {
-                throw new RemoteException("frontendId can't be null");
+                @NonNull int[] frontendHandle) throws RemoteException {
+            enforceTunerAccessPermission("requestFrontend");
+            enforceTrmAccessPermission("requestFrontend");
+            if (frontendHandle == null) {
+                throw new RemoteException("frontendHandle can't be null");
             }
             synchronized (mLock) {
                 try {
-                    return requestFrontendInternal(request, frontendId);
+                    return requestFrontendInternal(request, frontendHandle);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
@@ -185,6 +213,8 @@
 
         @Override
         public void shareFrontend(int selfClientId, int targetClientId) {
+            enforceTunerAccessPermission("shareFrontend");
+            enforceTrmAccessPermission("shareFrontend");
             if (DEBUG) {
                 Slog.d(TAG, "shareFrontend from " + selfClientId + " with " + targetClientId);
             }
@@ -192,25 +222,34 @@
 
         @Override
         public boolean requestDemux(@NonNull TunerDemuxRequest request,
-                    @NonNull int[] demuxHandle) {
-            if (DEBUG) {
-                Slog.d(TAG, "requestDemux(request=" + request + ")");
+                    @NonNull int[] demuxHandle)  throws RemoteException {
+            enforceTunerAccessPermission("requestDemux");
+            enforceTrmAccessPermission("requestDemux");
+            if (demuxHandle == null) {
+                throw new RemoteException("demuxHandle can't be null");
             }
-            return true;
+            synchronized (mLock) {
+                return requestDemuxInternal(request, demuxHandle);
+            }
         }
 
         @Override
         public boolean requestDescrambler(@NonNull TunerDescramblerRequest request,
-                    @NonNull int[] descrambleHandle) {
-            if (DEBUG) {
-                Slog.d(TAG, "requestDescrambler(request=" + request + ")");
+                    @NonNull int[] descrambleHandle) throws RemoteException {
+            enforceDescramblerAccessPermission("requestDescrambler");
+            enforceTrmAccessPermission("requestDescrambler");
+            if (descrambleHandle == null) {
+                throw new RemoteException("descrambleHandle can't be null");
             }
-            return true;
+            synchronized (mLock) {
+                return requestDescramblerInternal(request, descrambleHandle);
+            }
         }
 
         @Override
         public boolean requestCasSession(
-                @NonNull CasSessionRequest request, @NonNull int[] sessionResourceId) {
+                @NonNull CasSessionRequest request, @NonNull int[] sessionResourceHandle) {
+            enforceTrmAccessPermission("requestCasSession");
             if (DEBUG) {
                 Slog.d(TAG, "requestCasSession(request=" + request + ")");
             }
@@ -219,7 +258,9 @@
         }
 
         @Override
-        public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbId) {
+        public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbHandle) {
+            enforceTunerAccessPermission("requestLnb");
+            enforceTrmAccessPermission("requestLnb");
             if (DEBUG) {
                 Slog.d(TAG, "requestLnb(request=" + request + ")");
             }
@@ -228,6 +269,8 @@
 
         @Override
         public void releaseFrontend(int frontendId) {
+            enforceTunerAccessPermission("releaseFrontend");
+            enforceTrmAccessPermission("releaseFrontend");
             if (DEBUG) {
                 Slog.d(TAG, "releaseFrontend(id=" + frontendId + ")");
             }
@@ -235,6 +278,8 @@
 
         @Override
         public void releaseDemux(int demuxHandle) {
+            enforceTunerAccessPermission("releaseDemux");
+            enforceTrmAccessPermission("releaseDemux");
             if (DEBUG) {
                 Slog.d(TAG, "releaseDemux(demuxHandle=" + demuxHandle + ")");
             }
@@ -242,6 +287,8 @@
 
         @Override
         public void releaseDescrambler(int descramblerHandle) {
+            enforceTunerAccessPermission("releaseDescrambler");
+            enforceTrmAccessPermission("releaseDescrambler");
             if (DEBUG) {
                 Slog.d(TAG, "releaseDescrambler(descramblerHandle=" + descramblerHandle + ")");
             }
@@ -249,6 +296,7 @@
 
         @Override
         public void releaseCasSession(int sessionResourceId) {
+            enforceTrmAccessPermission("releaseCasSession");
             if (DEBUG) {
                 Slog.d(TAG, "releaseCasSession(sessionResourceId=" + sessionResourceId + ")");
             }
@@ -256,6 +304,8 @@
 
         @Override
         public void releaseLnb(int lnbId) {
+            enforceTunerAccessPermission("releaseLnb");
+            enforceTrmAccessPermission("releaseLnb");
             if (DEBUG) {
                 Slog.d(TAG, "releaseLnb(lnbId=" + lnbId + ")");
             }
@@ -264,6 +314,7 @@
         @Override
         public boolean isHigherPriority(
                 ResourceClientProfile challengerProfile, ResourceClientProfile holderProfile) {
+            enforceTrmAccessPermission("isHigherPriority");
             if (DEBUG) {
                 Slog.d(TAG,
                         "isHigherPriority(challengerProfile=" + challengerProfile
@@ -371,13 +422,13 @@
     }
 
     @VisibleForTesting
-    protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendId)
+    protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle)
             throws RemoteException {
         if (DEBUG) {
             Slog.d(TAG, "requestFrontend(request=" + request + ")");
         }
 
-        frontendId[0] = TunerResourceManager.INVALID_FRONTEND_ID;
+        frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
         if (!checkClientExists(request.getClientId())) {
             Slog.e(TAG, "Request frontend from unregistered client:" + request.getClientId());
             return false;
@@ -413,17 +464,20 @@
 
         // Grant frontend when there is unused resource.
         if (grantingFrontendId > -1) {
-            frontendId[0] = grantingFrontendId;
-            updateFrontendClientMappingOnNewGrant(frontendId[0], request.getClientId());
+            frontendHandle[0] = generateResourceHandle(
+                    TUNER_RESOURCE_TYPE_FRONTEND, grantingFrontendId);
+            updateFrontendClientMappingOnNewGrant(grantingFrontendId, request.getClientId());
             return true;
         }
 
         // When all the resources are occupied, grant the lowest priority resource if the
         // request client has higher priority.
         if (inUseLowestPriorityFrId > -1 && (requestClient.getPriority() > currentLowestPriority)) {
-            frontendId[0] = inUseLowestPriorityFrId;
-            reclaimFrontendResource(getFrontendResource(frontendId[0]).getOwnerClientId());
-            updateFrontendClientMappingOnNewGrant(frontendId[0], request.getClientId());
+            frontendHandle[0] = generateResourceHandle(
+                    TUNER_RESOURCE_TYPE_FRONTEND, inUseLowestPriorityFrId);
+            reclaimFrontendResource(getFrontendResource(
+                    inUseLowestPriorityFrId).getOwnerClientId());
+            updateFrontendClientMappingOnNewGrant(inUseLowestPriorityFrId, request.getClientId());
             return true;
         }
 
@@ -431,6 +485,24 @@
     }
 
     @VisibleForTesting
+    boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) {
+        if (DEBUG) {
+            Slog.d(TAG, "requestDemux(request=" + request + ")");
+        }
+        demuxHandle[0] = generateResourceHandle(TUNER_RESOURCE_TYPE_DEMUX, 0);
+        return true;
+    }
+
+    @VisibleForTesting
+    boolean requestDescramblerInternal(TunerDescramblerRequest request, int[] descramblerHandle) {
+        if (DEBUG) {
+            Slog.d(TAG, "requestDescrambler(request=" + request + ")");
+        }
+        descramblerHandle[0] = generateResourceHandle(TUNER_RESOURCE_TYPE_DESCRAMBLER, 0);
+        return true;
+    }
+
+    @VisibleForTesting
     protected class ResourcesReclaimListenerRecord implements IBinder.DeathRecipient {
         private final IResourcesReclaimListener mListener;
         private final int mClientId;
@@ -592,8 +664,24 @@
         return mClientProfiles.keySet().contains(clientId);
     }
 
-    private void enforceAccessPermission() {
-        getContext().enforceCallingOrSelfPermission(
-                "android.permission.TUNER_RESOURCE_ACCESS", TAG);
+    private int generateResourceHandle(@TunerResourceType int resourceType, int resourceId) {
+        return (resourceType & 0x000000ff) << 24
+                | (resourceId << 16)
+                | (mResourceRequestCount++ & 0xffff);
+    }
+
+    private void enforceTrmAccessPermission(String apiName) {
+        getContext().enforceCallingPermission("android.permission.TUNER_RESOURCE_ACCESS",
+                TAG + ": " + "apiName");
+    }
+
+    private void enforceTunerAccessPermission(String apiName) {
+        getContext().enforceCallingPermission("android.Manifest.permission.ACCESS_TV_TUNER",
+                TAG + ": " + "apiName");
+    }
+
+    private void enforceDescramblerAccessPermission(String apiName) {
+        getContext().enforceCallingPermission("android.Manifest.permission.ACCESS_TV_DESCRAMBLER",
+                TAG + ": " + "apiName");
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e73c928..c4545fa 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1351,11 +1351,16 @@
                 mLetterbox.attachInput(w);
             }
             getPosition(mTmpPoint);
-            // Get the bounds of the "space-to-fill". In multi-window mode, the task-level
-            // represents this. In fullscreen-mode, the stack does (since the orientation letterbox
-            // is also applied to the task).
-            Rect spaceToFill = (inMultiWindowMode() || getStack() == null)
-                    ? task.getDisplayedBounds() : getStack().getDisplayedBounds();
+            // Get the bounds of the "space-to-fill". The transformed bounds have the highest
+            // priority because the activity is launched in a rotated environment. In multi-window
+            // mode, the task-level represents this. In fullscreen-mode, the task container does
+            // (since the orientation letterbox is also applied to the task).
+            final Rect transformedBounds = getFixedRotationTransformDisplayBounds();
+            final Rect spaceToFill = transformedBounds != null
+                    ? transformedBounds
+                    : inMultiWindowMode()
+                            ? task.getDisplayedBounds()
+                            : getRootTask().getParent().getDisplayedBounds();
             mLetterbox.layout(spaceToFill, w.getFrameLw(), mTmpPoint);
         } else if (mLetterbox != null) {
             mLetterbox.hide();
@@ -6362,33 +6367,17 @@
         if (mCompatDisplayInsets != null) {
             resolveSizeCompatModeConfiguration(newParentConfiguration);
         } else {
-            // We ignore activities' requested orientation in multi-window modes. Task level may
-            // take them into consideration when calculating bounds.
             if (inMultiWindowMode()) {
+                // We ignore activities' requested orientation in multi-window modes. Task level may
+                // take them into consideration when calculating bounds.
                 resolvedConfig.orientation = Configuration.ORIENTATION_UNDEFINED;
-            }
-            final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
-            final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
-            // Use tmp bounds to calculate aspect ratio so we can know whether the activity should
-            // use restricted size (resolvedBounds may be the requested override bounds).
-            mTmpBounds.setEmpty();
-            applyAspectRatio(mTmpBounds, parentAppBounds, parentBounds);
-            // If the out bounds is not empty, it means the activity cannot fill parent's app
-            // bounds, then the relative configuration (e.g. screen size, layout) needs to be
-            // resolved according to the bounds.
-            if (!mTmpBounds.isEmpty()) {
-                final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
-                resolvedBounds.set(mTmpBounds);
-                // Exclude the horizontal decor area because the activity will be centered
-                // horizontally in parent's app bounds to balance the visual appearance.
-                resolvedBounds.left = parentAppBounds.left;
-                task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
-                        getFixedRotationTransformDisplayInfo());
-                final int offsetX = getHorizontalCenterOffset(
-                        parentAppBounds.width(), resolvedBounds.width());
-                if (offsetX > 0) {
-                    offsetBounds(resolvedConfig, offsetX - resolvedBounds.left, 0 /* offsetY */);
+                // If the activity has requested override bounds, the configuration needs to be
+                // computed accordingly.
+                if (!matchParentBounds()) {
+                    task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
                 }
+            } else {
+                resolveFullscreenConfiguration(newParentConfiguration);
             }
         }
 
@@ -6400,6 +6389,43 @@
     }
 
     /**
+     * Resolves the configuration of activity in fullscreen mode. If the bounds are restricted by
+     * aspect ratio, the position will be centered horizontally in parent's app bounds to balance
+     * the visual appearance. The policy of aspect ratio has higher priority than the requested
+     * override bounds.
+     */
+    private void resolveFullscreenConfiguration(Configuration newParentConfiguration) {
+        final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+        final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+        final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+        final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+        // Use tmp bounds to calculate aspect ratio so we can know whether the activity should use
+        // restricted size (resolved bounds may be the requested override bounds).
+        mTmpBounds.setEmpty();
+        applyAspectRatio(mTmpBounds, parentAppBounds, parentBounds);
+        // If the out bounds is not empty, it means the activity cannot fill parent's app bounds,
+        // then there is space to be centered.
+        final boolean needToBeCentered = !mTmpBounds.isEmpty();
+        if (needToBeCentered) {
+            resolvedBounds.set(mTmpBounds);
+            // Exclude the horizontal decor area.
+            resolvedBounds.left = parentAppBounds.left;
+        }
+        if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
+            // Compute the configuration based on the resolved bounds. If aspect ratio doesn't
+            // restrict, the bounds should be the requested override bounds.
+            task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
+                    getFixedRotationTransformDisplayInfo());
+        }
+        if (needToBeCentered) {
+            // Offset to center relative to parent's app bounds.
+            final int offsetX = getHorizontalCenterOffset(
+                    parentAppBounds.width(), resolvedBounds.width());
+            offsetBounds(resolvedConfig, offsetX, 0 /* offsetY */);
+        }
+    }
+
+    /**
      * Resolves consistent screen configuration for orientation and rotation changes without
      * inheriting the parent bounds.
      */
@@ -6507,7 +6533,7 @@
         // Above coordinates are in "@" space, now place "*" and "#" to screen space.
         final int screenPosX = parentAppBounds.left + offsetX;
         final int screenPosY = parentBounds.top;
-        if (screenPosX > 0 || screenPosY > 0) {
+        if (screenPosX != 0 || screenPosY != 0) {
             if (mSizeCompatBounds != null) {
                 mSizeCompatBounds.offset(screenPosX, screenPosY);
             }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 98e3d07..aad242d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -375,7 +375,7 @@
      */
     final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics();
 
-    /** @see #computeCompatSmallestWidth(boolean, int, int, int, DisplayCutout) */
+    /** @see #computeCompatSmallestWidth(boolean, int, int, int) */
     private final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics();
 
     /**
@@ -1814,7 +1814,7 @@
 
         final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
         outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, uiMode, dw,
-                dh, displayCutout);
+                dh);
     }
 
     /**
@@ -1922,8 +1922,7 @@
         mWmService.mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
     }
 
-    private int computeCompatSmallestWidth(boolean rotated, int uiMode, int dw, int dh,
-            DisplayCutout displayCutout) {
+    private int computeCompatSmallestWidth(boolean rotated, int uiMode, int dw, int dh) {
         mTmpDisplayMetrics.setTo(mDisplayMetrics);
         final DisplayMetrics tmpDm = mTmpDisplayMetrics;
         final int unrotDw, unrotDh;
@@ -1934,19 +1933,21 @@
             unrotDw = dw;
             unrotDh = dh;
         }
-        int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw, unrotDh,
-                displayCutout);
-        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh, unrotDw,
-                displayCutout);
-        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw, unrotDh,
-                displayCutout);
-        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh, unrotDw,
-                displayCutout);
+        int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw,
+                unrotDh);
+        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh,
+                unrotDw);
+        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw,
+                unrotDh);
+        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh,
+                unrotDw);
         return sw;
     }
 
     private int reduceCompatConfigWidthSize(int curSize, int rotation, int uiMode,
-            DisplayMetrics dm, int dw, int dh, DisplayCutout displayCutout) {
+            DisplayMetrics dm, int dw, int dh) {
+        final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(
+                rotation).getDisplayCutout();
         dm.noncompatWidthPixels = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
                 displayCutout);
         dm.noncompatHeightPixels = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode,
@@ -1987,20 +1988,20 @@
             return;
         }
         int sl = Configuration.resetScreenLayout(outConfig.screenLayout);
-        sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode,
-                displayInfo.displayCutout);
-        sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode,
-                displayInfo.displayCutout);
-        sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode,
-                displayInfo.displayCutout);
-        sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode,
-                displayInfo.displayCutout);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode);
         outConfig.smallestScreenWidthDp = (int)(displayInfo.smallestNominalAppWidth / density);
         outConfig.screenLayout = sl;
     }
 
     private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh,
-            int uiMode, DisplayCutout displayCutout) {
+            int uiMode) {
+        // Get the display cutout at this rotation.
+        final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(
+                rotation).getDisplayCutout();
+
         // Get the app screen size at this rotation.
         int w = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayCutout);
         int h = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, displayCutout);
@@ -6523,6 +6524,7 @@
             lastReparentedStack.postReparent();
         }
         releaseSelfIfNeeded();
+        mDisplayPolicy.release();
 
         if (!mAllSleepTokens.isEmpty()) {
             mRootWindowContainer.mSleepTokens.removeAll(mAllSleepTokens);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ba61667..ab96c61 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -179,6 +179,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.util.ScreenshotHelper;
 import com.android.internal.util.function.TriConsumer;
@@ -259,7 +260,9 @@
     @Px
     private int mBottomGestureAdditionalInset;
     @Px
-    private int mSideGestureInset;
+    private int mLeftGestureInset;
+    @Px
+    private int mRightGestureInset;
 
     StatusBarManagerInternal getStatusBarManagerInternal() {
         synchronized (mServiceAcquireLock) {
@@ -427,6 +430,8 @@
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
 
+    private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
+
     private class PolicyHandler extends Handler {
 
         PolicyHandler(Looper looper) {
@@ -651,6 +656,16 @@
         mRefreshRatePolicy = new RefreshRatePolicy(mService,
                 mDisplayContent.getDisplayInfo(),
                 mService.mHighRefreshRateBlacklist);
+
+        mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(mHandler,
+                mContext, () -> {
+            synchronized (mLock) {
+                onConfigurationChanged();
+                mSystemGestures.onConfigurationChanged();
+                mDisplayContent.updateSystemGestureExclusion();
+            }
+        });
+        mHandler.post(mGestureNavigationSettingsObserver::register);
     }
 
     void systemReady() {
@@ -723,7 +738,7 @@
     }
 
     boolean hasSideGestures() {
-        return mHasNavigationBar && mSideGestureInset > 0;
+        return mHasNavigationBar && (mLeftGestureInset > 0 || mRightGestureInset > 0);
     }
 
     public boolean navigationBarCanMove() {
@@ -1076,11 +1091,12 @@
                             inOutFrame.left = 0;
                             inOutFrame.top = 0;
                             inOutFrame.bottom = displayFrames.mDisplayHeight;
-                            inOutFrame.right = displayFrames.mUnrestricted.left + mSideGestureInset;
+                            inOutFrame.right = displayFrames.mUnrestricted.left + mLeftGestureInset;
                         });
                 mDisplayContent.setInsetProvider(ITYPE_RIGHT_GESTURES, win,
                         (displayFrames, windowState, inOutFrame) -> {
-                            inOutFrame.left = displayFrames.mUnrestricted.right - mSideGestureInset;
+                            inOutFrame.left = displayFrames.mUnrestricted.right
+                                    - mRightGestureInset;
                             inOutFrame.top = 0;
                             inOutFrame.bottom = displayFrames.mDisplayHeight;
                             inOutFrame.right = displayFrames.mDisplayWidth;
@@ -2819,7 +2835,8 @@
         }
 
         mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode);
-        mSideGestureInset = res.getDimensionPixelSize(R.dimen.config_backGestureInset);
+        mLeftGestureInset = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
+        mRightGestureInset = mGestureNavigationSettingsObserver.getRightSensitivity(res);
         mNavigationBarLetsThroughTaps = res.getBoolean(R.bool.config_navBarTapThrough);
         mNavigationBarAlwaysShowOnSideGesture =
                 res.getBoolean(R.bool.config_navBarAlwaysShowOnSideEdgeGesture);
@@ -3953,6 +3970,10 @@
         return false;
     }
 
+    void release() {
+        mHandler.post(mGestureNavigationSettingsObserver::unregister);
+    }
+
     @VisibleForTesting
     static boolean isOverlappingWithNavBar(WindowState targetWindow, WindowState navBarWindow) {
         if (navBarWindow == null || !navBarWindow.isVisibleLw()
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index fc358ce..bd5666d 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -846,10 +846,9 @@
     @VisibleForTesting
     Set<Integer> getProfileIds(int userId) {
         Set<Integer> userIds = new ArraySet<>();
-        final List<UserInfo> profiles = mService.getUserManager().getProfiles(userId,
-                false /* enabledOnly */);
-        for (int i = profiles.size() - 1; i >= 0; --i) {
-            userIds.add(profiles.get(i).id);
+        int[] profileIds = mService.getUserManager().getProfileIds(userId, false /* enabledOnly */);
+        for (int i = 0; i < profileIds.length; i++) {
+            userIds.add(Integer.valueOf(profileIds[i]));
         }
         return userIds;
     }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 15a49a7..ebf1bc9 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2695,7 +2695,9 @@
                 return true;
             }
         } else {
-            if (r.intent.getComponent().equals(cls)) {
+            // Compare the target component instead of intent component so we don't miss if the
+            // activity uses alias.
+            if (r.mActivityComponent.equals(cls)) {
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java
index 36861aa..a696daf 100644
--- a/services/core/java/com/android/server/wm/SurfaceFreezer.java
+++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java
@@ -103,7 +103,7 @@
      */
     void unfreeze(SurfaceControl.Transaction t) {
         if (mSnapshot != null) {
-            mSnapshot.destroy(t);
+            mSnapshot.cancelAnimation(t, false /* restarting */);
         }
         if (mLeash == null) {
             return;
@@ -212,7 +212,7 @@
          * @param t The transaction to use for all cancelling surface operations.
          * @param restarting Whether we are restarting the animation.
          */
-        private void cancelAnimation(SurfaceControl.Transaction t, boolean restarting) {
+        void cancelAnimation(SurfaceControl.Transaction t, boolean restarting) {
             final SurfaceControl leash = mSurfaceControl;
             final AnimationAdapter animation = mAnimation;
             final SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback =
@@ -229,7 +229,6 @@
                 }
             }
             if (!restarting) {
-                // TODO: do we need to destroy?
                 destroy(t);
             }
         }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5ab5fbc..e78f2ee 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2127,14 +2127,26 @@
         intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets);
     }
 
+    /**
+     * Forces the app bounds related configuration can be computed by
+     * {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo,
+     * ActivityRecord.CompatDisplayInsets)}.
+     */
+    private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) {
+        final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds();
+        if (appBounds != null) {
+            appBounds.setEmpty();
+        }
+        inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+        inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+    }
+
     void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
             @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) {
         if (overrideDisplayInfo != null) {
             // Make sure the screen related configs can be computed by the provided display info.
-            inOutConfig.windowConfiguration.setAppBounds(null);
             inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
-            inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
-            inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+            invalidateAppBoundsConfig(inOutConfig);
         }
         computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo,
                 null /* compatInsets */);
@@ -2149,6 +2161,10 @@
     void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
             @NonNull Configuration parentConfig,
             @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
+        if (compatInsets != null) {
+            // Make sure the app bounds can be computed by the compat insets.
+            invalidateAppBoundsConfig(inOutConfig);
+        }
         computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
                 compatInsets);
     }
@@ -3370,6 +3386,9 @@
         } else {
             info.pictureInPictureParams = mPictureInPictureParams;
         }
+        info.topActivityInfo = mReuseActivitiesReport.top != null
+                ? mReuseActivitiesReport.top.info
+                : null;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index a60db94..0b11dd2 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -96,8 +96,8 @@
         mAnimationFrameCallback = frameTimeNs -> {
             synchronized (mService.mGlobalLock) {
                 mAnimationFrameCallbackScheduled = false;
+                animate(frameTimeNs);
             }
-            animate(frameTimeNs);
         };
     }
 
@@ -115,110 +115,94 @@
         mInitialized = true;
     }
 
-    /**
-     * DO NOT HOLD THE WINDOW MANAGER LOCK WHILE CALLING THIS METHOD. Reason: the method closes
-     * an animation transaction, that might be blocking until the next sf-vsync, so we want to make
-     * sure other threads can make progress if this happens.
-     */
     private void animate(long frameTimeNs) {
-
-        synchronized (mService.mGlobalLock) {
-            if (!mInitialized) {
-                return;
-            }
-
-            // Schedule next frame already such that back-pressure happens continuously
-            scheduleAnimation();
+        if (!mInitialized) {
+            return;
         }
 
-        synchronized (mService.mGlobalLock) {
-            mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
-            mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
-            if (DEBUG_WINDOW_TRACE) {
-                Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
+        // Schedule next frame already such that back-pressure happens continuously.
+        scheduleAnimation();
+
+        mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
+        mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
+        if (DEBUG_WINDOW_TRACE) {
+            Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
+        }
+
+        ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION animate");
+        mService.openSurfaceTransaction();
+        try {
+            final AccessibilityController accessibilityController =
+                    mService.mAccessibilityController;
+            final int numDisplays = mDisplayContentsAnimators.size();
+            for (int i = 0; i < numDisplays; i++) {
+                final int displayId = mDisplayContentsAnimators.keyAt(i);
+                final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
+                // Update animations of all applications, including those associated with
+                // exiting/removed apps.
+                dc.updateWindowsForAnimator();
+                dc.prepareSurfaces();
             }
 
-            ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION animate");
-            mService.openSurfaceTransaction();
-            try {
-                final AccessibilityController accessibilityController =
-                        mService.mAccessibilityController;
-                final int numDisplays = mDisplayContentsAnimators.size();
-                for (int i = 0; i < numDisplays; i++) {
-                    final int displayId = mDisplayContentsAnimators.keyAt(i);
-                    final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
-                    // Update animations of all applications, including those
-                    // associated with exiting/removed apps
-                    dc.updateWindowsForAnimator();
-                    dc.prepareSurfaces();
+            for (int i = 0; i < numDisplays; i++) {
+                final int displayId = mDisplayContentsAnimators.keyAt(i);
+                final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
+
+                dc.checkAppWindowsReadyToShow();
+                if (accessibilityController != null) {
+                    accessibilityController.drawMagnifiedRegionBorderIfNeededLocked(displayId,
+                            mTransaction);
                 }
-
-                for (int i = 0; i < numDisplays; i++) {
-                    final int displayId = mDisplayContentsAnimators.keyAt(i);
-                    final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
-
-                    dc.checkAppWindowsReadyToShow();
-                    if (accessibilityController != null) {
-                        accessibilityController.drawMagnifiedRegionBorderIfNeededLocked(displayId,
-                                mTransaction);
-                    }
-                }
-
-                cancelAnimation();
-
-                if (mService.mWatermark != null) {
-                    mService.mWatermark.drawIfNeeded();
-                }
-
-                SurfaceControl.mergeToGlobalTransaction(mTransaction);
-            } catch (RuntimeException e) {
-                Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
-            } finally {
-                mService.closeSurfaceTransaction("WindowAnimator");
-                ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate");
             }
 
-            boolean hasPendingLayoutChanges = mService.mRoot.hasPendingLayoutChanges(this);
-            boolean doRequest = false;
-            if (mBulkUpdateParams != 0) {
-                doRequest = mService.mRoot.copyAnimToLayoutParams();
+            cancelAnimation();
+
+            if (mService.mWatermark != null) {
+                mService.mWatermark.drawIfNeeded();
             }
 
-            if (hasPendingLayoutChanges || doRequest) {
-                mService.mWindowPlacerLocked.requestTraversal();
-            }
+            SurfaceControl.mergeToGlobalTransaction(mTransaction);
+        } catch (RuntimeException e) {
+            Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
+        } finally {
+            mService.closeSurfaceTransaction("WindowAnimator");
+            ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate");
+        }
 
-            final boolean rootAnimating = mService.mRoot.isAnimating(TRANSITION | CHILDREN);
-            if (rootAnimating && !mLastRootAnimating) {
+        final boolean hasPendingLayoutChanges = mService.mRoot.hasPendingLayoutChanges(this);
+        final boolean doRequest = mBulkUpdateParams != 0 && mService.mRoot.copyAnimToLayoutParams();
+        if (hasPendingLayoutChanges || doRequest) {
+            mService.mWindowPlacerLocked.requestTraversal();
+        }
 
-                // Usually app transitions but quite a load onto the system already (with all the
-                // things happening in app), so pause task snapshot persisting to not increase the
-                // load.
-                mService.mTaskSnapshotController.setPersisterPaused(true);
-                Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
-            }
-            if (!rootAnimating && mLastRootAnimating) {
-                mService.mWindowPlacerLocked.requestTraversal();
-                mService.mTaskSnapshotController.setPersisterPaused(false);
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
-            }
+        final boolean rootAnimating = mService.mRoot.isAnimating(TRANSITION | CHILDREN);
+        if (rootAnimating && !mLastRootAnimating) {
+            // Usually app transitions but quite a load onto the system already (with all the things
+            // happening in app), so pause task snapshot persisting to not increase the load.
+            mService.mTaskSnapshotController.setPersisterPaused(true);
+            Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
+        }
+        if (!rootAnimating && mLastRootAnimating) {
+            mService.mWindowPlacerLocked.requestTraversal();
+            mService.mTaskSnapshotController.setPersisterPaused(false);
+            Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
+        }
 
-            mLastRootAnimating = rootAnimating;
+        mLastRootAnimating = rootAnimating;
 
-            if (mRemoveReplacedWindows) {
-                mService.mRoot.removeReplacedWindows();
-                mRemoveReplacedWindows = false;
-            }
+        if (mRemoveReplacedWindows) {
+            mService.mRoot.removeReplacedWindows();
+            mRemoveReplacedWindows = false;
+        }
 
-            mService.destroyPreservedSurfaceLocked();
+        mService.destroyPreservedSurfaceLocked();
 
-            executeAfterPrepareSurfacesRunnables();
+        executeAfterPrepareSurfacesRunnables();
 
-            if (DEBUG_WINDOW_TRACE) {
-                Slog.i(TAG, "!!! animate: exit"
-                        + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
-                        + " hasPendingLayoutChanges=" + hasPendingLayoutChanges);
-            }
+        if (DEBUG_WINDOW_TRACE) {
+            Slog.i(TAG, "!!! animate: exit"
+                    + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
+                    + " hasPendingLayoutChanges=" + hasPendingLayoutChanges);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index ec90097..9a92832 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2461,6 +2461,19 @@
         mWaitingSyncId = -1;
     }
 
+    /**
+     * Returns true if any of the children elected to participate in the Sync
+     */
+    boolean addChildrenToSyncSet(int localId) {
+        boolean willSync = false;
+
+        for (int i = 0; i < mChildren.size(); i++) {
+            final WindowContainer child = mChildren.get(i);
+            willSync |= mBLASTSyncEngine.addToSyncSet(localId, child);
+        }
+        return willSync;
+    }
+
     boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener,
             int waitingId) {
         boolean willSync = false;
@@ -2469,16 +2482,12 @@
         }
         mUsingBLASTSyncTransaction = true;
 
-        int localId = mBLASTSyncEngine.startSyncSet(this);
-        for (int i = 0; i < mChildren.size(); i++) {
-            final WindowContainer child = mChildren.get(i);
-            willSync = mBLASTSyncEngine.addToSyncSet(localId, child) | willSync;
-        }
-
         // Make sure to set these before we call setReady in case the sync was a no-op
         mWaitingSyncId = waitingId;
         mWaitingListener = waitingListener;
 
+        int localId = mBLASTSyncEngine.startSyncSet(this);
+        willSync |= addChildrenToSyncSet(localId);
         mBLASTSyncEngine.setReady(localId);
 
         return willSync;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ecbbb03..1a77807 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1007,9 +1007,7 @@
     void openSurfaceTransaction() {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction");
-            synchronized (mGlobalLock) {
-                SurfaceControl.openTransaction();
-            }
+            SurfaceControl.openTransaction();
         } finally {
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
@@ -1022,10 +1020,8 @@
     void closeSurfaceTransaction(String where) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
-            synchronized (mGlobalLock) {
-                SurfaceControl.closeTransaction();
-                mWindowTracing.logState(where);
-            }
+            SurfaceControl.closeTransaction();
+            mWindowTracing.logState(where);
         } finally {
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 32eb932..f356329 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1058,7 +1058,7 @@
     }
 
     private void registerActivityConfigurationListener(ActivityRecord activityRecord) {
-        if (activityRecord == null) {
+        if (activityRecord == null || activityRecord.containsListener(this)) {
             return;
         }
         // A process can only register to one activityRecord to listen to the override configuration
@@ -1093,7 +1093,7 @@
 
         for (int i = mActivities.size() - 1; i >= 0; i--) {
             final ActivityRecord activityRecord = mActivities.get(i);
-            if (!activityRecord.finishing && !activityRecord.containsListener(this)) {
+            if (!activityRecord.finishing) {
                 // Eligible activity is found, update listener.
                 registerActivityConfigurationListener(activityRecord);
                 return;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c44be4d..3617570 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -679,6 +679,15 @@
      */
     int mFrameRateSelectionPriority = RefreshRatePolicy.LAYER_PRIORITY_UNSET;
 
+    /**
+     * BLASTSyncEngine ID corresponding to a sync-set for all
+     * our children. We add our children to this set in Sync,
+     * but we save it and don't mark it as ready until finishDrawing
+     * this way we have a two way latch between all our children finishing
+     * and drawing ourselves.
+     */
+    private int mLocalSyncId = -1;
+
     static final int BLAST_TIMEOUT_DURATION = 5000; /* milliseconds */
 
     /**
@@ -2929,8 +2938,9 @@
         // and add the window only if the permission was granted. Therefore, if
         // the mode is MODE_DEFAULT we want the op to succeed as the window is
         // shown.
-        final int mode = mWmService.mAppOps.startOpNoThrow(mAppOp,
-                getOwningUid(), getOwningPackage(), true);
+        final int mode = mWmService.mAppOps.startOpNoThrow(mAppOp, getOwningUid(),
+                getOwningPackage(), true /* startIfModeDefault */, null /* featureId */,
+                "init-default-visibility");
         if (mode != MODE_ALLOWED && mode != MODE_DEFAULT) {
             setAppOpVisibilityLw(false);
         }
@@ -2938,7 +2948,8 @@
 
     void resetAppOpsState() {
         if (mAppOp != OP_NONE && mAppOpVisibility) {
-            mWmService.mAppOps.finishOp(mAppOp, getOwningUid(), getOwningPackage());
+            mWmService.mAppOps.finishOp(mAppOp, getOwningUid(), getOwningPackage(),
+                    null /* featureId */);
         }
     }
 
@@ -2953,11 +2964,12 @@
             // as this would mean we will get another change callback and will reconcile.
             int mode = mWmService.mAppOps.checkOpNoThrow(mAppOp, uid, packageName);
             if (mode != MODE_ALLOWED && mode != MODE_DEFAULT) {
-                mWmService.mAppOps.finishOp(mAppOp, uid, packageName);
+                mWmService.mAppOps.finishOp(mAppOp, uid, packageName, null /* featureId */);
                 setAppOpVisibilityLw(false);
             }
         } else {
-            final int mode = mWmService.mAppOps.startOpNoThrow(mAppOp, uid, packageName, true);
+            final int mode = mWmService.mAppOps.startOpNoThrow(mAppOp, uid, packageName,
+                    true /* startIfModeDefault */, null /* featureId */, "attempt-to-be-visible");
             if (mode == MODE_ALLOWED || mode == MODE_DEFAULT) {
                 setAppOpVisibilityLw(true);
             }
@@ -5684,11 +5696,16 @@
     @Override
     boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener,
             int waitingId) {
-        // TODO(b/148871522): Support child window
+        if (!isVisible()) {
+            return false;
+        }
         mWaitingListener = waitingListener;
         mWaitingSyncId = waitingId;
         mUsingBLASTSyncTransaction = true;
 
+        mLocalSyncId = mBLASTSyncEngine.startSyncSet(this);
+        addChildrenToSyncSet(mLocalSyncId);
+
         mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
         mWmService.mH.sendNewMessageDelayed(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this,
             BLAST_TIMEOUT_DURATION);
@@ -5702,11 +5719,21 @@
         }
 
         mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
-        if (postDrawTransaction == null) {
-            postDrawTransaction = new SurfaceControl.Transaction();
+        if (postDrawTransaction != null) {
+            mBLASTSyncTransaction.merge(postDrawTransaction);
         }
-        postDrawTransaction.merge(mBLASTSyncTransaction);
-        mWaitingListener.transactionReady(mWaitingSyncId, postDrawTransaction);
+
+        // If localSyncId is >0 then we are syncing with children and will
+        // invoke transaction ready from our own #transactionReady callback
+        // we just need to signal our side of the sync (setReady). But if we
+        // have no sync operation at this level transactionReady will never
+        // be invoked and we need to invoke it ourself.
+        if (mLocalSyncId >= 0) {
+            mBLASTSyncEngine.setReady(mLocalSyncId);
+        } else {
+            mWaitingListener.transactionReady(mWaitingSyncId, mBLASTSyncTransaction);
+        }
+
         mUsingBLASTSyncTransaction = false;
 
         mWaitingSyncId = 0;
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 6f9d012..29b2cd6 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -172,26 +172,25 @@
 
 BlockHeader readHeader(std::span<uint8_t>& data);
 
-static inline int32_t readBEInt32(borrowed_fd fd) {
+static inline int32_t readLEInt32(borrowed_fd fd) {
     int32_t result;
     ReadFully(fd, &result, sizeof(result));
-    result = int32_t(be32toh(result));
+    result = int32_t(le32toh(result));
     return result;
 }
 
 static inline std::vector<char> readBytes(borrowed_fd fd) {
-    int32_t size = readBEInt32(fd);
+    int32_t size = readLEInt32(fd);
     std::vector<char> result(size);
     ReadFully(fd, result.data(), size);
     return result;
 }
 
 static inline int32_t skipIdSigHeaders(borrowed_fd fd) {
-    readBEInt32(fd);        // version
-    readBytes(fd);          // verityRootHash
-    readBytes(fd);          // v3Digest
-    readBytes(fd);          // pkcs7SignatureBlock
-    return readBEInt32(fd); // size of the verity tree
+    readLEInt32(fd);        // version
+    readBytes(fd);          // hashingInfo
+    readBytes(fd);          // signingInfo
+    return readLEInt32(fd); // size of the verity tree
 }
 
 static inline IncFsSize verityTreeSizeForFile(IncFsSize fileSize) {
@@ -365,13 +364,8 @@
         }
     }
     void onDestroy() final {
-        ALOGE("Sending EXIT to server.");
-        sendRequest(mOutFd, EXIT);
         // Make sure the receiver thread stopped.
         CHECK(!mReceiverThread.joinable());
-
-        mInFd.reset();
-        mOutFd.reset();
     }
 
     // Installation.
@@ -569,13 +563,6 @@
 
     // Streaming.
     bool initStreaming(unique_fd inout) {
-        mInFd.reset(dup(inout));
-        mOutFd.reset(dup(inout));
-        if (mInFd < 0 || mOutFd < 0) {
-            ALOGE("Failed to dup FDs.");
-            return false;
-        }
-
         mEventFd.reset(eventfd(0, EFD_CLOEXEC));
         if (mEventFd < 0) {
             ALOGE("Failed to create eventfd.");
@@ -584,7 +571,7 @@
 
         // Awaiting adb handshake.
         char okay_buf[OKAY.size()];
-        if (!android::base::ReadFully(mInFd, okay_buf, OKAY.size())) {
+        if (!android::base::ReadFully(inout, okay_buf, OKAY.size())) {
             ALOGE("Failed to receive OKAY. Abort.");
             return false;
         }
@@ -594,13 +581,23 @@
             return false;
         }
 
-        mReceiverThread = std::thread([this]() { receiver(); });
+        {
+            std::lock_guard lock{mOutFdLock};
+            mOutFd.reset(::dup(inout));
+            if (mOutFd < 0) {
+                ALOGE("Failed to create streaming fd.");
+            }
+        }
+
+        mReceiverThread =
+                std::thread([this, io = std::move(inout)]() mutable { receiver(std::move(io)); });
         ALOGI("Started streaming...");
         return true;
     }
 
     // IFS callbacks.
     void onPendingReads(dataloader::PendingReads pendingReads) final {
+        std::lock_guard lock{mOutFdLock};
         CHECK(mIfs);
         for (auto&& pendingRead : pendingReads) {
             const android::dataloader::FileId& fileId = pendingRead.id;
@@ -626,12 +623,12 @@
         }
     }
 
-    void receiver() {
+    void receiver(unique_fd inout) {
         std::vector<uint8_t> data;
         std::vector<IncFsDataBlock> instructions;
         std::unordered_map<FileIdx, unique_fd> writeFds;
         while (!mStopReceiving) {
-            const int res = waitForDataOrSignal(mInFd, mEventFd);
+            const int res = waitForDataOrSignal(inout, mEventFd);
             if (res == 0) {
                 continue;
             }
@@ -641,10 +638,11 @@
                 break;
             }
             if (res == mEventFd) {
-                ALOGE("Received stop signal. Exit.");
+                ALOGE("Received stop signal. Sending EXIT to server.");
+                sendRequest(inout, EXIT);
                 break;
             }
-            if (!readChunk(mInFd, data)) {
+            if (!readChunk(inout, data)) {
                 ALOGE("Failed to read a message. Abort.");
                 mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
                 break;
@@ -657,7 +655,7 @@
                     ALOGI("Stop signal received. Sending exit command (remaining bytes: %d).",
                           int(remainingData.size()));
 
-                    sendRequest(mOutFd, EXIT);
+                    sendRequest(inout, EXIT);
                     mStopReceiving = true;
                     break;
                 }
@@ -700,6 +698,11 @@
             writeInstructions(instructions);
         }
         writeInstructions(instructions);
+
+        {
+            std::lock_guard lock{mOutFdLock};
+            mOutFd.reset();
+        }
     }
 
     void writeInstructions(std::vector<IncFsDataBlock>& instructions) {
@@ -743,7 +746,7 @@
     std::string mArgs;
     android::dataloader::FilesystemConnectorPtr mIfs = nullptr;
     android::dataloader::StatusListenerPtr mStatusListener = nullptr;
-    android::base::unique_fd mInFd;
+    std::mutex mOutFdLock;
     android::base::unique_fd mOutFd;
     android::base::unique_fd mEventFd;
     std::thread mReceiverThread;
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 239a101..fe05d4e 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -178,6 +178,92 @@
     return isOk;
 }
 
+enum class HalSupport {
+    UNKNOWN = 0,
+    ON,
+    OFF,
+};
+
+static void setPowerBoostWithHandle(sp<IPowerAidl> handle, Boost boost, int32_t durationMs) {
+    // Android framework only sends boost upto DISPLAY_UPDATE_IMMINENT.
+    // Need to increase the array size if more boost supported.
+    static std::array<std::atomic<HalSupport>,
+                      static_cast<int32_t>(Boost::DISPLAY_UPDATE_IMMINENT) + 1>
+            boostSupportedArray = {HalSupport::UNKNOWN};
+
+    // Quick return if boost is not supported by HAL
+    if (boost > Boost::DISPLAY_UPDATE_IMMINENT ||
+        boostSupportedArray[static_cast<int32_t>(boost)] == HalSupport::OFF) {
+        ALOGV("Skipped setPowerBoost %s because HAL doesn't support it", toString(boost).c_str());
+        return;
+    }
+
+    if (boostSupportedArray[static_cast<int32_t>(boost)] == HalSupport::UNKNOWN) {
+        bool isSupported = false;
+        handle->isBoostSupported(boost, &isSupported);
+        boostSupportedArray[static_cast<int32_t>(boost)] =
+            isSupported ? HalSupport::ON : HalSupport::OFF;
+        if (!isSupported) {
+            ALOGV("Skipped setPowerBoost %s because HAL doesn't support it",
+                  toString(boost).c_str());
+            return;
+        }
+    }
+
+    auto ret = handle->setBoost(boost, durationMs);
+    processPowerHalReturn(ret.isOk(), "setPowerBoost");
+}
+
+static void setPowerBoost(Boost boost, int32_t durationMs) {
+    std::unique_lock<std::mutex> lock(gPowerHalMutex);
+    if (connectPowerHalLocked() != HalVersion::AIDL) {
+        ALOGV("Power HAL AIDL not available");
+        return;
+    }
+    sp<IPowerAidl> handle = gPowerHalAidl_;
+    lock.unlock();
+    setPowerBoostWithHandle(handle, boost, durationMs);
+}
+
+static void setPowerModeWithHandle(sp<IPowerAidl> handle, Mode mode, bool enabled) {
+    // Android framework only sends mode upto DISPLAY_INACTIVE.
+    // Need to increase the array if more mode supported.
+    static std::array<std::atomic<HalSupport>, static_cast<int32_t>(Mode::DISPLAY_INACTIVE) + 1>
+            modeSupportedArray = {HalSupport::UNKNOWN};
+
+    // Quick return if mode is not supported by HAL
+    if (mode > Mode::DISPLAY_INACTIVE ||
+        modeSupportedArray[static_cast<int32_t>(mode)] == HalSupport::OFF) {
+        ALOGV("Skipped setPowerMode %s because HAL doesn't support it", toString(mode).c_str());
+        return;
+    }
+
+    if (modeSupportedArray[static_cast<int32_t>(mode)] == HalSupport::UNKNOWN) {
+        bool isSupported = false;
+        handle->isModeSupported(mode, &isSupported);
+        modeSupportedArray[static_cast<int32_t>(mode)] =
+            isSupported ? HalSupport::ON : HalSupport::OFF;
+        if (!isSupported) {
+            ALOGV("Skipped setPowerMode %s because HAL doesn't support it", toString(mode).c_str());
+            return;
+        }
+    }
+
+    auto ret = handle->setMode(mode, enabled);
+    processPowerHalReturn(ret.isOk(), "setPowerMode");
+}
+
+static void setPowerMode(Mode mode, bool enabled) {
+    std::unique_lock<std::mutex> lock(gPowerHalMutex);
+    if (connectPowerHalLocked() != HalVersion::AIDL) {
+        ALOGV("Power HAL AIDL not available");
+        return;
+    }
+    sp<IPowerAidl> handle = gPowerHalAidl_;
+    lock.unlock();
+    setPowerModeWithHandle(handle, mode, enabled);
+}
+
 static void sendPowerHint(PowerHint hintId, uint32_t data) {
     std::unique_lock<std::mutex> lock(gPowerHalMutex);
     switch (connectPowerHalLocked()) {
@@ -201,14 +287,28 @@
             if (hintId == PowerHint::INTERACTION) {
                 sp<IPowerAidl> handle = gPowerHalAidl_;
                 lock.unlock();
-                auto ret = handle->setBoost(Boost::INTERACTION, data);
-                processPowerHalReturn(ret.isOk(), "setBoost");
+                setPowerBoostWithHandle(handle, Boost::INTERACTION, data);
                 break;
             } else if (hintId == PowerHint::LAUNCH) {
                 sp<IPowerAidl> handle = gPowerHalAidl_;
                 lock.unlock();
-                auto ret = handle->setMode(Mode::LAUNCH, static_cast<bool>(data));
-                processPowerHalReturn(ret.isOk(), "setMode");
+                setPowerModeWithHandle(handle, Mode::LAUNCH, static_cast<bool>(data));
+                break;
+            } else if (hintId == PowerHint::LOW_POWER) {
+                sp<IPowerAidl> handle = gPowerHalAidl_;
+                lock.unlock();
+                setPowerModeWithHandle(handle, Mode::LOW_POWER, static_cast<bool>(data));
+                break;
+            } else if (hintId == PowerHint::SUSTAINED_PERFORMANCE) {
+                sp<IPowerAidl> handle = gPowerHalAidl_;
+                lock.unlock();
+                setPowerModeWithHandle(handle, Mode::SUSTAINED_PERFORMANCE,
+                                       static_cast<bool>(data));
+                break;
+            } else if (hintId == PowerHint::VR_MODE) {
+                sp<IPowerAidl> handle = gPowerHalAidl_;
+                lock.unlock();
+                setPowerModeWithHandle(handle, Mode::VR, static_cast<bool>(data));
                 break;
             } else {
                 ALOGE("Unsupported power hint: %s.", toString(hintId).c_str());
@@ -223,87 +323,6 @@
     SurfaceComposerClient::notifyPowerHint(static_cast<int32_t>(hintId));
 }
 
-enum class HalSupport {
-    UNKNOWN = 0,
-    ON,
-    OFF,
-};
-
-static void setPowerBoost(Boost boost, int32_t durationMs) {
-    // Android framework only sends boost upto DISPLAY_UPDATE_IMMINENT.
-    // Need to increase the array size if more boost supported.
-    static std::array<std::atomic<HalSupport>,
-                      static_cast<int32_t>(Boost::DISPLAY_UPDATE_IMMINENT) + 1>
-        boostSupportedArray = {HalSupport::UNKNOWN};
-
-    // Quick return if boost is not supported by HAL
-    if (boost > Boost::DISPLAY_UPDATE_IMMINENT ||
-        boostSupportedArray[static_cast<int32_t>(boost)] == HalSupport::OFF) {
-        ALOGV("Skipped setPowerBoost %s because HAL doesn't support it", toString(boost).c_str());
-        return;
-    }
-
-    std::unique_lock<std::mutex> lock(gPowerHalMutex);
-    if (connectPowerHalLocked() != HalVersion::AIDL) {
-        ALOGV("Power HAL AIDL not available");
-        return;
-    }
-    sp<IPowerAidl> handle = gPowerHalAidl_;
-    lock.unlock();
-
-    if (boostSupportedArray[static_cast<int32_t>(boost)] == HalSupport::UNKNOWN) {
-        bool isSupported = false;
-        handle->isBoostSupported(boost, &isSupported);
-        boostSupportedArray[static_cast<int32_t>(boost)] =
-            isSupported ? HalSupport::ON : HalSupport::OFF;
-        if (!isSupported) {
-            ALOGV("Skipped setPowerBoost %s because HAL doesn't support it",
-                  toString(boost).c_str());
-            return;
-        }
-    }
-
-    auto ret = handle->setBoost(boost, durationMs);
-    processPowerHalReturn(ret.isOk(), "setPowerBoost");
-}
-
-static void setPowerMode(Mode mode, bool enabled) {
-    // Android framework only sends mode upto DISPLAY_INACTIVE.
-    // Need to increase the array if more mode supported.
-    static std::array<std::atomic<HalSupport>,
-                      static_cast<int32_t>(Mode::DISPLAY_INACTIVE) + 1>
-        modeSupportedArray = {HalSupport::UNKNOWN};
-
-    // Quick return if mode is not supported by HAL
-    if (mode > Mode::DISPLAY_INACTIVE ||
-        modeSupportedArray[static_cast<int32_t>(mode)] == HalSupport::OFF) {
-        ALOGV("Skipped setPowerMode %s because HAL doesn't support it", toString(mode).c_str());
-        return;
-    }
-
-    std::unique_lock<std::mutex> lock(gPowerHalMutex);
-    if (connectPowerHalLocked() != HalVersion::AIDL) {
-        ALOGV("Power HAL AIDL not available");
-        return;
-    }
-    sp<IPowerAidl> handle = gPowerHalAidl_;
-    lock.unlock();
-
-    if (modeSupportedArray[static_cast<int32_t>(mode)] == HalSupport::UNKNOWN) {
-        bool isSupported = false;
-        handle->isModeSupported(mode, &isSupported);
-        modeSupportedArray[static_cast<int32_t>(mode)] =
-            isSupported ? HalSupport::ON : HalSupport::OFF;
-        if (!isSupported) {
-            ALOGV("Skipped setPowerMode %s because HAL doesn't support it", toString(mode).c_str());
-            return;
-        }
-    }
-
-    auto ret = handle->setMode(mode, enabled);
-    processPowerHalReturn(ret.isOk(), "setPowerMode");
-}
-
 void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType) {
     if (gPowerManagerServiceObj) {
         // Throttle calls into user activity by event type.
@@ -426,8 +445,7 @@
         case HalVersion::AIDL: {
             sp<IPowerAidl> handle = gPowerHalAidl_;
             lock.unlock();
-            auto ret = handle->setMode(Mode::INTERACTIVE, enable);
-            processPowerHalReturn(ret.isOk(), "setMode");
+            setPowerModeWithHandle(handle, Mode::INTERACTIVE, enable);
             return;
         }
         default: {
@@ -481,8 +499,9 @@
             return;
         }
         case HalVersion::AIDL: {
-            auto ret = gPowerHalAidl_->setMode(Mode::DOUBLE_TAP_TO_WAKE, static_cast<bool>(data));
-            processPowerHalReturn(ret.isOk(), "setMode");
+            sp<IPowerAidl> handle = gPowerHalAidl_;
+            lock.unlock();
+            setPowerModeWithHandle(handle, Mode::DOUBLE_TAP_TO_WAKE, static_cast<bool>(data));
             return;
         }
         default: {
diff --git a/services/core/jni/com_android_server_security_VerityUtils.cpp b/services/core/jni/com_android_server_security_VerityUtils.cpp
index be11b86..0277f16 100644
--- a/services/core/jni/com_android_server_security_VerityUtils.cpp
+++ b/services/core/jni/com_android_server_security_VerityUtils.cpp
@@ -25,17 +25,14 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/fsverity.h>
+#include <linux/stat.h>
 #include <string.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#include <type_traits>
-
 #include <android-base/unique_fd.h>
 
-const int kSha256Bytes = 32;
-
 namespace android {
 
 namespace {
@@ -69,30 +66,28 @@
     return 0;
 }
 
-int measureFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) {
-    using Storage = std::aligned_storage_t<sizeof(fsverity_digest) + kSha256Bytes>;
-
-    Storage bytes;
-    fsverity_digest *data = reinterpret_cast<fsverity_digest *>(&bytes);
-    data->digest_size = kSha256Bytes;  // the only input/output parameter
-
+// Returns whether the file has fs-verity enabled.
+// 0 if it is not present, 1 if is present, and -errno if there was an error.
+int statxForFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath) {
     ScopedUtfChars path(env, filePath);
-    if (path.c_str() == nullptr) {
-        return EINVAL;
+
+    struct statx out = {};
+    if (statx(AT_FDCWD, path.c_str(), 0 /* flags */, STATX_ALL, &out) != 0) {
+        return -errno;
     }
-    ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
-    if (rfd.get() < 0) {
-        return errno;
+
+    // Sanity check.
+    if ((out.stx_attributes_mask & STATX_ATTR_VERITY) == 0) {
+        ALOGE("Unexpected, STATX_ATTR_VERITY not supported by kernel");
+        return -ENOSYS;
     }
-    if (ioctl(rfd.get(), FS_IOC_MEASURE_VERITY, data) < 0) {
-        return errno;
-    }
-    return 0;
+
+    return (out.stx_attributes & STATX_ATTR_VERITY) != 0;
 }
 
 const JNINativeMethod sMethods[] = {
-    { "enableFsverityNative", "(Ljava/lang/String;[B)I", (void *)enableFsverity },
-    { "measureFsverityNative", "(Ljava/lang/String;)I", (void *)measureFsverity },
+        {"enableFsverityNative", "(Ljava/lang/String;[B)I", (void *)enableFsverity},
+        {"statxForFsverityNative", "(Ljava/lang/String;)I", (void *)statxForFsverity},
 };
 
 }  // namespace
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6f41631..f5d2c6a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -786,7 +786,7 @@
         List<String> mLockTaskPackages = new ArrayList<>();
 
         // List of packages protected by device owner
-        List<String> mProtectedPackages = new ArrayList<>();
+        List<String> mUserControlDisabledPackages = new ArrayList<>();
 
         // Bitfield of feature flags to be enabled during LockTask mode.
         // We default on the power button menu, in order to be consistent with pre-P behaviour.
@@ -3541,8 +3541,8 @@
                 out.endTag(null, TAG_OWNER_INSTALLED_CA_CERT);
             }
 
-            for (int i = 0, size = policy.mProtectedPackages.size(); i < size; i++) {
-                String packageName = policy.mProtectedPackages.get(i);
+            for (int i = 0, size = policy.mUserControlDisabledPackages.size(); i < size; i++) {
+                String packageName = policy.mUserControlDisabledPackages.get(i);
                 out.startTag(null, TAG_PROTECTED_PACKAGES);
                 out.attribute(null, ATTR_NAME, packageName);
                 out.endTag(null, TAG_PROTECTED_PACKAGES);
@@ -3667,7 +3667,7 @@
             policy.mAdminMap.clear();
             policy.mAffiliationIds.clear();
             policy.mOwnerInstalledCaCerts.clear();
-            policy.mProtectedPackages.clear();
+            policy.mUserControlDisabledPackages.clear();
             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
@@ -3769,7 +3769,8 @@
                 } else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
                     policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
                 } else if (TAG_PROTECTED_PACKAGES.equals(tag)) {
-                    policy.mProtectedPackages.add(parser.getAttributeValue(null, ATTR_NAME));
+                    policy.mUserControlDisabledPackages.add(
+                            parser.getAttributeValue(null, ATTR_NAME));
                 } else if (TAG_APPS_SUSPENDED.equals(tag)) {
                     policy.mAppsSuspended =
                             Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_VALUE));
@@ -3804,7 +3805,7 @@
         updateMaximumTimeToLockLocked(userHandle);
         updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
         updateLockTaskFeaturesLocked(policy.mLockTaskFeatures, userHandle);
-        updateProtectedPackagesLocked(policy.mProtectedPackages);
+        updateUserControlDisabledPackagesLocked(policy.mUserControlDisabledPackages);
         if (policy.mStatusBarDisabled) {
             setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle);
         }
@@ -3830,7 +3831,7 @@
         }
     }
 
-    private void updateProtectedPackagesLocked(List<String> packages) {
+    private void updateUserControlDisabledPackagesLocked(List<String> packages) {
         mInjector.getPackageManagerInternal().setDeviceOwnerProtectedPackages(packages);
     }
 
@@ -8830,8 +8831,8 @@
         policy.mLockTaskPackages.clear();
         updateLockTaskPackagesLocked(policy.mLockTaskPackages, userId);
         policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
-        policy.mProtectedPackages.clear();
-        updateProtectedPackagesLocked(policy.mProtectedPackages);
+        policy.mUserControlDisabledPackages.clear();
+        updateUserControlDisabledPackagesLocked(policy.mUserControlDisabledPackages);
         saveSettingsLocked(userId);
 
         try {
@@ -9584,7 +9585,8 @@
             pw.println();
             pw.increaseIndent();
             pw.print("mPasswordOwner="); pw.println(policy.mPasswordOwner);
-            pw.print("mProtectedPackages="); pw.println(policy.mProtectedPackages);
+            pw.print("mUserControlDisabledPackages=");
+            pw.println(policy.mUserControlDisabledPackages);
             pw.print("mAppsSuspended="); pw.println(policy.mAppsSuspended);
             pw.decreaseIndent();
         }
@@ -15559,39 +15561,39 @@
     }
 
     @Override
-    public void setProtectedPackages(ComponentName who, List<String> packages) {
+    public void setUserControlDisabledPackages(ComponentName who, List<String> packages) {
         Preconditions.checkNotNull(who, "ComponentName is null");
         Preconditions.checkNotNull(packages, "packages is null");
 
         enforceDeviceOwner(who);
         synchronized (getLockObject()) {
             final int userHandle = mInjector.userHandleGetCallingUserId();
-            setProtectedPackagesLocked(userHandle, packages);
+            setUserControlDisabledPackagesLocked(userHandle, packages);
             DevicePolicyEventLogger
-                    .createEvent(DevicePolicyEnums.SET_PACKAGES_PROTECTED)
+                    .createEvent(DevicePolicyEnums.SET_USER_CONTROL_DISABLED_PACKAGES)
                     .setAdmin(who)
                     .setStrings(packages.toArray(new String[packages.size()]))
                     .write();
         }
     }
 
-    private void setProtectedPackagesLocked(int userHandle, List<String> packages) {
+    private void setUserControlDisabledPackagesLocked(int userHandle, List<String> packages) {
         final DevicePolicyData policy = getUserData(userHandle);
-        policy.mProtectedPackages = packages;
+        policy.mUserControlDisabledPackages = packages;
 
         // Store the settings persistently.
         saveSettingsLocked(userHandle);
-        updateProtectedPackagesLocked(packages);
+        updateUserControlDisabledPackagesLocked(packages);
     }
 
     @Override
-    public List<String> getProtectedPackages(ComponentName who) {
+    public List<String> getUserControlDisabledPackages(ComponentName who) {
         Preconditions.checkNotNull(who, "ComponentName is null");
 
         enforceDeviceOwner(who);
         final int userHandle = mInjector.binderGetCallingUserHandle().getIdentifier();
         synchronized (getLockObject()) {
-            final List<String> packages = getUserData(userHandle).mProtectedPackages;
+            final List<String> packages = getUserData(userHandle).mUserControlDisabledPackages;
             return packages == null ? Collections.EMPTY_LIST : packages;
         }
     }
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 3fcb57a..2dbbc5a 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -178,15 +178,9 @@
     nfp.size = params.size;
     nfp.metadata = {(const char*)params.metadata.data(), (IncFsSize)params.metadata.size()};
     if (!params.signature) {
-        nfp.verification = {};
+        nfp.signature = {};
     } else {
-        nfp.verification.hashAlgorithm = IncFsHashAlgortithm(params.signature->hashAlgorithm);
-        nfp.verification.rootHash = {(const char*)params.signature->rootHash.data(),
-                                     (IncFsSize)params.signature->rootHash.size()};
-        nfp.verification.additionalData = {(const char*)params.signature->additionalData.data(),
-                                           (IncFsSize)params.signature->additionalData.size()};
-        nfp.verification.signature = {(const char*)params.signature->signature.data(),
-                                      (IncFsSize)params.signature->signature.size()};
+        nfp.signature = {(const char*)params.signature->data(), (IncFsSize)params.signature->size()};
     }
     return {0, id, nfp};
 }
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index cccd0133..7275936 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -1155,7 +1155,7 @@
         // Create new lib file without signature info
         incfs::NewFileParams libFileParams{};
         libFileParams.size = uncompressedLen;
-        libFileParams.verification.hashAlgorithm = INCFS_HASH_NONE;
+        libFileParams.signature = {};
         // Metadata of the new lib file is its relative path
         IncFsSpan libFileMetadata;
         libFileMetadata.data = targetLibPath.c_str();
diff --git a/services/robotests/Android.bp b/services/robotests/Android.bp
index 602e4e1..25ab5d3 100644
--- a/services/robotests/Android.bp
+++ b/services/robotests/Android.bp
@@ -27,7 +27,7 @@
         "services.net",
     ],
 
-    libs: ["ike-stubs"],
+    libs: ["android.net.ipsec.ike.stubs.system"],
 }
 
 //##################################################################
diff --git a/services/robotests/backup/Android.bp b/services/robotests/backup/Android.bp
index 5160eae..32587ac 100644
--- a/services/robotests/backup/Android.bp
+++ b/services/robotests/backup/Android.bp
@@ -29,7 +29,7 @@
         "services.net",
     ],
 
-    libs: ["ike-stubs"],
+    libs: ["android.net.ipsec.ike.stubs.system"],
 }
 
 //##################################################################
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index c45ee7b..efe8119 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -127,11 +127,14 @@
         mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal());
         mAms.mPackageManagerInt = mPackageManagerInt;
         doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+        // Remove stale instance of PackageManagerInternal if there is any
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
     }
 
     @After
     public void tearDown() {
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
         mHandlerThread.quit();
     }
 
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 053a798..7a175ca1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -94,6 +94,7 @@
 import com.android.server.wm.ActivityTaskManagerService;
 import com.android.server.wm.WindowProcessController;
 
+import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -141,6 +142,8 @@
         sPackageManagerInternal = mock(PackageManagerInternal.class);
         doReturn(new ComponentName("", "")).when(sPackageManagerInternal)
                 .getSystemUiServiceComponent();
+        // Remove stale instance of PackageManagerInternal if there is any
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, sPackageManagerInternal);
 
         sService = mock(ActivityManagerService.class);
@@ -172,6 +175,11 @@
         sService.mOomAdjuster.mAdjSeq = 10000;
     }
 
+    @AfterClass
+    public static void tearDownOnce() {
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+    }
+
     private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
         try {
             Field field = clazz.getDeclaredField(fieldName);
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 7571f09..d038d6c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5843,7 +5843,7 @@
         assertTrue(dpm.isPackageAllowedToAccessCalendar(testPackage));
     }
 
-    public void testSetProtectedPackages_asDO() throws Exception {
+    public void testSetUserControlDisabledPackages_asDO() throws Exception {
         final List<String> testPackages = new ArrayList<>();
         testPackages.add("package_1");
         testPackages.add("package_2");
@@ -5851,14 +5851,14 @@
         mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
         setDeviceOwner();
 
-        dpm.setProtectedPackages(admin1, testPackages);
+        dpm.setUserControlDisabledPackages(admin1, testPackages);
 
         verify(getServices().packageManagerInternal).setDeviceOwnerProtectedPackages(testPackages);
 
-        assertEquals(testPackages, dpm.getProtectedPackages(admin1));
+        assertEquals(testPackages, dpm.getUserControlDisabledPackages(admin1));
     }
 
-    public void testSetProtectedPackages_failingAsPO() throws Exception {
+    public void testSetUserControlDisabledPackages_failingAsPO() throws Exception {
         final List<String> testPackages = new ArrayList<>();
         testPackages.add("package_1");
         testPackages.add("package_2");
@@ -5867,10 +5867,10 @@
         setAsProfileOwner(admin1);
 
         assertExpectException(SecurityException.class, /* messageRegex= */ null,
-                () -> dpm.setProtectedPackages(admin1, testPackages));
+                () -> dpm.setUserControlDisabledPackages(admin1, testPackages));
 
         assertExpectException(SecurityException.class, /* messageRegex= */ null,
-                () -> dpm.getProtectedPackages(admin1));
+                () -> dpm.getUserControlDisabledPackages(admin1));
     }
 
     private void configureProfileOwnerOfOrgOwnedDevice(ComponentName who, int userId) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 0551f2e..a587029 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -538,6 +538,7 @@
     }
 
     @Test
+    @Ignore("b/151150320")
     public void handleSystemAudioModeRequest_fromNonTV_tVNotSupport() {
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildSystemAudioModeRequest(
@@ -587,6 +588,7 @@
     }
 
     @Test
+    @Ignore("b/151150320")
     public void handleRoutingChange_currentActivePortIsHome() {
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x3000, SELF_PHYSICAL_ADDRESS);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 4a1af51..f72d622 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -139,6 +139,7 @@
     }
 
     @Test
+    @Ignore("b/151147315")
     public void doNotWakeUpOnHotPlug_PlugIn() {
         mWokenUp = false;
         mHdmiCecLocalDevicePlayback.onHotplug(0, true);
@@ -146,6 +147,7 @@
     }
 
     @Test
+    @Ignore("b/151147315")
     public void doNotWakeUpOnHotPlug_PlugOut() {
         mWokenUp = false;
         mHdmiCecLocalDevicePlayback.onHotplug(0, false);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index 81545d4..d3a3e7e 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -480,6 +480,13 @@
         assertThat(mService.getCurrentRules().getList()).containsExactly(rule);
     }
 
+    @Test
+    public void getWhitelistedRuleProviders() throws Exception {
+        whitelistUsAsRuleProvider();
+
+        assertThat(mService.getWhitelistedRuleProviders()).containsExactly(TEST_FRAMEWORK_PACKAGE);
+    }
+
     private void whitelistUsAsRuleProvider() {
         Resources mockResources = mock(Resources.class);
         when(mockResources.getStringArray(R.array.config_integrityRuleProviderPackages))
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index 8da3bdf..155c6dd 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -28,6 +28,8 @@
 import android.media.tv.tuner.frontend.FrontendSettings;
 import android.media.tv.tunerresourcemanager.IResourcesReclaimListener;
 import android.media.tv.tunerresourcemanager.ResourceClientProfile;
+import android.media.tv.tunerresourcemanager.TunerDemuxRequest;
+import android.media.tv.tunerresourcemanager.TunerDescramblerRequest;
 import android.media.tv.tunerresourcemanager.TunerFrontendInfo;
 import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
 import android.media.tv.tunerresourcemanager.TunerResourceManager;
@@ -92,6 +94,13 @@
             }
         };
 
+    private static int getResourceIdFromHandle(int resourceHandle) {
+        if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
+            return resourceHandle;
+        }
+        return (resourceHandle & 0x00ff0000) >> 16;
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -229,14 +238,15 @@
     public void requestFrontendTest_ClientNotRegistered() {
         TunerFrontendRequest request =
                 new TunerFrontendRequest(0 /*clientId*/, FrontendSettings.TYPE_DVBT);
-        int[] frontendId = new int[1];
+        int[] frontendHandle = new int[1];
         try {
-            assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId))
-                    .isFalse();
+            assertThat(mTunerResourceManagerService
+                    .requestFrontendInternal(request, frontendHandle)).isFalse();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(frontendId[0]).isEqualTo(TunerResourceManager.INVALID_FRONTEND_ID);
+        assertThat(getResourceIdFromHandle(frontendHandle[0]))
+                .isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
     }
 
     @Test
@@ -256,14 +266,15 @@
 
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        int[] frontendId = new int[1];
+        int[] frontendHandle = new int[1];
         try {
-            assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId))
-                    .isFalse();
+            assertThat(mTunerResourceManagerService
+                    .requestFrontendInternal(request, frontendHandle)).isFalse();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(frontendId[0]).isEqualTo(TunerResourceManager.INVALID_FRONTEND_ID);
+        assertThat(getResourceIdFromHandle(frontendHandle[0]))
+                .isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
     }
 
     @Test
@@ -287,14 +298,14 @@
 
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        int[] frontendId = new int[1];
+        int[] frontendHandle = new int[1];
         try {
-            assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId))
-                    .isTrue();
+            assertThat(mTunerResourceManagerService
+                    .requestFrontendInternal(request, frontendHandle)).isTrue();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(frontendId[0]).isEqualTo(0);
+        assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(0);
     }
 
     @Test
@@ -322,26 +333,26 @@
                 new TunerFrontendInfo(2 /*id*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/);
         mTunerResourceManagerService.setFrontendInfoListInternal(infos);
 
-        int[] frontendId = new int[1];
+        int[] frontendHandle = new int[1];
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         try {
-            assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId))
-                    .isTrue();
+            assertThat(mTunerResourceManagerService
+                    .requestFrontendInternal(request, frontendHandle)).isTrue();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(frontendId[0]).isEqualTo(infos[0].getId());
+        assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(infos[0].getId());
 
         request =
                 new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         try {
-            assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId))
-                    .isTrue();
+            assertThat(mTunerResourceManagerService
+                    .requestFrontendInternal(request, frontendHandle)).isTrue();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(frontendId[0]).isEqualTo(infos[1].getId());
+        assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(infos[1].getId());
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
                 .isInUse()).isTrue();
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[2].getId())
@@ -382,10 +393,10 @@
 
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        int[] frontendId = new int[1];
+        int[] frontendHandle = new int[1];
         try {
-            assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId))
-                    .isTrue();
+            assertThat(mTunerResourceManagerService
+                    .requestFrontendInternal(request, frontendHandle)).isTrue();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -393,8 +404,8 @@
         request =
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         try {
-            assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId))
-                    .isFalse();
+            assertThat(mTunerResourceManagerService
+                    .requestFrontendInternal(request, frontendHandle)).isFalse();
             assertThat(listener.isRelaimed()).isFalse();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -403,8 +414,8 @@
         request =
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
         try {
-            assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId))
-                    .isFalse();
+            assertThat(mTunerResourceManagerService
+                    .requestFrontendInternal(request, frontendHandle)).isFalse();
             assertThat(listener.isRelaimed()).isFalse();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -444,24 +455,24 @@
 
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        int[] frontendId = new int[1];
+        int[] frontendHandle = new int[1];
         try {
-            assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId))
-                    .isTrue();
+            assertThat(mTunerResourceManagerService
+                    .requestFrontendInternal(request, frontendHandle)).isTrue();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(frontendId[0]).isEqualTo(infos[0].getId());
+        assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(infos[0].getId());
 
         request =
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
         try {
-            assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId))
-                    .isTrue();
+            assertThat(mTunerResourceManagerService
+                    .requestFrontendInternal(request, frontendHandle)).isTrue();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(frontendId[0]).isEqualTo(infos[1].getId());
+        assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(infos[1].getId());
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
                 .isInUse()).isTrue();
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
@@ -493,14 +504,14 @@
 
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        int[] frontendId = new int[1];
+        int[] frontendHandle = new int[1];
         try {
-            assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId))
-                    .isTrue();
+            assertThat(mTunerResourceManagerService
+                    .requestFrontendInternal(request, frontendHandle)).isTrue();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        assertThat(frontendId[0]).isEqualTo(infos[0].getId());
+        assertThat(getResourceIdFromHandle(frontendHandle[0])).isEqualTo(infos[0].getId());
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
                 .isInUse()).isTrue();
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
@@ -515,4 +526,38 @@
         assertThat(mTunerResourceManagerService.checkClientExists(clientId[0])).isFalse();
 
     }
+
+    @Test
+    public void requestDemuxTest() {
+        // Register client
+        ResourceClientProfile profile = new ResourceClientProfile("0" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        int[] clientId = new int[1];
+        mTunerResourceManagerService.registerClientProfileInternal(
+                profile, null /*listener*/, clientId);
+        assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+
+        int[] demuxHandle = new int[1];
+        TunerDemuxRequest request = new TunerDemuxRequest(clientId[0]);
+        assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle))
+                .isTrue();
+        assertThat(getResourceIdFromHandle(demuxHandle[0])).isEqualTo(0);
+    }
+
+    @Test
+    public void requestDescramblerTest() {
+        // Register client
+        ResourceClientProfile profile = new ResourceClientProfile("0" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        int[] clientId = new int[1];
+        mTunerResourceManagerService.registerClientProfileInternal(
+                profile, null /*listener*/, clientId);
+        assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+
+        int[] desHandle = new int[1];
+        TunerDescramblerRequest request = new TunerDescramblerRequest(clientId[0]);
+        assertThat(mTunerResourceManagerService.requestDescramblerInternal(request, desHandle))
+                .isTrue();
+        assertThat(getResourceIdFromHandle(desHandle[0])).isEqualTo(0);
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BubbleCheckerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BubbleCheckerTest.java
new file mode 100644
index 0000000..9636342
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BubbleCheckerTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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.notification;
+
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BubbleCheckerTest extends UiServiceTestCase {
+
+    private static final String SHORTCUT_ID = "shortcut";
+    private static final String PKG = "pkg";
+    private static final String KEY = "key";
+    private static final int USER_ID = 1;
+
+    @Mock
+    ActivityManager mActivityManager;
+    @Mock
+    RankingConfig mRankingConfig;
+    @Mock
+    ShortcutHelper mShortcutHelper;
+
+    @Mock
+    NotificationRecord mNr;
+    @Mock
+    UserHandle mUserHandle;
+    @Mock
+    Notification mNotif;
+    @Mock
+    StatusBarNotification mSbn;
+    @Mock
+    NotificationChannel mChannel;
+    @Mock
+    Notification.BubbleMetadata mBubbleMetadata;
+    @Mock
+    PendingIntent mPendingIntent;
+    @Mock
+    Intent mIntent;
+
+    BubbleExtractor.BubbleChecker mBubbleChecker;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mNr.getKey()).thenReturn(KEY);
+        when(mNr.getSbn()).thenReturn(mSbn);
+        when(mNr.getUser()).thenReturn(mUserHandle);
+        when(mUserHandle.getIdentifier()).thenReturn(USER_ID);
+        when(mNr.getChannel()).thenReturn(mChannel);
+        when(mSbn.getPackageName()).thenReturn(PKG);
+        when(mSbn.getUser()).thenReturn(mUserHandle);
+        when(mNr.getNotification()).thenReturn(mNotif);
+        when(mNotif.getBubbleMetadata()).thenReturn(mBubbleMetadata);
+
+        mBubbleChecker = new BubbleExtractor.BubbleChecker(mContext,
+                mShortcutHelper,
+                mRankingConfig,
+                mActivityManager);
+    }
+
+    void setUpIntentBubble() {
+        when(mPendingIntent.getIntent()).thenReturn(mIntent);
+        when(mBubbleMetadata.getBubbleIntent()).thenReturn(mPendingIntent);
+        when(mBubbleMetadata.getShortcutId()).thenReturn(null);
+    }
+
+    void setUpShortcutBubble(boolean isValid) {
+        when(mBubbleMetadata.getShortcutId()).thenReturn(SHORTCUT_ID);
+        when(mShortcutHelper.hasValidShortcutInfo(SHORTCUT_ID, PKG, mUserHandle))
+                .thenReturn(isValid);
+        when(mBubbleMetadata.getBubbleIntent()).thenReturn(null);
+    }
+
+    void setUpBubblesEnabled(boolean feature, boolean app, boolean channel) {
+        when(mRankingConfig.bubblesEnabled()).thenReturn(feature);
+        when(mRankingConfig.areBubblesAllowed(PKG, USER_ID)).thenReturn(app);
+        when(mChannel.canBubble()).thenReturn(channel);
+    }
+
+    void setUpActivityIntent(boolean isResizable) {
+        when(mPendingIntent.getIntent()).thenReturn(mIntent);
+        ActivityInfo info = new ActivityInfo();
+        info.resizeMode = isResizable
+                ? RESIZE_MODE_RESIZEABLE
+                : RESIZE_MODE_UNRESIZEABLE;
+        when(mIntent.resolveActivityInfo(any(), anyInt())).thenReturn(info);
+    }
+
+    //
+    // canBubble
+    //
+
+    @Test
+    public void testCanBubble_true_intentBubble() {
+        setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
+        setUpIntentBubble();
+        setUpActivityIntent(true /* isResizable */);
+        when(mActivityManager.isLowRamDevice()).thenReturn(false);
+        assertTrue(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+    }
+
+    @Test
+    public void testCanBubble_true_shortcutBubble() {
+        setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
+        setUpShortcutBubble(true /* isValid */);
+        assertTrue(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+    }
+
+    @Test
+    public void testCanBubble_false_noIntentInvalidShortcut() {
+        setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
+        setUpShortcutBubble(false /* isValid */);
+        assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+    }
+
+    @Test
+    public void testCanBubble_false_noIntentNoShortcut() {
+        setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
+        when(mBubbleMetadata.getBubbleIntent()).thenReturn(null);
+        when(mBubbleMetadata.getShortcutId()).thenReturn(null);
+        assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+    }
+
+    @Test
+    public void testCanBubbble_false_noMetadata() {
+        setUpBubblesEnabled(true/* feature */, true /* app */, true /* channel */);
+        when(mNotif.getBubbleMetadata()).thenReturn(null);
+        assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+    }
+
+    @Test
+    public void testCanBubble_false_bubblesNotEnabled() {
+        setUpBubblesEnabled(false /* feature */, true /* app */, true /* channel */);
+        assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+    }
+
+    @Test
+    public void testCanBubble_false_packageNotAllowed() {
+        setUpBubblesEnabled(true /* feature */, false /* app */, true /* channel */);
+        assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+    }
+
+    @Test
+    public void testCanBubble_false_channelNotAllowed() {
+        setUpBubblesEnabled(true /* feature */, true /* app */, false /* channel */);
+        assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+    }
+
+    //
+    // canLaunchInActivityView
+    //
+
+    @Test
+    public void testCanLaunchInActivityView_true() {
+        setUpActivityIntent(true /* resizable */);
+        assertTrue(mBubbleChecker.canLaunchInActivityView(mContext, mPendingIntent, PKG));
+    }
+
+    @Test
+    public void testCanLaunchInActivityView_false_noIntent() {
+        when(mPendingIntent.getIntent()).thenReturn(null);
+        assertFalse(mBubbleChecker.canLaunchInActivityView(mContext, mPendingIntent, PKG));
+    }
+
+    @Test
+    public void testCanLaunchInActivityView_false_noInfo() {
+        when(mPendingIntent.getIntent()).thenReturn(mIntent);
+        when(mIntent.resolveActivityInfo(any(), anyInt())).thenReturn(null);
+        assertFalse(mBubbleChecker.canLaunchInActivityView(mContext, mPendingIntent, PKG));
+    }
+
+    @Test
+    public void testCanLaunchInActivityView_false_notResizable() {
+        setUpActivityIntent(false  /* resizable */);
+        assertFalse(mBubbleChecker.canLaunchInActivityView(mContext, mPendingIntent, PKG));
+    }
+
+    //
+    // isNotificationAppropriateToBubble
+    //
+
+    @Test
+    public void testIsNotifAppropriateToBubble_true() {
+        setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
+        setUpIntentBubble();
+        when(mActivityManager.isLowRamDevice()).thenReturn(false);
+        setUpActivityIntent(true /* resizable */);
+        doReturn(Notification.MessagingStyle.class).when(mNotif).getNotificationStyle();
+
+        assertTrue(mBubbleChecker.isNotificationAppropriateToBubble(mNr));
+    }
+
+    @Test
+    public void testIsNotifAppropriateToBubble_false_lowRam() {
+        setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
+        when(mActivityManager.isLowRamDevice()).thenReturn(true);
+        setUpActivityIntent(true /* resizable */);
+        doReturn(Notification.MessagingStyle.class).when(mNotif).getNotificationStyle();
+
+        assertFalse(mBubbleChecker.isNotificationAppropriateToBubble(mNr));
+    }
+
+    @Test
+    public void testIsNotifAppropriateToBubble_false_notMessageStyle() {
+        setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
+        when(mActivityManager.isLowRamDevice()).thenReturn(false);
+        setUpActivityIntent(true /* resizable */);
+        doReturn(Notification.BigPictureStyle.class).when(mNotif).getNotificationStyle();
+
+        assertFalse(mBubbleChecker.isNotificationAppropriateToBubble(mNr));
+    }
+
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
index 7459c4b..c7cef05 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
@@ -21,6 +21,7 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
@@ -46,6 +47,7 @@
 public class BubbleExtractorTest extends UiServiceTestCase {
 
     @Mock RankingConfig mConfig;
+    BubbleExtractor mBubbleExtractor;
 
     private String mPkg = "com.android.server.notification";
     private int mId = 1001;
@@ -57,6 +59,10 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mBubbleExtractor = new BubbleExtractor();
+        mBubbleExtractor.initialize(mContext, mock(NotificationUsageStats.class));
+        mBubbleExtractor.setConfig(mConfig);
+        mBubbleExtractor.setShortcutHelper(mock(ShortcutHelper.class));
     }
 
     private NotificationRecord getNotificationRecord(boolean allow, int importanceHigh) {
@@ -83,70 +89,55 @@
 
     @Test
     public void testAppYesChannelNo() {
-        BubbleExtractor extractor = new BubbleExtractor();
-        extractor.setConfig(mConfig);
-
         when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
         NotificationRecord r = getNotificationRecord(false, IMPORTANCE_UNSPECIFIED);
 
-        extractor.process(r);
+        mBubbleExtractor.process(r);
 
         assertFalse(r.canBubble());
     }
 
     @Test
     public void testAppNoChannelYes() throws Exception {
-        BubbleExtractor extractor = new BubbleExtractor();
-        extractor.setConfig(mConfig);
-
         when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(false);
         NotificationRecord r = getNotificationRecord(true, IMPORTANCE_HIGH);
 
-        extractor.process(r);
+        mBubbleExtractor.process(r);
 
         assertFalse(r.canBubble());
     }
 
     @Test
     public void testAppYesChannelYes() {
-        BubbleExtractor extractor = new BubbleExtractor();
-        extractor.setConfig(mConfig);
-
         when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
         NotificationRecord r = getNotificationRecord(true, IMPORTANCE_UNSPECIFIED);
 
-        extractor.process(r);
+        mBubbleExtractor.process(r);
 
         assertTrue(r.canBubble());
     }
 
     @Test
     public void testAppNoChannelNo() {
-        BubbleExtractor extractor = new BubbleExtractor();
-        extractor.setConfig(mConfig);
-
         when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(false);
         NotificationRecord r = getNotificationRecord(false, IMPORTANCE_UNSPECIFIED);
 
-        extractor.process(r);
+        mBubbleExtractor.process(r);
 
         assertFalse(r.canBubble());
     }
 
     @Test
     public void testAppYesChannelYesUserNo() {
-        BubbleExtractor extractor = new BubbleExtractor();
-        extractor.setConfig(mConfig);
-
         when(mConfig.bubblesEnabled()).thenReturn(false);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
         NotificationRecord r = getNotificationRecord(true, IMPORTANCE_HIGH);
 
-        extractor.process(r);
+        mBubbleExtractor.process(r);
 
         assertFalse(r.canBubble());
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index dc8d010..8e78047 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -35,7 +35,6 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.PendingIntent;
-import android.app.Person;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -191,7 +190,8 @@
                 tweak.canBubble(),
                 tweak.visuallyInterruptive(),
                 tweak.isConversation(),
-                tweak.getShortcutInfo()
+                tweak.getShortcutInfo(),
+                tweak.isBubble()
         );
         assertNotEquals(nru, nru2);
     }
@@ -270,7 +270,8 @@
                     canBubble(i),
                     visuallyInterruptive(i),
                     isConversation(i),
-                    getShortcutInfo(i)
+                    getShortcutInfo(i),
+                    isBubble(i)
             );
             rankings[i] = ranking;
         }
@@ -394,6 +395,10 @@
         return si;
     }
 
+    private boolean isBubble(int index) {
+        return index % 4 == 0;
+    }
+
     private void assertActionsEqual(
             List<Notification.Action> expecteds, List<Notification.Action> actuals) {
         assertEquals(expecteds.size(), actuals.size());
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 64d481a..f179840 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -41,8 +41,6 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -204,6 +202,8 @@
     private TestableNotificationManagerService mService;
     private INotificationManager mBinderService;
     private NotificationManagerInternal mInternalService;
+    private TestableBubbleChecker mTestableBubbleChecker;
+    private ShortcutHelper mShortcutHelper;
     @Mock
     private IPackageManager mPackageManager;
     @Mock
@@ -286,6 +286,10 @@
             super(context, logger, notificationInstanceIdSequence);
         }
 
+        RankingHelper getRankingHelper() {
+            return mRankingHelper;
+        }
+
         @Override
         protected boolean isCallingUidSystem() {
             countSystemChecks++;
@@ -337,6 +341,14 @@
         interface NotificationAssistantAccessGrantedCallback {
             void onGranted(ComponentName assistant, int userId, boolean granted);
         }
+    }
+
+    private class TestableBubbleChecker extends BubbleExtractor.BubbleChecker {
+
+        TestableBubbleChecker(Context context, ShortcutHelper helper, RankingConfig config,
+                ActivityManager manager) {
+            super(context, helper, config, manager);
+        }
 
         @Override
         protected boolean canLaunchInActivityView(Context context, PendingIntent pendingIntent,
@@ -448,7 +460,16 @@
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
 
         mService.setAudioManager(mAudioManager);
-        mService.setLauncherApps(mLauncherApps);
+
+        mShortcutHelper = mService.getShortcutHelper();
+        mShortcutHelper.setLauncherApps(mLauncherApps);
+
+        // Set the testable bubble extractor
+        RankingHelper rankingHelper = mService.getRankingHelper();
+        BubbleExtractor extractor = rankingHelper.findExtractor(BubbleExtractor.class);
+        mTestableBubbleChecker = new TestableBubbleChecker(mContext, mShortcutHelper,
+                mService.mPreferencesHelper, mActivityManager);
+        extractor.setBubbleChecker(mTestableBubbleChecker);
 
         // Tests call directly into the Binder.
         mBinderService = mService.getBinderService();
@@ -462,6 +483,15 @@
     }
 
     @After
+    public void assertNotificationRecordLoggerCallsValid() {
+        for (NotificationRecordLoggerFake.CallRecord call : mNotificationRecordLogger.getCalls()) {
+            if (call.wasLogged) {
+                assertNotNull(call.event);
+            }
+        }
+    }
+
+    @After
     public void tearDown() throws Exception {
         if (mFile != null) mFile.delete();
         clearDeviceConfig();
@@ -1150,10 +1180,10 @@
         mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
                 generateNotificationRecord(null).getNotification(), 0);
         waitForIdle();
-        assertEquals(1, mNotificationRecordLogger.getCalls().size());
+        assertEquals(1, mNotificationRecordLogger.numCalls());
 
         NotificationRecordLoggerFake.CallRecord call = mNotificationRecordLogger.get(0);
-        assertTrue(call.shouldLogReported);
+        assertTrue(call.wasLogged);
         assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
                 call.event);
         assertNotNull(call.r);
@@ -1179,18 +1209,18 @@
                 .setCategory(Notification.CATEGORY_ALARM).build();
         mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, update, 0);
         waitForIdle();
-        assertEquals(2, mNotificationRecordLogger.getCalls().size());
+        assertEquals(2, mNotificationRecordLogger.numCalls());
 
-        assertTrue(mNotificationRecordLogger.get(0).shouldLogReported);
+        assertTrue(mNotificationRecordLogger.get(0).wasLogged);
         assertEquals(
                 NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
-                mNotificationRecordLogger.get(0).event);
+                mNotificationRecordLogger.event(0));
         assertEquals(1, mNotificationRecordLogger.get(0).getInstanceId());
 
-        assertTrue(mNotificationRecordLogger.get(1).shouldLogReported);
+        assertTrue(mNotificationRecordLogger.get(1).wasLogged);
         assertEquals(
                 NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED,
-                mNotificationRecordLogger.get(1).event);
+                mNotificationRecordLogger.event(1));
         // Instance ID doesn't change on update of an active notification
         assertEquals(1, mNotificationRecordLogger.get(1).getInstanceId());
     }
@@ -1203,13 +1233,13 @@
         mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
                 generateNotificationRecord(null).getNotification(), 0);
         waitForIdle();
-        assertEquals(2, mNotificationRecordLogger.getCalls().size());
-        assertTrue(mNotificationRecordLogger.get(0).shouldLogReported);
+        assertEquals(2, mNotificationRecordLogger.numCalls());
+        assertTrue(mNotificationRecordLogger.get(0).wasLogged);
         assertEquals(
                 NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
-                mNotificationRecordLogger.get(0).event);
-        assertFalse(mNotificationRecordLogger.get(1).shouldLogReported);
-        assertNull(mNotificationRecordLogger.get(1).event);
+                mNotificationRecordLogger.event(0));
+        assertFalse(mNotificationRecordLogger.get(1).wasLogged);
+        assertNull(mNotificationRecordLogger.event(1));
     }
 
     @Test
@@ -1222,11 +1252,11 @@
         notif.extras.putString(Notification.EXTRA_TITLE, "Changed title");
         mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notif, 0);
         waitForIdle();
-        assertEquals(2, mNotificationRecordLogger.getCalls().size());
+        assertEquals(2, mNotificationRecordLogger.numCalls());
         assertEquals(
                 NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
-                mNotificationRecordLogger.get(0).event);
-        assertNull(mNotificationRecordLogger.get(1).event);
+                mNotificationRecordLogger.event(0));
+        assertNull(mNotificationRecordLogger.event(1));
     }
 
     @Test
@@ -1241,23 +1271,23 @@
         waitForIdle();
         mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, 0);
         waitForIdle();
-        assertEquals(3, mNotificationRecordLogger.getCalls().size());
+        assertEquals(3, mNotificationRecordLogger.numCalls());
 
         assertEquals(
                 NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
-                mNotificationRecordLogger.get(0).event);
-        assertTrue(mNotificationRecordLogger.get(0).shouldLogReported);
+                mNotificationRecordLogger.event(0));
+        assertTrue(mNotificationRecordLogger.get(0).wasLogged);
         assertEquals(1, mNotificationRecordLogger.get(0).getInstanceId());
 
         assertEquals(
                 NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_APP_CANCEL,
-                mNotificationRecordLogger.get(1).event);
+                mNotificationRecordLogger.event(1));
         assertEquals(1, mNotificationRecordLogger.get(1).getInstanceId());
 
         assertEquals(
                 NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
-                mNotificationRecordLogger.get(2).event);
-        assertTrue(mNotificationRecordLogger.get(2).shouldLogReported);
+                mNotificationRecordLogger.event(2));
+        assertTrue(mNotificationRecordLogger.get(2).wasLogged);
         // New instance ID because notification was canceled before re-post
         assertEquals(2, mNotificationRecordLogger.get(2).getInstanceId());
     }
@@ -1269,7 +1299,7 @@
         waitForIdle();
         // The notification record logger doesn't even get called when a nonexistent notification
         // is cancelled, because that happens very frequently and is not interesting.
-        assertEquals(0, mNotificationRecordLogger.getCalls().size());
+        assertEquals(0, mNotificationRecordLogger.numCalls());
     }
 
     @Test
@@ -2527,6 +2557,13 @@
         // only snooze the one notification
         verify(mSnoozeHelper, times(1)).snooze(any(NotificationRecord.class), anyLong());
         assertTrue(nonGrouped.getStats().hasSnoozed());
+
+        assertEquals(2, mNotificationRecordLogger.numCalls());
+        assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SNOOZED,
+                mNotificationRecordLogger.event(0));
+        assertEquals(
+                NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_SNOOZED,
+                mNotificationRecordLogger.event(1));
     }
 
     @Test
@@ -2569,6 +2606,12 @@
 
         // only snooze the one child
         verify(mSnoozeHelper, times(1)).snooze(any(NotificationRecord.class), anyLong());
+
+        assertEquals(2, mNotificationRecordLogger.numCalls());
+        assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SNOOZED,
+                mNotificationRecordLogger.event(0));
+        assertEquals(NotificationRecordLogger.NotificationCancelledEvent
+                        .NOTIFICATION_CANCEL_SNOOZED, mNotificationRecordLogger.event(1));
     }
 
     @Test
@@ -2588,6 +2631,18 @@
 
         // snooze child and summary
         verify(mSnoozeHelper, times(2)).snooze(any(NotificationRecord.class), anyLong());
+
+        assertEquals(4, mNotificationRecordLogger.numCalls());
+        assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SNOOZED,
+                mNotificationRecordLogger.event(0));
+        assertEquals(
+                NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_SNOOZED,
+                mNotificationRecordLogger.event(1));
+        assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SNOOZED,
+                mNotificationRecordLogger.event(2));
+        assertEquals(
+                NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_SNOOZED,
+                mNotificationRecordLogger.event(3));
     }
 
     @Test
@@ -2603,6 +2658,13 @@
 
         // snooze child only
         verify(mSnoozeHelper, times(1)).snooze(any(NotificationRecord.class), anyLong());
+
+        assertEquals(2, mNotificationRecordLogger.numCalls());
+        assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SNOOZED,
+                mNotificationRecordLogger.event(0));
+        assertEquals(
+                NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_SNOOZED,
+                mNotificationRecordLogger.event(1));
     }
 
     @Test
@@ -3386,6 +3448,10 @@
         mService.mNotificationDelegate.onNotificationDirectReplied(r.getKey());
         assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasDirectReplied());
         verify(mAssistants).notifyAssistantNotificationDirectReplyLocked(eq(r.getSbn()));
+
+        assertEquals(1, mNotificationRecordLogger.numCalls());
+        assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_DIRECT_REPLIED,
+                mNotificationRecordLogger.event(0));
     }
 
     @Test
@@ -3404,6 +3470,12 @@
         verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.getSbn()), eq(true),
                 eq((false)));
         assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
+
+        assertEquals(2, mNotificationRecordLogger.numCalls());
+        assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_DETAIL_OPEN_USER,
+                mNotificationRecordLogger.event(0));
+        assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_DETAIL_CLOSE_USER,
+                mNotificationRecordLogger.event(1));
     }
 
     @Test
@@ -3465,11 +3537,11 @@
 
         // Using mService.addNotification() does not generate a NotificationRecordLogger log,
         // so we only get the cancel notification.
-        assertEquals(1, mNotificationRecordLogger.getCalls().size());
+        assertEquals(1, mNotificationRecordLogger.numCalls());
 
         assertEquals(
                 NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_AOD,
-                mNotificationRecordLogger.get(0).event);
+                mNotificationRecordLogger.event(0));
         assertEquals(1, mNotificationRecordLogger.get(0).getInstanceId());
     }
 
@@ -4351,9 +4423,9 @@
                 {NotificationVisibility.obtain(r.getKey(), 1, 1, true)},
                 new NotificationVisibility[]{});
 
-        assertEquals(1, mNotificationRecordLogger.getCalls().size());
+        assertEquals(1, mNotificationRecordLogger.numCalls());
         assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_OPEN,
-                mNotificationRecordLogger.get(0).event);
+                mNotificationRecordLogger.event(0));
         assertEquals(1, mNotificationRecordLogger.get(0).getInstanceId());
 
         mService.mNotificationDelegate.onNotificationVisibilityChanged(
@@ -4362,9 +4434,9 @@
                         {NotificationVisibility.obtain(r.getKey(), 1, 1, true)}
         );
 
-        assertEquals(2, mNotificationRecordLogger.getCalls().size());
+        assertEquals(2, mNotificationRecordLogger.numCalls());
         assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_CLOSE,
-                mNotificationRecordLogger.get(1).event);
+                mNotificationRecordLogger.event(1));
         assertEquals(1, mNotificationRecordLogger.get(1).getInstanceId());
     }
 
@@ -4788,6 +4860,12 @@
 
         mService.mNotificationDelegate.onPanelHidden();
         verify(mAssistants, times(1)).onPanelHidden();
+
+        assertEquals(2, mNotificationRecordLogger.numCalls());
+        assertEquals(NotificationRecordLogger.NotificationPanelEvent.NOTIFICATION_PANEL_OPEN,
+                mNotificationRecordLogger.event(0));
+        assertEquals(NotificationRecordLogger.NotificationPanelEvent.NOTIFICATION_PANEL_CLOSE,
+                mNotificationRecordLogger.event(1));
     }
 
     @Test
@@ -4806,6 +4884,9 @@
                 modifiedBeforeSending);
         verify(mAssistants).notifyAssistantSuggestedReplySent(
                 eq(r.getSbn()), eq(reply), eq(generatedByAssistant));
+        assertEquals(1, mNotificationRecordLogger.numCalls());
+        assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SMART_REPLIED,
+                mNotificationRecordLogger.event(0));
     }
 
     @Test
@@ -4825,6 +4906,10 @@
                 generatedByAssistant);
         verify(mAssistants).notifyAssistantActionClicked(
                 eq(r.getSbn()), eq(actionIndex), eq(action), eq(generatedByAssistant));
+
+        assertEquals(1, mNotificationRecordLogger.numCalls());
+        assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_ACTION_CLICKED,
+                mNotificationRecordLogger.event(0));
     }
 
     @Test
@@ -5489,6 +5574,10 @@
         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));
     }
 
     @Test
@@ -5710,6 +5799,9 @@
 
     @Test
     public void testOnBubbleNotificationSuppressionChanged() throws Exception {
+        // Bubbles are allowed!
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
+
         // Bubble notification
         NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag");
 
@@ -6111,8 +6203,10 @@
         assertTrue(notif.isBubbleNotification());
 
         // Test: Remove the shortcut
+        when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null);
         launcherAppsCallback.getValue().onShortcutsChanged(PKG, Collections.emptyList(),
                 new UserHandle(mUid));
+        waitForIdle();
 
         // Verify:
 
@@ -6125,6 +6219,58 @@
         assertFalse(notif2.isBubbleNotification());
     }
 
+
+    @Test
+    public void testNotificationBubbles_shortcut_stopListeningWhenNotifRemoved()
+            throws RemoteException {
+        // Bubbles are allowed!
+        setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
+
+        ArgumentCaptor<LauncherApps.Callback> launcherAppsCallback =
+                ArgumentCaptor.forClass(LauncherApps.Callback.class);
+
+        // Messaging notification with shortcut info
+        Notification.BubbleMetadata metadata =
+                getBubbleMetadataBuilder().createShortcutBubble("someshortcutId").build();
+        Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+                null /* groupKey */, false /* isSummary */);
+        nb.setBubbleMetadata(metadata);
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "tag", mUid, 0, nb.build(), new UserHandle(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        // Pretend the shortcut exists
+        List<ShortcutInfo> shortcutInfos = new ArrayList<>();
+        ShortcutInfo info = mock(ShortcutInfo.class);
+        when(info.isLongLived()).thenReturn(true);
+        shortcutInfos.add(info);
+        when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos);
+
+        // Test: Send the bubble notification
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        // Verify:
+
+        // Make sure we register the callback for shortcut changes
+        verify(mLauncherApps, times(1)).registerCallback(launcherAppsCallback.capture(), any());
+
+        // yes allowed, yes messaging w/shortcut, yes bubble
+        Notification notif = mService.getNotificationRecord(nr.getSbn().getKey()).getNotification();
+        assertTrue(notif.isBubbleNotification());
+
+        // Test: Remove the notification
+        mBinderService.cancelNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        // Verify:
+
+        // Make sure callback is unregistered
+        verify(mLauncherApps, times(1)).unregisterCallback(launcherAppsCallback.getValue());
+    }
+
     @Test
     public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryDismissed()
             throws Exception {
@@ -6168,6 +6314,17 @@
         // The bubble should still exist
         StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
         assertEquals(1, notifsAfter.length);
+
+        // Check we got the click log and associated dismissal logs
+        assertEquals(6, mNotificationRecordLogger.numCalls());
+        // Skip the notification-creation logs
+        assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_CLICKED,
+                mNotificationRecordLogger.event(3));
+        assertEquals(NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_CLICK,
+                mNotificationRecordLogger.event(4));
+        assertEquals(NotificationRecordLogger.NotificationCancelledEvent
+                        .NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED,
+                mNotificationRecordLogger.event(5));
     }
 
     @Test
@@ -6188,6 +6345,11 @@
         // THEN the bubble should still exist
         StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
         assertEquals(1, notifsAfter.length);
+
+        // Check we got the click log
+        assertEquals(1, mNotificationRecordLogger.numCalls());
+        assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_CLICKED,
+                mNotificationRecordLogger.event(0));
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
index 2a17bae..6b18cc6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
@@ -31,25 +31,29 @@
         // The following fields are only relevant to maybeLogNotificationPosted() calls.
         static final int INVALID = -1;
         public int position = INVALID, buzzBeepBlink = INVALID;
-        public boolean shouldLogReported;
+        public boolean wasLogged;
 
         CallRecord(NotificationRecord r, NotificationRecord old, int position,
                 int buzzBeepBlink) {
             super(r, old);
             this.position = position;
             this.buzzBeepBlink = buzzBeepBlink;
-            shouldLogReported = shouldLogReported(buzzBeepBlink);
-            event = shouldLogReported ? NotificationReportedEvent.fromRecordPair(this) : null;
+            wasLogged = shouldLogReported(buzzBeepBlink);
+            event = wasLogged ? NotificationReportedEvent.fromRecordPair(this) : null;
         }
 
         CallRecord(NotificationRecord r, UiEventLogger.UiEventEnum event) {
             super(r, null);
-            shouldLogReported = false;
+            wasLogged = true;
             this.event = event;
         }
     }
     private List<CallRecord> mCalls = new ArrayList<>();
 
+    public int numCalls() {
+        return mCalls.size();
+    }
+
     List<CallRecord> getCalls() {
         return mCalls;
     }
@@ -57,6 +61,9 @@
     CallRecord get(int index) {
         return mCalls.get(index);
     }
+    UiEventLogger.UiEventEnum event(int index) {
+        return mCalls.get(index).event;
+    }
 
     @Override
     public void maybeLogNotificationPosted(NotificationRecord r, NotificationRecord old,
@@ -65,13 +72,12 @@
     }
 
     @Override
-    public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) {
-        mCalls.add(new CallRecord(r,
-                NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface)));
+    public void log(UiEventLogger.UiEventEnum event, NotificationRecord r) {
+        mCalls.add(new CallRecord(r, event));
     }
 
     @Override
-    public void logNotificationVisibility(NotificationRecord r, boolean visible) {
-        mCalls.add(new CallRecord(r, NotificationEvent.fromVisibility(visible)));
+    public void log(UiEventLogger.UiEventEnum event) {
+        mCalls.add(new CallRecord(null, event));
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
index 551e186..5a527a2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
@@ -3,8 +3,10 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 
+import android.app.Application;
 import android.content.Intent;
 import android.net.Uri;
 import android.service.notification.Condition;
@@ -45,7 +47,7 @@
                 null,               // ActivityThread not actually used in Service
                 ScheduleConditionProvider.class.getName(),
                 null,               // token not needed when not talking with the activity manager
-                null,
+                mock(Application.class),
                 null                // mocked services don't talk with the activity manager
                 );
         service.onCreate();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
new file mode 100644
index 0000000..50fb9b4
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.notification;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableLooper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.UiServiceTestCase;
+
+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;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@TestableLooper.RunWithLooper
+public class ShortcutHelperTest extends UiServiceTestCase {
+
+    private static final String SHORTCUT_ID = "shortcut";
+    private static final String PKG = "pkg";
+    private static final String KEY = "key";
+
+    @Mock
+    LauncherApps mLauncherApps;
+    @Mock
+    ShortcutHelper.ShortcutListener mShortcutListener;
+    @Mock
+    NotificationRecord mNr;
+    @Mock
+    Notification mNotif;
+    @Mock
+    StatusBarNotification mSbn;
+    @Mock
+    Notification.BubbleMetadata mBubbleMetadata;
+
+    ShortcutHelper mShortcutHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mShortcutHelper = new ShortcutHelper(mLauncherApps, mShortcutListener);
+        when(mNr.getKey()).thenReturn(KEY);
+        when(mNr.getSbn()).thenReturn(mSbn);
+        when(mSbn.getPackageName()).thenReturn(PKG);
+        when(mNr.getNotification()).thenReturn(mNotif);
+        when(mNotif.getBubbleMetadata()).thenReturn(mBubbleMetadata);
+        when(mBubbleMetadata.getShortcutId()).thenReturn(SHORTCUT_ID);
+    }
+
+    private LauncherApps.Callback addShortcutBubbleAndVerifyListener() {
+        when(mNotif.isBubbleNotification()).thenReturn(true);
+
+        mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr,
+                false /* removed */,
+                null /* handler */);
+
+        ArgumentCaptor<LauncherApps.Callback> launcherAppsCallback =
+                ArgumentCaptor.forClass(LauncherApps.Callback.class);
+
+        verify(mLauncherApps, times(1)).registerCallback(
+                launcherAppsCallback.capture(), any());
+        return launcherAppsCallback.getValue();
+    }
+
+    @Test
+    public void testBubbleAdded_listenedAdded() {
+        addShortcutBubbleAndVerifyListener();
+    }
+
+    @Test
+    public void testBubbleRemoved_listenerRemoved() {
+        // First set it up to listen
+        addShortcutBubbleAndVerifyListener();
+
+        // Then remove the notif
+        mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr,
+                true /* removed */,
+                null /* handler */);
+
+        verify(mLauncherApps, times(1)).unregisterCallback(any());
+    }
+
+    @Test
+    public void testBubbleNoLongerBubble_listenerRemoved() {
+        // First set it up to listen
+        addShortcutBubbleAndVerifyListener();
+
+        // Then make it not a bubble
+        when(mNotif.isBubbleNotification()).thenReturn(false);
+        mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr,
+                false /* removed */,
+                null /* handler */);
+
+        verify(mLauncherApps, times(1)).unregisterCallback(any());
+    }
+
+    @Test
+    public void testListenerNotifiedOnShortcutRemoved() {
+        LauncherApps.Callback callback = addShortcutBubbleAndVerifyListener();
+
+        List<ShortcutInfo> shortcutInfos = new ArrayList<>();
+        when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos);
+
+        callback.onShortcutsChanged(PKG, shortcutInfos, mock(UserHandle.class));
+        verify(mShortcutListener).onShortcutRemoved(mNr.getKey());
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index c9c3649..9e87421 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1332,6 +1332,40 @@
         assertNotEquals(initialConf, wpc.getRequestedOverrideConfiguration());
     }
 
+    @Test
+    public void testActivityDestroyDoesntChangeProcessOverride() {
+        final ActivityRecord firstActivity =
+                createActivityOnDisplay(true /* defaultDisplay */, null /* process */);
+        final WindowProcessController wpc = firstActivity.app;
+        assertTrue(wpc.registeredForActivityConfigChanges());
+        assertEquals(0, firstActivity.getMergedOverrideConfiguration()
+                .diff(wpc.getRequestedOverrideConfiguration()));
+
+        final ActivityRecord secondActivity =
+                createActivityOnDisplay(false /* defaultDisplay */, wpc);
+        assertTrue(wpc.registeredForActivityConfigChanges());
+        assertEquals(0, secondActivity.getMergedOverrideConfiguration()
+                .diff(wpc.getRequestedOverrideConfiguration()));
+
+        final ActivityRecord thirdActivity =
+                createActivityOnDisplay(false /* defaultDisplay */, wpc);
+        assertTrue(wpc.registeredForActivityConfigChanges());
+        assertEquals(0, thirdActivity.getMergedOverrideConfiguration()
+                .diff(wpc.getRequestedOverrideConfiguration()));
+
+        secondActivity.destroyImmediately(true, "");
+
+        assertTrue(wpc.registeredForActivityConfigChanges());
+        assertEquals(0, thirdActivity.getMergedOverrideConfiguration()
+                .diff(wpc.getRequestedOverrideConfiguration()));
+
+        firstActivity.destroyImmediately(true, "");
+
+        assertTrue(wpc.registeredForActivityConfigChanges());
+        assertEquals(0, thirdActivity.getMergedOverrideConfiguration()
+                .diff(wpc.getRequestedOverrideConfiguration()));
+    }
+
     /**
      * Creates an activity on display. For non-default display request it will also create a new
      * display with custom DisplayInfo.
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 277bc41..67b1dac 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -27,6 +27,8 @@
 import static org.junit.Assert.assertTrue;
 
 import android.app.WindowConfiguration;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
@@ -109,5 +111,27 @@
         assertEquals(WindowConfiguration.WINDOWING_MODE_FREEFORM,
                 mWm.getDefaultDisplayContentLocked().getWindowingMode());
     }
+
+    /**
+     * This test ensures that an existing single instance activity with alias name can be found by
+     * the same activity info. So {@link ActivityStarter#getReusableTask} won't miss it that leads
+     * to create an unexpected new instance.
+     */
+    @Test
+    public void testFindActivityByTargetComponent() {
+        final ComponentName aliasComponent = ComponentName.createRelative(
+                ActivityTestsBase.DEFAULT_COMPONENT_PACKAGE_NAME, ".AliasActivity");
+        final ComponentName targetComponent = ComponentName.createRelative(
+                aliasComponent.getPackageName(), ".TargetActivity");
+        final ActivityRecord activity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+                .setComponent(aliasComponent)
+                .setTargetActivity(targetComponent.getClassName())
+                .setLaunchMode(ActivityInfo.LAUNCH_SINGLE_INSTANCE)
+                .setCreateTask(true)
+                .build();
+
+        assertEquals(activity, mWm.mRoot.findActivity(activity.intent, activity.info,
+                false /* compareIntentFilters */));
+    }
 }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index ba4a82f..edf81ea 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -107,10 +107,16 @@
         setUpApp(display);
 
         // Put app window into freeform and then make it a compat app.
-        mTask.setBounds(100, 100, 400, 600);
+        final Rect bounds = new Rect(100, 100, 400, 600);
+        mTask.setBounds(bounds);
         prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+        assertEquals(bounds, mActivity.getBounds());
 
-        final Rect bounds = new Rect(mActivity.getBounds());
+        // The activity should be able to accept negative x position [-150, 100 - 150, 600].
+        final int dx = bounds.left + bounds.width() / 2;
+        mTask.setBounds(bounds.left - dx, bounds.top, bounds.right - dx, bounds.bottom);
+        assertEquals(mTask.getBounds(), mActivity.getBounds());
+
         final int density = mActivity.getConfiguration().densityDpi;
 
         // change display configuration to fullscreen
@@ -231,11 +237,7 @@
         // The position should be horizontal centered.
         assertEquals((displayWidth - bounds.width()) / 2, bounds.left);
 
-        final WindowTestUtils.TestWindowState w = new WindowTestUtils.TestWindowState(
-                mService.mWindowManager, mock(Session.class), new TestIWindow(),
-                new WindowManager.LayoutParams(), mActivity);
-        mActivity.addWindow(w);
-        mActivity.mDisplayContent.mInputMethodTarget = w;
+        mActivity.mDisplayContent.mInputMethodTarget = addWindowToActivity(mActivity);
         // Make sure IME cannot attach to the app, otherwise IME window will also be shifted.
         assertFalse(mActivity.mDisplayContent.isImeAttachedToApp());
     }
@@ -475,6 +477,25 @@
         assertEquals((int) (dw * maxAspect), mActivity.getBounds().width());
         // The bounds should be horizontal centered: (2500-1900)/2=350.
         assertEquals((dh - mActivity.getBounds().width()) / 2, mActivity.getBounds().left);
+
+        // The letterbox needs a main window to layout.
+        addWindowToActivity(mActivity);
+        // Compute the frames of the window and invoke {@link ActivityRecord#layoutLetterbox}.
+        mActivity.mRootWindowContainer.performSurfacePlacement(false /* recoveringMemory */);
+        // The letterbox insets should be [350, 0 - 350, 0].
+        assertEquals(new Rect(mActivity.getBounds().left, 0, dh - mActivity.getBounds().right, 0),
+                mActivity.getLetterboxInsets());
+    }
+
+    private WindowState addWindowToActivity(ActivityRecord activity) {
+        final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+        params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+        final WindowTestUtils.TestWindowState w = new WindowTestUtils.TestWindowState(
+                mService.mWindowManager, mock(Session.class), new TestIWindow(), params, mActivity);
+        WindowTestsBase.makeWindowVisible(w);
+        w.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
+        mActivity.addWindow(w);
+        return w;
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 560d03f..091f493 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -322,6 +322,8 @@
     }
 
     private void tearDown() {
+        mWmService.mRoot.forAllDisplayPolicies(DisplayPolicy::release);
+
         // Unregister display listener from root to avoid issues with subsequent tests.
         mContext.getSystemService(DisplayManager.class)
                 .unregisterDisplayListener(mAtmService.mRootWindowContainer);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index 19ed7a9..bc81d5e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -32,6 +32,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -550,6 +551,7 @@
         final Task task = createTaskInStack(stackController1, 0 /* userId */);
         final ITaskOrganizer organizer = registerMockOrganizer();
         final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window");
+        makeWindowVisible(w);
 
         BLASTSyncEngine bse = new BLASTSyncEngine();
 
@@ -567,6 +569,60 @@
             .transactionReady(anyInt(), any());
     }
 
+    @Test
+    public void testBLASTCallbackWithInvisibleWindow() {
+        final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stackController1, 0 /* userId */);
+        final ITaskOrganizer organizer = registerMockOrganizer();
+        final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window");
+
+        BLASTSyncEngine bse = new BLASTSyncEngine();
+
+        BLASTSyncEngine.TransactionReadyListener transactionListener =
+            mock(BLASTSyncEngine.TransactionReadyListener.class);
+
+        int id = bse.startSyncSet(transactionListener);
+        bse.addToSyncSet(id, task);
+        bse.setReady(id);
+
+        // Since the window was invisible, the Task had no visible leaves and the sync should
+        // complete as soon as we call setReady.
+        verify(transactionListener)
+            .transactionReady(anyInt(), any());
+    }
+
+    @Test
+    public void testBLASTCallbackWithChildWindow() {
+        final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stackController1, 0 /* userId */);
+        final ITaskOrganizer organizer = registerMockOrganizer();
+        final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window");
+        final WindowState child = createWindow(w, TYPE_APPLICATION, "Other Window");
+
+        w.mActivityRecord.setVisible(true);
+        makeWindowVisible(w, child);
+
+        BLASTSyncEngine bse = new BLASTSyncEngine();
+
+        BLASTSyncEngine.TransactionReadyListener transactionListener =
+            mock(BLASTSyncEngine.TransactionReadyListener.class);
+
+        int id = bse.startSyncSet(transactionListener);
+        assertEquals(true, bse.addToSyncSet(id, task));
+        bse.setReady(id);
+        w.finishDrawing(null);
+
+        // Since we have a child window we still shouldn't be done.
+        verify(transactionListener, never())
+            .transactionReady(anyInt(), any());
+        reset(transactionListener);
+
+        child.finishDrawing(null);
+        // Ah finally! Done
+        verify(transactionListener)
+                .transactionReady(anyInt(), any());
+    }
+
     class StubOrganizer extends ITaskOrganizer.Stub {
         RunningTaskInfo mInfo;
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index ef9a73b..9d48955 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -98,8 +98,7 @@
 
     // Persist versioned backup files.
     // Should be false, except when testing new versions
-    // STOPSHIP: b/139937606 this should be false on launch
-    static final boolean KEEP_BACKUP_DIR = true;
+    static final boolean KEEP_BACKUP_DIR = false;
 
     private static final String TAG = "UsageStatsDatabase";
     private static final boolean DEBUG = UsageStatsService.DEBUG;
@@ -412,6 +411,7 @@
         }
 
         if (mBackupsDir.exists() && !KEEP_BACKUP_DIR) {
+            mUpgradePerformed = true; // updated here to ensure that data is cleaned up
             deleteDirectory(mBackupsDir);
         }
     }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 0d1b352..bbe9851 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -137,8 +137,7 @@
     private static final File USAGE_STATS_LEGACY_DIR = new File(
             Environment.getDataSystemDirectory(), "usagestats");
     // For migration purposes, indicates whether to keep the legacy usage stats directory or not
-    // STOPSHIP: b/138323140 this should be false on launch
-    private static final boolean KEEP_LEGACY_DIR = true;
+    private static final boolean KEEP_LEGACY_DIR = false;
 
     private static final char TOKEN_DELIMITER = '/';
 
@@ -648,7 +647,7 @@
 
     private void deleteLegacyDir(int userId) {
         final File legacyUserDir = new File(USAGE_STATS_LEGACY_DIR, Integer.toString(userId));
-        if (!KEEP_LEGACY_DIR) {
+        if (!KEEP_LEGACY_DIR && legacyUserDir.exists()) {
             deleteRecursively(legacyUserDir);
             if (legacyUserDir.exists()) {
                 Slog.w(TAG, "Error occurred while attempting to delete legacy usage stats "
diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java
index 34bac5d..3984bd7 100644
--- a/telephony/java/android/telephony/ImsManager.java
+++ b/telephony/java/android/telephony/ImsManager.java
@@ -19,9 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
 import android.annotation.SystemService;
-import android.annotation.TestApi;
 import android.content.Context;
 import android.telephony.SubscriptionManager;
 
@@ -44,8 +42,6 @@
      * issues.
      * @hide
      */
-    @SystemApi
-    @TestApi
     // Moved from TelephonyIntents, need to keep backwards compatibility with OEM apps that have
     // this value hard-coded in BroadcastReceiver.
     @SuppressLint("ActionValue")
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 1a79bf7..4940cb2 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -297,7 +297,7 @@
         mDataSpecificInfo = new DataSpecificRegistrationInfo(
                 maxDataCalls, isDcNrRestricted, isNrAvailable, isEndcAvailable, lteVopsSupportInfo,
                 isUsingCarrierAggregation);
-        updateNrState(mDataSpecificInfo);
+        updateNrState();
     }
 
     private NetworkRegistrationInfo(Parcel source) {
@@ -686,12 +686,12 @@
      * DCNR is not restricted and NR is supported by the selected PLMN. Otherwise the use of 5G
      * NR is restricted.
      *
-     * @param state data specific registration state contains the 5G NR indicators.
+     * @hide
      */
-    private void updateNrState(DataSpecificRegistrationInfo state) {
+    public void updateNrState() {
         mNrState = NR_STATE_NONE;
-        if (state.isEnDcAvailable) {
-            if (!state.isDcNrRestricted && state.isNrAvailable) {
+        if (mDataSpecificInfo.isEnDcAvailable) {
+            if (!mDataSpecificInfo.isDcNrRestricted && mDataSpecificInfo.isNrAvailable) {
                 mNrState = NR_STATE_NOT_RESTRICTED;
             } else {
                 mNrState = NR_STATE_RESTRICTED;
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index dd20d06..deba551 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1650,7 +1650,6 @@
      * @return Current data network type
      * @hide
      */
-    @SystemApi
     @TestApi
     public @NetworkType int getDataNetworkType() {
         final NetworkRegistrationInfo iwlanRegInfo = getNetworkRegistrationInfo(
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index d3afa4a..f6a305f 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -975,11 +975,7 @@
      *
      * @param packageName serves as the default package name if the package name that is
      *        associated with the user id is null.
-     *
-     * @hide
      */
-    @SystemApi
-    @TestApi
     public void sendMultipartTextMessage(
             @NonNull String destinationAddress, @Nullable String scAddress,
             @NonNull List<String> parts, @Nullable List<PendingIntent> sentIntents,
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 610ec5e..e559c2a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1565,6 +1565,7 @@
      * @hide
      */
     @SystemApi
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     @SuppressLint("ActionValue")
     public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED =
             "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
@@ -1786,6 +1787,7 @@
      * @hide
      */
     @SystemApi
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     @SuppressLint("ActionValue")
     public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED =
             "android.intent.action.EMERGENCY_CALL_STATE_CHANGED";
@@ -5280,8 +5282,8 @@
      *      not present or not loaded
      * @hide
      */
+    @UnsupportedAppUsage
     @Nullable
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String[] getIsimImpu() {
         try {
@@ -9189,7 +9191,6 @@
      *
      * @hide
      */
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public @CdmaRoamingMode int getCdmaRoamingMode() {
         int mode = CDMA_ROAMING_MODE_RADIO_DEFAULT;
@@ -9218,7 +9219,6 @@
      *
      * @hide
      */
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public boolean setCdmaRoamingMode(@CdmaRoamingMode int mode) {
         try {
@@ -9244,19 +9244,16 @@
     /** Used for CDMA subscription mode, it'll be UNKNOWN if there is no Subscription source.
      * @hide
      */
-    @SystemApi
     public static final int CDMA_SUBSCRIPTION_UNKNOWN  = -1;
 
     /** Used for CDMA subscription mode: RUIM/SIM (default)
      * @hide
      */
-    @SystemApi
     public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0;
 
     /** Used for CDMA subscription mode: NV -> non-volatile memory
      * @hide
      */
-    @SystemApi
     public static final int CDMA_SUBSCRIPTION_NV       = 1;
 
     /** @hide */
@@ -9275,7 +9272,6 @@
      *
      * @hide
      */
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public boolean setCdmaSubscriptionMode(@CdmaSubscription int mode) {
         try {
@@ -9983,18 +9979,30 @@
     }
 
     /**
-     * Gets the default Respond Via Message application
-     * @param context context from the calling app
-     * @param updateIfNeeded update the default app if there is no valid default app configured.
+     * Gets the default Respond Via Message application, updating the cache if there is no
+     * respond-via-message application currently configured.
      * @return component name of the app and class to direct Respond Via Message intent to, or
      * {@code null} if the functionality is not supported.
      * @hide
      */
     @SystemApi
     @TestApi
-    public static @Nullable ComponentName getDefaultRespondViaMessageApplication(
-            @NonNull Context context, boolean updateIfNeeded) {
-        return SmsApplication.getDefaultRespondViaMessageApplication(context, updateIfNeeded);
+    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+    public @Nullable ComponentName getAndUpdateDefaultRespondViaMessageApplication() {
+        return SmsApplication.getDefaultRespondViaMessageApplication(mContext, true);
+    }
+
+    /**
+     * Gets the default Respond Via Message application.
+     * @return component name of the app and class to direct Respond Via Message intent to, or
+     * {@code null} if the functionality is not supported.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+    public @Nullable ComponentName getDefaultRespondViaMessageApplication() {
+        return SmsApplication.getDefaultRespondViaMessageApplication(mContext, false);
     }
 
     /**
@@ -12886,7 +12894,6 @@
      *
      * @hide
      */
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public boolean setDataAllowedDuringVoiceCall(boolean allow) {
         try {
@@ -12915,7 +12922,6 @@
      *
      * @hide
      */
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public boolean isDataAllowedInVoiceCall() {
         try {
@@ -12962,7 +12968,6 @@
      * The IccLock state or password was changed successfully.
      * @hide
      */
-    @SystemApi
     public static final int CHANGE_ICC_LOCK_SUCCESS = Integer.MAX_VALUE;
 
     /**
@@ -12975,7 +12980,6 @@
      *
      * @hide
      */
-    @SystemApi
     @WorkerThread
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public boolean isIccLockEnabled() {
@@ -13012,7 +13016,6 @@
      *
      * @hide
      */
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public int setIccLockEnabled(boolean enabled, @NonNull String password) {
         checkNotNull(password, "setIccLockEnabled password can't be null.");
@@ -13046,7 +13049,6 @@
      *
      * @hide
      */
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public int changeIccLockPassword(@NonNull String oldPassword, @NonNull String newPassword) {
         checkNotNull(oldPassword, "changeIccLockPassword oldPassword can't be null.");
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 4b5303f..bd531da 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -21,7 +21,6 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressAutoDoc;
 import android.annotation.SuppressLint;
@@ -124,7 +123,7 @@
          * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
          */
         @Override
-        public void onUnregistered(@Nullable ImsReasonInfo info) {
+        public void onUnregistered(@NonNull ImsReasonInfo info) {
         }
 
         /**
@@ -136,7 +135,7 @@
         @Override
         public void onTechnologyChangeFailed(
                 @AccessNetworkConstants.TransportType int imsTransportType,
-                @Nullable ImsReasonInfo info) {
+                @NonNull ImsReasonInfo info) {
         }
     }
 
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 00fa942..1a606b7 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -85,6 +85,7 @@
 
     /**
      * There is no existing configuration for the queried provisioning key.
+     * @hide
      */
     public static final int PROVISIONING_RESULT_UNKNOWN = -1;
 
@@ -120,6 +121,7 @@
      * Value is in String format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_AMR_CODEC_MODE_SET_VALUES = 0;
 
@@ -143,6 +145,7 @@
      * Value is in String format.
      * @see #setProvisioningStringValue(int, String)
      * @see #getProvisioningStringValue(int)
+     * @hide
      */
     public static final int KEY_AMR_WB_CODEC_MODE_SET_VALUES = 1;
 
@@ -154,6 +157,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_SIP_SESSION_TIMER_SEC = 2;
 
@@ -165,6 +169,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_MINIMUM_SIP_SESSION_EXPIRATION_TIMER_SEC = 3;
 
@@ -176,6 +181,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_SIP_INVITE_CANCELLATION_TIMER_MS = 4;
 
@@ -184,6 +190,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_TRANSITION_TO_LTE_DELAY_MS = 5;
 
@@ -192,6 +199,7 @@
      * Value is in boolean format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_ENABLE_SILENT_REDIAL = 6;
 
@@ -207,6 +215,7 @@
      * The value is an integer.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_T1_TIMER_VALUE_MS = 7;
 
@@ -218,6 +227,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_T2_TIMER_VALUE_MS = 8;
 
@@ -229,6 +239,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_TF_TIMER_VALUE_MS = 9;
 
@@ -241,6 +252,7 @@
      * {@link #PROVISIONING_VALUE_DISABLED} to disable VoLTE provisioning.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_VOLTE_PROVISIONING_STATUS = 10;
 
@@ -253,6 +265,7 @@
      * {@link #PROVISIONING_VALUE_DISABLED} to disable VT provisioning.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_VT_PROVISIONING_STATUS = 11;
 
@@ -261,6 +274,7 @@
      * Value is in String format.
      * @see #setProvisioningStringValue(int, String)
      * @see #getProvisioningStringValue(int)
+     * @hide
      */
     public static final int KEY_REGISTRATION_DOMAIN_NAME = 12;
 
@@ -270,18 +284,21 @@
      * Valid values are {@link #SMS_FORMAT_3GPP} and {@link #SMS_FORMAT_3GPP2}.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_SMS_FORMAT = 13;
 
     /**
      * Value used with {@link #KEY_SMS_FORMAT} to indicate 3GPP2 SMS format is used.
      * See {@link android.telephony.SmsMessage#FORMAT_3GPP2} for more information.
+     * @hide
      */
     public static final int SMS_FORMAT_3GPP2 = 0;
 
     /**
      * Value used with {@link #KEY_SMS_FORMAT} to indicate 3GPP SMS format is used.
      * See {@link android.telephony.SmsMessage#FORMAT_3GPP} for more information.
+     * @hide
      */
     public static final int SMS_FORMAT_3GPP = 1;
 
@@ -290,6 +307,7 @@
      * Value is in Integer format. ON (1), OFF(0).
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_SMS_OVER_IP_ENABLED = 14;
 
@@ -300,6 +318,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15;
 
@@ -310,6 +329,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_RCS_PUBLISH_OFFLINE_AVAILABILITY_TIMER_SEC = 16;
 
@@ -322,6 +342,7 @@
      * enabled.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17;
 
@@ -334,6 +355,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18;
 
@@ -345,6 +367,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19;
 
@@ -356,6 +379,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20;
 
@@ -366,6 +390,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21;
 
@@ -376,6 +401,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22;
 
@@ -387,6 +413,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23;
 
@@ -395,6 +422,7 @@
      * Value is in Integer format. Enable (1), Disable(0).
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_USE_GZIP_FOR_LIST_SUBSCRIPTION = 24;
 
@@ -407,6 +435,7 @@
      * {@link #PROVISIONING_VALUE_DISABLED} to disable EAB provisioning.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_EAB_PROVISIONING_STATUS = 25;
 
@@ -440,6 +469,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE = 28;
 
@@ -448,6 +478,7 @@
      * Value is in Integer format. On (1), OFF(0).
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_MOBILE_DATA_ENABLED = 29;
 
@@ -456,12 +487,14 @@
      * Value is in Integer format. Opted-in (1) Opted-out (0).
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_VOLTE_USER_OPT_IN_STATUS = 30;
 
     /**
      * Proxy for Call Session Control Function(P-CSCF) address for Local-BreakOut(LBO).
      * Value is in String format.
+     * @hide
      */
     public static final int KEY_LOCAL_BREAKOUT_PCSCF_ADDRESS = 31;
 
@@ -470,6 +503,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_SIP_KEEP_ALIVE_ENABLED = 32;
 
@@ -479,6 +513,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_REGISTRATION_RETRY_BASE_TIME_SEC = 33;
 
@@ -488,6 +523,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_REGISTRATION_RETRY_MAX_TIME_SEC = 34;
 
@@ -496,6 +532,7 @@
      * Value is in integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
 
     public static final int KEY_RTP_SPEECH_START_PORT = 35;
@@ -505,6 +542,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_RTP_SPEECH_END_PORT = 36;
 
@@ -514,6 +552,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_SIP_INVITE_REQUEST_TRANSMIT_INTERVAL_MS = 37;
 
@@ -523,6 +562,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_SIP_INVITE_ACK_WAIT_TIME_MS = 38;
 
@@ -532,6 +572,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_WAIT_TIME_MS = 39;
 
@@ -541,6 +582,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMIT_INTERVAL_MS = 40;
 
@@ -550,6 +592,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_SIP_NON_INVITE_TRANSACTION_TIMEOUT_TIMER_MS = 41;
 
@@ -559,6 +602,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_INTERVAL_MS = 42;
 
@@ -568,6 +612,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_SIP_ACK_RECEIPT_WAIT_TIME_MS = 43;
 
@@ -577,6 +622,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_SIP_ACK_RETRANSMIT_WAIT_TIME_MS = 44;
 
@@ -586,6 +632,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMISSION_WAIT_TIME_MS = 45;
 
@@ -595,6 +642,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_SIP_NON_INVITE_RESPONSE_RETRANSMISSION_WAIT_TIME_MS = 46;
 
@@ -603,6 +651,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_AMR_WB_OCTET_ALIGNED_PAYLOAD_TYPE = 47;
 
@@ -611,6 +660,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_AMR_WB_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 48;
 
@@ -619,6 +669,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_AMR_OCTET_ALIGNED_PAYLOAD_TYPE = 49;
 
@@ -627,6 +678,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_AMR_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 50;
 
@@ -635,6 +687,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_DTMF_WB_PAYLOAD_TYPE = 51;
 
@@ -643,6 +696,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_DTMF_NB_PAYLOAD_TYPE = 52;
 
@@ -651,12 +705,14 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_AMR_DEFAULT_ENCODING_MODE = 53;
 
     /**
      * SMS Public Service Identity.
      * Value is in String format.
+     * @hide
      */
     public static final int KEY_SMS_PUBLIC_SERVICE_IDENTITY = 54;
 
@@ -666,16 +722,19 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_VIDEO_QUALITY = 55;
 
     /**
      * Used with {@link #KEY_VIDEO_QUALITY} to indicate low video quality.
+     * @hide
      */
     public static final int VIDEO_QUALITY_LOW = 0;
 
     /**
      * Used with {@link #KEY_VIDEO_QUALITY} to indicate high video quality.
+     * @hide
      */
     public static final int VIDEO_QUALITY_HIGH = 1;
 
@@ -685,6 +744,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_LTE_THRESHOLD_1 = 56;
 
@@ -696,6 +756,7 @@
      *
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_LTE_THRESHOLD_2 = 57;
 
@@ -707,6 +768,7 @@
      *
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_LTE_THRESHOLD_3 = 58;
 
@@ -716,6 +778,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_1X_THRESHOLD = 59;
 
@@ -727,6 +790,7 @@
      *
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_WIFI_THRESHOLD_A = 60;
 
@@ -737,6 +801,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_WIFI_THRESHOLD_B = 61;
 
@@ -746,6 +811,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_LTE_EPDG_TIMER_SEC = 62;
 
@@ -755,12 +821,14 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_WIFI_EPDG_TIMER_SEC = 63;
 
     /**
      * 1x ePDG timer (in seconds).
      * Device shall not re-register on 1x until the T_ePDG_1x timer expires.
+     * @hide
      */
     public static final int KEY_1X_EPDG_TIMER_SEC = 64;
 
@@ -769,6 +837,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_MULTIENDPOINT_ENABLED = 65;
 
@@ -777,6 +846,7 @@
      * Value is in Integer format.
      * @see #setProvisioningIntValue(int, int)
      * @see #getProvisioningIntValue(int)
+     * @hide
      */
     public static final int KEY_RTT_ENABLED = 66;
 
@@ -928,7 +998,7 @@
      *
      * @param key An integer that represents the provisioning key, which is defined by the OEM.
      * @return an integer value for the provided key, or
-     * {@link #PROVISIONING_RESULT_UNKNOWN} if the key doesn't exist.
+     * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist.
      * @throws IllegalArgumentException if the key provided was invalid.
      */
     @WorkerThread
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index 57b9b7a..dc36edf 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -19,8 +19,6 @@
 import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -37,8 +35,6 @@
  * Contains the User Capability Exchange capabilities corresponding to a contact's URI.
  * @hide
  */
-@SystemApi
-@TestApi
 public final class RcsContactUceCapability implements Parcelable {
 
     /** Supports 1-to-1 chat */
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index 5c86ba7..1dbaff5 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -196,11 +196,11 @@
         }
 
         /**
-         * Notifies the framework when the IMS Provider is deregistered from the IMS network.
+         * Notifies the framework when the IMS Provider is unregistered from the IMS network.
          *
          * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
          */
-        public void onUnregistered(@Nullable ImsReasonInfo info) {
+        public void onUnregistered(@NonNull ImsReasonInfo info) {
         }
 
         /**
@@ -211,7 +211,7 @@
          */
         public void onTechnologyChangeFailed(
                 @AccessNetworkConstants.TransportType int imsTransportType,
-                @Nullable ImsReasonInfo info) {
+                @NonNull ImsReasonInfo info) {
         }
 
         /**
diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index 14a64d2..7069e0a 100644
--- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -175,9 +175,11 @@
      */
     public final void onDeregistered(ImsReasonInfo info) {
         updateToDisconnectedState(info);
+        // ImsReasonInfo should never be null.
+        final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
         mCallbacks.broadcastAction((c) -> {
             try {
-                c.onDeregistered(info);
+                c.onDeregistered(reasonInfo);
             } catch (RemoteException e) {
                 Log.w(LOG_TAG, e + " " + "onRegistrationDisconnected() - Skipping " +
                         "callback.");
@@ -194,9 +196,10 @@
      */
     public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech,
             ImsReasonInfo info) {
+        final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
         mCallbacks.broadcastAction((c) -> {
             try {
-                c.onTechnologyChangeFailed(imsRadioTech, info);
+                c.onTechnologyChangeFailed(imsRadioTech, reasonInfo);
             } catch (RemoteException e) {
                 Log.w(LOG_TAG, e + " " + "onRegistrationChangeFailed() - Skipping " +
                         "callback.");
diff --git a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
index 386a58e..8564f7a 100644
--- a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
@@ -349,6 +349,7 @@
 
     /**
      * Updates the configuration of the call barring for specified service class with password.
+     * @hide
      */
     public int updateCallBarringWithPassword(int cbType, int action, @Nullable String[] barrList,
             int serviceClass, @NonNull String password) {
diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java
index 504bd17..b805744 100644
--- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java
+++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java
@@ -38,38 +38,75 @@
 public class DummyBlobData {
     private static final long DEFAULT_SIZE_BYTES = 10 * 1024L * 1024L;
 
-    private final Context mContext;
     private final Random mRandom;
     private final File mFile;
     private final long mFileSize;
-    private final String mLabel;
+    private final CharSequence mLabel;
 
     byte[] mFileDigest;
     long mExpiryTimeMs;
 
-    public DummyBlobData(Context context) {
-        this(context, new Random(0), "blob_" + System.nanoTime());
+    public DummyBlobData(Builder builder) {
+        mRandom = new Random(builder.getRandomSeed());
+        mFile = new File(builder.getContext().getFilesDir(), builder.getFileName());
+        mFileSize = builder.getFileSize();
+        mLabel = builder.getLabel();
     }
 
-    public DummyBlobData(Context context, long fileSize) {
-        this(context, fileSize, new Random(0), "blob_" + System.nanoTime(), "Test label");
-    }
+    public static class Builder {
+        private final Context mContext;
+        private int mRandomSeed = 0;
+        private long mFileSize = DEFAULT_SIZE_BYTES;
+        private CharSequence mLabel = "Test label";
+        private String mFileName = "blob_" + System.nanoTime();
 
-    public DummyBlobData(Context context, Random random, String fileName) {
-        this(context, DEFAULT_SIZE_BYTES, random, fileName, "Test label");
-    }
+        public Builder(Context context) {
+            mContext = context;
+        }
 
-    public DummyBlobData(Context context, Random random, String fileName, String label) {
-        this(context, DEFAULT_SIZE_BYTES, random, fileName, label);
-    }
+        public Context getContext() {
+            return mContext;
+        }
 
-    public DummyBlobData(Context context, long fileSize, Random random, String fileName,
-            String label) {
-        mContext = context;
-        mRandom = random;
-        mFile = new File(mContext.getFilesDir(), fileName);
-        mFileSize = fileSize;
-        mLabel = label;
+        public Builder setRandomSeed(int randomSeed) {
+            mRandomSeed = randomSeed;
+            return this;
+        }
+
+        public int getRandomSeed() {
+            return mRandomSeed;
+        }
+
+        public Builder setFileSize(int fileSize) {
+            mFileSize = fileSize;
+            return this;
+        }
+
+        public long getFileSize() {
+            return mFileSize;
+        }
+
+        public Builder setLabel(CharSequence label) {
+            mLabel = label;
+            return this;
+        }
+
+        public CharSequence getLabel() {
+            return mLabel;
+        }
+
+        public Builder setFileName(String fileName) {
+            mFileName = fileName;
+            return this;
+        }
+
+        public String getFileName() {
+            return mFileName;
+        }
+
+        public DummyBlobData build() {
+            return new DummyBlobData(this);
+        }
     }
 
     public void prepare() throws Exception {
diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
index c35385c..654c1e2 100644
--- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
+++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
@@ -16,7 +16,13 @@
 
 package com.android.utils.blob;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.blob.BlobHandle;
 import android.app.blob.BlobStoreManager;
+import android.app.blob.LeaseInfo;
+import android.content.Context;
+import android.content.res.Resources;
 import android.os.ParcelFileDescriptor;
 
 import java.io.FileInputStream;
@@ -56,4 +62,76 @@
             copy(in, out, lengthBytes);
         }
     }
+
+    public static void assertLeasedBlobs(BlobStoreManager blobStoreManager,
+            BlobHandle... expectedBlobHandles) throws IOException {
+        assertThat(blobStoreManager.getLeasedBlobs()).containsExactly(expectedBlobHandles);
+    }
+
+    public static void assertNoLeasedBlobs(BlobStoreManager blobStoreManager)
+            throws IOException {
+        assertThat(blobStoreManager.getLeasedBlobs()).isEmpty();
+    }
+
+    public static void acquireLease(Context context,
+            BlobHandle blobHandle, CharSequence description) throws IOException {
+        final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService(
+                Context.BLOB_STORE_SERVICE);
+        blobStoreManager.acquireLease(blobHandle, description);
+
+        final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle);
+        assertLeaseInfo(leaseInfo, context.getPackageName(), 0,
+                Resources.ID_NULL, description);
+    }
+
+    public static void acquireLease(Context context,
+            BlobHandle blobHandle, int descriptionResId) throws IOException {
+        final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService(
+                Context.BLOB_STORE_SERVICE);
+        blobStoreManager.acquireLease(blobHandle, descriptionResId);
+
+        final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle);
+        assertLeaseInfo(leaseInfo, context.getPackageName(), 0,
+                descriptionResId, context.getString(descriptionResId));
+    }
+
+    public static void acquireLease(Context context,
+            BlobHandle blobHandle, CharSequence description,
+            long expiryTimeMs) throws IOException {
+        final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService(
+                Context.BLOB_STORE_SERVICE);
+        blobStoreManager.acquireLease(blobHandle, description, expiryTimeMs);
+
+        final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle);
+        assertLeaseInfo(leaseInfo, context.getPackageName(), expiryTimeMs,
+                Resources.ID_NULL, description);
+    }
+
+    public static void acquireLease(Context context,
+            BlobHandle blobHandle, int descriptionResId,
+            long expiryTimeMs) throws IOException {
+        final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService(
+                Context.BLOB_STORE_SERVICE);
+        blobStoreManager.acquireLease(blobHandle, descriptionResId, expiryTimeMs);
+
+        final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle);
+        assertLeaseInfo(leaseInfo, context.getPackageName(), expiryTimeMs,
+                descriptionResId, context.getString(descriptionResId));
+    }
+
+    public static void releaseLease(Context context,
+            BlobHandle blobHandle) throws IOException {
+        final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService(
+                Context.BLOB_STORE_SERVICE);
+        blobStoreManager.releaseLease(blobHandle);
+        assertThat(blobStoreManager.getLeaseInfo(blobHandle)).isNull();
+    }
+
+    private static void assertLeaseInfo(LeaseInfo leaseInfo, String packageName,
+            long expiryTimeMs, int descriptionResId, CharSequence description) {
+        assertThat(leaseInfo.getPackageName()).isEqualTo(packageName);
+        assertThat(leaseInfo.getExpiryTimeMillis()).isEqualTo(expiryTimeMs);
+        assertThat(leaseInfo.getDescriptionResId()).isEqualTo(descriptionResId);
+        assertThat(leaseInfo.getDescription()).isEqualTo(description);
+    }
 }
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 8cc8cf4..dfaac2c 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -1063,6 +1063,20 @@
         assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
     }
 
+    /**
+     * Ensure that passing a null list of failed packages does not cause any mitigation logic to
+     * execute.
+     */
+    @Test
+    public void testNullFailedPackagesList() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+        watchdog.startObservingHealth(observer1, List.of(APP_A), LONG_DURATION);
+
+        raiseFatalFailureAndDispatch(watchdog, null, PackageWatchdog.FAILURE_REASON_APP_CRASH);
+        assertThat(observer1.mMitigatedPackages).isEmpty();
+    }
+
     private void adoptShellPermissions(String... permissions) {
         InstrumentationRegistry
                 .getInstrumentation()
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
index 6169671..3bc5309 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
@@ -66,7 +66,7 @@
         WindowManager.LayoutParams lp =
             new WindowManager.LayoutParams(500, 500, WindowManager.LayoutParams.TYPE_APPLICATION,
                     0, PixelFormat.OPAQUE);
-        mVr.addView(v, lp);
+        mVr.setView(v, lp);
     }
 
     @Override
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java
index e415170..f254e4d 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java
@@ -34,9 +34,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
-import android.view.Window;
 import android.view.WindowInsets;
-import android.view.WindowInsets.Type;
 import android.view.WindowInsetsAnimation;
 import android.view.WindowInsetsAnimation.Callback;
 import android.view.WindowInsetsAnimationControlListener;
@@ -101,7 +99,7 @@
                                 && !mRequestedController) {
                             mRequestedController = true;
                             v.getWindowInsetsController().controlWindowInsetsAnimation(ime(),
-                                    1000, new LinearInterpolator(),
+                                    1000, new LinearInterpolator(), null /* cancellationSignal */,
                                     mCurrentRequest = new WindowInsetsAnimationControlListener() {
                                         @Override
                                         public void onReady(
@@ -208,7 +206,7 @@
                         if ((types & ime()) != 0 && !hasControl) {
                             hasControl = true;
                             controller.controlWindowInsetsAnimation(ime(), -1,
-                                    new LinearInterpolator(),
+                                    new LinearInterpolator(), null /* cancellationSignal */,
                                     new WindowInsetsAnimationControlListener() {
                                         @Override
                                         public void onReady(
diff --git a/tests/net/common/java/android/net/LinkAddressTest.java b/tests/net/common/java/android/net/LinkAddressTest.java
index 99dac14..c74c112 100644
--- a/tests/net/common/java/android/net/LinkAddressTest.java
+++ b/tests/net/common/java/android/net/LinkAddressTest.java
@@ -367,6 +367,9 @@
                     -2, 100000L);
             fail("negative deprecation time should cause exception");
         } catch (IllegalArgumentException expected) { }
+
+        LinkAddress addr = new LinkAddress(V6_ADDRESS, 64, 0, 456, 100000L, 200000L);
+        assertEquals(100000L, addr.getDeprecationTime());
     }
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
@@ -382,6 +385,9 @@
                     100000L, -2);
             fail("negative expiration time should cause exception");
         } catch (IllegalArgumentException expected) { }
+
+        LinkAddress addr = new LinkAddress(V6_ADDRESS, 64, 0, 456, 100000L, 200000L);
+        assertEquals(200000L, addr.getExpirationTime());
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 6985415..671c564 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -2425,7 +2425,7 @@
         assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
         assertTrue(testFactory.getMyStartRequested());
 
-        testFactory.unregister();
+        testFactory.terminate();
         if (networkCallback != null) mCm.unregisterNetworkCallback(networkCallback);
         handlerThread.quit();
     }
@@ -2451,6 +2451,38 @@
     }
 
     @Test
+    public void testNetworkFactoryUnregister() throws Exception {
+        final NetworkCapabilities filter = new NetworkCapabilities();
+        filter.clearAll();
+
+        final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
+        handlerThread.start();
+
+        // Checks that calling setScoreFilter on a NetworkFactory immediately before closing it
+        // does not crash.
+        for (int i = 0; i < 100; i++) {
+            final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
+                    mServiceContext, "testFactory", filter);
+            // Register the factory and don't be surprised when the default request arrives.
+            testFactory.register();
+            testFactory.expectAddRequestsWithScores(0);
+            testFactory.waitForNetworkRequests(1);
+
+            testFactory.setScoreFilter(42);
+            testFactory.terminate();
+
+            if (i % 2 == 0) {
+                try {
+                    testFactory.register();
+                    fail("Re-registering terminated NetworkFactory should throw");
+                } catch (IllegalStateException expected) {
+                }
+            }
+        }
+        handlerThread.quit();
+    }
+
+    @Test
     public void testNoMutableNetworkRequests() throws Exception {
         PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent("a"), 0);
         NetworkRequest request1 = new NetworkRequest.Builder()
@@ -3482,7 +3514,7 @@
         cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         assertLength(1, mCm.getAllNetworks());
 
-        testFactory.unregister();
+        testFactory.terminate();
         mCm.unregisterNetworkCallback(cellNetworkCallback);
         handlerThread.quit();
     }
@@ -3723,7 +3755,7 @@
         mCm.requestNetwork(nr, networkCallback, timeoutMs);
 
         // pass timeout and validate that UNAVAILABLE is called
-        networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, null);
+        networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null);
 
         // create a network satisfying request - validate that request not triggered
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -3814,7 +3846,7 @@
             // Simulate the factory releasing the request as unfulfillable and expect onUnavailable!
             testFactory.triggerUnfulfillable(requests.get(newRequestId));
 
-            networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, null);
+            networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null);
             testFactory.waitForRequests();
 
             // unregister network callback - a no-op (since already freed by the
@@ -3822,7 +3854,7 @@
             mCm.unregisterNetworkCallback(networkCallback);
         }
 
-        testFactory.unregister();
+        testFactory.terminate();
         handlerThread.quit();
     }
 
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 70542b5..727952c 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -695,31 +695,6 @@
      */
     public AnqpInformationElement[] anqpElements;
 
-    /**
-     * Flag indicating if this AP is a carrier AP. The determination is based
-     * on the AP's SSID and if AP is using EAP security.
-     *
-     * @hide
-     */
-    // TODO(b/144431927): remove once migrated to Suggestions
-    public boolean isCarrierAp;
-
-    /**
-     * The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
-     *
-     * @hide
-     */
-    // TODO(b/144431927): remove once migrated to Suggestions
-    public int carrierApEapType;
-
-    /**
-     * The name of the carrier that's associated with this AP if it is a carrier AP.
-     *
-     * @hide
-     */
-    // TODO(b/144431927): remove once migrated to Suggestions
-    public String carrierName;
-
     /** {@hide} */
     public ScanResult(WifiSsid wifiSsid, String BSSID, long hessid, int anqpDomainId,
             byte[] osuProviders, String caps, int level, int frequency, long tsf) {
@@ -744,9 +719,6 @@
         this.centerFreq0 = UNSPECIFIED;
         this.centerFreq1 = UNSPECIFIED;
         this.flags = 0;
-        this.isCarrierAp = false;
-        this.carrierApEapType = UNSPECIFIED;
-        this.carrierName = null;
         this.radioChainInfos = null;
         this.mWifiStandard = WIFI_STANDARD_UNKNOWN;
     }
@@ -767,9 +739,6 @@
         this.centerFreq0 = UNSPECIFIED;
         this.centerFreq1 = UNSPECIFIED;
         this.flags = 0;
-        this.isCarrierAp = false;
-        this.carrierApEapType = UNSPECIFIED;
-        this.carrierName = null;
         this.radioChainInfos = null;
         this.mWifiStandard = WIFI_STANDARD_UNKNOWN;
     }
@@ -797,9 +766,6 @@
         } else {
             this.flags = 0;
         }
-        this.isCarrierAp = false;
-        this.carrierApEapType = UNSPECIFIED;
-        this.carrierName = null;
         this.radioChainInfos = null;
         this.mWifiStandard = WIFI_STANDARD_UNKNOWN;
     }
@@ -839,9 +805,6 @@
             venueName = source.venueName;
             operatorFriendlyName = source.operatorFriendlyName;
             flags = source.flags;
-            isCarrierAp = source.isCarrierAp;
-            carrierApEapType = source.carrierApEapType;
-            carrierName = source.carrierName;
             radioChainInfos = source.radioChainInfos;
             this.mWifiStandard = source.mWifiStandard;
         }
@@ -881,9 +844,6 @@
         sb.append(", standard: ").append(wifiStandardToString(mWifiStandard));
         sb.append(", 80211mcResponder: ");
         sb.append(((flags & FLAG_80211mc_RESPONDER) != 0) ? "is supported" : "is not supported");
-        sb.append(", Carrier AP: ").append(isCarrierAp ? "yes" : "no");
-        sb.append(", Carrier AP EAP Type: ").append(carrierApEapType);
-        sb.append(", Carrier name: ").append(carrierName);
         sb.append(", Radio Chain Infos: ").append(Arrays.toString(radioChainInfos));
         return sb.toString();
     }
@@ -954,9 +914,6 @@
         } else {
             dest.writeInt(0);
         }
-        dest.writeInt(isCarrierAp ? 1 : 0);
-        dest.writeInt(carrierApEapType);
-        dest.writeString(carrierName);
 
         if (radioChainInfos != null) {
             dest.writeInt(radioChainInfos.length);
@@ -1036,9 +993,6 @@
                                 new AnqpInformationElement(vendorId, elementId, payload);
                     }
                 }
-                sr.isCarrierAp = in.readInt() != 0;
-                sr.carrierApEapType = in.readInt();
-                sr.carrierName = in.readString();
                 n = in.readInt();
                 if (n != 0) {
                     sr.radioChainInfos = new RadioChainInfo[n];
diff --git a/wifi/java/android/net/wifi/WifiMigration.java b/wifi/java/android/net/wifi/WifiMigration.java
index a3482d7..008b18b 100755
--- a/wifi/java/android/net/wifi/WifiMigration.java
+++ b/wifi/java/android/net/wifi/WifiMigration.java
@@ -16,8 +16,6 @@
 
 package android.net.wifi;
 
-import static com.android.internal.util.Preconditions.checkNotNull;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -26,8 +24,6 @@
 import android.os.Parcelable;
 import android.provider.Settings;
 
-import java.util.List;
-
 /**
  * Class used to provide one time hooks for existing OEM devices to migrate their config store
  * data and other settings to the wifi mainline module.
@@ -39,166 +35,6 @@
     private WifiMigration() { }
 
     /**
-     * Container for all the wifi config data to migrate.
-     */
-    public static final class ConfigStoreMigrationData implements Parcelable {
-        /**
-         * Builder to create instance of {@link ConfigStoreMigrationData}.
-         */
-        public static final class Builder {
-            private List<WifiConfiguration> mUserSavedNetworkConfigurations;
-            private SoftApConfiguration mUserSoftApConfiguration;
-
-            public Builder() {
-                mUserSavedNetworkConfigurations = null;
-                mUserSoftApConfiguration = null;
-            }
-
-            /**
-             * Sets the list of all user's saved network configurations parsed from OEM config
-             * store files.
-             *
-             * @param userSavedNetworkConfigurations List of {@link WifiConfiguration} representing
-             *                                       the list of user's saved networks
-             * @return Instance of {@link Builder} to enable chaining of the builder method.
-             */
-            public @NonNull Builder setUserSavedNetworkConfigurations(
-                    @NonNull List<WifiConfiguration> userSavedNetworkConfigurations) {
-                checkNotNull(userSavedNetworkConfigurations);
-                mUserSavedNetworkConfigurations = userSavedNetworkConfigurations;
-                return this;
-            }
-
-            /**
-             * Sets the user's softap configuration parsed from OEM config store files.
-             *
-             * @param userSoftApConfiguration {@link SoftApConfiguration} representing user's
-             *                                SoftAp configuration
-             * @return Instance of {@link Builder} to enable chaining of the builder method.
-             */
-            public @NonNull Builder setUserSoftApConfiguration(
-                    @NonNull SoftApConfiguration userSoftApConfiguration) {
-                checkNotNull(userSoftApConfiguration);
-                mUserSoftApConfiguration  = userSoftApConfiguration;
-                return this;
-            }
-
-            /**
-             * Build an instance of {@link ConfigStoreMigrationData}.
-             *
-             * @return Instance of {@link ConfigStoreMigrationData}.
-             */
-            public @NonNull ConfigStoreMigrationData build() {
-                return new ConfigStoreMigrationData(
-                        mUserSavedNetworkConfigurations, mUserSoftApConfiguration);
-            }
-        }
-
-        private final List<WifiConfiguration> mUserSavedNetworkConfigurations;
-        private final SoftApConfiguration mUserSoftApConfiguration;
-
-        private ConfigStoreMigrationData(
-                @Nullable List<WifiConfiguration> userSavedNetworkConfigurations,
-                @Nullable SoftApConfiguration userSoftApConfiguration) {
-            mUserSavedNetworkConfigurations = userSavedNetworkConfigurations;
-            mUserSoftApConfiguration = userSoftApConfiguration;
-        }
-
-        public static final @NonNull Parcelable.Creator<ConfigStoreMigrationData> CREATOR =
-                new Parcelable.Creator<ConfigStoreMigrationData>() {
-                    @Override
-                    public ConfigStoreMigrationData createFromParcel(Parcel in) {
-                        List<WifiConfiguration> userSavedNetworkConfigurations =
-                                in.readArrayList(null);
-                        SoftApConfiguration userSoftApConfiguration = in.readParcelable(null);
-                        return new ConfigStoreMigrationData(
-                                userSavedNetworkConfigurations, userSoftApConfiguration);
-                    }
-
-                    @Override
-                    public ConfigStoreMigrationData[] newArray(int size) {
-                        return new ConfigStoreMigrationData[size];
-                    }
-                };
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(@NonNull Parcel dest, int flags) {
-            dest.writeList(mUserSavedNetworkConfigurations);
-            dest.writeParcelable(mUserSoftApConfiguration, flags);
-        }
-
-        /**
-         * Returns list of all user's saved network configurations.
-         *
-         * Note: Only to be returned if there is any format change in how OEM persisted this info.
-         * @return List of {@link WifiConfiguration} representing the list of user's saved networks,
-         * or null if no migration necessary.
-         */
-        @Nullable
-        public List<WifiConfiguration> getUserSavedNetworkConfigurations() {
-            return mUserSavedNetworkConfigurations;
-        }
-
-        /**
-         * Returns user's softap configuration.
-         *
-         * Note: Only to be returned if there is any format change in how OEM persisted this info.
-         * @return {@link SoftApConfiguration} representing user's SoftAp configuration,
-         * or null if no migration necessary.
-         */
-        @Nullable
-        public SoftApConfiguration getUserSoftApConfiguration() {
-            return mUserSoftApConfiguration;
-        }
-    }
-
-    /**
-     * Load data from OEM's config store.
-     * <p>
-     * Note:
-     * <li>OEMs need to implement {@link #loadFromConfigStore()} ()} only if their
-     * existing config store format or file locations differs from the vanilla AOSP implementation.
-     * </li>
-     * <li>The wifi mainline module will invoke {@link #loadFromConfigStore()} method on every
-     * bootup, its the responsibility of the OEM implementation to ensure that this method returns
-     * non-null data only on the first bootup. Once the migration is done, the OEM can safely delete
-     * their config store files when {@link #removeConfigStore()} is invoked.
-     * <li>The first & only relevant invocation of {@link #loadFromConfigStore()} occurs when a
-     * previously released device upgrades to the wifi mainline module from an OEM implementation
-     * of the wifi stack.
-     * </li>
-     *
-     * @return Instance of {@link ConfigStoreMigrationData} for migrating data, null if no
-     * migration is necessary.
-     */
-    @Nullable
-    public static ConfigStoreMigrationData loadFromConfigStore() {
-        // Note: OEMs should add code to parse data from their config store format here!
-        return null;
-    }
-
-    /**
-     * Remove OEM's config store.
-     * <p>
-     * Note:
-     * <li>OEMs need to implement {@link #removeConfigStore()} only if their
-     * existing config store format or file locations differs from the vanilla AOSP implementation (
-     * which is what the wifi mainline module understands).
-     * </li>
-     * <li> The wifi mainline module will invoke {@link #removeConfigStore()} after it migrates
-     * all the existing data retrieved from {@link #loadFromConfigStore()}.
-     * </li>
-     */
-    public static void removeConfigStore() {
-        // Note: OEMs should remove their custom config store files here!
-    }
-
-    /**
      * Container for all the wifi settings data to migrate.
      */
     public static final class SettingsMigrationData implements Parcelable {
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 6dbb0bd..72ca900 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -545,6 +545,7 @@
                     mIsMetered ? WifiConfiguration.METERED_OVERRIDE_METERED
                             : WifiConfiguration.METERED_OVERRIDE_NONE;
             wifiConfiguration.carrierId = mCarrierId;
+            wifiConfiguration.trusted = !mIsNetworkUntrusted;
             return wifiConfiguration;
         }
 
@@ -684,8 +685,7 @@
                     mIsAppInteractionRequired,
                     mIsUserInteractionRequired,
                     mIsSharedWithUser,
-                    mIsInitialAutojoinEnabled,
-                    mIsNetworkUntrusted);
+                    mIsInitialAutojoinEnabled);
         }
     }
 
@@ -728,13 +728,6 @@
      */
     public final boolean isInitialAutoJoinEnabled;
 
-    /**
-     * Whether this network will be brought up as untrusted (TRUSTED capability bit removed).
-     * @hide
-     */
-    public final boolean isNetworkUntrusted;
-
-
     /** @hide */
     public WifiNetworkSuggestion() {
         this.wifiConfiguration = new WifiConfiguration();
@@ -743,7 +736,6 @@
         this.isUserInteractionRequired = false;
         this.isUserAllowedToManuallyConnect = true;
         this.isInitialAutoJoinEnabled = true;
-        this.isNetworkUntrusted = false;
     }
 
     /** @hide */
@@ -752,8 +744,7 @@
                                  boolean isAppInteractionRequired,
                                  boolean isUserInteractionRequired,
                                  boolean isUserAllowedToManuallyConnect,
-                                 boolean isInitialAutoJoinEnabled,
-                                 boolean isNetworkUntrusted) {
+                                 boolean isInitialAutoJoinEnabled) {
         checkNotNull(networkConfiguration);
         this.wifiConfiguration = networkConfiguration;
         this.passpointConfiguration = passpointConfiguration;
@@ -762,7 +753,6 @@
         this.isUserInteractionRequired = isUserInteractionRequired;
         this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect;
         this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled;
-        this.isNetworkUntrusted = isNetworkUntrusted;
     }
 
     public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR =
@@ -775,8 +765,7 @@
                             in.readBoolean(), // isAppInteractionRequired
                             in.readBoolean(), // isUserInteractionRequired
                             in.readBoolean(), // isSharedCredentialWithUser
-                            in.readBoolean(),  // isAutojoinEnabled
-                            in.readBoolean()
+                            in.readBoolean()  // isAutojoinEnabled
                     );
                 }
 
@@ -799,7 +788,6 @@
         dest.writeBoolean(isUserInteractionRequired);
         dest.writeBoolean(isUserAllowedToManuallyConnect);
         dest.writeBoolean(isInitialAutoJoinEnabled);
-        dest.writeBoolean(isNetworkUntrusted);
     }
 
     @Override
@@ -842,7 +830,7 @@
                 .append(", isUserInteractionRequired=").append(isUserInteractionRequired)
                 .append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect)
                 .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled)
-                .append(", isUnTrusted=").append(isNetworkUntrusted)
+                .append(", isUnTrusted=").append(!wifiConfiguration.trusted)
                 .append(" ]");
         return sb.toString();
     }
@@ -933,7 +921,7 @@
 
     /** @see Builder#setUntrusted(boolean)  */
     public boolean isUntrusted() {
-        return isNetworkUntrusted;
+        return !wifiConfiguration.trusted;
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 65e8b3d..fa806e7 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -1037,18 +1037,7 @@
      * @return a Unique identifier for a Credential object
      */
     public int getUniqueId() {
-        int usedCredential;
-
-        // Initialize usedCredential based on the credential type of the profile
-        if (mUserCredential != null) {
-            usedCredential = 0;
-        } else if (mCertCredential != null) {
-            usedCredential = 1;
-        } else {
-            usedCredential = 2;
-        }
-
-        return Objects.hash(usedCredential, mRealm);
+        return Objects.hash(mUserCredential, mCertCredential, mSimCredential, mRealm);
     }
 
     @Override
diff --git a/wifi/tests/src/android/net/wifi/ScanResultTest.java b/wifi/tests/src/android/net/wifi/ScanResultTest.java
index 4c22d5d..6cb8324 100644
--- a/wifi/tests/src/android/net/wifi/ScanResultTest.java
+++ b/wifi/tests/src/android/net/wifi/ScanResultTest.java
@@ -156,8 +156,7 @@
                 + "distance: 0(cm), distanceSd: 0(cm), "
                 + "passpoint: no, ChannelBandwidth: 0, centerFreq0: 0, centerFreq1: 0, "
                 + "standard: 11ac, "
-                + "80211mcResponder: is not supported, Carrier AP: no, "
-                + "Carrier AP EAP Type: 0, Carrier name: null, "
+                + "80211mcResponder: is not supported, "
                 + "Radio Chain Infos: null", scanResult.toString());
     }
 
@@ -179,8 +178,7 @@
                 + "distanceSd: 0(cm), "
                 + "passpoint: no, ChannelBandwidth: 0, centerFreq0: 0, centerFreq1: 0, "
                 + "standard: 11ac, "
-                + "80211mcResponder: is not supported, Carrier AP: no, "
-                + "Carrier AP EAP Type: 0, Carrier name: null, "
+                + "80211mcResponder: is not supported, "
                 + "Radio Chain Infos: [RadioChainInfo: id=0, level=-45, "
                 + "RadioChainInfo: id=1, level=-54]", scanResult.toString());
     }
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 51bf738..01b2a8d 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -532,7 +532,7 @@
         configuration.BSSID = TEST_BSSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion(
-                configuration, null, false, true, true, true, false);
+                configuration, null, false, true, true, true);
 
         Parcel parcelW = Parcel.obtain();
         suggestion.writeToParcel(parcelW, 0);
@@ -603,14 +603,14 @@
         configuration.BSSID = TEST_BSSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null, true, false, true, true, false);
+                new WifiNetworkSuggestion(configuration, null, true, false, true, true);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID;
         configuration1.BSSID = TEST_BSSID;
         configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, true, true, true, false);
+                new WifiNetworkSuggestion(configuration1, null, false, true, true, true);
 
         assertEquals(suggestion, suggestion1);
         assertEquals(suggestion.hashCode(), suggestion1.hashCode());
@@ -626,13 +626,13 @@
         configuration.SSID = TEST_SSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null, false, false, true, true, false);
+                new WifiNetworkSuggestion(configuration, null, false, false, true, true);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID_1;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, false, true, true, false);
+                new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
 
         assertNotEquals(suggestion, suggestion1);
     }
@@ -648,13 +648,13 @@
         configuration.BSSID = TEST_BSSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null,  false, false, true, true, false);
+                new WifiNetworkSuggestion(configuration, null,  false, false, true, true);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID;
         configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, false, true, true, false);
+                new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
 
         assertNotEquals(suggestion, suggestion1);
     }
@@ -669,13 +669,13 @@
         configuration.SSID = TEST_SSID;
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         WifiNetworkSuggestion suggestion =
-                new WifiNetworkSuggestion(configuration, null, false, false, true, true, false);
+                new WifiNetworkSuggestion(configuration, null, false, false, true, true);
 
         WifiConfiguration configuration1 = new WifiConfiguration();
         configuration1.SSID = TEST_SSID;
         configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         WifiNetworkSuggestion suggestion1 =
-                new WifiNetworkSuggestion(configuration1, null, false, false, true, true, false);
+                new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
 
         assertNotEquals(suggestion, suggestion1);
     }
@@ -770,7 +770,7 @@
                 .setWpa2Passphrase(TEST_PRESHARED_KEY)
                 .setUntrusted(true)
                 .build();
-        assertTrue(suggestion.isNetworkUntrusted);
+        assertTrue(suggestion.isUntrusted());
         assertFalse(suggestion.isUserAllowedToManuallyConnect);
     }
 
@@ -786,7 +786,7 @@
                 .setPasspointConfig(passpointConfiguration)
                 .setUntrusted(true)
                 .build();
-        assertTrue(suggestion.isNetworkUntrusted);
+        assertTrue(suggestion.isUntrusted());
         assertFalse(suggestion.isUserAllowedToManuallyConnect);
     }
 
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index c682582..829d8f0 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import android.net.wifi.EAPConstants;
@@ -551,4 +552,68 @@
     public void validateTwoCertificateDifferent() {
         assertFalse(Credential.isX509CertificateEquals(FakeKeys.CA_CERT0, FakeKeys.CA_CERT1));
     }
+
+    /**
+     * Verify that unique identifiers are the same for objects with the same credentials
+     */
+    @Test
+    public void testUniqueIdSameCredentialTypes() throws Exception {
+        assertEquals(createCredentialWithSimCredential().getUniqueId(),
+                createCredentialWithSimCredential().getUniqueId());
+        assertEquals(createCredentialWithCertificateCredential().getUniqueId(),
+                createCredentialWithCertificateCredential().getUniqueId());
+        assertEquals(createCredentialWithUserCredential().getUniqueId(),
+                createCredentialWithUserCredential().getUniqueId());
+    }
+
+    /**
+     * Verify that unique identifiers are different for each credential
+     */
+    @Test
+    public void testUniqueIdDifferentForDifferentCredentialTypes() throws Exception {
+        Credential simCred = createCredentialWithSimCredential();
+        Credential certCred = createCredentialWithCertificateCredential();
+        Credential userCred = createCredentialWithUserCredential();
+
+        assertNotEquals(simCred.getUniqueId(), userCred.getUniqueId());
+        assertNotEquals(simCred.getUniqueId(), certCred.getUniqueId());
+        assertNotEquals(certCred.getUniqueId(), userCred.getUniqueId());
+    }
+
+    /**
+     * Verify that unique identifiers are different for a credential with different values
+     */
+    @Test
+    public void testUniqueIdDifferentForSimCredentialsWithDifferentValues() throws Exception {
+        Credential simCred1 = createCredentialWithSimCredential();
+        Credential simCred2 = createCredentialWithSimCredential();
+        simCred2.getSimCredential().setImsi("567890*");
+
+        assertNotEquals(simCred1.getUniqueId(), simCred2.getUniqueId());
+    }
+
+    /**
+     * Verify that unique identifiers are different for a credential with different values
+     */
+    @Test
+    public void testUniqueIdDifferentForUserCredentialsWithDifferentValues() throws Exception {
+        Credential userCred1 = createCredentialWithUserCredential();
+        Credential userCred2 = createCredentialWithUserCredential();
+        userCred2.getUserCredential().setUsername("anotheruser");
+
+        assertNotEquals(userCred1.getUniqueId(), userCred2.getUniqueId());
+    }
+
+    /**
+     * Verify that unique identifiers are different for a credential with different values
+     */
+    @Test
+    public void testUniqueIdDifferentForCertCredentialsWithDifferentValues() throws Exception {
+        Credential certCred1 = createCredentialWithCertificateCredential();
+        Credential certCred2 = createCredentialWithCertificateCredential();
+        certCred2.getCertCredential().setCertSha256Fingerprint(
+                MessageDigest.getInstance("SHA-256").digest(FakeKeys.CA_CERT0.getEncoded()));
+
+        assertNotEquals(certCred1.getUniqueId(), certCred2.getUniqueId());
+    }
 }