Merge changes I696e3c5d,Iea7a5610,If627e613 into rvc-dev

* changes:
  update javadoc for InstallationFile
  fix error message
  remove incremental.check_loader
diff --git a/apex/blobstore/framework/java/android/app/blob/AccessorInfo.java b/apex/blobstore/framework/java/android/app/blob/AccessorInfo.java
new file mode 100644
index 0000000..3725ad4
--- /dev/null
+++ b/apex/blobstore/framework/java/android/app/blob/AccessorInfo.java
@@ -0,0 +1,119 @@
+/*
+ * 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.app.blob;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Class to provide information about an accessor of a shared blob.
+ *
+ * @hide
+ */
+public final class AccessorInfo implements Parcelable {
+    private final String mPackageName;
+    private final long mExpiryTimeMs;
+    private final int mDescriptionResId;
+    private final CharSequence mDescription;
+
+    public AccessorInfo(String packageName, long expiryTimeMs,
+            int descriptionResId, CharSequence description) {
+        mPackageName = packageName;
+        mExpiryTimeMs = expiryTimeMs;
+        mDescriptionResId = descriptionResId;
+        mDescription = description;
+    }
+
+    private AccessorInfo(Parcel in) {
+        mPackageName = in.readString();
+        mExpiryTimeMs = in.readLong();
+        mDescriptionResId = in.readInt();
+        mDescription = in.readCharSequence();
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public long getExpiryTimeMs() {
+        return mExpiryTimeMs;
+    }
+
+    public int getDescriptionResId() {
+        return mDescriptionResId;
+    }
+
+    public CharSequence getDescription() {
+        return mDescription;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mPackageName);
+        dest.writeLong(mExpiryTimeMs);
+        dest.writeInt(mDescriptionResId);
+        dest.writeCharSequence(mDescription);
+    }
+
+    @Override
+    public String toString() {
+        return "AccessorInfo {"
+                + "package: " + mPackageName + ","
+                + "expiryMs: " + mExpiryTimeMs + ","
+                + "descriptionResId: " + mDescriptionResId + ","
+                + "description: " + mDescription + ","
+                + "}";
+    }
+
+    private String toShortString() {
+        return mPackageName;
+    }
+
+    public static String toShortString(List<AccessorInfo> accessors) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        for (int i = 0, size = accessors.size(); i < size; ++i) {
+            sb.append(accessors.get(i).toShortString());
+            sb.append(",");
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<AccessorInfo> CREATOR = new Creator<AccessorInfo>() {
+        @Override
+        @NonNull
+        public AccessorInfo createFromParcel(Parcel source) {
+            return new AccessorInfo(source);
+        }
+
+        @Override
+        @NonNull
+        public AccessorInfo[] newArray(int size) {
+            return new AccessorInfo[size];
+        }
+    };
+}
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobInfo.aidl b/apex/blobstore/framework/java/android/app/blob/BlobInfo.aidl
new file mode 100644
index 0000000..2549773
--- /dev/null
+++ b/apex/blobstore/framework/java/android/app/blob/BlobInfo.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 BlobInfo;
\ No newline at end of file
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
new file mode 100644
index 0000000..9746dd0
--- /dev/null
+++ b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.blob;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Class to provide information about a shared blob.
+ *
+ * @hide
+ */
+public final class BlobInfo implements Parcelable {
+    private final long mId;
+    private final long mExpiryTimeMs;
+    private final CharSequence mLabel;
+    private final List<AccessorInfo> mAccessors;
+
+    public BlobInfo(long id, long expiryTimeMs, CharSequence label,
+            List<AccessorInfo> accessors) {
+        mId = id;
+        mExpiryTimeMs = expiryTimeMs;
+        mLabel = label;
+        mAccessors = accessors;
+    }
+
+    private BlobInfo(Parcel in) {
+        mId = in.readLong();
+        mExpiryTimeMs = in.readLong();
+        mLabel = in.readCharSequence();
+        mAccessors = in.readArrayList(null /* classloader */);
+    }
+
+    public long getId() {
+        return mId;
+    }
+
+    public long getExpiryTimeMs() {
+        return mExpiryTimeMs;
+    }
+
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    public List<AccessorInfo> getAccessors() {
+        return Collections.unmodifiableList(mAccessors);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeLong(mId);
+        dest.writeLong(mExpiryTimeMs);
+        dest.writeCharSequence(mLabel);
+        dest.writeList(mAccessors);
+    }
+
+    @Override
+    public String toString() {
+        return toShortString();
+    }
+
+    private String toShortString() {
+        return "BlobInfo {"
+                + "id: " + mId + ","
+                + "expiryMs: " + mExpiryTimeMs + ","
+                + "label: " + mLabel + ","
+                + "accessors: " + AccessorInfo.toShortString(mAccessors) + ","
+                + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<BlobInfo> CREATOR = new Creator<BlobInfo>() {
+        @Override
+        @NonNull
+        public BlobInfo createFromParcel(Parcel source) {
+            return new BlobInfo(source);
+        }
+
+        @Override
+        @NonNull
+        public BlobInfo[] newArray(int size) {
+            return new BlobInfo[size];
+        }
+    };
+}
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
index d1e28e9..7af3f66 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
@@ -27,11 +27,13 @@
 import android.os.ParcelableException;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.os.UserHandle;
 
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.io.Closeable;
 import java.io.IOException;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -145,9 +147,6 @@
     /** @hide */
     public static final int INVALID_RES_ID = -1;
 
-    /** @hide */
-    public static final String DESC_RES_TYPE_STRING = "string";
-
     private final Context mContext;
     private final IBlobStoreManager mService;
 
@@ -495,6 +494,31 @@
         }
     }
 
+    /** @hide */
+    @NonNull
+    public List<BlobInfo> queryBlobsForUser(@NonNull UserHandle user) throws IOException {
+        try {
+            return mService.queryBlobsForUser(user.getIdentifier());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void deleteBlob(@NonNull BlobInfo blobInfo) throws IOException {
+        try {
+            mService.deleteBlob(blobInfo.getId());
+        } 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 a85a25c..beeb03f 100644
--- a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl
+++ b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl
@@ -16,6 +16,7 @@
 package android.app.blob;
 
 import android.app.blob.BlobHandle;
+import android.app.blob.BlobInfo;
 import android.app.blob.IBlobStoreSession;
 import android.os.RemoteCallback;
 
@@ -31,4 +32,7 @@
     void releaseLease(in BlobHandle handle, in String packageName);
 
     void waitForIdle(in RemoteCallback callback);
+
+    List<BlobInfo> queryBlobsForUser(int userId);
+    void deleteBlob(long blobId);
 }
\ No newline at end of file
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 dab4797..970766d 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -15,7 +15,6 @@
  */
 package com.android.server.blob;
 
-import static android.app.blob.BlobStoreManager.DESC_RES_TYPE_STRING;
 import static android.app.blob.XmlTags.ATTR_DESCRIPTION;
 import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_NAME;
 import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME;
@@ -30,16 +29,16 @@
 import static android.os.Process.INVALID_UID;
 import static android.system.OsConstants.O_RDONLY;
 
-import static com.android.server.blob.BlobStoreConfig.LOGV;
 import static com.android.server.blob.BlobStoreConfig.TAG;
 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_DESC_RES_NAME;
 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC;
+import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId;
+import static com.android.server.blob.BlobStoreUtils.getPackageResources;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.blob.BlobHandle;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.content.res.ResourceId;
 import android.content.res.Resources;
 import android.os.ParcelFileDescriptor;
@@ -65,6 +64,7 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.util.Objects;
+import java.util.function.Consumer;
 
 class BlobMetadata {
     private final Object mMetadataLock = new Object();
@@ -281,6 +281,10 @@
         return false;
     }
 
+    void forEachLeasee(Consumer<Leasee> consumer) {
+        mLeasees.forEach(consumer);
+    }
+
     File getBlobFile() {
         if (mBlobFile == null) {
             mBlobFile = BlobStoreConfig.getBlobFile(mBlobId);
@@ -506,16 +510,9 @@
             if (resources == null) {
                 return null;
             }
-            try {
-                final int resId = resources.getIdentifier(descriptionResEntryName,
-                        DESC_RES_TYPE_STRING, packageName);
-                return resId <= 0 ? null : resources.getString(resId);
-            } catch (Resources.NotFoundException e) {
-                if (LOGV) {
-                    Slog.w(TAG, "Description resource not found", e);
-                }
-                return null;
-            }
+            final int resId = getDescriptionResourceId(resources, descriptionResEntryName,
+                    packageName);
+            return resId == Resources.ID_NULL ? null : resources.getString(resId);
         }
 
         @Nullable
@@ -546,19 +543,6 @@
             return desc == null ? "<none>" : desc;
         }
 
-        @Nullable
-        private static Resources getPackageResources(@NonNull Context context,
-                @NonNull String packageName, int userId) {
-            try {
-                return context.getPackageManager()
-                        .getResourcesForApplicationAsUser(packageName, userId);
-            } catch (PackageManager.NameNotFoundException e) {
-                Slog.d(TAG, "Unknown package in user " + userId + ": "
-                        + packageName, e);
-                return null;
-            }
-        }
-
         void writeToXml(@NonNull XmlSerializer out) throws IOException {
             XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName);
             XmlUtils.writeIntAttribute(out, ATTR_UID, uid);
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 96f7b7a..ed3dda4 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -36,6 +36,8 @@
 import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID;
 import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_VALID;
 import static com.android.server.blob.BlobStoreSession.stateToString;
+import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId;
+import static com.android.server.blob.BlobStoreUtils.getPackageResources;
 
 import android.annotation.CurrentTimeSecondsLong;
 import android.annotation.IdRes;
@@ -43,7 +45,9 @@
 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.content.BroadcastReceiver;
@@ -100,6 +104,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.nio.charset.StandardCharsets;
 import java.security.SecureRandom;
 import java.util.ArrayList;
@@ -110,6 +115,7 @@
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * Service responsible for maintaining and facilitating access to data blobs published by apps.
@@ -434,6 +440,48 @@
         }
     }
 
+    private List<BlobInfo> queryBlobsForUserInternal(int userId) {
+        final ArrayList<BlobInfo> blobInfos = new ArrayList<>();
+        synchronized (mBlobsLock) {
+            final ArrayMap<String, WeakReference<Resources>> resources = new ArrayMap<>();
+            final Function<String, Resources> resourcesGetter = (packageName) -> {
+                final WeakReference<Resources> resourcesRef = resources.get(packageName);
+                Resources packageResources = resourcesRef == null ? null : resourcesRef.get();
+                if (packageResources == null) {
+                    packageResources = getPackageResources(mContext, packageName, userId);
+                    resources.put(packageName, new WeakReference<>(packageResources));
+                }
+                return packageResources;
+            };
+            getUserBlobsLocked(userId).forEach((blobHandle, blobMetadata) -> {
+                final ArrayList<AccessorInfo> accessorInfos = 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,
+                            descriptionResId, leasee.description));
+                });
+                blobInfos.add(new BlobInfo(blobMetadata.getBlobId(),
+                        blobHandle.getExpiryTimeMillis(), blobHandle.getLabel(), accessorInfos));
+            });
+        }
+        return blobInfos;
+    }
+
+    private void deleteBlobInternal(long blobId, int callingUid) {
+        synchronized (mBlobsLock) {
+            final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
+                    UserHandle.getUserId(callingUid));
+            userBlobs.entrySet().removeIf(entry -> {
+                final BlobMetadata blobMetadata = entry.getValue();
+                return blobMetadata.getBlobId() == blobId;
+            });
+            writeBlobsInfoAsync();
+        }
+    }
+
     private void verifyCallingPackage(int callingUid, String callingPackage) {
         if (mPackageManagerInternal.getPackageUid(
                 callingPackage, 0, UserHandle.getUserId(callingUid)) != callingUid) {
@@ -1250,6 +1298,28 @@
         }
 
         @Override
+        @NonNull
+        public List<BlobInfo> queryBlobsForUser(@UserIdInt int userId) {
+            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+                throw new SecurityException("Only system uid is allowed to call "
+                        + "queryBlobsForUser()");
+            }
+
+            return queryBlobsForUserInternal(userId);
+        }
+
+        @Override
+        public void deleteBlob(long blobId) {
+            final int callingUid = Binder.getCallingUid();
+            if (callingUid != Process.SYSTEM_UID) {
+                throw new SecurityException("Only system uid is allowed to call "
+                        + "deleteBlob()");
+            }
+
+            deleteBlobInternal(blobId, callingUid);
+        }
+
+        @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
new file mode 100644
index 0000000..6af540a
--- /dev/null
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java
@@ -0,0 +1,50 @@
+/*
+ * 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.blob;
+
+import static com.android.server.blob.BlobStoreConfig.TAG;
+
+import android.annotation.IdRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.util.Slog;
+
+class BlobStoreUtils {
+    private static final String DESC_RES_TYPE_STRING = "string";
+
+    @Nullable
+    static Resources getPackageResources(@NonNull Context context,
+            @NonNull String packageName, int userId) {
+        try {
+            return context.getPackageManager()
+                    .getResourcesForApplicationAsUser(packageName, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.d(TAG, "Unknown package in user " + userId + ": "
+                    + packageName, e);
+            return null;
+        }
+    }
+
+    @IdRes
+    static int getDescriptionResourceId(@NonNull Resources resources,
+            @NonNull String resourceEntryName, @NonNull String packageName) {
+        return resources.getIdentifier(resourceEntryName, DESC_RES_TYPE_STRING, packageName);
+    }
+}
diff --git a/api/current.txt b/api/current.txt
index 59afab2..6052d93 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -18118,9 +18118,7 @@
     ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull java.security.Signature);
     ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher);
     ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac);
-    ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull android.security.identity.IdentityCredential);
     method @Deprecated public javax.crypto.Cipher getCipher();
-    method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
     method @Deprecated public javax.crypto.Mac getMac();
     method @Deprecated public java.security.Signature getSignature();
   }
@@ -42690,7 +42688,7 @@
     ctor public PersonalizationData.Builder();
     method @NonNull public android.security.identity.PersonalizationData.Builder addAccessControlProfile(@NonNull android.security.identity.AccessControlProfile);
     method @NonNull public android.security.identity.PersonalizationData build();
-    method @NonNull public android.security.identity.PersonalizationData.Builder setEntry(@NonNull String, @NonNull String, @NonNull java.util.Collection<android.security.identity.AccessControlProfileId>, @NonNull byte[]);
+    method @NonNull public android.security.identity.PersonalizationData.Builder putEntry(@NonNull String, @NonNull String, @NonNull java.util.Collection<android.security.identity.AccessControlProfileId>, @NonNull byte[]);
   }
 
   public abstract class ResultData {
@@ -42698,7 +42696,7 @@
     method @Nullable public abstract byte[] getEntry(@NonNull String, @NonNull String);
     method @Nullable public abstract java.util.Collection<java.lang.String> getEntryNames(@NonNull String);
     method @Nullable public abstract byte[] getMessageAuthenticationCode();
-    method @NonNull public abstract java.util.Collection<java.lang.String> getNamespaceNames();
+    method @NonNull public abstract java.util.Collection<java.lang.String> getNamespaces();
     method @Nullable public abstract java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String);
     method @NonNull public abstract byte[] getStaticAuthenticationData();
     method public abstract int getStatus(@NonNull String, @NonNull String);
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index f18aaa5..cd10457 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -930,8 +930,6 @@
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::informOnePackage was called");
-    // TODO(b/149254662): This is gross. We should consider changing statsd
-    // internals to use std::string.
     String16 utf16App = String16(app.c_str());
     String16 utf16VersionString = String16(versionString.c_str());
     String16 utf16Installer = String16(installer.c_str());
diff --git a/cmds/statsd/src/anomaly/AlarmMonitor.cpp b/cmds/statsd/src/anomaly/AlarmMonitor.cpp
index 02291181..b632d04 100644
--- a/cmds/statsd/src/anomaly/AlarmMonitor.cpp
+++ b/cmds/statsd/src/anomaly/AlarmMonitor.cpp
@@ -38,8 +38,6 @@
 void AlarmMonitor::setStatsCompanionService(
         shared_ptr<IStatsCompanionService> statsCompanionService) {
     std::lock_guard<std::mutex> lock(mLock);
-    // TODO(b/149254662): determine if tmpForLock is needed now that we have moved
-    // from sp to shared_ptr
     shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService;
     mStatsCompanionService = statsCompanionService;
     if (statsCompanionService == nullptr) {
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 9bdb588..6d9c644 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -54,20 +54,22 @@
     shared_ptr<IPendingIntentRef> mPir;
 };
 
-static void configReceiverDied(void* cookie) {
+void ConfigManager::configReceiverDied(void* cookie) {
     auto cookie_ = static_cast<ConfigReceiverDeathCookie*>(cookie);
-    sp<ConfigManager> configManager = cookie_->mConfigManager;
-    ConfigKey configKey = cookie_->mConfigKey;
-    shared_ptr<IPendingIntentRef> pir = cookie_->mPir;
+    sp<ConfigManager>& thiz = cookie_->mConfigManager;
+    ConfigKey& configKey = cookie_->mConfigKey;
+    shared_ptr<IPendingIntentRef>& pir = cookie_->mPir;
 
-    // TODO(b/149254662): Fix threading. This currently fails if a new
-    // pir gets set between the get and the remove.
-    if (configManager->GetConfigReceiver(configKey) == pir) {
-        configManager->RemoveConfigReceiver(configKey);
+    // Erase the mapping from the config key to the config receiver (pir) if the
+    // mapping still exists.
+    lock_guard<mutex> lock(thiz->mMutex);
+    auto it = thiz->mConfigReceivers.find(configKey);
+    if (it != thiz->mConfigReceivers.end() && it->second == pir) {
+        thiz->mConfigReceivers.erase(configKey);
     }
-    // The death recipient corresponding to this specific pir can never
-    // be triggered again, so free up resources.
-    // TODO(b/149254662): Investigate other options to manage the memory.
+
+    // The death recipient corresponding to this specific pir can never be
+    // triggered again, so free up resources.
     delete cookie_;
 }
 
@@ -83,26 +85,29 @@
     shared_ptr<IPendingIntentRef> mPir;
 };
 
-static void activeConfigChangedReceiverDied(void* cookie) {
+void ConfigManager::activeConfigChangedReceiverDied(void* cookie) {
     auto cookie_ = static_cast<ActiveConfigChangedReceiverDeathCookie*>(cookie);
-    sp<ConfigManager> configManager = cookie_->mConfigManager;
+    sp<ConfigManager>& thiz = cookie_->mConfigManager;
     int uid = cookie_->mUid;
-    shared_ptr<IPendingIntentRef> pir = cookie_->mPir;
+    shared_ptr<IPendingIntentRef>& pir = cookie_->mPir;
 
-    // TODO(b/149254662): Fix threading. This currently fails if a new
-    // pir gets set between the get and the remove.
-    if (configManager->GetActiveConfigsChangedReceiver(uid) == pir) {
-        configManager->RemoveActiveConfigsChangedReceiver(uid);
+    // Erase the mapping from the config key to the active config changed
+    // receiver (pir) if the mapping still exists.
+    lock_guard<mutex> lock(thiz->mMutex);
+    auto it = thiz->mActiveConfigsChangedReceivers.find(uid);
+    if (it != thiz->mActiveConfigsChangedReceivers.end() && it->second == pir) {
+        thiz->mActiveConfigsChangedReceivers.erase(uid);
     }
+
     // The death recipient corresponding to this specific pir can never
     // be triggered again, so free up resources.
     delete cookie_;
 }
 
-ConfigManager::ConfigManager():
+ConfigManager::ConfigManager() :
     mConfigReceiverDeathRecipient(AIBinder_DeathRecipient_new(configReceiverDied)),
     mActiveConfigChangedReceiverDeathRecipient(
-        AIBinder_DeathRecipient_new(activeConfigChangedReceiverDied)) {
+            AIBinder_DeathRecipient_new(activeConfigChangedReceiverDied)) {
 }
 
 ConfigManager::~ConfigManager() {
@@ -189,8 +194,10 @@
 
 void ConfigManager::SetActiveConfigsChangedReceiver(const int uid,
                                                     const shared_ptr<IPendingIntentRef>& pir) {
-    lock_guard<mutex> lock(mMutex);
-    mActiveConfigsChangedReceivers[uid] = pir;
+    {
+        lock_guard<mutex> lock(mMutex);
+        mActiveConfigsChangedReceivers[uid] = pir;
+    }
     AIBinder_linkToDeath(pir->asBinder().get(), mActiveConfigChangedReceiverDeathRecipient.get(),
                          new ActiveConfigChangedReceiverDeathCookie(this, uid, pir));
 }
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
index 824e588..40146b1 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -161,6 +161,19 @@
     // IPendingIntentRef dies.
     ::ndk::ScopedAIBinder_DeathRecipient mConfigReceiverDeathRecipient;
     ::ndk::ScopedAIBinder_DeathRecipient mActiveConfigChangedReceiverDeathRecipient;
+
+    /**
+     * Death recipient callback that is called when a config receiver dies.
+     * The cookie is a pointer to a ConfigReceiverDeathCookie.
+     */
+    static void configReceiverDied(void* cookie);
+
+    /**
+     * Death recipient callback that is called when an active config changed
+     * receiver dies. The cookie is a pointer to an
+     * ActiveConfigChangedReceiverDeathCookie.
+     */
+    static void activeConfigChangedReceiverDied(void* cookie);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 1c38542..0115ba2 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -85,12 +85,9 @@
         return;
     }
 
-    // TODO(b/149254662): Why are we creating a copy here? This is different
-    // from the other places where we create a copy because we don't reassign
-    // mStatsCompanionService so a destructor can't implicitly be called...
-    shared_ptr<IStatsCompanionService> statsCompanionServiceCopy = mStatsCompanionService;
-    if (statsCompanionServiceCopy != nullptr) {
-        statsCompanionServiceCopy->setPullingAlarm(mNextPullTimeNs / 1000000);
+    // TODO(b/151045771): do not hold a lock while making a binder call
+    if (mStatsCompanionService != nullptr) {
+        mStatsCompanionService->setPullingAlarm(mNextPullTimeNs / 1000000);
     } else {
         VLOG("StatsCompanionService not available. Alarm not set.");
     }
@@ -99,8 +96,6 @@
 
 void StatsPullerManager::SetStatsCompanionService(
         shared_ptr<IStatsCompanionService> statsCompanionService) {
-    // TODO(b/149254662): Why are we using AutoMutex instead of lock_guard?
-    // Additionally, do we need the temporary shared_ptr to prevent deadlocks?
     AutoMutex _l(mLock);
     shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService;
     mStatsCompanionService = statsCompanionService;
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
index 93af5e9..c915ef3 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
@@ -39,33 +39,47 @@
     shared_ptr<IPendingIntentRef> mPir;
 };
 
-static void broadcastSubscriberDied(void* cookie) {
-    BroadcastSubscriberDeathCookie* cookie_ = (BroadcastSubscriberDeathCookie*)cookie;
-    ConfigKey configKey = cookie_->mConfigKey;
+void SubscriberReporter::broadcastSubscriberDied(void* cookie) {
+    auto cookie_ = static_cast<BroadcastSubscriberDeathCookie*>(cookie);
+    ConfigKey& configKey = cookie_->mConfigKey;
     int64_t subscriberId = cookie_->mSubscriberId;
-    shared_ptr<IPendingIntentRef> pir = cookie_->mPir;
+    shared_ptr<IPendingIntentRef>& pir = cookie_->mPir;
 
-    // TODO(b/149254662): Fix threading. This currently fails if a new pir gets
-    // set between the get and the unset.
-    if (SubscriberReporter::getInstance().getBroadcastSubscriber(configKey, subscriberId) == pir) {
-        SubscriberReporter::getInstance().unsetBroadcastSubscriber(configKey, subscriberId);
+    SubscriberReporter& thiz = getInstance();
+
+    // Erase the mapping from a (config_key, subscriberId) to a pir if the
+    // mapping exists.
+    lock_guard<mutex> lock(thiz.mLock);
+    auto subscriberMapIt = thiz.mIntentMap.find(configKey);
+    if (subscriberMapIt != thiz.mIntentMap.end()) {
+        auto subscriberMap = subscriberMapIt->second;
+        auto pirIt = subscriberMap.find(subscriberId);
+        if (pirIt != subscriberMap.end() && pirIt->second == pir) {
+            subscriberMap.erase(subscriberId);
+            if (subscriberMap.empty()) {
+                thiz.mIntentMap.erase(configKey);
+            }
+        }
     }
+
     // The death recipient corresponding to this specific pir can never be
     // triggered again, so free up resources.
     delete cookie_;
 }
 
-static ::ndk::ScopedAIBinder_DeathRecipient sBroadcastSubscriberDeathRecipient(
-        AIBinder_DeathRecipient_new(broadcastSubscriberDied));
+SubscriberReporter::SubscriberReporter() :
+    mBroadcastSubscriberDeathRecipient(AIBinder_DeathRecipient_new(broadcastSubscriberDied)) {
+}
 
 void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey,
                                                 int64_t subscriberId,
                                                 const shared_ptr<IPendingIntentRef>& pir) {
     VLOG("SubscriberReporter::setBroadcastSubscriber called.");
-    lock_guard<mutex> lock(mLock);
-    mIntentMap[configKey][subscriberId] = pir;
-    // TODO(b/149254662): Is it ok to call linkToDeath while holding a lock?
-    AIBinder_linkToDeath(pir->asBinder().get(), sBroadcastSubscriberDeathRecipient.get(),
+    {
+        lock_guard<mutex> lock(mLock);
+        mIntentMap[configKey][subscriberId] = pir;
+    }
+    AIBinder_linkToDeath(pir->asBinder().get(), mBroadcastSubscriberDeathRecipient.get(),
                          new BroadcastSubscriberDeathCookie(configKey, subscriberId, pir));
 }
 
@@ -103,8 +117,6 @@
     }
     int64_t subscriberId = subscription.broadcast_subscriber_details().subscriber_id();
 
-    // TODO(b/149254662): Is there a way to convert a RepeatedPtrField into a
-    // vector without copying?
     vector<string> cookies;
     cookies.reserve(subscription.broadcast_subscriber_details().cookie_size());
     for (auto& cookie : subscription.broadcast_subscriber_details().cookie()) {
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h
index 0f97d39..4fe4281 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.h
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.h
@@ -78,7 +78,7 @@
                                                          int64_t subscriberId);
 
 private:
-    SubscriberReporter() {};
+    SubscriberReporter();
 
     mutable mutex mLock;
 
@@ -94,6 +94,14 @@
                              const Subscription& subscription,
                              const vector<string>& cookies,
                              const MetricDimensionKey& dimKey) const;
+
+    ::ndk::ScopedAIBinder_DeathRecipient mBroadcastSubscriberDeathRecipient;
+
+    /**
+     * Death recipient callback that is called when a broadcast subscriber dies.
+     * The cookie is a pointer to a BroadcastSubscriberDeathCookie.
+     */
+    static void broadcastSubscriberDied(void* cookie);
 };
 
 }  // namespace statsd
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9b28cb5e8..4065b11 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2227,6 +2227,9 @@
      * <li>{@code VkPhysicalDeviceSamplerYcbcrConversionFeatures::samplerYcbcrConversion} is
      *     supported.</li>
      * </ul>
+     * A subset of devices that support Vulkan 1.1 do so via software emulation. For more
+     * information, see
+     * <a href="{@docRoot}ndk/guides/graphics/design-notes">Vulkan Design Guidelines</a>.
      */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 2a71da8..9145142 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -129,10 +129,6 @@
             super(mac);
         }
 
-        public CryptoObject(@NonNull IdentityCredential credential) {
-            super(credential);
-        }
-
         /**
          * Get {@link Signature} object.
          * @return {@link Signature} object or null if this doesn't contain one.
@@ -160,8 +156,9 @@
         /**
          * Get {@link IdentityCredential} object.
          * @return {@link IdentityCredential} object or null if this doesn't contain one.
+         * @hide
          */
-        public @Nullable IdentityCredential getIdentityCredential() {
+        public IdentityCredential getIdentityCredential() {
             return super.getIdentityCredential();
         }
     }
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index fe7c7c9..c889ee6 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -248,6 +248,27 @@
         }
     }
 
+    static ThreadLocal<Boolean> sWarnOnBlockingOnCurrentThread =
+            ThreadLocal.withInitial(() -> sWarnOnBlocking);
+
+    /**
+     * Allow blocking calls for the current thread.  See {@link #allowBlocking}.
+     *
+     * @hide
+     */
+    public static void allowBlockingForCurrentThread() {
+        sWarnOnBlockingOnCurrentThread.set(false);
+    }
+
+    /**
+     * Reset the current thread to the default blocking behavior.  See {@link #defaultBlocking}.
+     *
+     * @hide
+     */
+    public static void defaultBlockingForCurrentThread() {
+        sWarnOnBlockingOnCurrentThread.set(sWarnOnBlocking);
+    }
+
     /**
      * Raw native pointer to JavaBBinderHolder object. Owned by this Java object. Not null.
      */
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index be307ab..dd3f9fd 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -479,16 +479,21 @@
     public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
         Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
 
-        if (mWarnOnBlocking && ((flags & FLAG_ONEWAY) == 0)) {
+        if (mWarnOnBlocking && ((flags & FLAG_ONEWAY) == 0)
+                && Binder.sWarnOnBlockingOnCurrentThread.get()) {
+
             // For now, avoid spamming the log by disabling after we've logged
             // about this interface at least once
             mWarnOnBlocking = false;
+
             if (Build.IS_USERDEBUG) {
                 // Log this as a WTF on userdebug builds.
-                Log.wtf(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY",
+                Log.wtf(Binder.TAG,
+                        "Outgoing transactions from this process must be FLAG_ONEWAY",
                         new Throwable());
             } else {
-                Log.w(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY",
+                Log.w(Binder.TAG,
+                        "Outgoing transactions from this process must be FLAG_ONEWAY",
                         new Throwable());
             }
         }
diff --git a/core/java/android/service/controls/TokenProvider.aidl b/core/java/android/service/controls/TokenProvider.aidl
deleted file mode 100644
index 8f4b795..0000000
--- a/core/java/android/service/controls/TokenProvider.aidl
+++ /dev/null
@@ -1,7 +0,0 @@
-package android.service.controls;
-
-/** @hide */
-interface TokenProvider {
-    void setAuthToken(String token);
-    String getAccountName();
-}
\ No newline at end of file
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 573d8fc..f52960c 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -992,4 +992,23 @@
         }
         return mViewRoot.mWindowAttributes.insetsFlags.behavior;
     }
+
+    /**
+     * At the time we receive new leashes (e.g. InsetsSourceConsumer is processing
+     * setControl) we need to release the old leash. But we may have already scheduled
+     * a SyncRtSurfaceTransaction applier to use it from the RenderThread. To avoid
+     * synchronization issues we also release from the RenderThread so this release
+     * happens after any existing items on the work queue.
+     */
+    public void releaseSurfaceControlFromRt(SurfaceControl sc) {
+        if (mViewRoot.mView != null && mViewRoot.mView.isHardwareAccelerated()) {
+            mViewRoot.registerRtFrameCallback(frame -> {
+                  sc.release();
+            });
+            // Make sure a frame gets scheduled.
+            mViewRoot.mView.invalidate();
+        } else {
+              sc.release();
+        }
+    }
 }
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index f07f1ce..252fc0c 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -117,7 +117,7 @@
             }
         }
         if (lastControl != null) {
-            lastControl.release();
+            lastControl.release(mController);
         }
     }
 
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 29ba56a..75f6eab 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -94,9 +94,9 @@
         dest.writeParcelable(mSurfacePosition, 0 /* flags*/);
     }
 
-    public void release() {
+    public void release(InsetsController controller) {
         if (mLeash != null) {
-            mLeash.release();
+            controller.releaseSurfaceControlFromRt(mLeash);
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8d3cffc..9228fbd 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1735,7 +1735,7 @@
             mBoundsLayer = new SurfaceControl.Builder(mSurfaceSession)
                     .setContainerLayer()
                     .setName("Bounds for - " + getTitle().toString())
-                    .setParent(mSurfaceControl)
+                    .setParent(getRenderSurfaceControl())
                     .build();
             setBoundsLayerCrop();
             mTransaction.show(mBoundsLayer).apply();
diff --git a/core/java/com/android/internal/compat/OverrideAllowedState.java b/core/java/com/android/internal/compat/OverrideAllowedState.java
index bed41b3..9a78ad2 100644
--- a/core/java/com/android/internal/compat/OverrideAllowedState.java
+++ b/core/java/com/android/internal/compat/OverrideAllowedState.java
@@ -50,7 +50,7 @@
     public static final int DISABLED_NOT_DEBUGGABLE = 1;
     /**
      * Change cannot be overridden, due to the build being non-debuggable and the change being
-     * non-targetSdk.
+     * enabled regardless of targetSdk.
      */
     public static final int DISABLED_NON_TARGET_SDK = 2;
     /**
@@ -159,4 +159,28 @@
                 && appTargetSdk == otherState.appTargetSdk
                 && changeIdTargetSdk == otherState.changeIdTargetSdk;
     }
+
+    private String stateName() {
+        switch (state) {
+            case ALLOWED:
+                return "ALLOWED";
+            case DISABLED_NOT_DEBUGGABLE:
+                return "DISABLED_NOT_DEBUGGABLE";
+            case DISABLED_NON_TARGET_SDK:
+                return "DISABLED_NON_TARGET_SDK";
+            case DISABLED_TARGET_SDK_TOO_HIGH:
+                return "DISABLED_TARGET_SDK_TOO_HIGH";
+            case PACKAGE_DOES_NOT_EXIST:
+                return "PACKAGE_DOES_NOT_EXIST";
+            case LOGGING_ONLY_CHANGE:
+                return "LOGGING_ONLY_CHANGE";
+        }
+        return "UNKNOWN";
+    }
+
+    @Override
+    public String toString() {
+        return "OverrideAllowedState(state=" + stateName() + "; appTargetSdk=" + appTargetSdk
+                + "; changeIdTargetSdk=" + changeIdTargetSdk + ")";
+    }
 }
diff --git a/identity/java/android/security/identity/CredstoreResultData.java b/identity/java/android/security/identity/CredstoreResultData.java
index ef7afca..2ef735e 100644
--- a/identity/java/android/security/identity/CredstoreResultData.java
+++ b/identity/java/android/security/identity/CredstoreResultData.java
@@ -66,7 +66,7 @@
     }
 
     @Override
-    public @NonNull Collection<String> getNamespaceNames() {
+    public @NonNull Collection<String> getNamespaces() {
         return Collections.unmodifiableCollection(mData.keySet());
     }
 
diff --git a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
index 335636c..725e3d8 100644
--- a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
+++ b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
@@ -105,11 +105,11 @@
             n++;
         }
 
-        Collection<String> namespaceNames = personalizationData.getNamespaceNames();
+        Collection<String> namespaces = personalizationData.getNamespaces();
 
-        EntryNamespaceParcel[] ensParcels  = new EntryNamespaceParcel[namespaceNames.size()];
+        EntryNamespaceParcel[] ensParcels  = new EntryNamespaceParcel[namespaces.size()];
         n = 0;
-        for (String namespaceName : namespaceNames) {
+        for (String namespaceName : namespaces) {
             PersonalizationData.NamespaceData nsd =
                     personalizationData.getNamespaceData(namespaceName);
 
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index bd43919..1db2f63 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -209,6 +209,11 @@
      * <p>Note that only items referenced in {@code entriesToRequest} are returned - the
      * {@code requestMessage} parameter is only used to for enforcing reader authentication.
      *
+     * <p>The reason for having {@code requestMessage} and {@code entriesToRequest} as separate
+     * parameters is that the former represents a request from the remote verifier device
+     * (optionally signed) and this allows the application to filter the request to not include
+     * data elements which the user has not consented to sharing.
+     *
      * @param requestMessage         If not {@code null}, must contain CBOR data conforming to
      *                               the schema mentioned above.
      * @param entriesToRequest       The entries to request, organized as a map of namespace
diff --git a/identity/java/android/security/identity/PersonalizationData.java b/identity/java/android/security/identity/PersonalizationData.java
index 44370a1..b34f250 100644
--- a/identity/java/android/security/identity/PersonalizationData.java
+++ b/identity/java/android/security/identity/PersonalizationData.java
@@ -46,7 +46,7 @@
         return Collections.unmodifiableCollection(mProfiles);
     }
 
-    Collection<String> getNamespaceNames() {
+    Collection<String> getNamespaces() {
         return Collections.unmodifiableCollection(mNamespaces.keySet());
     }
 
@@ -120,7 +120,7 @@
          * @param value                   The value to add, in CBOR encoding.
          * @return The builder.
          */
-        public @NonNull Builder setEntry(@NonNull String namespace, @NonNull String name,
+        public @NonNull Builder putEntry(@NonNull String namespace, @NonNull String name,
                 @NonNull Collection<AccessControlProfileId> accessControlProfileIds,
                 @NonNull byte[] value) {
             NamespaceData namespaceData = mData.mNamespaces.get(namespace);
diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java
index 0982c8a..13552d6 100644
--- a/identity/java/android/security/identity/ResultData.java
+++ b/identity/java/android/security/identity/ResultData.java
@@ -152,7 +152,7 @@
      * @return collection of name of namespaces containing retrieved entries. May be empty if no
      *     data was retrieved.
      */
-    public abstract @NonNull Collection<String> getNamespaceNames();
+    public abstract @NonNull Collection<String> getNamespaces();
 
     /**
      * Get the names of all entries.
@@ -196,8 +196,7 @@
      * @param name the name of the entry to get the value for.
      * @return the status indicating whether the value was retrieved and if not, why.
      */
-    @Status
-    public abstract int getStatus(@NonNull String namespaceName, @NonNull String name);
+    public abstract @Status int getStatus(@NonNull String namespaceName, @NonNull String name);
 
     /**
      * Gets the raw CBOR data for the value of an entry.
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index e230917..550e41f 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -103,6 +103,16 @@
     private final Object mLock = new Object();
 
     /**
+     * For apps targeting Android R and above, {@link #getProvider(String)} will no longer throw any
+     * security exceptions.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    private static final long GET_PROVIDER_SECURITY_EXCEPTIONS = 150935354L;
+
+    /**
      * For apps targeting Android K and above, supplied {@link PendingIntent}s must be targeted to a
      * specific package.
      *
@@ -1401,6 +1411,22 @@
      */
     public @Nullable LocationProvider getProvider(@NonNull String provider) {
         Preconditions.checkArgument(provider != null, "invalid null provider");
+
+        if (!Compatibility.isChangeEnabled(GET_PROVIDER_SECURITY_EXCEPTIONS)) {
+            if (NETWORK_PROVIDER.equals(provider) || FUSED_PROVIDER.equals(provider)) {
+                try {
+                    mContext.enforcePermission(ACCESS_FINE_LOCATION, Process.myPid(),
+                            Process.myUid(), null);
+                } catch (SecurityException e) {
+                    mContext.enforcePermission(ACCESS_COARSE_LOCATION, Process.myPid(),
+                            Process.myUid(), null);
+                }
+            } else {
+                mContext.enforcePermission(ACCESS_FINE_LOCATION, Process.myPid(), Process.myUid(),
+                        null);
+            }
+        }
+
         try {
             ProviderProperties properties = mService.getProviderProperties(provider);
             if (properties == null) {
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index 22c3acf..6f985a4 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -24,6 +24,7 @@
 import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.CarDeviceProvisionedControllerImpl;
+import com.android.systemui.car.CarNotificationInterruptionStateProvider;
 import com.android.systemui.dagger.SystemUIRootComponent;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
@@ -40,6 +41,7 @@
 import com.android.systemui.statusbar.car.CarStatusBar;
 import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
@@ -63,6 +65,10 @@
 @Module(includes = {DividerModule.class})
 abstract class CarSystemUIModule {
 
+    @Binds
+    abstract NotificationInterruptionStateProvider bindNotificationInterruptionStateProvider(
+            CarNotificationInterruptionStateProvider notificationInterruptionStateProvider);
+
     @Singleton
     @Provides
     @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
new file mode 100644
index 0000000..447e579
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car;
+
+import android.content.Context;
+
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.BatteryController;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/** Auto-specific implementation of {@link NotificationInterruptionStateProvider}. */
+@Singleton
+public class CarNotificationInterruptionStateProvider extends
+        NotificationInterruptionStateProvider {
+
+    @Inject
+    public CarNotificationInterruptionStateProvider(Context context,
+            NotificationFilter filter,
+            StatusBarStateController stateController,
+            BatteryController batteryController) {
+        super(context, filter, stateController, batteryController);
+    }
+
+    @Override
+    public boolean shouldHeadsUp(NotificationEntry entry) {
+        // Because space is usually constrained in the auto use-case, there should not be a
+        // pinned notification when the shade has been expanded. Ensure this by not pinning any
+        // notification if the shade is already opened.
+        if (!getPresenter().isPresenterFullyCollapsed()) {
+            return false;
+        }
+
+        return super.shouldHeadsUp(entry);
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index de768cb..b2e2104 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -95,14 +95,13 @@
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationAlertingManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
@@ -250,9 +249,10 @@
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
-            NotificationInterruptStateProvider notificationInterruptStateProvider,
+            NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             KeyguardViewMediator keyguardViewMediator,
+            NotificationAlertingManager notificationAlertingManager,
             DisplayMetrics displayMetrics,
             MetricsLogger metricsLogger,
             @UiBackground Executor uiBgExecutor,
@@ -335,9 +335,10 @@
                 remoteInputQuickSettingsDisabler,
                 notificationGutsManager,
                 notificationLogger,
-                notificationInterruptStateProvider,
+                notificationInterruptionStateProvider,
                 notificationViewHierarchyManager,
                 keyguardViewMediator,
+                notificationAlertingManager,
                 displayMetrics,
                 metricsLogger,
                 uiBgExecutor,
@@ -487,22 +488,6 @@
                                 .isCurrentUserSetupInProgress();
                     }
                 });
-
-        mNotificationInterruptStateProvider.addSuppressor(new NotificationInterruptSuppressor() {
-            @Override
-            public String getName() {
-                return TAG;
-            }
-
-            @Override
-            public boolean suppressInterruptions(NotificationEntry entry) {
-                // Because space is usually constrained in the auto use-case, there should not be a
-                // pinned notification when the shade has been expanded.
-                // Ensure this by not allowing any interruptions (ie: pinning any notifications) if
-                // the shade is already opened.
-                return !getPresenter().isPresenterFullyCollapsed();
-            }
-        });
     }
 
     @Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
index 9a53584..4754118 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -62,12 +62,13 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.dagger.StatusBarDependenciesModule;
+import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationAlertingManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationRowModule;
@@ -145,9 +146,10 @@
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
-            NotificationInterruptStateProvider notificationInterruptionStateProvider,
+            NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             KeyguardViewMediator keyguardViewMediator,
+            NotificationAlertingManager notificationAlertingManager,
             DisplayMetrics displayMetrics,
             MetricsLogger metricsLogger,
             @UiBackground Executor uiBgExecutor,
@@ -232,6 +234,7 @@
                 notificationInterruptionStateProvider,
                 notificationViewHierarchyManager,
                 keyguardViewMediator,
+                notificationAlertingManager,
                 displayMetrics,
                 metricsLogger,
                 uiBgExecutor,
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 3f42ad4..4ea1047 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -2,6 +2,30 @@
 
     name: "SettingsLib",
 
+    defaults: [
+        "SettingsLibDependenciesWithoutWifiTracker",
+    ],
+
+    // TODO(b/149540986): revert this change.
+    static_libs: [
+         // All other dependent components should be put in
+         // "SettingsLibDependenciesWithoutWifiTracker".
+        "WifiTrackerLib",
+    ],
+
+    // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
+    // LOCAL_SHARED_JAVA_LIBRARIES := androidx.lifecycle_lifecycle-common
+
+    resource_dirs: ["res"],
+
+    srcs: ["src/**/*.java", "src/**/*.kt"],
+
+    min_sdk_version: "21",
+
+}
+
+java_defaults {
+    name: "SettingsLibDependenciesWithoutWifiTracker",
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.legacy_legacy-support-v4",
@@ -25,20 +49,9 @@
         "SettingsLibProgressBar",
         "SettingsLibAdaptiveIcon",
         "SettingsLibRadioButtonPreference",
-        "WifiTrackerLib",
         "SettingsLibDisplayDensityUtils",
         "SettingsLibSchedulesProvider",
     ],
-
-    // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
-    // LOCAL_SHARED_JAVA_LIBRARIES := androidx.lifecycle_lifecycle-common
-
-    resource_dirs: ["res"],
-
-    srcs: ["src/**/*.java", "src/**/*.kt"],
-
-    min_sdk_version: "21",
-
 }
 
 // NOTE: Keep this module in sync with ./common.mk
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index e066230..0eadcc7 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -46,6 +46,7 @@
         "SystemUIPluginLib",
         "SystemUISharedLib",
         "SettingsLib",
+        "androidx.viewpager2_viewpager2",
         "androidx.legacy_legacy-support-v4",
         "androidx.recyclerview_recyclerview",
         "androidx.preference_preference",
@@ -106,6 +107,7 @@
         "SystemUIPluginLib",
         "SystemUISharedLib",
         "SettingsLib",
+        "androidx.viewpager2_viewpager2",
         "androidx.legacy_legacy-support-v4",
         "androidx.recyclerview_recyclerview",
         "androidx.preference_preference",
diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml
index 6533c18..34a966c 100644
--- a/packages/SystemUI/res/layout/controls_management.xml
+++ b/packages/SystemUI/res/layout/controls_management.xml
@@ -25,13 +25,40 @@
     android:paddingStart="@dimen/controls_management_side_padding"
     android:paddingEnd="@dimen/controls_management_side_padding" >
 
-    <TextView
-        android:id="@+id/title"
+    <LinearLayout
+        android:orientation="horizontal"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textAppearance="?android:attr/textAppearanceLarge"
-        android:textSize="@dimen/controls_title_size"
-        android:textAlignment="center" />
+        android:gravity="center_vertical">
+
+        <FrameLayout
+            android:id="@+id/icon_frame"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="start|center_vertical"
+            android:minWidth="56dp"
+            android:visibility="gone"
+            android:paddingTop="@dimen/controls_app_icon_frame_top_padding"
+            android:paddingBottom="@dimen/controls_app_icon_frame_bottom_padding"
+            android:paddingEnd="@dimen/controls_app_icon_frame_side_padding"
+            android:paddingStart="@dimen/controls_app_icon_frame_side_padding" >
+
+            <ImageView
+                android:id="@android:id/icon"
+                android:layout_width="@dimen/controls_app_icon_size"
+                android:layout_height="@dimen/controls_app_icon_size" />
+        </FrameLayout>
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:textSize="@dimen/controls_title_size"
+            android:textAlignment="center" />
+
+    </LinearLayout>
+
 
     <TextView
         android:id="@+id/subtitle"
@@ -41,19 +68,11 @@
         android:textAppearance="?android:attr/textAppearanceSmall"
         android:textAlignment="center" />
 
-    <androidx.core.widget.NestedScrollView
+    <ViewStub
+        android:id="@+id/stub"
         android:layout_width="match_parent"
         android:layout_height="0dp"
-        android:layout_weight="1"
-        android:orientation="vertical"
-        android:layout_marginTop="@dimen/controls_management_list_margin">
-
-        <ViewStub
-            android:id="@+id/stub"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"/>
-
-    </androidx.core.widget.NestedScrollView>
+        android:layout_weight="1"/>
 
     <FrameLayout
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/controls_management_apps.xml b/packages/SystemUI/res/layout/controls_management_apps.xml
index 2bab433..42d73f3 100644
--- a/packages/SystemUI/res/layout/controls_management_apps.xml
+++ b/packages/SystemUI/res/layout/controls_management_apps.xml
@@ -14,12 +14,18 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<androidx.recyclerview.widget.RecyclerView
+<androidx.core.widget.NestedScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/list"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    >
+    android:layout_height="0dp"
+    android:layout_weight="1"
+    android:orientation="vertical"
+    android:layout_marginTop="@dimen/controls_management_list_margin">
 
-</androidx.recyclerview.widget.RecyclerView>
\ No newline at end of file
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+    />
+
+</androidx.core.widget.NestedScrollView>
diff --git a/packages/SystemUI/res/layout/controls_management_favorites.xml b/packages/SystemUI/res/layout/controls_management_favorites.xml
index aab32f4..d2ccfcb 100644
--- a/packages/SystemUI/res/layout/controls_management_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_management_favorites.xml
@@ -17,7 +17,7 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_height="0dp"
     android:orientation="vertical">
 
     <TextView
@@ -29,11 +29,17 @@
         android:gravity="center_horizontal"
     />
 
-    <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/listAll"
-        android:layout_width="match_parent"
+    <com.android.systemui.controls.management.ManagementPageIndicator
+        android:id="@+id/structure_page_indicator"
+        android:layout_width="wrap_content"
         android:layout_height="match_parent"
+        android:layout_gravity="center"
         android:layout_marginTop="@dimen/controls_management_list_margin"
-        android:nestedScrollingEnabled="false"/>
+        android:visibility="gone" />
+
+    <androidx.viewpager2.widget.ViewPager2
+        android:id="@+id/structure_pager"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
 
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_structure_page.xml b/packages/SystemUI/res/layout/controls_structure_page.xml
new file mode 100644
index 0000000..2c7e168
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_structure_page.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<androidx.core.widget.NestedScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:layout_marginTop="@dimen/controls_management_list_margin">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/listAll"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+    />
+
+</androidx.core.widget.NestedScrollView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
index ff0c6a7..92ae1b9 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_v2.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
@@ -1,71 +1,75 @@
 <?xml version="1.0" encoding="utf-8"?>
-<ScrollView
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/global_actions_container"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-  <androidx.constraintlayout.widget.ConstraintLayout
-      android:id="@+id/global_actions_grid_root"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+>
+  <com.android.systemui.globalactions.GlobalActionsFlatLayout
+      android:id="@id/global_actions_view"
       android:layout_width="match_parent"
-      android:layout_height="match_parent"
+      android:layout_height="wrap_content"
+      android:orientation="horizontal"
+      android:theme="@style/qs_theme"
+      android:gravity="top | center_horizontal"
       android:clipChildren="false"
       android:clipToPadding="false"
-      android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset"
-      android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset">
-
-    <com.android.systemui.globalactions.GlobalActionsFlatLayout
-        android:id="@id/global_actions_view"
-        android:layout_width="match_parent"
+      android:layout_marginTop="@dimen/global_actions_top_margin"
+  >
+    <LinearLayout
+        android:id="@android:id/list"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_marginLeft="@dimen/global_actions_grid_side_margin"
+        android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+        android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
+        android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
+        android:paddingTop="@dimen/global_actions_grid_vertical_padding"
+        android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
         android:orientation="horizontal"
-        android:theme="@style/qs_theme"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toRightOf="parent"
-        android:gravity="top | center_horizontal"
+        android:gravity="left"
+        android:translationZ="@dimen/global_actions_translate"
+    />
+  </com.android.systemui.globalactions.GlobalActionsFlatLayout>
+
+  <com.android.systemui.globalactions.MinHeightScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset"
+    android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset"
+    android:orientation="vertical"
+  >
+    <LinearLayout
+        android:id="@+id/global_actions_grid_root"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
         android:clipChildren="false"
+        android:orientation="vertical"
         android:clipToPadding="false"
-        android:layout_marginTop="@dimen/global_actions_top_margin">
+    >
       <LinearLayout
-          android:id="@android:id/list"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:layout_marginLeft="@dimen/global_actions_grid_side_margin"
-          android:layout_marginRight="@dimen/global_actions_grid_side_margin"
-          android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
-          android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
-          android:paddingTop="@dimen/global_actions_grid_vertical_padding"
-          android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
-          android:orientation="horizontal"
-          android:gravity="left"
-          android:translationZ="@dimen/global_actions_translate" />
-    </com.android.systemui.globalactions.GlobalActionsFlatLayout>
-
-    <LinearLayout
-        android:id="@+id/global_actions_panel"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toRightOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/global_actions_view">
-
-      <FrameLayout
-          android:id="@+id/global_actions_panel_container"
+          android:id="@+id/global_actions_panel"
           android:layout_width="match_parent"
-          android:layout_height="wrap_content" />
-    </LinearLayout>
+          android:layout_height="wrap_content"
+          android:orientation="vertical"
+      >
+        <FrameLayout
+            android:id="@+id/global_actions_panel_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+        />
+      </LinearLayout>
 
-    <LinearLayout
-        android:id="@+id/global_actions_controls"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toRightOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/global_actions_panel"
-        app:layout_constraintBottom_toBottomOf="parent" />
-  </androidx.constraintlayout.widget.ConstraintLayout>
-</ScrollView>
+      <LinearLayout
+          android:id="@+id/global_actions_controls"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:orientation="vertical"
+          android:layout_marginRight="@dimen/global_actions_grid_horizontal_padding"
+          android:layout_marginLeft="@dimen/global_actions_grid_horizontal_padding"
+      />
+    </LinearLayout>
+  </com.android.systemui.globalactions.MinHeightScrollView>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_actions_wrapped.xml b/packages/SystemUI/res/layout/global_actions_wrapped.xml
deleted file mode 100644
index d441070..0000000
--- a/packages/SystemUI/res/layout/global_actions_wrapped.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<com.android.systemui.HardwareUiLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@id/global_actions_view"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layout_gravity="top|right"
-    android:layout_marginBottom="0dp"
-    android:orientation="vertical"
-    android:paddingTop="@dimen/global_actions_top_padding"
-    android:clipToPadding="false"
-    android:theme="@style/qs_theme"
-    android:clipChildren="false">
-
-    <!-- Global actions is right-aligned to be physically near power button -->
-    <LinearLayout
-        android:id="@android:id/list"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="top|right"
-        android:gravity="center"
-        android:orientation="vertical"
-        android:padding="@dimen/global_actions_padding"
-        android:translationZ="@dimen/global_actions_translate" />
-
-    <!-- For separated button-->
-    <FrameLayout
-        android:id="@+id/separated_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="top|right"
-        android:layout_marginTop="6dp"
-        android:gravity="center"
-        android:orientation="vertical"
-        android:padding="@dimen/global_actions_padding"
-        android:translationZ="@dimen/global_actions_translate" />
-
-</com.android.systemui.HardwareUiLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9437485..291db65 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1249,6 +1249,7 @@
     <dimen name="controls_app_icon_size">32dp</dimen>
     <dimen name="controls_app_icon_frame_side_padding">8dp</dimen>
     <dimen name="controls_app_icon_frame_top_padding">4dp</dimen>
+    <dimen name="controls_app_icon_frame_bottom_padding">@dimen/controls_app_icon_frame_top_padding</dimen>
     <dimen name="controls_app_bottom_margin">8dp</dimen>
     <dimen name="controls_app_text_padding">8dp</dimen>
     <dimen name="controls_app_divider_height">2dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index b6152da..a868cf5 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -71,11 +71,12 @@
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.notification.NotificationAlertingManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ChannelEditorDialogController;
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
@@ -288,6 +289,7 @@
     @Inject Lazy<NotificationLogger> mNotificationLogger;
     @Inject Lazy<NotificationViewHierarchyManager> mNotificationViewHierarchyManager;
     @Inject Lazy<NotificationFilter> mNotificationFilter;
+    @Inject Lazy<NotificationInterruptionStateProvider> mNotificationInterruptionStateProvider;
     @Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
     @Inject Lazy<SmartReplyController> mSmartReplyController;
     @Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler;
@@ -487,6 +489,8 @@
         mProviders.put(NotificationViewHierarchyManager.class,
                 mNotificationViewHierarchyManager::get);
         mProviders.put(NotificationFilter.class, mNotificationFilter::get);
+        mProviders.put(NotificationInterruptionStateProvider.class,
+                mNotificationInterruptionStateProvider::get);
         mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get);
         mProviders.put(SmartReplyController.class, mSmartReplyController::get);
         mProviders.put(RemoteInputQuickSettingsDisabler.class,
diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
deleted file mode 100644
index ad2e002..0000000
--- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
+++ /dev/null
@@ -1,570 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
-import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
-import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.provider.Settings;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.ViewTreeObserver;
-import android.widget.LinearLayout;
-
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.leak.RotationUtils;
-
-/**
- * Layout for placing two containers at a specific physical position on the device, relative to the
- * device's hardware, regardless of screen rotation.
- */
-public class HardwareUiLayout extends MultiListLayout implements Tunable {
-
-    private static final String EDGE_BLEED = "sysui_hwui_edge_bleed";
-    private static final String ROUNDED_DIVIDER = "sysui_hwui_rounded_divider";
-    private final int[] mTmp2 = new int[2];
-    private ViewGroup mList;
-    private ViewGroup mSeparatedView;
-    private int mOldHeight;
-    private boolean mAnimating;
-    private AnimatorSet mAnimation;
-    private View mDivision;
-    private HardwareBgDrawable mListBackground;
-    private HardwareBgDrawable mSeparatedViewBackground;
-    private Animator mAnimator;
-    private boolean mCollapse;
-    private int mEndPoint;
-    private boolean mEdgeBleed;
-    private boolean mRoundedDivider;
-    private boolean mRotatedBackground;
-    private boolean mSwapOrientation = true;
-
-    public HardwareUiLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        // Manually re-initialize mRotation to portrait-mode, since this view must always
-        // be constructed in portrait mode and rotated into the correct initial position.
-        mRotation = ROTATION_NONE;
-        updateSettings();
-    }
-
-    @Override
-    protected ViewGroup getSeparatedView() {
-        return findViewById(com.android.systemui.R.id.separated_button);
-    }
-
-    @Override
-    protected ViewGroup getListView() {
-        return findViewById(android.R.id.list);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        updateSettings();
-        Dependency.get(TunerService.class).addTunable(this, EDGE_BLEED, ROUNDED_DIVIDER);
-        getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsListener);
-        Dependency.get(TunerService.class).removeTunable(this);
-    }
-
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        updateSettings();
-    }
-
-    private void updateSettings() {
-        mEdgeBleed = Settings.Secure.getInt(getContext().getContentResolver(),
-                EDGE_BLEED, 0) != 0;
-        mRoundedDivider = Settings.Secure.getInt(getContext().getContentResolver(),
-                ROUNDED_DIVIDER, 0) != 0;
-        updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding());
-        mListBackground = new HardwareBgDrawable(mRoundedDivider, !mEdgeBleed, getContext());
-        mSeparatedViewBackground = new HardwareBgDrawable(mRoundedDivider, !mEdgeBleed,
-                getContext());
-        if (mList != null) {
-            mList.setBackground(mListBackground);
-            mSeparatedView.setBackground(mSeparatedViewBackground);
-            requestLayout();
-        }
-    }
-
-    private void updateEdgeMargin(int edge) {
-        if (mList != null) {
-            MarginLayoutParams params = (MarginLayoutParams) mList.getLayoutParams();
-            if (mRotation == ROTATION_LANDSCAPE) {
-                params.topMargin = edge;
-            } else if (mRotation == ROTATION_SEASCAPE) {
-                params.bottomMargin = edge;
-            } else {
-                params.rightMargin = edge;
-            }
-            mList.setLayoutParams(params);
-        }
-
-        if (mSeparatedView != null) {
-            MarginLayoutParams params = (MarginLayoutParams) mSeparatedView.getLayoutParams();
-            if (mRotation == ROTATION_LANDSCAPE) {
-                params.topMargin = edge;
-            } else if (mRotation == ROTATION_SEASCAPE) {
-                params.bottomMargin = edge;
-            } else {
-                params.rightMargin = edge;
-            }
-            mSeparatedView.setLayoutParams(params);
-        }
-    }
-
-    private int getEdgePadding() {
-        return getContext().getResources().getDimensionPixelSize(R.dimen.edge_margin);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        if (mList == null) {
-            if (getChildCount() != 0) {
-                mList = getListView();
-                mList.setBackground(mListBackground);
-                mSeparatedView = getSeparatedView();
-                mSeparatedView.setBackground(mSeparatedViewBackground);
-                updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding());
-                mOldHeight = mList.getMeasuredHeight();
-
-                // Must be called to initialize view rotation correctly.
-                // Requires LayoutParams, hence why this isn't called during the constructor.
-                updateRotation();
-            } else {
-                return;
-            }
-        }
-        int newHeight = mList.getMeasuredHeight();
-        if (newHeight != mOldHeight) {
-            animateChild(mOldHeight, newHeight);
-        }
-
-        post(() -> updatePaddingAndGravityIfTooTall());
-        post(() -> updatePosition());
-    }
-
-    public void setSwapOrientation(boolean swapOrientation) {
-        mSwapOrientation = swapOrientation;
-    }
-
-    private void updateRotation() {
-        int rotation = RotationUtils.getRotation(getContext());
-        if (rotation != mRotation) {
-            rotate(mRotation, rotation);
-            mRotation = rotation;
-        }
-    }
-
-    /**
-     *  Requires LayoutParams to be set to work correctly, and therefore must be run after after
-     *  the HardwareUILayout has been added to the view hierarchy.
-     */
-    protected void rotate(int from, int to) {
-        super.rotate(from, to);
-        if (from != ROTATION_NONE && to != ROTATION_NONE) {
-            // Rather than handling this confusing case, just do 2 rotations.
-            rotate(from, ROTATION_NONE);
-            rotate(ROTATION_NONE, to);
-            return;
-        }
-        if (from == ROTATION_LANDSCAPE || to == ROTATION_SEASCAPE) {
-            rotateRight();
-        } else {
-            rotateLeft();
-        }
-        if (mAdapter.hasSeparatedItems()) {
-            if (from == ROTATION_SEASCAPE || to == ROTATION_SEASCAPE) {
-                // Separated view has top margin, so seascape separated view need special rotation,
-                // not a full left or right rotation.
-                swapLeftAndTop(mSeparatedView);
-            } else if (from == ROTATION_LANDSCAPE) {
-                rotateRight(mSeparatedView);
-            } else {
-                rotateLeft(mSeparatedView);
-            }
-        }
-        if (to != ROTATION_NONE) {
-            if (mList instanceof LinearLayout) {
-                mRotatedBackground = true;
-                mListBackground.setRotatedBackground(true);
-                mSeparatedViewBackground.setRotatedBackground(true);
-                LinearLayout linearLayout = (LinearLayout) mList;
-                if (mSwapOrientation) {
-                    linearLayout.setOrientation(LinearLayout.HORIZONTAL);
-                    setOrientation(LinearLayout.HORIZONTAL);
-                }
-                swapDimens(mList);
-                swapDimens(mSeparatedView);
-            }
-        } else {
-            if (mList instanceof LinearLayout) {
-                mRotatedBackground = false;
-                mListBackground.setRotatedBackground(false);
-                mSeparatedViewBackground.setRotatedBackground(false);
-                LinearLayout linearLayout = (LinearLayout) mList;
-                if (mSwapOrientation) {
-                    linearLayout.setOrientation(LinearLayout.VERTICAL);
-                    setOrientation(LinearLayout.VERTICAL);
-                }
-                swapDimens(mList);
-                swapDimens(mSeparatedView);
-            }
-        }
-    }
-
-    @Override
-    public void onUpdateList() {
-        super.onUpdateList();
-
-        for (int i = 0; i < mAdapter.getCount(); i++) {
-            ViewGroup parent;
-            boolean separated = mAdapter.shouldBeSeparated(i);
-            if (separated) {
-                parent = getSeparatedView();
-            } else {
-                parent = getListView();
-            }
-            View v = mAdapter.getView(i, null, parent);
-            parent.addView(v);
-        }
-    }
-
-    private void rotateRight() {
-        rotateRight(this);
-        rotateRight(mList);
-        swapDimens(this);
-
-        LayoutParams p = (LayoutParams) mList.getLayoutParams();
-        p.gravity = rotateGravityRight(p.gravity);
-        mList.setLayoutParams(p);
-
-        LayoutParams separatedViewLayoutParams = (LayoutParams) mSeparatedView.getLayoutParams();
-        separatedViewLayoutParams.gravity = rotateGravityRight(separatedViewLayoutParams.gravity);
-        mSeparatedView.setLayoutParams(separatedViewLayoutParams);
-
-        setGravity(rotateGravityRight(getGravity()));
-    }
-
-    private void swapDimens(View v) {
-        ViewGroup.LayoutParams params = v.getLayoutParams();
-        int h = params.width;
-        params.width = params.height;
-        params.height = h;
-        v.setLayoutParams(params);
-    }
-
-    private int rotateGravityRight(int gravity) {
-        int retGravity = 0;
-        int layoutDirection = getLayoutDirection();
-        final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
-        final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
-
-        switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
-            case Gravity.CENTER_HORIZONTAL:
-                retGravity |= Gravity.CENTER_VERTICAL;
-                break;
-            case Gravity.RIGHT:
-                retGravity |= Gravity.BOTTOM;
-                break;
-            case Gravity.LEFT:
-            default:
-                retGravity |= Gravity.TOP;
-                break;
-        }
-
-        switch (verticalGravity) {
-            case Gravity.CENTER_VERTICAL:
-                retGravity |= Gravity.CENTER_HORIZONTAL;
-                break;
-            case Gravity.BOTTOM:
-                retGravity |= Gravity.LEFT;
-                break;
-            case Gravity.TOP:
-            default:
-                retGravity |= Gravity.RIGHT;
-                break;
-        }
-        return retGravity;
-    }
-
-    private void rotateLeft() {
-        rotateLeft(this);
-        rotateLeft(mList);
-        swapDimens(this);
-
-        LayoutParams p = (LayoutParams) mList.getLayoutParams();
-        p.gravity = rotateGravityLeft(p.gravity);
-        mList.setLayoutParams(p);
-
-        LayoutParams separatedViewLayoutParams = (LayoutParams) mSeparatedView.getLayoutParams();
-        separatedViewLayoutParams.gravity = rotateGravityLeft(separatedViewLayoutParams.gravity);
-        mSeparatedView.setLayoutParams(separatedViewLayoutParams);
-
-        setGravity(rotateGravityLeft(getGravity()));
-    }
-
-    private int rotateGravityLeft(int gravity) {
-        if (gravity == -1) {
-            gravity = Gravity.TOP | Gravity.START;
-        }
-        int retGravity = 0;
-        int layoutDirection = getLayoutDirection();
-        final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
-        final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
-
-        switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
-            case Gravity.CENTER_HORIZONTAL:
-                retGravity |= Gravity.CENTER_VERTICAL;
-                break;
-            case Gravity.RIGHT:
-                retGravity |= Gravity.TOP;
-                break;
-            case Gravity.LEFT:
-            default:
-                retGravity |= Gravity.BOTTOM;
-                break;
-        }
-
-        switch (verticalGravity) {
-            case Gravity.CENTER_VERTICAL:
-                retGravity |= Gravity.CENTER_HORIZONTAL;
-                break;
-            case Gravity.BOTTOM:
-                retGravity |= Gravity.RIGHT;
-                break;
-            case Gravity.TOP:
-            default:
-                retGravity |= Gravity.LEFT;
-                break;
-        }
-        return retGravity;
-    }
-
-    private void rotateLeft(View v) {
-        v.setPadding(v.getPaddingTop(), v.getPaddingRight(), v.getPaddingBottom(),
-                v.getPaddingLeft());
-        MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams();
-        params.setMargins(params.topMargin, params.rightMargin, params.bottomMargin,
-                params.leftMargin);
-        v.setLayoutParams(params);
-    }
-
-    private void rotateRight(View v) {
-        v.setPadding(v.getPaddingBottom(), v.getPaddingLeft(), v.getPaddingTop(),
-                v.getPaddingRight());
-        MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams();
-        params.setMargins(params.bottomMargin, params.leftMargin, params.topMargin,
-                params.rightMargin);
-        v.setLayoutParams(params);
-    }
-
-    private void swapLeftAndTop(View v) {
-        v.setPadding(v.getPaddingTop(), v.getPaddingLeft(), v.getPaddingBottom(),
-                v.getPaddingRight());
-        MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams();
-        params.setMargins(params.topMargin, params.leftMargin, params.bottomMargin,
-                params.rightMargin);
-        v.setLayoutParams(params);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-
-        post(() -> updatePosition());
-
-    }
-
-    private void animateChild(int oldHeight, int newHeight) {
-        if (true) return;
-        if (mAnimating) {
-            mAnimation.cancel();
-        }
-        mAnimating = true;
-        mAnimation = new AnimatorSet();
-        mAnimation.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mAnimating = false;
-            }
-        });
-        int fromTop = mList.getTop();
-        int fromBottom = mList.getBottom();
-        int toTop = fromTop - ((newHeight - oldHeight) / 2);
-        int toBottom = fromBottom + ((newHeight - oldHeight) / 2);
-        ObjectAnimator top = ObjectAnimator.ofInt(mList, "top", fromTop, toTop);
-        top.addUpdateListener(animation -> mListBackground.invalidateSelf());
-        mAnimation.playTogether(top,
-                ObjectAnimator.ofInt(mList, "bottom", fromBottom, toBottom));
-    }
-
-    public void setDivisionView(View v) {
-        mDivision = v;
-        if (mDivision != null) {
-            mDivision.addOnLayoutChangeListener(
-                    (v1, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
-                            updatePosition());
-        }
-        updatePosition();
-    }
-
-    private void updatePosition() {
-        if (mList == null) return;
-        // If got separated button, setRotatedBackground to false,
-        // all items won't get white background.
-        boolean separated = mAdapter.hasSeparatedItems();
-        mListBackground.setRotatedBackground(separated);
-        mSeparatedViewBackground.setRotatedBackground(separated);
-        if (mDivision != null && mDivision.getVisibility() == VISIBLE) {
-            int index = mRotatedBackground ? 0 : 1;
-            mDivision.getLocationOnScreen(mTmp2);
-            float trans = mRotatedBackground ? mDivision.getTranslationX()
-                    : mDivision.getTranslationY();
-            int viewTop = (int) (mTmp2[index] + trans);
-            mList.getLocationOnScreen(mTmp2);
-            viewTop -= mTmp2[index];
-            setCutPoint(viewTop);
-        } else {
-            setCutPoint(mList.getMeasuredHeight());
-        }
-    }
-
-    private void setCutPoint(int point) {
-        int curPoint = mListBackground.getCutPoint();
-        if (curPoint == point) return;
-        if (getAlpha() == 0 || curPoint == 0) {
-            mListBackground.setCutPoint(point);
-            return;
-        }
-        if (mAnimator != null) {
-            if (mEndPoint == point) {
-                return;
-            }
-            mAnimator.cancel();
-        }
-        mEndPoint = point;
-        mAnimator = ObjectAnimator.ofInt(mListBackground, "cutPoint", curPoint, point);
-        if (mCollapse) {
-            mAnimator.setStartDelay(300);
-            mCollapse = false;
-        }
-        mAnimator.start();
-    }
-
-    // If current power menu height larger then screen height, remove padding to break power menu
-    // alignment and set menu center vertical within the screen.
-    private void updatePaddingAndGravityIfTooTall() {
-        int defaultTopPadding;
-        int viewsTotalHeight;
-        int separatedViewTopMargin;
-        int screenHeight;
-        int totalHeight;
-        int targetGravity;
-        boolean separated = mAdapter.hasSeparatedItems();
-        MarginLayoutParams params = (MarginLayoutParams) mSeparatedView.getLayoutParams();
-        switch (RotationUtils.getRotation(getContext())) {
-            case RotationUtils.ROTATION_LANDSCAPE:
-                defaultTopPadding = getPaddingLeft();
-                viewsTotalHeight = mList.getMeasuredWidth() + mSeparatedView.getMeasuredWidth();
-                separatedViewTopMargin = separated ? params.leftMargin : 0;
-                screenHeight = getMeasuredWidth();
-                targetGravity = Gravity.CENTER_HORIZONTAL|Gravity.TOP;
-                break;
-            case RotationUtils.ROTATION_SEASCAPE:
-                defaultTopPadding = getPaddingRight();
-                viewsTotalHeight = mList.getMeasuredWidth() + mSeparatedView.getMeasuredWidth();
-                separatedViewTopMargin = separated ? params.leftMargin : 0;
-                screenHeight = getMeasuredWidth();
-                targetGravity = Gravity.CENTER_HORIZONTAL|Gravity.BOTTOM;
-                break;
-            default: // Portrait
-                defaultTopPadding = getPaddingTop();
-                viewsTotalHeight = mList.getMeasuredHeight() + mSeparatedView.getMeasuredHeight();
-                separatedViewTopMargin = separated ? params.topMargin : 0;
-                screenHeight = getMeasuredHeight();
-                targetGravity = Gravity.CENTER_VERTICAL|Gravity.RIGHT;
-                break;
-        }
-        totalHeight = defaultTopPadding + viewsTotalHeight + separatedViewTopMargin;
-        if (totalHeight >= screenHeight) {
-            setPadding(0, 0, 0, 0);
-            setGravity(targetGravity);
-        }
-    }
-
-    @Override
-    public ViewOutlineProvider getOutlineProvider() {
-        return super.getOutlineProvider();
-    }
-
-    public void setCollapse() {
-        mCollapse = true;
-    }
-
-    private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = inoutInfo -> {
-        if (mHasOutsideTouch || (mList == null)) {
-            inoutInfo.setTouchableInsets(
-                    ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
-            return;
-        }
-        inoutInfo.setTouchableInsets(
-                ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT);
-        inoutInfo.contentInsets.set(mList.getLeft(), mList.getTop(),
-                0, getBottom() - mList.getBottom());
-    };
-
-    private float getAnimationDistance() {
-        return getContext().getResources().getDimension(
-                com.android.systemui.R.dimen.global_actions_panel_width) / 2;
-    }
-
-    @Override
-    public float getAnimationOffsetX() {
-        if (RotationUtils.getRotation(mContext) == ROTATION_NONE) {
-            return getAnimationDistance();
-        }
-        return 0;
-    }
-
-    @Override
-    public float getAnimationOffsetY() {
-        switch (RotationUtils.getRotation(getContext())) {
-            case RotationUtils.ROTATION_LANDSCAPE:
-                return -getAnimationDistance();
-            case RotationUtils.ROTATION_SEASCAPE:
-                return getAnimationDistance();
-            default: // Portrait
-                return 0;
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index f873f42..d754725 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -83,11 +83,11 @@
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -169,7 +169,7 @@
     // Callback that updates BubbleOverflowActivity on data change.
     @Nullable private Runnable mOverflowCallback = null;
 
-    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
     private IStatusBarService mBarService;
 
     // Used for determining view rect for touch interaction
@@ -279,7 +279,7 @@
             ShadeController shadeController,
             BubbleData data,
             ConfigurationController configurationController,
-            NotificationInterruptStateProvider interruptionStateProvider,
+            NotificationInterruptionStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
             NotificationGroupManager groupManager,
@@ -304,7 +304,7 @@
             BubbleData data,
             @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
             ConfigurationController configurationController,
-            NotificationInterruptStateProvider interruptionStateProvider,
+            NotificationInterruptionStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
             NotificationGroupManager groupManager,
@@ -316,7 +316,7 @@
         dumpManager.registerDumpable(TAG, this);
         mContext = context;
         mShadeController = shadeController;
-        mNotificationInterruptStateProvider = interruptionStateProvider;
+        mNotificationInterruptionStateProvider = interruptionStateProvider;
         mNotifUserManager = notifUserManager;
         mZenModeController = zenModeController;
         mFloatingContentCoordinator = floatingContentCoordinator;
@@ -632,7 +632,7 @@
         for (NotificationEntry e :
                 mNotificationEntryManager.getActiveNotificationsForCurrentUser()) {
             if (savedBubbleKeys.contains(e.getKey())
-                    && mNotificationInterruptStateProvider.shouldBubbleUp(e)
+                    && mNotificationInterruptionStateProvider.shouldBubbleUp(e)
                     && canLaunchInActivityView(mContext, e)) {
                 updateBubble(e, /* suppressFlyout= */ true);
             }
@@ -894,7 +894,7 @@
         boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
                 mContext, entry, previouslyUserCreated, userBlocked);
 
-        if (mNotificationInterruptStateProvider.shouldBubbleUp(entry)
+        if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                 && (canLaunchInActivityView(mContext, entry) || wasAdjusted)) {
             if (wasAdjusted && !previouslyUserCreated) {
                 // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated
@@ -910,7 +910,7 @@
         boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
                 mContext, entry, previouslyUserCreated, userBlocked);
 
-        boolean shouldBubble = mNotificationInterruptStateProvider.shouldBubbleUp(entry)
+        boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                 && (canLaunchInActivityView(mContext, entry) || wasAdjusted);
         if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) {
             // It was previously a bubble but no longer a bubble -- lets remove it
@@ -1311,7 +1311,7 @@
     private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
-            if (mStackView != null && mStackView.getBubbleCount() > 0) {
+            if (mStackView != null) {
                 mStackView.post(() -> mStackView.onImeVisibilityChanged(imeVisible, imeHeight));
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 8cc10d9..96c887f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -126,6 +126,11 @@
     @VisibleForTesting
     static final int FLYOUT_HIDE_AFTER = 5000;
 
+    private static final PhysicsAnimator.SpringConfig FLYOUT_IME_ANIMATION_SPRING_CONFIG =
+            new PhysicsAnimator.SpringConfig(
+                    StackAnimationController.IME_ANIMATION_STIFFNESS,
+                    StackAnimationController.DEFAULT_BOUNCINESS);
+
     /**
      * Interface to synchronize {@link View} state and the screen.
      *
@@ -1354,8 +1359,23 @@
     public void onImeVisibilityChanged(boolean visible, int height) {
         mStackAnimationController.setImeHeight(visible ? height + mImeOffset : 0);
 
-        if (!mIsExpanded) {
-            mStackAnimationController.animateForImeVisibility(visible);
+        if (!mIsExpanded && getBubbleCount() > 0) {
+            final float stackDestinationY =
+                    mStackAnimationController.animateForImeVisibility(visible);
+
+            // How far the stack is animating due to IME, we'll just animate the flyout by that
+            // much too.
+            final float stackDy =
+                    stackDestinationY - mStackAnimationController.getStackPosition().y;
+
+            // If the flyout is visible, translate it along with the bubble stack.
+            if (mFlyout.getVisibility() == VISIBLE) {
+                PhysicsAnimator.getInstance(mFlyout)
+                        .spring(DynamicAnimation.TRANSLATION_Y,
+                                mFlyout.getTranslationY() + stackDy,
+                                FLYOUT_IME_ANIMATION_SPRING_CONFIG)
+                        .start();
+            }
         }
     }
 
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 b81665c..86387f1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -68,9 +68,10 @@
     /**
      * Values to use for the default {@link SpringForce} provided to the physics animation layout.
      */
-    private static final int DEFAULT_STIFFNESS = 12000;
+    public static final int DEFAULT_STIFFNESS = 12000;
+    public static final float IME_ANIMATION_STIFFNESS = SpringForce.STIFFNESS_LOW;
     private static final int FLING_FOLLOW_STIFFNESS = 20000;
-    private static final float DEFAULT_BOUNCINESS = 0.9f;
+    public static final float DEFAULT_BOUNCINESS = 0.9f;
 
     /**
      * Friction applied to fling animations. Since the stack must land on one of the sides of the
@@ -118,8 +119,11 @@
     /** Whether or not the stack's start position has been set. */
     private boolean mStackMovedToStartPosition = false;
 
-    /** The most recent position in which the stack was resting on the edge of the screen. */
-    @Nullable private PointF mRestingStackPosition;
+    /**
+     * The stack's most recent position along the edge of the screen. This is saved when the last
+     * bubble is removed, so that the stack can be restored in its previous position.
+     */
+    private PointF mRestingStackPosition;
 
     /** The height of the most recently visible IME. */
     private float mImeHeight = 0f;
@@ -465,7 +469,6 @@
 
                 .addEndListener((animation, canceled, endValue, endVelocity) -> {
                     if (!canceled) {
-                        mRestingStackPosition = new PointF();
                         mRestingStackPosition.set(mStackPosition);
 
                         springFirstBubbleWithStackFollowing(property, spring, endVelocity,
@@ -501,8 +504,10 @@
     /**
      * Animates the stack either away from the newly visible IME, or back to its original position
      * due to the IME going away.
+     *
+     * @return The destination Y value of the stack due to the IME movement.
      */
-    public void animateForImeVisibility(boolean imeVisible) {
+    public float animateForImeVisibility(boolean imeVisible) {
         final float maxBubbleY = getAllowableStackPositionRegion().bottom;
         float destinationY = Float.MIN_VALUE;
 
@@ -523,12 +528,14 @@
             springFirstBubbleWithStackFollowing(
                     DynamicAnimation.TRANSLATION_Y,
                     getSpringForce(DynamicAnimation.TRANSLATION_Y, /* view */ null)
-                            .setStiffness(SpringForce.STIFFNESS_LOW),
+                            .setStiffness(IME_ANIMATION_STIFFNESS),
                     /* startVel */ 0f,
                     destinationY);
 
             notifyFloatingCoordinatorStackAnimatingTo(mStackPosition.x, destinationY);
         }
+
+        return destinationY;
     }
 
     /**
@@ -583,7 +590,7 @@
                             - mBubblePaddingTop
                             - (mImeHeight > Float.MIN_VALUE ? mImeHeight + mBubblePaddingTop : 0f)
                             - Math.max(
-                            insets.getSystemWindowInsetBottom(),
+                            insets.getStableInsetBottom(),
                             insets.getDisplayCutout() != null
                                     ? insets.getDisplayCutout().getSafeInsetBottom()
                                     : 0);
@@ -853,7 +860,12 @@
     public void setStackPosition(PointF pos) {
         Log.d(TAG, String.format("Setting position to (%f, %f).", pos.x, pos.y));
         mStackPosition.set(pos.x, pos.y);
-        mRestingStackPosition = mStackPosition;
+
+        if (mRestingStackPosition == null) {
+            mRestingStackPosition = new PointF();
+        }
+
+        mRestingStackPosition.set(mStackPosition);
 
         // If we're not the active controller, we don't want to physically move the bubble views.
         if (isActiveController()) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
index 27c9e98..ac97d8a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -25,8 +25,8 @@
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -54,7 +54,7 @@
             ShadeController shadeController,
             BubbleData data,
             ConfigurationController configurationController,
-            NotificationInterruptStateProvider interruptionStateProvider,
+            NotificationInterruptionStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager notifUserManager,
             NotificationGroupManager groupManager,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 50bd1ad..5e1ed58 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -253,17 +253,21 @@
                     }
 
                     override fun error(message: String) {
-                        val loadData = Favorites.getControlsForComponent(componentName).let {
-                            controls ->
+                        executor.execute {
+                            val loadData = Favorites.getControlsForComponent(componentName)
+                                .let { controls ->
                                 val keys = controls.map { it.controlId }
                                 createLoadDataObject(
-                                    controls.map { createRemovedStatus(componentName, it, false) },
-                                    keys,
-                                    true
+                                        controls.map {
+                                            createRemovedStatus(componentName, it, false)
+                                        },
+                                        keys,
+                                        true
                                 )
-                        }
+                            }
 
-                        dataCallback.accept(loadData)
+                            dataCallback.accept(loadData)
+                        }
                     }
                 }
         )
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
index c053517..01f9069 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
@@ -22,14 +22,20 @@
 import com.android.systemui.controls.controller.ControlInfo
 
 /**
- * This model is used to show all controls separated by zones.
+ * This model is used to show controls separated by zones.
  *
  * The model will sort the controls and zones in the following manner:
  *  * The zones will be sorted in a first seen basis
  *  * The controls in each zone will be sorted in a first seen basis.
  *
- * @property controls List of all controls as returned by loading
- * @property initialFavoriteIds sorted ids of favorite controls
+ *  The controls passed should belong to the same structure, as an instance of this model will be
+ *  created for each structure.
+ *
+ *  The list of favorite ids can contain ids for controls not passed to this model. Those will be
+ *  filtered out.
+ *
+ * @property controls List of controls as returned by loading
+ * @property initialFavoriteIds sorted ids of favorite controls.
  * @property noZoneString text to use as header for all controls that have blank or `null` zone.
  */
 class AllModel(
@@ -50,7 +56,10 @@
             }
         }
 
-    private val favoriteIds = initialFavoriteIds.toMutableList()
+    private val favoriteIds = run {
+        val ids = controls.mapTo(HashSet()) { it.control.controlId }
+        initialFavoriteIds.filter { it in ids }.toMutableList()
+    }
 
     override val elements: List<ElementWrapper> = createWrappers(controls)
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index c21f724..179e9fb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -42,7 +42,6 @@
  * @param onlyFavorites set to true to only display favorites instead of all controls
  */
 class ControlAdapter(
-    private val layoutInflater: LayoutInflater,
     private val elevation: Float
 ) : RecyclerView.Adapter<Holder>() {
 
@@ -60,6 +59,7 @@
     private var model: ControlsModel? = null
 
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
+        val layoutInflater = LayoutInflater.from(parent.context)
         return when (viewType) {
             TYPE_CONTROL -> {
                 ControlHolder(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 471f9d3..04715ab 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -19,20 +19,24 @@
 import android.app.Activity
 import android.content.ComponentName
 import android.content.Intent
+import android.graphics.drawable.Drawable
 import android.os.Bundle
-import android.view.LayoutInflater
+import android.text.TextUtils
 import android.view.View
 import android.view.ViewStub
 import android.widget.Button
+import android.widget.ImageView
 import android.widget.TextView
-import androidx.recyclerview.widget.GridLayoutManager
-import androidx.recyclerview.widget.RecyclerView
+import androidx.viewpager2.widget.ViewPager2
 import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.PageIndicator
 import com.android.systemui.settings.CurrentUserTracker
+import java.text.Collator
 import java.util.concurrent.Executor
 import java.util.function.Consumer
 import javax.inject.Inject
@@ -40,6 +44,7 @@
 class ControlsFavoritingActivity @Inject constructor(
     @Main private val executor: Executor,
     private val controller: ControlsControllerImpl,
+    private val listingController: ControlsListingController,
     broadcastDispatcher: BroadcastDispatcher
 ) : Activity() {
 
@@ -48,12 +53,18 @@
         const val EXTRA_APP = "extra_app_label"
     }
 
-    private lateinit var recyclerViewAll: RecyclerView
-    private lateinit var adapterAll: ControlAdapter
-    private lateinit var statusText: TextView
-    private var model: ControlsModel? = null
     private var component: ComponentName? = null
-    private var structureName: CharSequence = ""
+    private var appName: CharSequence? = null
+
+    private lateinit var structurePager: ViewPager2
+    private lateinit var statusText: TextView
+    private lateinit var titleView: TextView
+    private lateinit var iconView: ImageView
+    private lateinit var iconFrame: View
+    private lateinit var pageIndicator: PageIndicator
+    private var listOfStructures = emptyList<StructureContainer>()
+
+    private lateinit var comparator: Comparator<StructureContainer>
 
     private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
         private val startingUser = controller.currentUserId
@@ -66,29 +77,117 @@
         }
     }
 
+    private val listingCallback = object : ControlsListingController.ControlsListingCallback {
+        private var icon: Drawable? = null
+
+        override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+            val newIcon = serviceInfos.firstOrNull { it.componentName == component }?.loadIcon()
+            if (icon == newIcon) return
+            icon = newIcon
+            executor.execute {
+                if (icon != null) {
+                    iconView.setImageDrawable(icon)
+                }
+                iconFrame.visibility = if (icon != null) View.VISIBLE else View.GONE
+            }
+        }
+    }
+
     override fun onBackPressed() {
         finish()
     }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+        val collator = Collator.getInstance(resources.configuration.locales[0])
+        comparator = compareBy(collator) { it.structureName }
+        appName = intent.getCharSequenceExtra(EXTRA_APP)
+        component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
+
+        bindViews()
+
+        setUpPager()
+
+        loadControls()
+
+        listingController.addCallback(listingCallback)
+
+        currentUserTracker.startTracking()
+    }
+
+    private fun loadControls() {
+        component?.let {
+            statusText.text = resources.getText(com.android.internal.R.string.loading)
+            val emptyZoneString = resources.getText(
+                    R.string.controls_favorite_other_zone_header)
+            controller.loadForComponent(it, Consumer { data ->
+                val allControls = data.allControls
+                val favoriteKeys = data.favoritesIds
+                val error = data.errorOnLoad
+                val controlsByStructure = allControls.groupBy { it.control.structure ?: "" }
+                listOfStructures = controlsByStructure.map {
+                    StructureContainer(it.key, AllModel(it.value, favoriteKeys, emptyZoneString))
+                }.sortedWith(comparator)
+                executor.execute {
+                    structurePager.adapter = StructureAdapter(listOfStructures)
+                    if (error) {
+                        statusText.text = resources.getText(R.string.controls_favorite_load_error)
+                    } else {
+                        statusText.visibility = View.GONE
+                    }
+                    pageIndicator.setNumPages(listOfStructures.size)
+                    pageIndicator.setLocation(0f)
+                    pageIndicator.visibility =
+                        if (listOfStructures.size > 1) View.VISIBLE else View.GONE
+                }
+            })
+        }
+    }
+
+    private fun setUpPager() {
+        structurePager.apply {
+            adapter = StructureAdapter(emptyList())
+            registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
+                override fun onPageSelected(position: Int) {
+                    super.onPageSelected(position)
+                    val name = listOfStructures[position].structureName
+                    titleView.text = if (!TextUtils.isEmpty(name)) name else appName
+                }
+
+                override fun onPageScrolled(
+                    position: Int,
+                    positionOffset: Float,
+                    positionOffsetPixels: Int
+                ) {
+                    super.onPageScrolled(position, positionOffset, positionOffsetPixels)
+                    pageIndicator.setLocation(position + positionOffset)
+                }
+            })
+        }
+    }
+
+    private fun bindViews() {
         setContentView(R.layout.controls_management)
         requireViewById<ViewStub>(R.id.stub).apply {
             layoutResource = R.layout.controls_management_favorites
             inflate()
         }
 
-        val app = intent.getCharSequenceExtra(EXTRA_APP)
-        component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
         statusText = requireViewById(R.id.status_message)
+        pageIndicator = requireViewById(R.id.structure_page_indicator)
 
-        setUpRecyclerView()
-
-        requireViewById<TextView>(R.id.title).text = app?.let { it }
-                ?: resources.getText(R.string.controls_favorite_default_title)
+        titleView = requireViewById<TextView>(R.id.title).apply {
+            text = appName ?: resources.getText(R.string.controls_favorite_default_title)
+        }
         requireViewById<TextView>(R.id.subtitle).text =
                 resources.getText(R.string.controls_favorite_subtitle)
+        iconView = requireViewById(com.android.internal.R.id.icon)
+        iconFrame = requireViewById(R.id.icon_frame)
+        structurePager = requireViewById<ViewPager2>(R.id.structure_pager)
+        bindButtons()
+    }
 
+    private fun bindButtons() {
         requireViewById<Button>(R.id.other_apps).apply {
             visibility = View.VISIBLE
             setOnClickListener {
@@ -98,64 +197,21 @@
 
         requireViewById<Button>(R.id.done).setOnClickListener {
             if (component == null) return@setOnClickListener
-            val favoritesForStorage = model?.favorites?.map {
-                it.build()
-            }
-            if (favoritesForStorage != null) {
-                controller.replaceFavoritesForStructure(StructureInfo(component!!, structureName,
+            listOfStructures.forEach {
+                val favoritesForStorage = it.model.favorites.map { it.build() }
+                controller.replaceFavoritesForStructure(StructureInfo(component!!, it.structureName,
                         favoritesForStorage))
-                finishAffinity()
             }
-        }
 
-        component?.let {
-            statusText.text = resources.getText(com.android.internal.R.string.loading)
-            controller.loadForComponent(it, Consumer { data ->
-                val allControls = data.allControls
-                val favoriteKeys = data.favoritesIds
-                val error = data.errorOnLoad
-                val structures = allControls.fold(hashSetOf<CharSequence>()) {
-                    s, c ->
-                        s.add(c.control.structure ?: "")
-                        s
-                }
-                // TODO add multi structure switching support
-                executor.execute {
-                    val emptyZoneString = resources.getText(
-                            R.string.controls_favorite_other_zone_header)
-                    val model = AllModel(allControls, favoriteKeys, emptyZoneString)
-                    adapterAll.changeModel(model)
-                    this.model = model
-                    if (error) {
-                        statusText.text = resources.getText(R.string.controls_favorite_load_error)
-                    } else {
-                        statusText.visibility = View.GONE
-                    }
-                }
-            })
-        }
-
-        currentUserTracker.startTracking()
-    }
-
-    private fun setUpRecyclerView() {
-        val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin)
-        val itemDecorator = MarginItemDecorator(margin, margin)
-        val layoutInflater = LayoutInflater.from(applicationContext)
-        val elevation = resources.getFloat(R.dimen.control_card_elevation)
-
-        adapterAll = ControlAdapter(layoutInflater, elevation)
-        recyclerViewAll = requireViewById<RecyclerView>(R.id.listAll).apply {
-            adapter = adapterAll
-            layoutManager = GridLayoutManager(applicationContext, 2).apply {
-                spanSizeLookup = adapterAll.spanSizeLookup
-            }
-            addItemDecoration(itemDecorator)
+            finishAffinity()
         }
     }
 
     override fun onDestroy() {
         currentUserTracker.stopTracking()
+        listingController.removeCallback(listingCallback)
         super.onDestroy()
     }
 }
+
+data class StructureContainer(val structureName: CharSequence, val model: ControlsModel)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt
new file mode 100644
index 0000000..4289274
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.controls.management
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import com.android.systemui.qs.PageIndicator
+
+/**
+ * Page indicator for management screens.
+ *
+ * Adds RTL support to [PageIndicator]. To be used with [ViewPager2].
+ */
+class ManagementPageIndicator(
+    context: Context,
+    attrs: AttributeSet
+) : PageIndicator(context, attrs) {
+
+    override fun setLocation(location: Float) {
+        // Location doesn't know about RTL
+        if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
+            val numPages = childCount
+            super.setLocation(numPages - 1 - location)
+        } else {
+            super.setLocation(location)
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt
new file mode 100644
index 0000000..cb67454
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.controls.management
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.R
+
+class StructureAdapter(
+    private val models: List<StructureContainer>
+) : RecyclerView.Adapter<StructureAdapter.StructureHolder>() {
+
+    override fun onCreateViewHolder(parent: ViewGroup, p1: Int): StructureHolder {
+        val layoutInflater = LayoutInflater.from(parent.context)
+        return StructureHolder(
+            layoutInflater.inflate(R.layout.controls_structure_page, parent, false)
+        )
+    }
+
+    override fun getItemCount() = models.size
+
+    override fun onBindViewHolder(holder: StructureHolder, index: Int) {
+        holder.bind(models[index].model)
+    }
+
+    class StructureHolder(view: View) : RecyclerView.ViewHolder(view) {
+
+        private val recyclerView: RecyclerView
+        private val controlAdapter: ControlAdapter
+
+        init {
+            recyclerView = itemView.requireViewById<RecyclerView>(R.id.listAll)
+            val elevation = itemView.context.resources.getFloat(R.dimen.control_card_elevation)
+            controlAdapter = ControlAdapter(elevation)
+            setUpRecyclerView()
+        }
+
+        fun bind(model: ControlsModel) {
+            controlAdapter.changeModel(model)
+        }
+
+        private fun setUpRecyclerView() {
+            val margin = itemView.context.resources
+                .getDimensionPixelSize(R.dimen.controls_card_margin)
+            val itemDecorator = MarginItemDecorator(margin, margin)
+
+            recyclerView.apply {
+                this.adapter = controlAdapter
+                layoutManager = GridLayoutManager(recyclerView.context, 2).apply {
+                    spanSizeLookup = controlAdapter.spanSizeLookup
+                }
+                addItemDecoration(itemDecorator)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index ca6619b..826618a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -16,19 +16,14 @@
 
 package com.android.systemui.controls.ui
 
-import android.accounts.Account
-import android.accounts.AccountManager
 import android.app.Dialog
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
-import android.content.ServiceConnection
 import android.content.SharedPreferences
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.LayerDrawable
-import android.os.IBinder
 import android.service.controls.Control
-import android.service.controls.TokenProvider
 import android.service.controls.actions.ControlAction
 import android.util.Log
 import android.view.ContextThemeWrapper
@@ -61,68 +56,6 @@
 import javax.inject.Inject
 import javax.inject.Singleton
 
-// TEMP CODE for MOCK
-private const val TOKEN = "https://www.googleapis.com/auth/assistant"
-private const val SCOPE = "oauth2:" + TOKEN
-private var tokenProviderConnection: TokenProviderConnection? = null
-class TokenProviderConnection(
-    val cc: ControlsController,
-    val context: Context,
-    val structure: StructureInfo?
-) : ServiceConnection {
-    private var mTokenProvider: TokenProvider? = null
-
-    override fun onServiceConnected(cName: ComponentName, binder: IBinder) {
-        Thread({
-            Log.i(ControlsUiController.TAG, "TokenProviderConnection connected")
-            mTokenProvider = TokenProvider.Stub.asInterface(binder)
-
-            val mLastAccountName = mTokenProvider?.getAccountName()
-
-            if (mLastAccountName == null || mLastAccountName.isEmpty()) {
-                Log.e(ControlsUiController.TAG, "NO ACCOUNT IS SET. Open HomeMock app")
-            } else {
-                mTokenProvider?.setAuthToken(getAuthToken(mLastAccountName))
-                structure?.let {
-                    cc.subscribeToFavorites(it)
-                }
-            }
-        }, "TokenProviderThread").start()
-    }
-
-    override fun onServiceDisconnected(cName: ComponentName) {
-        mTokenProvider = null
-    }
-
-    fun getAuthToken(accountName: String): String? {
-        val am = AccountManager.get(context)
-        val accounts = am.getAccountsByType("com.google")
-        if (accounts == null || accounts.size == 0) {
-            Log.w(ControlsUiController.TAG, "No com.google accounts found")
-            return null
-        }
-
-        var account: Account? = null
-        for (a in accounts) {
-            if (a.name.equals(accountName)) {
-                account = a
-                break
-            }
-        }
-
-        if (account == null) {
-            account = accounts[0]
-        }
-
-        try {
-            return am.blockingGetAuthToken(account!!, SCOPE, true)
-        } catch (e: Throwable) {
-            Log.e(ControlsUiController.TAG, "Error getting auth token", e)
-            return null
-        }
-    }
-}
-
 private data class ControlKey(val componentName: ComponentName, val controlId: String)
 
 @Singleton
@@ -215,21 +148,10 @@
                 ControlKey(selectedStructure.componentName, it.ci.controlId)
             }
             listingCallback = createCallback(::showControlsView)
+            controlsController.get().subscribeToFavorites(selectedStructure)
         }
 
         controlsListingController.get().addCallback(listingCallback)
-
-        // Temp code to pass auth
-        tokenProviderConnection = TokenProviderConnection(controlsController.get(), context,
-                selectedStructure)
-
-        val serviceIntent = Intent()
-        serviceIntent.setComponent(ComponentName("com.android.systemui.home.mock",
-                "com.android.systemui.home.mock.AuthService"))
-        if (!context.bindService(serviceIntent, tokenProviderConnection!!,
-                Context.BIND_AUTO_CREATE)) {
-            controlsController.get().subscribeToFavorites(selectedStructure)
-        }
     }
 
     private fun showInitialSetupView(items: List<SelectionItem>) {
@@ -393,8 +315,6 @@
         activeDialog?.dismiss()
 
         controlsController.get().unsubscribe()
-        context.unbindService(tokenProviderConnection)
-        tokenProviderConnection = null
 
         parent.removeAllViews()
         controlsById.clear()
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 3a4b273..6c502d2 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -90,7 +90,7 @@
 
     /** */
     @Provides
-    public AmbientDisplayConfiguration provideAmbientDisplayConfiguration(Context context) {
+    public AmbientDisplayConfiguration provideAmbientDispalyConfiguration(Context context) {
         return new AmbientDisplayConfiguration(context);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 24f505d..786ad2c 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -1574,7 +1574,6 @@
 
         private ControlsUiController mControlsUiController;
         private ViewGroup mControlsView;
-        private ViewGroup mContainerView;
 
         ActionsDialog(Context context, MyAdapter adapter,
                 GlobalActionsPanelPlugin.PanelViewController plugin, BlurUtils blurUtils,
@@ -1671,7 +1670,6 @@
             mControlsView = findViewById(com.android.systemui.R.id.global_actions_controls);
             mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view);
             mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss());
-            ((View) mGlobalActionsLayout.getParent()).setOnClickListener(view -> dismiss());
             mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() {
                 @Override
                 public boolean dispatchPopulateAccessibilityEvent(
@@ -1684,6 +1682,15 @@
             mGlobalActionsLayout.setRotationListener(this::onRotate);
             mGlobalActionsLayout.setAdapter(mAdapter);
 
+            View globalActionsParent = (View) mGlobalActionsLayout.getParent();
+            globalActionsParent.setOnClickListener(v -> dismiss());
+
+            // add fall-through dismiss handling to root view
+            View rootView = findViewById(com.android.systemui.R.id.global_actions_grid_root);
+            if (rootView != null) {
+                rootView.setOnClickListener(v -> dismiss());
+            }
+
             if (shouldUsePanel()) {
                 initializePanel();
             }
@@ -1692,14 +1699,6 @@
                 mScrimAlpha = ScrimController.BUSY_SCRIM_ALPHA;
             }
             getWindow().setBackgroundDrawable(mBackgroundDrawable);
-
-            if (mControlsView != null) {
-                mContainerView = findViewById(com.android.systemui.R.id.global_actions_container);
-                mContainerView.setOnTouchListener((v, e) -> {
-                    dismiss();
-                    return true;
-                });
-            }
         }
 
         private void fixNavBarClipping() {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java b/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java
new file mode 100644
index 0000000..622fa65
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java
@@ -0,0 +1,43 @@
+/*
+ * 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.globalactions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ScrollView;
+
+/**
+ * When measured, this view sets the minimum height of its first child to be equal to its own
+ * target height.
+ *
+ * This ensures fall-through click handlers can be placed on this view's child component.
+ */
+public class MinHeightScrollView extends ScrollView {
+    public MinHeightScrollView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        View firstChild = getChildAt(0);
+        if (firstChild != null) {
+            firstChild.setMinimumHeight(MeasureSpec.getSize(heightMeasureSpec));
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt
index 88888d1..269a7a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-package com.android.systemui.statusbar.notification.interruption
+package com.android.systemui.statusbar.notification
 
 import android.content.Context
 import android.media.MediaMetadata
@@ -24,7 +24,6 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
 import com.android.systemui.statusbar.phone.KeyguardBypassController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
index b572502..df21f0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.interruption;
+package com.android.systemui.statusbar.notification;
 
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
@@ -27,9 +27,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -42,7 +39,7 @@
     private final NotificationRemoteInputManager mRemoteInputManager;
     private final VisualStabilityManager mVisualStabilityManager;
     private final StatusBarStateController mStatusBarStateController;
-    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
     private final NotificationListener mNotificationListener;
 
     private HeadsUpManager mHeadsUpManager;
@@ -55,13 +52,13 @@
             NotificationRemoteInputManager remoteInputManager,
             VisualStabilityManager visualStabilityManager,
             StatusBarStateController statusBarStateController,
-            NotificationInterruptStateProvider notificationInterruptionStateProvider,
+            NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationListener notificationListener,
             HeadsUpManager headsUpManager) {
         mRemoteInputManager = remoteInputManager;
         mVisualStabilityManager = visualStabilityManager;
         mStatusBarStateController = statusBarStateController;
-        mNotificationInterruptStateProvider = notificationInterruptionStateProvider;
+        mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
         mNotificationListener = notificationListener;
         mHeadsUpManager = headsUpManager;
 
@@ -97,7 +94,7 @@
         if (entry.getRow().getPrivateLayout().getHeadsUpChild() != null) {
             // Possible for shouldHeadsUp to change between the inflation starting and ending.
             // If it does and we no longer need to heads up, we should free the view.
-            if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) {
+            if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
                 mHeadsUpManager.showNotification(entry);
                 if (!mStatusBarStateController.isDozing()) {
                     // Mark as seen immediately
@@ -112,7 +109,7 @@
     private void updateAlertState(NotificationEntry entry) {
         boolean alertAgain = alertAgain(entry, entry.getSbn().getNotification());
         // includes check for whether this notification should be filtered:
-        boolean shouldAlert = mNotificationInterruptStateProvider.shouldHeadsUp(entry);
+        boolean shouldAlert = mNotificationInterruptionStateProvider.shouldHeadsUp(entry);
         final boolean wasAlerting = mHeadsUpManager.isAlerting(entry.getKey());
         if (wasAlerting) {
             if (shouldAlert) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
index 46d5044..bbf2dde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -14,35 +14,33 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.interruption;
+package com.android.systemui.statusbar.notification;
 
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 
 import android.app.NotificationManager;
-import android.content.ContentResolver;
+import android.content.Context;
 import android.database.ContentObserver;
 import android.hardware.display.AmbientDisplayConfiguration;
-import android.os.Handler;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.Dependency;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
@@ -50,84 +48,120 @@
  * Provides heads-up and pulsing state for notification entries.
  */
 @Singleton
-public class NotificationInterruptStateProviderImpl implements NotificationInterruptStateProvider {
+public class NotificationInterruptionStateProvider {
+
     private static final String TAG = "InterruptionStateProvider";
-    private static final boolean DEBUG = true; //false;
+    private static final boolean DEBUG = false;
     private static final boolean DEBUG_HEADS_UP = true;
     private static final boolean ENABLE_HEADS_UP = true;
     private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
 
-    private final List<NotificationInterruptSuppressor> mSuppressors = new ArrayList<>();
     private final StatusBarStateController mStatusBarStateController;
     private final NotificationFilter mNotificationFilter;
-    private final ContentResolver mContentResolver;
+    private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+
+    private final Context mContext;
     private final PowerManager mPowerManager;
     private final IDreamManager mDreamManager;
-    private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
     private final BatteryController mBatteryController;
-    private final ContentObserver mHeadsUpObserver;
-    private HeadsUpManager mHeadsUpManager;
 
+    private NotificationPresenter mPresenter;
+    private HeadsUpManager mHeadsUpManager;
+    private HeadsUpSuppressor mHeadsUpSuppressor;
+
+    private ContentObserver mHeadsUpObserver;
     @VisibleForTesting
     protected boolean mUseHeadsUp = false;
+    private boolean mDisableNotificationAlerts;
 
     @Inject
-    public NotificationInterruptStateProviderImpl(
-            ContentResolver contentResolver,
+    public NotificationInterruptionStateProvider(Context context, NotificationFilter filter,
+            StatusBarStateController stateController, BatteryController batteryController) {
+        this(context,
+                (PowerManager) context.getSystemService(Context.POWER_SERVICE),
+                IDreamManager.Stub.asInterface(
+                        ServiceManager.checkService(DreamService.DREAM_SERVICE)),
+                new AmbientDisplayConfiguration(context),
+                filter,
+                batteryController,
+                stateController);
+    }
+
+    @VisibleForTesting
+    protected NotificationInterruptionStateProvider(
+            Context context,
             PowerManager powerManager,
             IDreamManager dreamManager,
             AmbientDisplayConfiguration ambientDisplayConfiguration,
             NotificationFilter notificationFilter,
             BatteryController batteryController,
-            StatusBarStateController statusBarStateController,
-            HeadsUpManager headsUpManager,
-            @Main Handler mainHandler) {
-        mContentResolver = contentResolver;
+            StatusBarStateController statusBarStateController) {
+        mContext = context;
         mPowerManager = powerManager;
         mDreamManager = dreamManager;
         mBatteryController = batteryController;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
         mNotificationFilter = notificationFilter;
         mStatusBarStateController = statusBarStateController;
-        mHeadsUpManager = headsUpManager;
-        mHeadsUpObserver = new ContentObserver(mainHandler) {
-            @Override
-            public void onChange(boolean selfChange) {
-                boolean wasUsing = mUseHeadsUp;
-                mUseHeadsUp = ENABLE_HEADS_UP
-                        && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
-                        mContentResolver,
-                        Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
-                        Settings.Global.HEADS_UP_OFF);
-                Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
-                if (wasUsing != mUseHeadsUp) {
-                    if (!mUseHeadsUp) {
-                        Log.d(TAG, "dismissing any existing heads up notification on "
-                                + "disable event");
-                        mHeadsUpManager.releaseAllImmediately();
+    }
+
+    /** Sets up late-binding dependencies for this component. */
+    public void setUpWithPresenter(
+            NotificationPresenter notificationPresenter,
+            HeadsUpManager headsUpManager,
+            HeadsUpSuppressor headsUpSuppressor) {
+        setUpWithPresenter(notificationPresenter, headsUpManager, headsUpSuppressor,
+                new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        boolean wasUsing = mUseHeadsUp;
+                        mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
+                                && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
+                                mContext.getContentResolver(),
+                                Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+                                Settings.Global.HEADS_UP_OFF);
+                        Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
+                        if (wasUsing != mUseHeadsUp) {
+                            if (!mUseHeadsUp) {
+                                Log.d(TAG,
+                                        "dismissing any existing heads up notification on disable"
+                                                + " event");
+                                mHeadsUpManager.releaseAllImmediately();
+                            }
+                        }
                     }
-                }
-            }
-        };
+                });
+    }
+
+    /** Sets up late-binding dependencies for this component. */
+    public void setUpWithPresenter(
+            NotificationPresenter notificationPresenter,
+            HeadsUpManager headsUpManager,
+            HeadsUpSuppressor headsUpSuppressor,
+            ContentObserver observer) {
+        mPresenter = notificationPresenter;
+        mHeadsUpManager = headsUpManager;
+        mHeadsUpSuppressor = headsUpSuppressor;
+        mHeadsUpObserver = observer;
 
         if (ENABLE_HEADS_UP) {
-            mContentResolver.registerContentObserver(
+            mContext.getContentResolver().registerContentObserver(
                     Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
                     true,
                     mHeadsUpObserver);
-            mContentResolver.registerContentObserver(
+            mContext.getContentResolver().registerContentObserver(
                     Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
                     mHeadsUpObserver);
         }
         mHeadsUpObserver.onChange(true); // set up
     }
 
-    @Override
-    public void addSuppressor(NotificationInterruptSuppressor suppressor) {
-        mSuppressors.add(suppressor);
-    }
-
-    @Override
+    /**
+     * Whether the notification should appear as a bubble with a fly-out on top of the screen.
+     *
+     * @param entry the entry to check
+     * @return true if the entry should bubble up, false otherwise
+     */
     public boolean shouldBubbleUp(NotificationEntry entry) {
         final StatusBarNotification sbn = entry.getSbn();
 
@@ -167,8 +201,12 @@
         return true;
     }
 
-
-    @Override
+    /**
+     * Whether the notification should peek in from the top and alert the user.
+     *
+     * @param entry the entry to check
+     * @return true if the entry should heads up, false otherwise
+     */
     public boolean shouldHeadsUp(NotificationEntry entry) {
         if (mStatusBarStateController.isDozing()) {
             return shouldHeadsUpWhenDozing(entry);
@@ -177,17 +215,6 @@
         }
     }
 
-    /**
-     * When an entry was added, should we launch its fullscreen intent? Examples are Alarms or
-     * incoming calls.
-     */
-    @Override
-    public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) {
-        return entry.getSbn().getNotification().fullScreenIntent != null
-                && (!shouldHeadsUp(entry)
-                || mStatusBarStateController.getState() == StatusBarState.KEYGUARD);
-    }
-
     private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
         StatusBarNotification sbn = entry.getSbn();
 
@@ -244,15 +271,13 @@
             return false;
         }
 
-        for (int i = 0; i < mSuppressors.size(); i++) {
-            if (mSuppressors.get(i).suppressAwakeHeadsUp(entry)) {
-                if (DEBUG_HEADS_UP) {
-                    Log.d(TAG, "No heads up: aborted by suppressor: "
-                            + mSuppressors.get(i).getName() + " sbnKey=" + sbn.getKey());
-                }
-                return false;
+        if (!mHeadsUpSuppressor.canHeadsUp(entry, sbn)) {
+            if (DEBUG_HEADS_UP) {
+                Log.d(TAG, "No heads up: aborted by suppressor: " + sbn.getKey());
             }
+            return false;
         }
+
         return true;
     }
 
@@ -300,7 +325,7 @@
             }
             return false;
         }
-        return true;
+         return true;
     }
 
     /**
@@ -309,7 +334,8 @@
      * @param entry the entry to check
      * @return true if these checks pass, false if the notification should not alert
      */
-    private boolean canAlertCommon(NotificationEntry entry) {
+    @VisibleForTesting
+    public boolean canAlertCommon(NotificationEntry entry) {
         StatusBarNotification sbn = entry.getSbn();
 
         if (mNotificationFilter.shouldFilterOut(entry)) {
@@ -326,16 +352,6 @@
             }
             return false;
         }
-
-        for (int i = 0; i < mSuppressors.size(); i++) {
-            if (mSuppressors.get(i).suppressInterruptions(entry)) {
-                if (DEBUG_HEADS_UP) {
-                    Log.d(TAG, "No alerting: aborted by suppressor: "
-                            + mSuppressors.get(i).getName() + " sbnKey=" + sbn.getKey());
-                }
-                return false;
-            }
-        }
         return true;
     }
 
@@ -345,17 +361,15 @@
      * @param entry the entry to check
      * @return true if these checks pass, false if the notification should not alert
      */
-    private boolean canAlertAwakeCommon(NotificationEntry entry) {
+    @VisibleForTesting
+    public boolean canAlertAwakeCommon(NotificationEntry entry) {
         StatusBarNotification sbn = entry.getSbn();
 
-        for (int i = 0; i < mSuppressors.size(); i++) {
-            if (mSuppressors.get(i).suppressAwakeInterruptions(entry)) {
-                if (DEBUG_HEADS_UP) {
-                    Log.d(TAG, "No alerting: aborted by suppressor: "
-                            + mSuppressors.get(i).getName() + " sbnKey=" + sbn.getKey());
-                }
-                return false;
+        if (mPresenter.isDeviceInVrMode()) {
+            if (DEBUG_HEADS_UP) {
+                Log.d(TAG, "No alerting: no huns or vr mode");
             }
+            return false;
         }
 
         if (isSnoozedPackage(sbn)) {
@@ -378,4 +392,54 @@
     private boolean isSnoozedPackage(StatusBarNotification sbn) {
         return mHeadsUpManager.isSnoozed(sbn.getPackageName());
     }
+
+    /** Sets whether to disable all alerts. */
+    public void setDisableNotificationAlerts(boolean disableNotificationAlerts) {
+        mDisableNotificationAlerts = disableNotificationAlerts;
+        mHeadsUpObserver.onChange(true);
+    }
+
+    /** Whether all alerts are disabled. */
+    @VisibleForTesting
+    public boolean areNotificationAlertsDisabled() {
+        return mDisableNotificationAlerts;
+    }
+
+    /** Whether HUNs should be used. */
+    @VisibleForTesting
+    public boolean getUseHeadsUp() {
+        return mUseHeadsUp;
+    }
+
+    protected NotificationPresenter getPresenter() {
+        return mPresenter;
+    }
+
+    /**
+     * When an entry was added, should we launch its fullscreen intent? Examples are Alarms or
+     * incoming calls.
+     *
+     * @param entry the entry that was added
+     * @return {@code true} if we should launch the full screen intent
+     */
+    public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) {
+        return entry.getSbn().getNotification().fullScreenIntent != null
+            && (!shouldHeadsUp(entry)
+                || mStatusBarStateController.getState() == StatusBarState.KEYGUARD);
+    }
+
+    /** A component which can suppress heads-up notifications due to the overall state of the UI. */
+    public interface HeadsUpSuppressor {
+        /**
+         * Returns false if the provided notification is ineligible for heads-up according to this
+         * component.
+         *
+         * @param entry entry of the notification that might be heads upped
+         * @param sbn   notification that might be heads upped
+         * @return false if the notification can not be heads upped
+         */
+        boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn);
+
+    }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 2747696..8a23e37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification
 
 import android.animation.ObjectAnimator
+import android.content.Context
 import android.util.FloatProperty
 import com.android.systemui.Interpolators
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -25,10 +26,10 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.phone.PanelExpansionListener
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
 
 import javax.inject.Inject
@@ -36,14 +37,15 @@
 
 @Singleton
 class NotificationWakeUpCoordinator @Inject constructor(
-    private val mHeadsUpManager: HeadsUpManager,
-    private val statusBarStateController: StatusBarStateController,
-    private val bypassController: KeyguardBypassController,
-    private val dozeParameters: DozeParameters
-) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener {
+        private val mHeadsUpManagerPhone: HeadsUpManagerPhone,
+        private val statusBarStateController: StatusBarStateController,
+        private val bypassController: KeyguardBypassController,
+        private val dozeParameters: DozeParameters)
+    : OnHeadsUpChangedListener, StatusBarStateController.StateListener,
+        PanelExpansionListener {
 
-    private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>(
-        "notificationVisibility") {
+    private val mNotificationVisibility
+            = object : FloatProperty<NotificationWakeUpCoordinator>("notificationVisibility") {
 
         override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) {
             coordinator.setVisibilityAmount(value)
@@ -76,10 +78,10 @@
             field = value
             willWakeUp = false
             if (value) {
-                if (mNotificationsVisible && !mNotificationsVisibleForExpansion &&
-                    !bypassController.bypassEnabled) {
+                if (mNotificationsVisible && !mNotificationsVisibleForExpansion
+                        && !bypassController.bypassEnabled) {
                     // We're waking up while pulsing, let's make sure the animation looks nice
-                    mStackScroller.wakeUpFromPulse()
+                    mStackScroller.wakeUpFromPulse();
                 }
                 if (bypassController.bypassEnabled && !mNotificationsVisible) {
                     // Let's make sure our huns become visible once we are waking up in case
@@ -98,7 +100,7 @@
         }
 
     private var collapsedEnoughToHide: Boolean = false
-    lateinit var iconAreaController: NotificationIconAreaController
+    lateinit var iconAreaController : NotificationIconAreaController
 
     var pulsing: Boolean = false
         set(value) {
@@ -130,8 +132,8 @@
             var canShow = pulsing
             if (bypassController.bypassEnabled) {
                 // We also allow pulsing on the lock screen!
-                canShow = canShow || (wakingUp || willWakeUp || fullyAwake) &&
-                    statusBarStateController.state == StatusBarState.KEYGUARD
+                canShow = canShow || (wakingUp || willWakeUp || fullyAwake)
+                        && statusBarStateController.state == StatusBarState.KEYGUARD
                 // We want to hide the notifications when collapsed too much
                 if (collapsedEnoughToHide) {
                     canShow = false
@@ -141,7 +143,7 @@
         }
 
     init {
-        mHeadsUpManager.addListener(this)
+        mHeadsUpManagerPhone.addListener(this)
         statusBarStateController.addCallback(this)
         addListener(object : WakeUpListener {
             override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
@@ -153,7 +155,7 @@
                             increaseSpeed = false)
                 }
             }
-        })
+        });
     }
 
     fun setStackScroller(stackScroller: NotificationStackScrollLayout) {
@@ -176,55 +178,46 @@
      * @param animate should this change be animated
      * @param increaseSpeed should the speed be increased of the animation
      */
-    fun setNotificationsVisibleForExpansion(
-        visible: Boolean,
-        animate: Boolean,
-        increaseSpeed: Boolean
-    ) {
+    fun setNotificationsVisibleForExpansion(visible: Boolean, animate: Boolean,
+                                                    increaseSpeed: Boolean) {
         mNotificationsVisibleForExpansion = visible
         updateNotificationVisibility(animate, increaseSpeed)
         if (!visible && mNotificationsVisible) {
             // If we stopped expanding and we're still visible because we had a pulse that hasn't
             // times out, let's release them all to make sure were not stuck in a state where
             // notifications are visible
-            mHeadsUpManager.releaseAllImmediately()
+            mHeadsUpManagerPhone.releaseAllImmediately()
         }
     }
 
     fun addListener(listener: WakeUpListener) {
-        wakeUpListeners.add(listener)
+        wakeUpListeners.add(listener);
     }
 
     fun removeListener(listener: WakeUpListener) {
-        wakeUpListeners.remove(listener)
+        wakeUpListeners.remove(listener);
     }
 
-    private fun updateNotificationVisibility(
-        animate: Boolean,
-        increaseSpeed: Boolean
-    ) {
+    private fun updateNotificationVisibility(animate: Boolean, increaseSpeed: Boolean) {
         // TODO: handle Lockscreen wakeup for bypass when we're not pulsing anymore
-        var visible = mNotificationsVisibleForExpansion || mHeadsUpManager.hasNotifications()
+        var visible = mNotificationsVisibleForExpansion || mHeadsUpManagerPhone.hasNotifications()
         visible = visible && canShowPulsingHuns
 
         if (!visible && mNotificationsVisible && (wakingUp || willWakeUp) && mDozeAmount != 0.0f) {
             // let's not make notifications invisible while waking up, otherwise the animation
             // is strange
-            return
+            return;
         }
         setNotificationsVisible(visible, animate, increaseSpeed)
     }
 
-    private fun setNotificationsVisible(
-        visible: Boolean,
-        animate: Boolean,
-        increaseSpeed: Boolean
-    ) {
+    private fun setNotificationsVisible(visible: Boolean, animate: Boolean,
+                                        increaseSpeed: Boolean) {
         if (mNotificationsVisible == visible) {
             return
         }
         mNotificationsVisible = visible
-        mVisibilityAnimator?.cancel()
+        mVisibilityAnimator?.cancel();
         if (animate) {
             notifyAnimationStart(visible)
             startVisibilityAnimation(increaseSpeed)
@@ -237,8 +230,8 @@
         if (updateDozeAmountIfBypass()) {
             return
         }
-        if (linear != 1.0f && linear != 0.0f &&
-            (mLinearDozeAmount == 0.0f || mLinearDozeAmount == 1.0f)) {
+        if (linear != 1.0f && linear != 0.0f
+                && (mLinearDozeAmount == 0.0f || mLinearDozeAmount == 1.0f)) {
             // Let's notify the scroller that an animation started
             notifyAnimationStart(mLinearDozeAmount == 1.0f)
         }
@@ -252,17 +245,17 @@
         mStackScroller.setDozeAmount(mDozeAmount)
         updateHideAmount()
         if (changed && linear == 0.0f) {
-            setNotificationsVisible(visible = false, animate = false, increaseSpeed = false)
+            setNotificationsVisible(visible = false, animate = false, increaseSpeed = false);
             setNotificationsVisibleForExpansion(visible = false, animate = false,
                     increaseSpeed = false)
         }
     }
 
     override fun onStateChanged(newState: Int) {
-        updateDozeAmountIfBypass()
+        updateDozeAmountIfBypass();
         if (bypassController.bypassEnabled &&
-                newState == StatusBarState.KEYGUARD && state == StatusBarState.SHADE_LOCKED &&
-            (!statusBarStateController.isDozing || shouldAnimateVisibility())) {
+                newState == StatusBarState.KEYGUARD && state == StatusBarState.SHADE_LOCKED
+                && (!statusBarStateController.isDozing || shouldAnimateVisibility())) {
             // We're leaving shade locked. Let's animate the notifications away
             setNotificationsVisible(visible = true, increaseSpeed = false, animate = false)
             setNotificationsVisible(visible = false, increaseSpeed = false, animate = true)
@@ -273,23 +266,23 @@
     override fun onPanelExpansionChanged(expansion: Float, tracking: Boolean) {
         val collapsedEnough = expansion <= 0.9f
         if (collapsedEnough != this.collapsedEnoughToHide) {
-            val couldShowPulsingHuns = canShowPulsingHuns
+            val couldShowPulsingHuns = canShowPulsingHuns;
             this.collapsedEnoughToHide = collapsedEnough
             if (couldShowPulsingHuns && !canShowPulsingHuns) {
                 updateNotificationVisibility(animate = true, increaseSpeed = true)
-                mHeadsUpManager.releaseAllImmediately()
+                mHeadsUpManagerPhone.releaseAllImmediately()
             }
         }
     }
 
     private fun updateDozeAmountIfBypass(): Boolean {
         if (bypassController.bypassEnabled) {
-            var amount = 1.0f
-            if (statusBarStateController.state == StatusBarState.SHADE ||
-                statusBarStateController.state == StatusBarState.SHADE_LOCKED) {
-                amount = 0.0f
+            var amount = 1.0f;
+            if (statusBarStateController.state == StatusBarState.SHADE
+                    || statusBarStateController.state == StatusBarState.SHADE_LOCKED) {
+                amount = 0.0f;
             }
-            setDozeAmount(amount, amount)
+            setDozeAmount(amount,  amount)
             return true
         }
         return false
@@ -307,7 +300,7 @@
         visibilityAnimator.setInterpolator(Interpolators.LINEAR)
         var duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP.toLong()
         if (increaseSpeed) {
-            duration = (duration.toFloat() / 1.5F).toLong()
+            duration = (duration.toFloat() / 1.5F).toLong();
         }
         visibilityAnimator.setDuration(duration)
         visibilityAnimator.start()
@@ -318,7 +311,7 @@
         mLinearVisibilityAmount = visibilityAmount
         mVisibilityAmount = mVisibilityInterpolator.getInterpolation(
                 visibilityAmount)
-        handleAnimationFinished()
+        handleAnimationFinished();
         updateHideAmount()
     }
 
@@ -329,7 +322,7 @@
         }
     }
 
-    fun getWakeUpHeight(): Float {
+    fun getWakeUpHeight() : Float {
         return mStackScroller.wakeUpHeight
     }
 
@@ -337,7 +330,7 @@
         val linearAmount = Math.min(1.0f - mLinearVisibilityAmount, mLinearDozeAmount)
         val amount = Math.min(1.0f - mVisibilityAmount, mDozeAmount)
         mStackScroller.setHideAmount(linearAmount, amount)
-        notificationsFullyHidden = linearAmount == 1.0f
+        notificationsFullyHidden = linearAmount == 1.0f;
     }
 
     private fun notifyAnimationStart(awake: Boolean) {
@@ -368,7 +361,7 @@
                     // if we animate, we see the shelf briefly visible. Instead we fully animate
                     // the notification and its background out
                     animate = false
-                } else if (!wakingUp && !willWakeUp) {
+                } else if (!wakingUp && !willWakeUp){
                     // TODO: look that this is done properly and not by anyone else
                     entry.setHeadsUpAnimatingAway(true)
                     mEntrySetToClearWhenFinished.add(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..e8a62e4 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
@@ -37,8 +37,8 @@
 import com.android.systemui.statusbar.NotificationUiAdjustment;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.NotificationClicker;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-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;
@@ -66,7 +66,7 @@
 
     private static final String TAG = "NotificationViewManager";
 
-    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
 
     private final Context mContext;
     private final NotifBindPipeline mNotifBindPipeline;
@@ -97,7 +97,7 @@
             StatusBarStateController statusBarStateController,
             NotificationGroupManager notificationGroupManager,
             NotificationGutsManager notificationGutsManager,
-            NotificationInterruptStateProvider notificationInterruptionStateProvider,
+            NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             Provider<RowInflaterTask> rowInflaterTaskProvider,
             ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder) {
         mContext = context;
@@ -106,7 +106,7 @@
         mMessagingUtil = notificationMessagingUtil;
         mNotificationRemoteInputManager = notificationRemoteInputManager;
         mNotificationLockscreenUserManager = notificationLockscreenUserManager;
-        mNotificationInterruptStateProvider = notificationInterruptionStateProvider;
+        mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
         mRowInflaterTaskProvider = rowInflaterTaskProvider;
         mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder;
     }
@@ -243,7 +243,7 @@
         params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
         params.setUseLowPriority(entry.isAmbient());
 
-        if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) {
+        if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
             params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
         }
         //TODO: Replace this API with RowContentBindParams directly
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index cd6affd..3c0ac7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -31,8 +31,10 @@
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
+import com.android.systemui.statusbar.notification.NotificationAlertingManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
@@ -42,9 +44,6 @@
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
-import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -57,7 +56,6 @@
 
 import javax.inject.Singleton;
 
-import dagger.Binds;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
@@ -127,7 +125,7 @@
             NotificationRemoteInputManager remoteInputManager,
             VisualStabilityManager visualStabilityManager,
             StatusBarStateController statusBarStateController,
-            NotificationInterruptStateProvider notificationInterruptStateProvider,
+            NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationListener notificationListener,
             HeadsUpManager headsUpManager) {
         return new NotificationAlertingManager(
@@ -135,7 +133,7 @@
                 remoteInputManager,
                 visualStabilityManager,
                 statusBarStateController,
-                notificationInterruptStateProvider,
+                notificationInterruptionStateProvider,
                 notificationListener,
                 headsUpManager);
     }
@@ -201,9 +199,4 @@
             NotificationEntryManager entryManager) {
         return featureFlags.isNewNotifPipelineRenderingEnabled() ? pipeline.get() : entryManager;
     }
-
-    /** */
-    @Binds
-    NotificationInterruptStateProvider bindNotificationInterruptStateProvider(
-            NotificationInterruptStateProviderImpl notificationInterruptStateProviderImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
deleted file mode 100644
index 3292a8f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ /dev/null
@@ -1,59 +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 com.android.systemui.statusbar.notification.interruption;
-
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-/**
- * Provides bubble-up and heads-up state for notification entries.
- *
- * When a notification is heads-up when dozing, this is also called "pulsing."
- */
-public interface NotificationInterruptStateProvider {
-    /**
-     * If the device is awake (not dozing):
-     *  Whether the notification should peek in from the top and alert the user.
-     *
-     * If the device is dozing:
-     *  Whether the notification should show the ambient view of the notification ("pulse").
-     *
-     * @param entry the entry to check
-     * @return true if the entry should heads up, false otherwise
-     */
-    boolean shouldHeadsUp(NotificationEntry entry);
-
-    /**
-     * Whether the notification should appear as a bubble with a fly-out on top of the screen.
-     *
-     * @param entry the entry to check
-     * @return true if the entry should bubble up, false otherwise
-     */
-    boolean shouldBubbleUp(NotificationEntry entry);
-
-    /**
-     * Whether to launch the entry's full screen intent when the entry is added.
-     *
-     * @param entry the entry that was added
-     * @return {@code true} if we should launch the full screen intent
-     */
-    boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry);
-
-    /**
-     * Add a component that can suppress visual interruptions.
-     */
-    void addSuppressor(NotificationInterruptSuppressor suppressor);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptSuppressor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptSuppressor.java
deleted file mode 100644
index c19f8bd..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptSuppressor.java
+++ /dev/null
@@ -1,64 +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 com.android.systemui.statusbar.notification.interruption;
-
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-/** A component which can suppress visual interruptions of notifications such as heads-up and
- *  bubble-up.
- */
-public interface NotificationInterruptSuppressor {
-    /**
-     * A unique name to identify this suppressor.
-     */
-    default String getName() {
-        return this.getClass().getName();
-    }
-
-    /**
-     * Returns true if the provided notification is, when the device is awake, ineligible for
-     * heads-up according to this component.
-     *
-     * @param entry entry of the notification that might heads-up
-     * @return true if the heads up interruption should be suppressed when the device is awake
-     */
-    default boolean suppressAwakeHeadsUp(NotificationEntry entry) {
-        return false;
-    }
-
-    /**
-     * Returns true if the provided notification is, when the device is awake, ineligible for
-     * heads-up or bubble-up according to this component.
-     *
-     * @param entry entry of the notification that might heads-up or bubble-up
-     * @return true if interruptions should be suppressed when the device is awake
-     */
-    default boolean suppressAwakeInterruptions(NotificationEntry entry) {
-        return false;
-    }
-
-    /**
-     * Returns true if the provided notification is, regardless of awake/dozing state,
-     * ineligible for heads-up or bubble-up according to this component.
-     *
-     * @param entry entry of the notification that might heads-up or bubble-up
-     * @return true if interruptions should be suppressed
-     */
-    default boolean suppressInterruptions(NotificationEntry entry) {
-        return false;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 287ede4..b3a62d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -185,14 +185,15 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
+import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
+import com.android.systemui.statusbar.notification.NotificationAlertingManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -403,9 +404,10 @@
 
     private final NotificationGutsManager mGutsManager;
     private final NotificationLogger mNotificationLogger;
+    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
     private final NotificationViewHierarchyManager mViewHierarchyManager;
     private final KeyguardViewMediator mKeyguardViewMediator;
-    protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+    private final NotificationAlertingManager mNotificationAlertingManager;
 
     // for disabling the status bar
     private int mDisabled1 = 0;
@@ -619,9 +621,10 @@
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
-            NotificationInterruptStateProvider notificationInterruptStateProvider,
+            NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             KeyguardViewMediator keyguardViewMediator,
+            NotificationAlertingManager notificationAlertingManager,
             DisplayMetrics displayMetrics,
             MetricsLogger metricsLogger,
             @UiBackground Executor uiBgExecutor,
@@ -698,9 +701,10 @@
         mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler;
         mGutsManager = notificationGutsManager;
         mNotificationLogger = notificationLogger;
-        mNotificationInterruptStateProvider = notificationInterruptStateProvider;
+        mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
         mViewHierarchyManager = notificationViewHierarchyManager;
         mKeyguardViewMediator = keyguardViewMediator;
+        mNotificationAlertingManager = notificationAlertingManager;
         mDisplayMetrics = displayMetrics;
         mMetricsLogger = metricsLogger;
         mUiBgExecutor = uiBgExecutor;
@@ -1234,9 +1238,9 @@
         mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController,
                 mHeadsUpManager, mNotificationShadeWindowView, mStackScroller, mDozeScrimController,
                 mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
-                mKeyguardStateController, mKeyguardIndicationController,
-                this /* statusBar */, mShadeController, mCommandQueue, mInitController,
-                mNotificationInterruptStateProvider);
+                mNotificationAlertingManager, mKeyguardStateController,
+                mKeyguardIndicationController,
+                this /* statusBar */, mShadeController, mCommandQueue, mInitController);
 
         mNotificationShelf.setOnActivatedListener(mPresenter);
         mRemoteInputManager.getController().addCallback(mNotificationShadeWindowController);
@@ -1585,9 +1589,8 @@
         }
 
         if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
-            if (areNotificationAlertsDisabled()) {
-                mHeadsUpManager.releaseAllImmediately();
-            }
+            mNotificationInterruptionStateProvider.setDisableNotificationAlerts(
+                    (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0);
         }
 
         if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
@@ -1602,10 +1605,6 @@
         }
     }
 
-    boolean areNotificationAlertsDisabled() {
-        return (mDisabled1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
-    }
-
     protected H createHandler() {
         return new StatusBar.H();
     }
@@ -2333,7 +2332,7 @@
 
     void checkBarModes() {
         if (mDemoMode) return;
-        if (mNotificationShadeWindowViewController != null) {
+        if (mNotificationShadeWindowViewController != null && getStatusBarTransitions() != null) {
             checkBarMode(mStatusBarMode, mStatusBarWindowState, getStatusBarTransitions());
         }
         mNavigationBarController.checkNavBarModes(mDisplayId);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 53fa263..e1a20b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -68,12 +68,12 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
@@ -108,7 +108,7 @@
     private final NotifCollection mNotifCollection;
     private final FeatureFlags mFeatureFlags;
     private final StatusBarStateController mStatusBarStateController;
-    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
     private final MetricsLogger mMetricsLogger;
     private final Context mContext;
     private final NotificationPanelViewController mNotificationPanel;
@@ -142,7 +142,7 @@
             NotificationLockscreenUserManager lockscreenUserManager,
             ShadeController shadeController, StatusBar statusBar,
             KeyguardStateController keyguardStateController,
-            NotificationInterruptStateProvider notificationInterruptStateProvider,
+            NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils,
             Handler mainThreadHandler, Handler backgroundHandler, Executor uiBgExecutor,
             ActivityIntentHelper activityIntentHelper, BubbleController bubbleController,
@@ -167,7 +167,7 @@
         mActivityStarter = activityStarter;
         mEntryManager = entryManager;
         mStatusBarStateController = statusBarStateController;
-        mNotificationInterruptStateProvider = notificationInterruptStateProvider;
+        mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
         mMetricsLogger = metricsLogger;
         mAssistManagerLazy = assistManagerLazy;
         mGroupManager = groupManager;
@@ -436,7 +436,7 @@
     }
 
     private void handleFullScreenIntent(NotificationEntry entry) {
-        if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
+        if (mNotificationInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
             if (shouldSuppressFullScreenIntent(entry)) {
                 if (DEBUG) {
                     Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + entry.getKey());
@@ -603,7 +603,7 @@
         private final ActivityIntentHelper mActivityIntentHelper;
         private final BubbleController mBubbleController;
         private NotificationPanelViewController mNotificationPanelViewController;
-        private NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+        private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
         private final ShadeController mShadeController;
         private NotificationPresenter mNotificationPresenter;
         private ActivityLaunchAnimator mActivityLaunchAnimator;
@@ -626,7 +626,7 @@
                 NotificationGroupManager groupManager,
                 NotificationLockscreenUserManager lockscreenUserManager,
                 KeyguardStateController keyguardStateController,
-                NotificationInterruptStateProvider notificationInterruptStateProvider,
+                NotificationInterruptionStateProvider notificationInterruptionStateProvider,
                 MetricsLogger metricsLogger,
                 LockPatternUtils lockPatternUtils,
                 @Main Handler mainThreadHandler,
@@ -654,7 +654,7 @@
             mGroupManager = groupManager;
             mLockscreenUserManager = lockscreenUserManager;
             mKeyguardStateController = keyguardStateController;
-            mNotificationInterruptStateProvider = notificationInterruptStateProvider;
+            mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
             mMetricsLogger = metricsLogger;
             mLockPatternUtils = lockPatternUtils;
             mMainThreadHandler = mainThreadHandler;
@@ -712,7 +712,7 @@
                     mShadeController,
                     mStatusBar,
                     mKeyguardStateController,
-                    mNotificationInterruptStateProvider,
+                    mNotificationInterruptionStateProvider,
                     mMetricsLogger,
                     mLockPatternUtils,
                     mMainThreadHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 79cea91..30d6b507 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -60,13 +60,13 @@
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationAlertingManager;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -98,6 +98,8 @@
             (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class);
     private final NotificationEntryManager mEntryManager =
             Dependency.get(NotificationEntryManager.class);
+    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
+            Dependency.get(NotificationInterruptionStateProvider.class);
     private final NotificationMediaManager mMediaManager =
             Dependency.get(NotificationMediaManager.class);
     private final VisualStabilityManager mVisualStabilityManager =
@@ -138,13 +140,13 @@
             ScrimController scrimController,
             ActivityLaunchAnimator activityLaunchAnimator,
             DynamicPrivacyController dynamicPrivacyController,
+            NotificationAlertingManager notificationAlertingManager,
             KeyguardStateController keyguardStateController,
             KeyguardIndicationController keyguardIndicationController,
             StatusBar statusBar,
             ShadeController shadeController,
             CommandQueue commandQueue,
-            InitController initController,
-            NotificationInterruptStateProvider notificationInterruptStateProvider) {
+            InitController initController) {
         mContext = context;
         mKeyguardStateController = keyguardStateController;
         mNotificationPanel = panel;
@@ -214,7 +216,8 @@
             mEntryManager.addNotificationLifetimeExtender(mGutsManager);
             mEntryManager.addNotificationLifetimeExtenders(
                     remoteInputManager.getLifetimeExtenders());
-            notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
+            mNotificationInterruptionStateProvider.setUpWithPresenter(
+                    this, mHeadsUpManager, this::canHeadsUp);
             mLockscreenUserManager.setUpWithPresenter(this);
             mMediaManager.setUpWithPresenter(this);
             mVisualStabilityManager.setUpWithPresenter(this);
@@ -333,6 +336,39 @@
         return mEntryManager.hasActiveNotifications();
     }
 
+    public boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn) {
+        if (mStatusBar.isOccluded()) {
+            boolean devicePublic = mLockscreenUserManager.
+                    isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
+            boolean userPublic = devicePublic
+                    || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
+            boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
+            if (userPublic && needsRedaction) {
+                // TODO(b/135046837): we can probably relax this with dynamic privacy
+                return false;
+            }
+        }
+
+        if (!mCommandQueue.panelsEnabled()) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (sbn.getNotification().fullScreenIntent != null) {
+            if (mAccessibilityManager.isTouchExplorationEnabled()) {
+                if (DEBUG) Log.d(TAG, "No heads up: accessible fullscreen: " + sbn.getKey());
+                return false;
+            } else {
+                // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
+                return !mKeyguardStateController.isShowing()
+                        || mStatusBar.isOccluded();
+            }
+        }
+        return true;
+    }
+
     @Override
     public void onUserSwitched(int newUserId) {
         // Begin old BaseStatusBar.userSwitched
@@ -471,66 +507,4 @@
             }
         }
     };
-
-    private final NotificationInterruptSuppressor mInterruptSuppressor =
-            new NotificationInterruptSuppressor() {
-        @Override
-        public String getName() {
-            return TAG;
-        }
-
-        @Override
-        public boolean suppressAwakeHeadsUp(NotificationEntry entry) {
-            final StatusBarNotification sbn = entry.getSbn();
-            if (mStatusBar.isOccluded()) {
-                boolean devicePublic = mLockscreenUserManager
-                        .isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
-                boolean userPublic = devicePublic
-                        || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
-                boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
-                if (userPublic && needsRedaction) {
-                    // TODO(b/135046837): we can probably relax this with dynamic privacy
-                    return true;
-                }
-            }
-
-            if (!mCommandQueue.panelsEnabled()) {
-                if (DEBUG) {
-                    Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey());
-                }
-                return true;
-            }
-
-            if (sbn.getNotification().fullScreenIntent != null) {
-                // we don't allow head-up on the lockscreen (unless there's a
-                // "showWhenLocked" activity currently showing)  if
-                // the potential HUN has a fullscreen intent
-                if (mKeyguardStateController.isShowing() && !mStatusBar.isOccluded()) {
-                    if (DEBUG) {
-                        Log.d(TAG, "No heads up: entry has fullscreen intent on lockscreen "
-                                + sbn.getKey());
-                    }
-                    return true;
-                }
-
-                if (mAccessibilityManager.isTouchExplorationEnabled()) {
-                    if (DEBUG) {
-                        Log.d(TAG, "No heads up: accessible fullscreen: " + sbn.getKey());
-                    }
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public boolean suppressAwakeInterruptions(NotificationEntry entry) {
-            return isDeviceInVrMode();
-        }
-
-        @Override
-        public boolean suppressInterruptions(NotificationEntry entry) {
-            return mStatusBar.areNotificationAlertsDisabled();
-        }
-    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 824e0f0..eec8d50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -56,12 +56,13 @@
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationAlertingManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
@@ -138,9 +139,10 @@
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
-            NotificationInterruptStateProvider notificationInterruptStateProvider,
+            NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             KeyguardViewMediator keyguardViewMediator,
+            NotificationAlertingManager notificationAlertingManager,
             DisplayMetrics displayMetrics,
             MetricsLogger metricsLogger,
             @UiBackground Executor uiBgExecutor,
@@ -216,9 +218,10 @@
                 remoteInputQuickSettingsDisabler,
                 notificationGutsManager,
                 notificationLogger,
-                notificationInterruptStateProvider,
+                notificationInterruptionStateProvider,
                 notificationViewHierarchyManager,
                 keyguardViewMediator,
+                notificationAlertingManager,
                 displayMetrics,
                 metricsLogger,
                 uiBgExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
index 2276ba1..812a1e4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
@@ -358,7 +358,7 @@
             targetObjectIsStuckTo = targetObjectIsInMagneticFieldOf
             cancelAnimations()
             magnetListener.onStuckToTarget(targetObjectIsInMagneticFieldOf!!)
-            animateStuckToTarget(targetObjectIsInMagneticFieldOf!!, velX, velY, false)
+            animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false)
 
             vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
         } else if (targetObjectIsInMagneticFieldOf == null && objectStuckToTarget) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 977d0bb..742e652 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -45,11 +45,7 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.res.Resources;
-import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.face.FaceManager;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.service.dreams.IDreamManager;
 import android.service.notification.ZenModeConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -65,12 +61,14 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -229,17 +227,15 @@
         mZenModeConfig.suppressedVisualEffects = 0;
         when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
 
-        TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
-                new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
-                        mock(PowerManager.class),
-                        mock(IDreamManager.class),
-                        mock(AmbientDisplayConfiguration.class),
+        TestableNotificationInterruptionStateProvider interruptionStateProvider =
+                new TestableNotificationInterruptionStateProvider(mContext,
                         mock(NotificationFilter.class),
                         mock(StatusBarStateController.class),
-                        mock(BatteryController.class),
-                        mock(HeadsUpManager.class),
-                        mock(Handler.class)
-                );
+                        mock(BatteryController.class));
+        interruptionStateProvider.setUpWithPresenter(
+                mock(NotificationPresenter.class),
+                mock(HeadsUpManager.class),
+                mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class));
         mBubbleData = new BubbleData(mContext);
         when(mFeatureFlagsOldPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
         mBubbleController = new TestableBubbleController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 7fc83da..22ef3f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -41,11 +41,7 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.res.Resources;
-import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.face.FaceManager;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.service.dreams.IDreamManager;
 import android.service.notification.ZenModeConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -61,10 +57,12 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
@@ -218,17 +216,15 @@
         mZenModeConfig.suppressedVisualEffects = 0;
         when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
 
-        TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
-                new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
-                        mock(PowerManager.class),
-                        mock(IDreamManager.class),
-                        mock(AmbientDisplayConfiguration.class),
+        TestableNotificationInterruptionStateProvider interruptionStateProvider =
+                new TestableNotificationInterruptionStateProvider(mContext,
                         mock(NotificationFilter.class),
                         mock(StatusBarStateController.class),
-                        mock(BatteryController.class),
-                        mock(HeadsUpManager.class),
-                        mock(Handler.class)
-                );
+                        mock(BatteryController.class));
+        interruptionStateProvider.setUpWithPresenter(
+                mock(NotificationPresenter.class),
+                mock(HeadsUpManager.class),
+                mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class));
         mBubbleData = new BubbleData(mContext);
         when(mFeatureFlagsNewPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(true);
         mBubbleController = new TestableBubbleController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
index d3d90c4..de1fb41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
@@ -23,8 +23,8 @@
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -44,7 +44,7 @@
             ShadeController shadeController,
             BubbleData data,
             ConfigurationController configurationController,
-            NotificationInterruptStateProvider interruptionStateProvider,
+            NotificationInterruptionStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager lockscreenUserManager,
             NotificationGroupManager groupManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptStateProviderImpl.java
deleted file mode 100644
index 17dc76b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptStateProviderImpl.java
+++ /dev/null
@@ -1,55 +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 com.android.systemui.bubbles;
-
-import android.content.ContentResolver;
-import android.hardware.display.AmbientDisplayConfiguration;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.service.dreams.IDreamManager;
-
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.NotificationFilter;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-
-public class TestableNotificationInterruptStateProviderImpl
-        extends NotificationInterruptStateProviderImpl {
-
-    TestableNotificationInterruptStateProviderImpl(
-            ContentResolver contentResolver,
-            PowerManager powerManager,
-            IDreamManager dreamManager,
-            AmbientDisplayConfiguration ambientDisplayConfiguration,
-            NotificationFilter filter,
-            StatusBarStateController statusBarStateController,
-            BatteryController batteryController,
-            HeadsUpManager headsUpManager,
-            Handler mainHandler) {
-        super(contentResolver,
-                powerManager,
-                dreamManager,
-                ambientDisplayConfiguration,
-                filter,
-                batteryController,
-                statusBarStateController,
-                headsUpManager,
-                mainHandler);
-        mUseHeadsUp = true;
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptionStateProvider.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptionStateProvider.java
new file mode 100644
index 0000000..5d192b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptionStateProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import android.content.Context;
+
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.policy.BatteryController;
+
+public class TestableNotificationInterruptionStateProvider
+        extends NotificationInterruptionStateProvider {
+
+    TestableNotificationInterruptionStateProvider(Context context,
+            NotificationFilter filter, StatusBarStateController controller,
+            BatteryController batteryController) {
+        super(context, filter, controller, batteryController);
+        mUseHeadsUp = true;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index 183dde8..a8c4851 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -340,6 +340,8 @@
 
         controlLoadCallbackCaptor.value.error("")
 
+        delayableExecutor.runAllReady()
+
         assertTrue(loaded)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
similarity index 63%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
index f9c62e1..1693e7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.statusbar.notification.interruption;
+package com.android.systemui.statusbar;
 
 
 import static android.app.Notification.FLAG_BUBBLE;
@@ -30,14 +30,15 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
 import android.app.PendingIntent;
+import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.hardware.display.AmbientDisplayConfiguration;
-import android.os.Handler;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.service.dreams.IDreamManager;
@@ -49,6 +50,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -66,7 +68,7 @@
  */
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
-public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
+public class NotificationInterruptionStateProviderTest extends SysuiTestCase {
 
     @Mock
     PowerManager mPowerManager;
@@ -79,36 +81,38 @@
     @Mock
     StatusBarStateController mStatusBarStateController;
     @Mock
+    NotificationPresenter mPresenter;
+    @Mock
     HeadsUpManager mHeadsUpManager;
     @Mock
-    BatteryController mBatteryController;
+    NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor;
     @Mock
-    Handler mMockHandler;
+    BatteryController mBatteryController;
 
-    private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider;
+    private NotificationInterruptionStateProvider mNotifInterruptionStateProvider;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
         mNotifInterruptionStateProvider =
-                new NotificationInterruptStateProviderImpl(
-                        mContext.getContentResolver(),
+                new TestableNotificationInterruptionStateProvider(mContext,
                         mPowerManager,
                         mDreamManager,
                         mAmbientDisplayConfiguration,
                         mNotificationFilter,
-                        mBatteryController,
                         mStatusBarStateController,
-                        mHeadsUpManager,
-                        mMockHandler);
+                        mBatteryController);
 
-        mNotifInterruptionStateProvider.mUseHeadsUp = true;
+        mNotifInterruptionStateProvider.setUpWithPresenter(
+                mPresenter,
+                mHeadsUpManager,
+                mHeadsUpSuppressor);
     }
 
     /**
      * Sets up the state such that any requests to
-     * {@link NotificationInterruptStateProviderImpl#canAlertCommon(NotificationEntry)} will
+     * {@link NotificationInterruptionStateProvider#canAlertCommon(NotificationEntry)} will
      * pass as long its provided NotificationEntry fulfills group suppression check.
      */
     private void ensureStateForAlertCommon() {
@@ -117,16 +121,17 @@
 
     /**
      * Sets up the state such that any requests to
-     * {@link NotificationInterruptStateProviderImpl#canAlertAwakeCommon(NotificationEntry)} will
+     * {@link NotificationInterruptionStateProvider#canAlertAwakeCommon(NotificationEntry)} will
      * pass as long its provided NotificationEntry fulfills launch fullscreen check.
      */
     private void ensureStateForAlertAwakeCommon() {
+        when(mPresenter.isDeviceInVrMode()).thenReturn(false);
         when(mHeadsUpManager.isSnoozed(any())).thenReturn(false);
     }
 
     /**
      * Sets up the state such that any requests to
-     * {@link NotificationInterruptStateProviderImpl#shouldHeadsUp(NotificationEntry)} will
+     * {@link NotificationInterruptionStateProvider#shouldHeadsUp(NotificationEntry)} will
      * pass as long its provided NotificationEntry fulfills importance & DND checks.
      */
     private void ensureStateForHeadsUpWhenAwake() throws RemoteException {
@@ -136,11 +141,12 @@
         when(mStatusBarStateController.isDozing()).thenReturn(false);
         when(mDreamManager.isDreaming()).thenReturn(false);
         when(mPowerManager.isScreenOn()).thenReturn(true);
+        when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
     }
 
     /**
      * Sets up the state such that any requests to
-     * {@link NotificationInterruptStateProviderImpl#shouldHeadsUp(NotificationEntry)} will
+     * {@link NotificationInterruptionStateProvider#shouldHeadsUp(NotificationEntry)} will
      * pass as long its provided NotificationEntry fulfills importance & DND checks.
      */
     private void ensureStateForHeadsUpWhenDozing() {
@@ -152,7 +158,7 @@
 
     /**
      * Sets up the state such that any requests to
-     * {@link NotificationInterruptStateProviderImpl#shouldBubbleUp(NotificationEntry)} will
+     * {@link NotificationInterruptionStateProvider#shouldBubbleUp(NotificationEntry)} will
      * pass as long its provided NotificationEntry fulfills importance & bubble checks.
      */
     private void ensureStateForBubbleUp() {
@@ -160,53 +166,75 @@
         ensureStateForAlertAwakeCommon();
     }
 
+    /**
+     * Ensure that the disabled state is set correctly.
+     */
     @Test
-    public void testDefaultSuppressorDoesNotSuppress() {
-        // GIVEN a suppressor without any overrides
-        final NotificationInterruptSuppressor defaultSuppressor =
-                new NotificationInterruptSuppressor() {
-                    @Override
-                    public String getName() {
-                        return "defaultSuppressor";
-                    }
-                };
+    public void testDisableNotificationAlerts() {
+        // Enabled by default
+        assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isFalse();
+
+        // Disable alerts
+        mNotifInterruptionStateProvider.setDisableNotificationAlerts(true);
+        assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isTrue();
+
+        // Enable alerts
+        mNotifInterruptionStateProvider.setDisableNotificationAlerts(false);
+        assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isFalse();
+    }
+
+    /**
+     * Ensure that the disabled alert state effects whether HUNs are enabled.
+     */
+    @Test
+    public void testHunSettingsChange_enabled_butAlertsDisabled() {
+        // Set up but without a mock change observer
+        mNotifInterruptionStateProvider.setUpWithPresenter(
+                mPresenter,
+                mHeadsUpManager,
+                mHeadsUpSuppressor);
+
+        // HUNs enabled by default
+        assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isTrue();
+
+        // Set alerts disabled
+        mNotifInterruptionStateProvider.setDisableNotificationAlerts(true);
+
+        // No more HUNs
+        assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isFalse();
+    }
+
+    /**
+     * Alerts can happen.
+     */
+    @Test
+    public void testCanAlertCommon_true() {
+        ensureStateForAlertCommon();
 
         NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
-
-        // THEN this suppressor doesn't suppress anything by default
-        assertThat(defaultSuppressor.suppressAwakeHeadsUp(entry)).isFalse();
-        assertThat(defaultSuppressor.suppressAwakeInterruptions(entry)).isFalse();
-        assertThat(defaultSuppressor.suppressInterruptions(entry)).isFalse();
+        assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isTrue();
     }
 
+    /**
+     * Filtered out notifications don't alert.
+     */
     @Test
-    public void testShouldHeadsUpAwake() throws RemoteException {
-        ensureStateForHeadsUpWhenAwake();
+    public void testCanAlertCommon_false_filteredOut() {
+        ensureStateForAlertCommon();
+        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
 
-        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
-        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isFalse();
     }
 
+    /**
+     * Grouped notifications have different alerting behaviours, sometimes the alert for a
+     * grouped notification may be suppressed {@link android.app.Notification#GROUP_ALERT_CHILDREN}.
+     */
     @Test
-    public void testShouldNotHeadsUpAwake_flteredOut() throws RemoteException {
-        // GIVEN state for "heads up when awake" is true
-        ensureStateForHeadsUpWhenAwake();
+    public void testCanAlertCommon_false_suppressedForGroups() {
+        ensureStateForAlertCommon();
 
-        // WHEN this entry should be filtered out
-        NotificationEntry entry  = createNotification(IMPORTANCE_DEFAULT);
-        when(mNotificationFilter.shouldFilterOut(entry)).thenReturn(true);
-
-        // THEN we shouldn't heads up this entry
-        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
-    }
-
-    @Test
-    public void testShouldNotHeadsUp_suppressedForGroups() throws RemoteException {
-        // GIVEN state for "heads up when awake" is true
-        ensureStateForHeadsUpWhenAwake();
-
-        // WHEN the alert for a grouped notification is suppressed
-        // see {@link android.app.Notification#GROUP_ALERT_CHILDREN}
         NotificationEntry entry = new NotificationEntryBuilder()
                 .setPkg("a")
                 .setOpPkg("a")
@@ -219,40 +247,40 @@
                 .setImportance(IMPORTANCE_DEFAULT)
                 .build();
 
-        // THEN this entry shouldn't HUN
-        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+        assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isFalse();
     }
 
+    /**
+     * HUNs while dozing can happen.
+     */
     @Test
-    public void testShouldHeadsUpWhenDozing() {
+    public void testShouldHeadsUpWhenDozing_true() {
         ensureStateForHeadsUpWhenDozing();
 
         NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
         assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
     }
 
+    /**
+     * Ambient display can show HUNs for new notifications, this may be disabled.
+     */
     @Test
-    public void testShouldNotHeadsUpWhenDozing_pulseDisabled() {
-        // GIVEN state for "heads up when dozing" is true
+    public void testShouldHeadsUpWhenDozing_false_pulseDisabled() {
         ensureStateForHeadsUpWhenDozing();
-
-        // WHEN pulsing (HUNs when dozing) is disabled
         when(mAmbientDisplayConfiguration.pulseOnNotificationEnabled(anyInt())).thenReturn(false);
 
-        // THEN this entry shouldn't HUN
         NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
         assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
     }
 
+    /**
+     * If the device is not in ambient display or sleeping then we don't HUN.
+     */
     @Test
-    public void testShouldNotHeadsUpWhenDozing_notDozing() {
-        // GIVEN state for "heads up when dozing" is true
+    public void testShouldHeadsUpWhenDozing_false_notDozing() {
         ensureStateForHeadsUpWhenDozing();
-
-        // WHEN we're not dozing (in ambient display or sleeping)
         when(mStatusBarStateController.isDozing()).thenReturn(false);
 
-        // THEN this entry shouldn't HUN
         NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
         assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
     }
@@ -262,7 +290,7 @@
      * {@link android.app.NotificationManager.Policy#SUPPRESSED_EFFECT_AMBIENT}.
      */
     @Test
-    public void testShouldNotHeadsUpWhenDozing_suppressingAmbient() {
+    public void testShouldHeadsUpWhenDozing_false_suppressingAmbient() {
         ensureStateForHeadsUpWhenDozing();
 
         NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
@@ -273,18 +301,23 @@
         assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
     }
 
+    /**
+     * Notifications that are < {@link android.app.NotificationManager#IMPORTANCE_DEFAULT} don't
+     * get to pulse.
+     */
     @Test
-    public void testShouldNotHeadsUpWhenDozing_lessImportant() {
+    public void testShouldHeadsUpWhenDozing_false_lessImportant() {
         ensureStateForHeadsUpWhenDozing();
 
-        // Notifications that are < {@link android.app.NotificationManager#IMPORTANCE_DEFAULT} don't
-        // get to pulse
         NotificationEntry entry = createNotification(IMPORTANCE_LOW);
         assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
     }
 
+    /**
+     * Heads up can happen.
+     */
     @Test
-    public void testShouldHeadsUp() throws RemoteException {
+    public void testShouldHeadsUp_true() throws RemoteException {
         ensureStateForHeadsUpWhenAwake();
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
@@ -292,11 +325,38 @@
     }
 
     /**
+     * Heads up notifications can be disabled in general.
+     */
+    @Test
+    public void testShouldHeadsUp_false_noHunsAllowed() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+
+        // Set alerts disabled, this should cause heads up to be false
+        mNotifInterruptionStateProvider.setDisableNotificationAlerts(true);
+        assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isFalse();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * If the device is dozing, we don't show as heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_dozing() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+        when(mStatusBarStateController.isDozing()).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
      * If the notification is a bubble, and the user is not on AOD / lockscreen, then
      * the bubble is shown rather than the heads up.
      */
     @Test
-    public void testShouldNotHeadsUp_bubble() throws RemoteException {
+    public void testShouldHeadsUp_false_bubble() throws RemoteException {
         ensureStateForHeadsUpWhenAwake();
 
         // Bubble bit only applies to interruption when we're in the shade
@@ -309,7 +369,7 @@
      * If we're not allowed to alert in general, we shouldn't be shown as heads up.
      */
     @Test
-    public void testShouldNotHeadsUp_filtered() throws RemoteException {
+    public void testShouldHeadsUp_false_alertCommonFalse() throws RemoteException {
         ensureStateForHeadsUpWhenAwake();
         // Make canAlertCommon false by saying it's filtered out
         when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
@@ -323,7 +383,7 @@
      * {@link android.app.NotificationManager.Policy#SUPPRESSED_EFFECT_PEEK}.
      */
     @Test
-    public void testShouldNotHeadsUp_suppressPeek() throws RemoteException {
+    public void testShouldHeadsUp_false_suppressPeek() throws RemoteException {
         ensureStateForHeadsUpWhenAwake();
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
@@ -339,7 +399,7 @@
      * to show as a heads up.
      */
     @Test
-    public void testShouldNotHeadsUp_lessImportant() throws RemoteException {
+    public void testShouldHeadsUp_false_lessImportant() throws RemoteException {
         ensureStateForHeadsUpWhenAwake();
 
         NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
@@ -350,7 +410,7 @@
      * If the device is not in use then we shouldn't be shown as heads up.
      */
     @Test
-    public void testShouldNotHeadsUp_deviceNotInUse() throws RemoteException {
+    public void testShouldHeadsUp_false_deviceNotInUse() throws RemoteException {
         ensureStateForHeadsUpWhenAwake();
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
 
@@ -364,58 +424,61 @@
         assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
     }
 
+    /**
+     * If something wants to suppress this heads up, then it shouldn't be shown as a heads up.
+     */
     @Test
-    public void testShouldNotHeadsUp_headsUpSuppressed() throws RemoteException {
+    public void testShouldHeadsUp_false_suppressed() throws RemoteException {
         ensureStateForHeadsUpWhenAwake();
-
-        // If a suppressor is suppressing heads up, then it shouldn't be shown as a heads up.
-        mNotifInterruptionStateProvider.addSuppressor(mSuppressAwakeHeadsUp);
+        when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(false);
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
         assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+        verify(mHeadsUpSuppressor).canHeadsUp(any(), any());
     }
 
+    /**
+     * On screen alerts don't happen when the device is in VR Mode.
+     */
     @Test
-    public void testShouldNotHeadsUpAwake_awakeInterruptsSuppressed() throws RemoteException {
-        ensureStateForHeadsUpWhenAwake();
+    public void testCanAlertAwakeCommon__false_vrMode() {
+        ensureStateForAlertAwakeCommon();
+        when(mPresenter.isDeviceInVrMode()).thenReturn(true);
 
-        // If a suppressor is suppressing heads up, then it shouldn't be shown as a heads up.
-        mNotifInterruptionStateProvider.addSuppressor(mSuppressAwakeInterruptions);
-
-        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
-        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
     }
 
     /**
      * On screen alerts don't happen when the notification is snoozed.
      */
     @Test
-    public void testShouldNotHeadsUp_snoozedPackage() {
-        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+    public void testCanAlertAwakeCommon_false_snoozedPackage() {
         ensureStateForAlertAwakeCommon();
+        when(mHeadsUpManager.isSnoozed(any())).thenReturn(true);
 
-        when(mHeadsUpManager.isSnoozed(entry.getSbn().getPackageName())).thenReturn(true);
-
-        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
     }
 
-
+    /**
+     * On screen alerts don't happen when that package has just launched fullscreen.
+     */
     @Test
-    public void testShouldNotHeadsUp_justLaunchedFullscreen() {
+    public void testCanAlertAwakeCommon_false_justLaunchedFullscreen() {
         ensureStateForAlertAwakeCommon();
 
-        // On screen alerts don't happen when that package has just launched fullscreen.
         NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
         entry.notifyFullScreenIntentLaunched();
 
-        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+        assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
     }
 
     /**
      * Bubbles can happen.
      */
     @Test
-    public void testShouldBubbleUp() {
+    public void testShouldBubbleUp_true() {
         ensureStateForBubbleUp();
         assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isTrue();
     }
@@ -424,7 +487,7 @@
      * If the notification doesn't have permission to bubble, it shouldn't bubble.
      */
     @Test
-    public void shouldNotBubbleUp_notAllowedToBubble() {
+    public void shouldBubbleUp_false_notAllowedToBubble() {
         ensureStateForBubbleUp();
 
         NotificationEntry entry = createBubble();
@@ -439,7 +502,7 @@
      * If the notification isn't a bubble, it should definitely not show as a bubble.
      */
     @Test
-    public void shouldNotBubbleUp_notABubble() {
+    public void shouldBubbleUp_false_notABubble() {
         ensureStateForBubbleUp();
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
@@ -454,7 +517,7 @@
      * If the notification doesn't have bubble metadata, it shouldn't bubble.
      */
     @Test
-    public void shouldNotBubbleUp_invalidMetadata() {
+    public void shouldBubbleUp_false_invalidMetadata() {
         ensureStateForBubbleUp();
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
@@ -466,18 +529,24 @@
         assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
     }
 
+    /**
+     * If the notification can't heads up in general, it shouldn't bubble.
+     */
     @Test
-    public void shouldNotBubbleUp_suppressedInterruptions() {
+    public void shouldBubbleUp_false_alertAwakeCommonFalse() {
         ensureStateForBubbleUp();
 
-        // If the notification can't heads up in general, it shouldn't bubble.
-        mNotifInterruptionStateProvider.addSuppressor(mSuppressInterruptions);
+        // Make alert common return false by pretending we're in VR mode
+        when(mPresenter.isDeviceInVrMode()).thenReturn(true);
 
         assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse();
     }
 
+    /**
+     * If the notification can't heads up in general, it shouldn't bubble.
+     */
     @Test
-    public void shouldNotBubbleUp_filteredOut() {
+    public void shouldBubbleUp_false_alertCommonFalse() {
         ensureStateForBubbleUp();
 
         // Make canAlertCommon false by saying it's filtered out
@@ -523,45 +592,20 @@
                 .build();
     }
 
-    private final NotificationInterruptSuppressor
-            mSuppressAwakeHeadsUp =
-            new NotificationInterruptSuppressor() {
-        @Override
-        public String getName() {
-            return "suppressAwakeHeadsUp";
-        }
+    /**
+     * Testable class overriding constructor.
+     */
+    public static class TestableNotificationInterruptionStateProvider extends
+            NotificationInterruptionStateProvider {
 
-        @Override
-        public boolean suppressAwakeHeadsUp(NotificationEntry entry) {
-            return true;
+        TestableNotificationInterruptionStateProvider(Context context,
+                PowerManager powerManager, IDreamManager dreamManager,
+                AmbientDisplayConfiguration ambientDisplayConfiguration,
+                NotificationFilter notificationFilter,
+                StatusBarStateController statusBarStateController,
+                BatteryController batteryController) {
+            super(context, powerManager, dreamManager, ambientDisplayConfiguration,
+                    notificationFilter, batteryController, statusBarStateController);
         }
-    };
-
-    private final NotificationInterruptSuppressor
-            mSuppressAwakeInterruptions =
-            new NotificationInterruptSuppressor() {
-        @Override
-        public String getName() {
-            return "suppressAwakeInterruptions";
-        }
-
-        @Override
-        public boolean suppressAwakeInterruptions(NotificationEntry entry) {
-            return true;
-        }
-    };
-
-    private final NotificationInterruptSuppressor
-            mSuppressInterruptions =
-            new NotificationInterruptSuppressor() {
-        @Override
-        public String getName() {
-            return "suppressInterruptions";
-        }
-
-        @Override
-        public boolean suppressInterruptions(NotificationEntry entry) {
-            return true;
-        }
-    };
+    }
 }
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..5d0349d 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
@@ -56,12 +56,12 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
 import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 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.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -108,7 +108,7 @@
     @Mock private NotificationEntryListener mEntryListener;
     @Mock private NotificationRowBinderImpl.BindRowCallback mBindCallback;
     @Mock private HeadsUpManager mHeadsUpManager;
-    @Mock private NotificationInterruptStateProvider mNotificationInterruptionStateProvider;
+    @Mock private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock private NotificationGutsManager mGutsManager;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index b9c5b7c..1e4df27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -67,10 +67,10 @@
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -183,7 +183,7 @@
                 mock(StatusBarRemoteInputCallback.class), mock(NotificationGroupManager.class),
                 mock(NotificationLockscreenUserManager.class),
                 mKeyguardStateController,
-                mock(NotificationInterruptStateProvider.class), mock(MetricsLogger.class),
+                mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
                 mock(LockPatternUtils.class), mHandler, mHandler, mUiBgExecutor,
                 mActivityIntentHelper, mBubbleController, mShadeController, mFeatureFlags,
                 mNotifPipeline, mNotifCollection)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 318e9b8..b9d2d22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -16,9 +16,8 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
@@ -36,7 +35,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.logging.testing.FakeMetricsLogger;
-import com.android.systemui.ForegroundServiceNotificationListener;
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -50,12 +48,12 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationAlertingManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -64,7 +62,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 
 import java.util.ArrayList;
 
@@ -75,9 +72,6 @@
 
 
     private StatusBarNotificationPresenter mStatusBarNotificationPresenter;
-    private NotificationInterruptStateProvider mNotificationInterruptStateProvider =
-            mock(NotificationInterruptStateProvider.class);
-    private NotificationInterruptSuppressor mInterruptSuppressor;
     private CommandQueue mCommandQueue;
     private FakeMetricsLogger mMetricsLogger;
     private ShadeController mShadeController = mock(ShadeController.class);
@@ -101,11 +95,11 @@
         mDependency.injectMockDependency(NotificationViewHierarchyManager.class);
         mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class);
         mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
+        mDependency.injectMockDependency(NotificationInterruptionStateProvider.class);
         mDependency.injectMockDependency(NotificationMediaManager.class);
         mDependency.injectMockDependency(VisualStabilityManager.class);
         mDependency.injectMockDependency(NotificationGutsManager.class);
         mDependency.injectMockDependency(NotificationShadeWindowController.class);
-        mDependency.injectMockDependency(ForegroundServiceNotificationListener.class);
         NotificationEntryManager entryManager =
                 mDependency.injectMockDependency(NotificationEntryManager.class);
         when(entryManager.getActiveNotificationsForCurrentUser()).thenReturn(new ArrayList<>());
@@ -113,25 +107,18 @@
         NotificationShadeWindowView notificationShadeWindowView =
                 mock(NotificationShadeWindowView.class);
         when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
-
         mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(mContext,
                 mock(NotificationPanelViewController.class), mock(HeadsUpManagerPhone.class),
                 notificationShadeWindowView, mock(NotificationListContainerViewGroup.class),
                 mock(DozeScrimController.class), mock(ScrimController.class),
                 mock(ActivityLaunchAnimator.class), mock(DynamicPrivacyController.class),
-                mock(KeyguardStateController.class),
+                mock(NotificationAlertingManager.class), mock(KeyguardStateController.class),
                 mock(KeyguardIndicationController.class), mStatusBar,
-                mock(ShadeControllerImpl.class), mCommandQueue, mInitController,
-                mNotificationInterruptStateProvider);
-        mInitController.executePostInitTasks();
-        ArgumentCaptor<NotificationInterruptSuppressor> suppressorCaptor =
-                ArgumentCaptor.forClass(NotificationInterruptSuppressor.class);
-        verify(mNotificationInterruptStateProvider).addSuppressor(suppressorCaptor.capture());
-        mInterruptSuppressor = suppressorCaptor.getValue();
+                mock(ShadeControllerImpl.class), mCommandQueue, mInitController);
     }
 
     @Test
-    public void testSuppressHeadsUp_disabledStatusBar() {
+    public void testHeadsUp_disabledStatusBar() {
         Notification n = new Notification.Builder(getContext(), "a").build();
         NotificationEntry entry = new NotificationEntryBuilder()
                 .setPkg("a")
@@ -143,12 +130,12 @@
                 false /* animate */);
         TestableLooper.get(this).processAllMessages();
 
-        assertTrue("The panel should suppress heads up while disabled",
-                mInterruptSuppressor.suppressAwakeHeadsUp(entry));
+        assertFalse("The panel shouldn't allow heads up while disabled",
+                mStatusBarNotificationPresenter.canHeadsUp(entry, entry.getSbn()));
     }
 
     @Test
-    public void testSuppressHeadsUp_disabledNotificationShade() {
+    public void testHeadsUp_disabledNotificationShade() {
         Notification n = new Notification.Builder(getContext(), "a").build();
         NotificationEntry entry = new NotificationEntryBuilder()
                 .setPkg("a")
@@ -160,39 +147,8 @@
                 false /* animate */);
         TestableLooper.get(this).processAllMessages();
 
-        assertTrue("The panel should suppress interruptions while notification shade "
-                        + "disabled",
-                mInterruptSuppressor.suppressAwakeHeadsUp(entry));
-    }
-
-    @Test
-    public void testSuppressInterruptions_vrMode() {
-        Notification n = new Notification.Builder(getContext(), "a").build();
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setPkg("a")
-                .setOpPkg("a")
-                .setTag("a")
-                .setNotification(n)
-                .build();
-        mStatusBarNotificationPresenter.mVrMode = true;
-
-        assertTrue("Vr mode should suppress interruptions",
-                mInterruptSuppressor.suppressAwakeInterruptions(entry));
-    }
-
-    @Test
-    public void testSuppressInterruptions_statusBarAlertsDisabled() {
-        Notification n = new Notification.Builder(getContext(), "a").build();
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setPkg("a")
-                .setOpPkg("a")
-                .setTag("a")
-                .setNotification(n)
-                .build();
-        when(mStatusBar.areNotificationAlertsDisabled()).thenReturn(true);
-
-        assertTrue("StatusBar alerts disabled shouldn't allow interruptions",
-                mInterruptSuppressor.suppressInterruptions(entry));
+        assertFalse("The panel shouldn't allow heads up while notitifcation shade disabled",
+                mStatusBarNotificationPresenter.canHeadsUp(entry, entry.getSbn()));
     }
 
     @Test
@@ -216,3 +172,4 @@
         }
     }
 }
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index d9f4d4b..0d7734e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -42,7 +42,7 @@
 import android.app.StatusBarManager;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
+import android.content.Context;
 import android.content.IntentFilter;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.fingerprint.FingerprintManager;
@@ -109,17 +109,18 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationAlertingManager;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -128,7 +129,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
@@ -160,7 +160,7 @@
     private StatusBar mStatusBar;
     private FakeMetricsLogger mMetricsLogger;
     private PowerManager mPowerManager;
-    private TestableNotificationInterruptStateProviderImpl mNotificationInterruptStateProvider;
+    private TestableNotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
 
     @Mock private NotificationsController mNotificationsController;
     @Mock private LightBarController mLightBarController;
@@ -178,6 +178,7 @@
     @Mock private DozeScrimController mDozeScrimController;
     @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     @Mock private BiometricUnlockController mBiometricUnlockController;
+    @Mock private NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor;
     @Mock private VisualStabilityManager mVisualStabilityManager;
     @Mock private NotificationListener mNotificationListener;
     @Mock private KeyguardViewMediator mKeyguardViewMediator;
@@ -190,9 +191,10 @@
     @Mock private StatusBarNotificationPresenter mNotificationPresenter;
     @Mock private NotificationEntryListener mEntryListener;
     @Mock private NotificationFilter mNotificationFilter;
-    @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+    @Mock private NotificationAlertingManager mNotificationAlertingManager;
     @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
     @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private AssistManager mAssistManager;
@@ -260,12 +262,10 @@
         mPowerManager = new PowerManager(mContext, powerManagerService, thermalService,
                 Handler.createAsync(Looper.myLooper()));
 
-        mNotificationInterruptStateProvider =
-                new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
-                        mPowerManager,
+        mNotificationInterruptionStateProvider =
+                new TestableNotificationInterruptionStateProvider(mContext, mPowerManager,
                         mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter,
-                        mStatusBarStateController, mBatteryController, mHeadsUpManager,
-                        new Handler(TestableLooper.get(this).getLooper()));
+                        mStatusBarStateController, mBatteryController);
 
         mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
         mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
@@ -298,6 +298,9 @@
             return null;
         }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
 
+        mNotificationInterruptionStateProvider.setUpWithPresenter(mNotificationPresenter,
+                mHeadsUpManager, mHeadsUpSuppressor);
+
         when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
 
         WakefulnessLifecycle wakefulnessLifecycle = new WakefulnessLifecycle();
@@ -344,9 +347,10 @@
                 ),
                 mNotificationGutsManager,
                 notificationLogger,
-                mNotificationInterruptStateProvider,
+                mNotificationInterruptionStateProvider,
                 mNotificationViewHierarchyManager,
                 mKeyguardViewMediator,
+                mNotificationAlertingManager,
                 new DisplayMetrics(),
                 mMetricsLogger,
                 mUiBgExecutor,
@@ -557,6 +561,7 @@
         when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
         when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
         when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
 
         Notification n = new Notification.Builder(getContext(), "a")
                 .setGroup("a")
@@ -572,7 +577,7 @@
                 .setImportance(IMPORTANCE_HIGH)
                 .build();
 
-        assertTrue(mNotificationInterruptStateProvider.shouldHeadsUp(entry));
+        assertTrue(mNotificationInterruptionStateProvider.shouldHeadsUp(entry));
     }
 
     @Test
@@ -581,6 +586,7 @@
         when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
         when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
         when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
 
         Notification n = new Notification.Builder(getContext(), "a")
                 .setGroup("a")
@@ -596,7 +602,7 @@
                 .setImportance(IMPORTANCE_HIGH)
                 .build();
 
-        assertFalse(mNotificationInterruptStateProvider.shouldHeadsUp(entry));
+        assertFalse(mNotificationInterruptionStateProvider.shouldHeadsUp(entry));
     }
 
     @Test
@@ -605,6 +611,7 @@
         when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
         when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
         when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
 
         Notification n = new Notification.Builder(getContext(), "a").build();
 
@@ -617,7 +624,7 @@
                 .setSuppressedVisualEffects(SUPPRESSED_EFFECT_PEEK)
                 .build();
 
-        assertFalse(mNotificationInterruptStateProvider.shouldHeadsUp(entry));
+        assertFalse(mNotificationInterruptionStateProvider.shouldHeadsUp(entry));
     }
 
     @Test
@@ -626,6 +633,7 @@
         when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
         when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
         when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
 
         Notification n = new Notification.Builder(getContext(), "a").build();
 
@@ -637,7 +645,7 @@
                 .setImportance(IMPORTANCE_HIGH)
                 .build();
 
-        assertTrue(mNotificationInterruptStateProvider.shouldHeadsUp(entry));
+        assertTrue(mNotificationInterruptionStateProvider.shouldHeadsUp(entry));
     }
 
     @Test
@@ -863,21 +871,19 @@
         verify(mDozeServiceHost).setDozeSuppressed(false);
     }
 
-    public static class TestableNotificationInterruptStateProviderImpl extends
-            NotificationInterruptStateProviderImpl {
+    public static class TestableNotificationInterruptionStateProvider extends
+            NotificationInterruptionStateProvider {
 
-        TestableNotificationInterruptStateProviderImpl(
-                ContentResolver contentResolver,
+        TestableNotificationInterruptionStateProvider(
+                Context context,
                 PowerManager powerManager,
                 IDreamManager dreamManager,
                 AmbientDisplayConfiguration ambientDisplayConfiguration,
                 NotificationFilter filter,
                 StatusBarStateController controller,
-                BatteryController batteryController,
-                HeadsUpManager headsUpManager,
-                Handler mainHandler) {
-            super(contentResolver, powerManager, dreamManager, ambientDisplayConfiguration, filter,
-                    batteryController, controller, headsUpManager, mainHandler);
+                BatteryController batteryController) {
+            super(context, powerManager, dreamManager, ambientDisplayConfiguration, filter,
+                    batteryController, controller);
             mUseHeadsUp = true;
         }
     }
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 1415433..7dedad7 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -167,14 +167,13 @@
 
         @Override
         public String toString() {
-            return "{callingPackage=" + callingPackage + " binder=" + binder
-                    + " callback=" + callback
+            return "{callingPackage=" + pii(callingPackage) + " callerUid=" + callerUid + " binder="
+                    + binder + " callback=" + callback
                     + " onSubscriptionsChangedListenererCallback="
                     + onSubscriptionsChangedListenerCallback
                     + " onOpportunisticSubscriptionsChangedListenererCallback="
-                    + onOpportunisticSubscriptionsChangedListenerCallback
-                    + " callerUid=" + callerUid + " subId=" + subId + " phoneId=" + phoneId
-                    + " events=" + Integer.toHexString(events) + "}";
+                    + onOpportunisticSubscriptionsChangedListenerCallback + " subId=" + subId
+                    + " phoneId=" + phoneId + " events=" + Integer.toHexString(events) + "}";
         }
     }
 
@@ -598,9 +597,9 @@
         int callerUserId = UserHandle.getCallingUserId();
         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
         if (VDBG) {
-            log("listen oscl: E pkg=" + callingPackage + " myUserId=" + UserHandle.myUserId()
-                + " callerUserId="  + callerUserId + " callback=" + callback
-                + " callback.asBinder=" + callback.asBinder());
+            log("listen oscl: E pkg=" + pii(callingPackage) + " uid=" + Binder.getCallingUid()
+                    + " myUserId=" + UserHandle.myUserId() + " callerUserId=" + callerUserId
+                    + " callback=" + callback + " callback.asBinder=" + callback.asBinder());
         }
 
         synchronized (mRecords) {
@@ -652,9 +651,9 @@
         int callerUserId = UserHandle.getCallingUserId();
         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
         if (VDBG) {
-            log("listen ooscl: E pkg=" + callingPackage + " myUserId=" + UserHandle.myUserId()
-                    + " callerUserId="  + callerUserId + " callback=" + callback
-                    + " callback.asBinder=" + callback.asBinder());
+            log("listen ooscl: E pkg=" + pii(callingPackage) + " uid=" + Binder.getCallingUid()
+                    + " myUserId=" + UserHandle.myUserId() + " callerUserId=" + callerUserId
+                    + " callback=" + callback + " callback.asBinder=" + callback.asBinder());
         }
 
         synchronized (mRecords) {
@@ -769,9 +768,9 @@
             IPhoneStateListener callback, int events, boolean notifyNow, int subId) {
         int callerUserId = UserHandle.getCallingUserId();
         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
-        String str = "listen: E pkg=" + callingPackage + " events=0x" + Integer.toHexString(events)
-                + " notifyNow=" + notifyNow + " subId=" + subId + " myUserId="
-                + UserHandle.myUserId() + " callerUserId=" + callerUserId;
+        String str = "listen: E pkg=" + pii(callingPackage) + " uid=" + Binder.getCallingUid()
+                + " events=0x" + Integer.toHexString(events) + " notifyNow=" + notifyNow + " subId="
+                + subId + " myUserId=" + UserHandle.myUserId() + " callerUserId=" + callerUserId;
         mListenLog.log(str);
         if (VDBG) {
             log(str);
@@ -2957,4 +2956,14 @@
         if (info == null) return INVALID_SIM_SLOT_INDEX;
         return info.getSimSlotIndex();
     }
+
+    /**
+     * On certain build types, we should redact information by default. UID information will be
+     * preserved in the same log line, so no debugging capability is lost in full bug reports.
+     * However, privacy-constrained bug report types (e.g. connectivity) cannot display raw
+     * package names on user builds as it's considered an information leak.
+     */
+    private static String pii(String packageName) {
+        return Build.IS_DEBUGGABLE ? packageName : "***";
+    }
 }
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index a09aa64..df9417e 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -129,7 +129,7 @@
     private final ProcessMap<AppExitInfoContainer> mData;
 
     /** A pool of raw {@link android.app.ApplicationExitInfo} records. */
-    @GuardedBy("mService")
+    @GuardedBy("mLock")
     private final SynchronizedPool<ApplicationExitInfo> mRawRecordsPool;
 
     /**
@@ -204,8 +204,7 @@
         });
     }
 
-    @GuardedBy("mService")
-    void scheduleNoteProcessDiedLocked(final ProcessRecord app) {
+    void scheduleNoteProcessDied(final ProcessRecord app) {
         if (app == null || app.info == null) {
             return;
         }
@@ -214,11 +213,9 @@
             if (!mAppExitInfoLoaded) {
                 return;
             }
+            mKillHandler.obtainMessage(KillHandler.MSG_PROC_DIED, obtainRawRecordLocked(app))
+                    .sendToTarget();
         }
-        // The current thread is holding the global lock, let's extract the info from it
-        // and schedule the info note task in the kill handler.
-        mKillHandler.obtainMessage(KillHandler.MSG_PROC_DIED, obtainRawRecordLocked(app))
-                .sendToTarget();
     }
 
     void scheduleNoteAppKill(final ProcessRecord app, final @Reason int reason,
@@ -227,8 +224,6 @@
             if (!mAppExitInfoLoaded) {
                 return;
             }
-        }
-        synchronized (mService) {
             if (app == null || app.info == null) {
                 return;
             }
@@ -247,8 +242,6 @@
             if (!mAppExitInfoLoaded) {
                 return;
             }
-        }
-        synchronized (mService) {
             ProcessRecord app;
             synchronized (mService.mPidsSelfLocked) {
                 app = mService.mPidsSelfLocked.get(pid);
@@ -823,7 +816,7 @@
     }
 
     @VisibleForTesting
-    @GuardedBy("mService")
+    @GuardedBy("mLock")
     ApplicationExitInfo obtainRawRecordLocked(ProcessRecord app) {
         ApplicationExitInfo info = mRawRecordsPool.acquire();
         if (info == null) {
@@ -850,7 +843,7 @@
     }
 
     @VisibleForTesting
-    @GuardedBy("mService")
+    @GuardedBy("mLock")
     void recycleRawRecordLocked(ApplicationExitInfo info) {
         info.setProcessName(null);
         info.setDescription(null);
@@ -1135,8 +1128,6 @@
                     ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
                     synchronized (mLock) {
                         handleNoteProcessDiedLocked(raw);
-                    }
-                    synchronized (mService) {
                         recycleRawRecordLocked(raw);
                     }
                 }
@@ -1145,8 +1136,6 @@
                     ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
                     synchronized (mLock) {
                         handleNoteAppKillLocked(raw);
-                    }
-                    synchronized (mService) {
                         recycleRawRecordLocked(raw);
                     }
                 }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 644f0f7..91348aa 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2588,8 +2588,13 @@
             return;
         }
 
+        // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
+        if (app.frozen && app.shouldNotFreeze) {
+            mCachedAppOptimizer.unfreezeAppLocked(app);
+        }
+
         // Use current adjustment when freezing, set adjustment when unfreezing.
-        if (app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ && !app.frozen) {
+        if (app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ && !app.frozen && !app.shouldNotFreeze) {
             mCachedAppOptimizer.freezeAppAsync(app);
         } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ && app.frozen) {
             mCachedAppOptimizer.unfreezeAppLocked(app);
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index a9d18e0..6b16513 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3773,7 +3773,7 @@
         }
 
         Watchdog.getInstance().processDied(app.processName, app.pid);
-        mAppExitInfoTracker.scheduleNoteProcessDiedLocked(app);
+        mAppExitInfoTracker.scheduleNoteProcessDied(app);
     }
 
     /**
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index bfc2f82..61bede9 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -219,6 +219,19 @@
     }
 
     /**
+     * Returns whether the change is marked as disabled.
+     */
+    boolean isDisabled(long changeId) {
+        synchronized (mChanges) {
+            CompatChange c = mChanges.get(changeId);
+            if (c == null) {
+                return false;
+            }
+            return c.getDisabled();
+        }
+    }
+
+    /**
      * Removes an override previously added via {@link #addOverride(long, String, boolean)}. This
      * restores the default behaviour for the given change and app, once any app processes have been
      * restarted.
diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
index 9e18c74..f5d6e5a 100644
--- a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
+++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
@@ -59,6 +59,7 @@
         boolean debuggableBuild = mAndroidBuildClassifier.isDebuggableBuild();
         boolean finalBuild = mAndroidBuildClassifier.isFinalBuild();
         int minTargetSdk = mCompatConfig.minTargetSdkForChangeId(changeId);
+        boolean disabled = mCompatConfig.isDisabled(changeId);
 
         // Allow any override for userdebug or eng builds.
         if (debuggableBuild) {
@@ -83,12 +84,12 @@
         if (!finalBuild) {
             return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk);
         }
-        // Do not allow overriding non-target sdk gated changes on user builds
-        if (minTargetSdk == -1) {
+        // Do not allow overriding default enabled changes on user builds
+        if (minTargetSdk == -1 && !disabled) {
             return new OverrideAllowedState(DISABLED_NON_TARGET_SDK, appTargetSdk, minTargetSdk);
         }
         // Only allow to opt-in for a targetSdk gated change.
-        if (applicationInfo.targetSdkVersion < minTargetSdk) {
+        if (disabled || applicationInfo.targetSdkVersion < minTargetSdk) {
             return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk);
         }
         return new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, appTargetSdk, minTargetSdk);
diff --git a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
index 73bb4bf..948439d 100644
--- a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
+import android.os.Binder;
 import android.util.EventLog;
 import android.util.Slog;
 
@@ -134,7 +135,12 @@
 
     private BufferedInputStream getAltContent(Context c, Intent i) throws IOException {
         Uri content = getContentFromIntent(i);
-        return new BufferedInputStream(c.getContentResolver().openInputStream(content));
+        Binder.allowBlockingForCurrentThread();
+        try {
+            return new BufferedInputStream(c.getContentResolver().openInputStream(content));
+        } finally {
+            Binder.defaultBlockingForCurrentThread();
+        }
     }
 
     private byte[] getCurrentContent() {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 984ae21..688c9ae 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1570,10 +1570,16 @@
 
         mService.mUgmInternal.grantUriPermissionFromIntent(mCallingUid, mStartActivity.packageName,
                 mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.mUserId);
-        mService.getPackageManagerInternalLocked().grantImplicitAccess(
-                mStartActivity.mUserId, mIntent,
-                UserHandle.getAppId(mStartActivity.info.applicationInfo.uid), mCallingUid,
-                true /*direct*/);
+        if (mStartActivity.resultTo != null && mStartActivity.resultTo.info != null) {
+            // we need to resolve resultTo to a uid as grantImplicitAccess deals explicitly in UIDs
+            final PackageManagerInternal pmInternal =
+                    mService.getPackageManagerInternalLocked();
+            final int resultToUid = pmInternal.getPackageUidInternal(
+                            mStartActivity.resultTo.info.packageName, 0, mStartActivity.mUserId);
+            pmInternal.grantImplicitAccess(mStartActivity.mUserId, mIntent,
+                    UserHandle.getAppId(mStartActivity.info.applicationInfo.uid) /*recipient*/,
+                    resultToUid /*visible*/, true /*direct*/);
+        }
         if (newTask) {
             EventLogTags.writeWmCreateTask(mStartActivity.mUserId,
                     mStartActivity.getTask().mTaskId);
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 58aefdc..ada6d47 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -274,7 +274,7 @@
             // window crop of the surface controls (including the leash) until the client finishes
             // drawing the new frame of the new orientation. Although we cannot defer the reparent
             // operation, it is fine, because reparent won't cause any visual effect.
-            final SurfaceControl barrier = mWin.getDeferTransactionBarrier();
+            final SurfaceControl barrier = mWin.getClientViewRootSurface();
             t.deferTransactionUntil(mWin.getSurfaceControl(), barrier, frameNumber);
             t.deferTransactionUntil(leash, barrier, frameNumber);
         }
diff --git a/services/core/java/com/android/server/wm/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java
index 024da88..8e1c632 100644
--- a/services/core/java/com/android/server/wm/SeamlessRotator.java
+++ b/services/core/java/com/android/server/wm/SeamlessRotator.java
@@ -118,9 +118,9 @@
         finish(t, win);
         if (win.mWinAnimator.mSurfaceController != null && !timeout) {
             t.deferTransactionUntil(win.mSurfaceControl,
-                    win.getDeferTransactionBarrier(), win.getFrameNumber());
+                    win.getClientViewRootSurface(), win.getFrameNumber());
             t.deferTransactionUntil(win.mWinAnimator.mSurfaceController.mSurfaceControl,
-                    win.getDeferTransactionBarrier(), win.getFrameNumber());
+                    win.getClientViewRootSurface(), win.getFrameNumber());
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 27f1ca0..386755d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5660,8 +5660,8 @@
         return mSession.mPid == pid && isNonToastOrStarting() && isVisibleNow();
     }
 
-    SurfaceControl getDeferTransactionBarrier() {
-        return mWinAnimator.getDeferTransactionBarrier();
+    SurfaceControl getClientViewRootSurface() {
+        return mWinAnimator.getClientViewRootSurface();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 81d0e3e..79dc536 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -383,8 +383,9 @@
             // Make sure to reparent any children of the new surface back to the preserved
             // surface before destroying it.
             if (mSurfaceController != null && mPendingDestroySurface != null) {
-                mPostDrawTransaction.reparentChildren(mSurfaceController.mSurfaceControl,
-                        mPendingDestroySurface.mSurfaceControl).apply();
+                mPostDrawTransaction.reparentChildren(
+                    mSurfaceController.getClientViewRootSurface(),
+                    mPendingDestroySurface.mSurfaceControl).apply();
             }
             destroySurfaceLocked();
             mSurfaceDestroyDeferred = true;
@@ -413,9 +414,9 @@
                 // child layers need to be reparented to the new surface to make this
                 // transparent to the app.
                 if (mWin.mActivityRecord == null || mWin.mActivityRecord.isRelaunching() == false) {
-                    mPostDrawTransaction.reparentChildren(mPendingDestroySurface.mSurfaceControl,
-                            mSurfaceController.mSurfaceControl)
-                            .apply();
+                    mPostDrawTransaction.reparentChildren(
+                        mPendingDestroySurface.getClientViewRootSurface(),
+                        mSurfaceController.mSurfaceControl).apply();
                 }
             }
         }
@@ -875,7 +876,7 @@
 
         if (mSurfaceResized && (mAttrType == TYPE_BASE_APPLICATION) &&
             (task != null) && (task.getMainWindowSizeChangeTransaction() != null)) {
-            mSurfaceController.deferTransactionUntil(mWin.getDeferTransactionBarrier(),
+            mSurfaceController.deferTransactionUntil(mWin.getClientViewRootSurface(),
                     mWin.getFrameNumber());
             SurfaceControl.mergeToGlobalTransaction(task.getMainWindowSizeChangeTransaction());
             task.setMainWindowSizeChangeTransaction(null);
@@ -1012,7 +1013,7 @@
                         // the WS position is reset (so the stack position is shown) at the same
                         // time that the buffer size changes.
                         setOffsetPositionForStackResize(false);
-                        mSurfaceController.deferTransactionUntil(mWin.getDeferTransactionBarrier(),
+                        mSurfaceController.deferTransactionUntil(mWin.getClientViewRootSurface(),
                                 mWin.getFrameNumber());
                     } else {
                         final ActivityStack stack = mWin.getRootTask();
@@ -1043,7 +1044,7 @@
         // comes in at the new size (normally position and crop are unfrozen).
         // deferTransactionUntil accomplishes this for us.
         if (wasForceScaled && !mForceScaleUntilResize) {
-            mSurfaceController.deferTransactionUntil(mWin.getDeferTransactionBarrier(),
+            mSurfaceController.deferTransactionUntil(mWin.getClientViewRootSurface(),
                     mWin.getFrameNumber());
             mSurfaceController.forceScaleableInTransaction(false);
         }
@@ -1288,8 +1289,9 @@
         if (mPendingDestroySurface != null && mDestroyPreservedSurfaceUponRedraw) {
             final SurfaceControl pendingSurfaceControl = mPendingDestroySurface.mSurfaceControl;
             mPostDrawTransaction.reparent(pendingSurfaceControl, null);
-            mPostDrawTransaction.reparentChildren(pendingSurfaceControl,
-                    mSurfaceController.mSurfaceControl);
+            mPostDrawTransaction.reparentChildren(
+                mPendingDestroySurface.getClientViewRootSurface(),
+                mSurfaceController.mSurfaceControl);
         }
 
         SurfaceControl.mergeToGlobalTransaction(mPostDrawTransaction);
@@ -1521,10 +1523,10 @@
         mOffsetPositionForStackResize = offsetPositionForStackResize;
     }
 
-    SurfaceControl getDeferTransactionBarrier() {
+    SurfaceControl getClientViewRootSurface() {
         if (!hasSurface()) {
             return null;
         }
-        return mSurfaceController.getDeferTransactionBarrier();
+        return mSurfaceController.getClientViewRootSurface();
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 383c0d9..d7c97b9 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -533,7 +533,15 @@
         return mSurfaceH;
     }
 
-    SurfaceControl getDeferTransactionBarrier() {
+    /**
+     * Returns the Surface which the client-framework ViewRootImpl will be using.
+     * This is either the WSA SurfaceControl or it's BLAST child surface.
+     * This has too main uses:
+     * 1. This is the Surface the client will add children to, we use this to make
+     *    sure we don't reparent the BLAST surface itself when calling reparentChildren
+     * 2. We use this as the barrier Surface for some deferTransaction operations.
+     */
+    SurfaceControl getClientViewRootSurface() {
         if (mBLASTSurfaceControl != null) {
             return mBLASTSurfaceControl;
         }
diff --git a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
index 16291b2..425c724 100644
--- a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
@@ -176,7 +176,8 @@
         CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext)
                         .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
                         .addTargetSdkChangeWithId(TARGET_SDK, 2)
-                        .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3).build();
+                        .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3)
+                        .addDisabledChangeWithId(4).build();
         IOverrideValidator overrideValidator = config.getOverrideValidator();
         when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
                 .thenReturn(ApplicationInfoBuilder.create()
@@ -190,6 +191,8 @@
                 overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
         OverrideAllowedState stateTargetSdkAfterChange =
                 overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+        OverrideAllowedState stateDisabledChange =
+                overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
 
         assertThat(stateTargetSdkLessChange)
                 .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_BEFORE));
@@ -197,6 +200,8 @@
                 .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK));
         assertThat(stateTargetSdkAfterChange)
                 .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_AFTER));
+        assertThat(stateDisabledChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, -1));
     }
 
     @Test
@@ -343,21 +348,22 @@
     }
 
     @Test
-    public void getOverrideAllowedState_finalBuildDisabledChangeDebugApp_rejectOverride()
+    public void getOverrideAllowedState_finalBuildDisabledChangeDebugApp_allowOverride()
             throws Exception {
         CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
-                        .addDisabledChangeWithId(1).build();
+                .addDisabledChangeWithId(1).build();
         IOverrideValidator overrideValidator = config.getOverrideValidator();
         when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
                 .thenReturn(ApplicationInfoBuilder.create()
                         .withPackageName(PACKAGE_NAME)
+                        .withTargetSdk(TARGET_SDK)
                         .debuggable().build());
 
         OverrideAllowedState allowedState =
                 overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
 
         assertThat(allowedState)
-                .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+                .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, -1));
     }
 
     @Test
diff --git a/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java b/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java
new file mode 100644
index 0000000..2fbfeba
--- /dev/null
+++ b/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.service.watchdog.ExplicitHealthCheckService;
+import android.service.watchdog.IExplicitHealthCheckService;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+
+public class ExplicitHealthCheckServiceTest {
+
+    private ExplicitHealthCheckService mExplicitHealthCheckService;
+    private static final String PACKAGE_NAME = "com.test.package";
+
+    @Before
+    public void setup() throws Exception {
+        mExplicitHealthCheckService = spy(ExplicitHealthCheckService.class);
+    }
+
+    /**
+     * Test to verify that the correct information is sent in the callback when a package has
+     * passed an explicit health check.
+     */
+    @Test
+    public void testNotifyHealthCheckPassed() throws Exception {
+        IBinder binder = mExplicitHealthCheckService.onBind(new Intent());
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        RemoteCallback callback = new RemoteCallback(result -> {
+            assertThat(result.get(ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE))
+                    .isEqualTo(PACKAGE_NAME);
+            countDownLatch.countDown();
+        });
+        IExplicitHealthCheckService.Stub.asInterface(binder).setCallback(callback);
+        mExplicitHealthCheckService.notifyHealthCheckPassed(PACKAGE_NAME);
+        countDownLatch.await();
+    }
+}