[DO NOT MERGE] AudioService: fix disconnect/connect of A2DP device
am: 8c92bde392

Change-Id: If8198c491d6d18bbe58d874fbdf6444f98366b74
diff --git a/api/test-current.txt b/api/test-current.txt
index 87295fa..d3bea18 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2344,6 +2344,7 @@
     field public static final String LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST = "location_ignore_settings_package_whitelist";
     field public static final String LOW_POWER_MODE = "low_power";
     field public static final String LOW_POWER_MODE_STICKY = "low_power_sticky";
+    field public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
     field public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices";
     field public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
   }
@@ -2367,7 +2368,7 @@
     field public static final String LOCATION_ACCESS_CHECK_DELAY_MILLIS = "location_access_check_delay_millis";
     field public static final String LOCATION_ACCESS_CHECK_INTERVAL_MILLIS = "location_access_check_interval_millis";
     field public static final String NOTIFICATION_BADGING = "notification_badging";
-    field public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
+    field @Deprecated public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
     field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds";
     field public static final String USER_SETUP_COMPLETE = "user_setup_complete";
     field public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index f08be49..3d48a02 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -5887,7 +5887,8 @@
     optional bool is_ongoing = 10;
 
     // Whether the bubble is produced by an app running in foreground.
-    optional bool is_foreground = 11;
+    // This is deprecated and the value should be ignored.
+    optional bool is_foreground = 11 [deprecated = true];
 }
 
 /**
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 3bf659b..755f047 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -120,6 +120,7 @@
 
         mActivityTaskManager = ActivityTaskManager.getService();
         mSurfaceView = new SurfaceView(context);
+        mSurfaceView.setUseAlpha();
         mSurfaceView.setAlpha(0f);
         mSurfaceCallback = new SurfaceCallback();
         mSurfaceView.getHolder().addCallback(mSurfaceCallback);
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 650147b..6fd43a6 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -178,6 +178,13 @@
      */
     void onSingleTaskDisplayDrawn(int displayId);
 
+    /*
+     * Called when the last task is removed from a display which can only contain one task.
+     *
+     * @param displayId the id of the display from which the window is removed.
+     */
+    void onSingleTaskDisplayEmpty(int displayId);
+
     /**
      * Called when a task is reparented to a stack on a different display.
      *
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ceadd85..16a98c6 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -8539,24 +8539,34 @@
          * If set and the app creating the bubble is in the foreground, the bubble will be posted
          * in its expanded state, with the contents of {@link #getIntent()} in a floating window.
          *
-         * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p>
+         * <p>This flag has no effect if the app posting the bubble is not in the foreground.
+         * The app is considered foreground if it is visible and on the screen, note that
+         * a foreground service does not qualify.
+         * </p>
          *
          * <p>Generally this flag should only be set if the user has performed an action to request
          * or create a bubble.</p>
+         *
+         * @hide
          */
-        private static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
+        public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
 
         /**
          * If set and the app posting the bubble is in the foreground, the bubble will
          * be posted <b>without</b> the associated notification in the notification shade.
          *
-         * <p>If the app posting the bubble is not in the foreground this flag has no effect.</p>
+         * <p>This flag has no effect if the app posting the bubble is not in the foreground.
+         * The app is considered foreground if it is visible and on the screen, note that
+         * a foreground service does not qualify.
+         * </p>
          *
          * <p>Generally this flag should only be set if the user has performed an action to request
          * or create a bubble, or if the user has seen the content in the notification and the
          * notification is no longer relevant.</p>
+         *
+         * @hide
          */
-        private static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
+        public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
 
         private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent,
                 Icon icon, int height, @DimenRes int heightResId) {
@@ -8672,11 +8682,21 @@
             out.writeInt(mDesiredHeightResId);
         }
 
-        private void setFlags(int flags) {
+        /**
+         * @hide
+         */
+        public void setFlags(int flags) {
             mFlags = flags;
         }
 
         /**
+         * @hide
+         */
+        public int getFlags() {
+            return mFlags;
+        }
+
+        /**
          * Builder to construct a {@link BubbleMetadata} object.
          */
         public static final class Builder {
@@ -8792,6 +8812,8 @@
              * {@link #getIntent()} in a floating window).
              *
              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
+             * The app is considered foreground if it is visible and on the screen, note that
+             * a foreground service does not qualify.
              * </p>
              *
              * <p>Generally, this flag should only be set if the user has performed an action to
@@ -8810,6 +8832,8 @@
              * the notification shade.
              *
              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
+             * The app is considered foreground if it is visible and on the screen, note that
+             * a foreground service does not qualify.
              * </p>
              *
              * <p>Generally, this flag should only be set if the user has performed an action to
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index b63feb5..b4b7df9 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -180,6 +180,10 @@
     }
 
     @Override
+    public void onSingleTaskDisplayEmpty(int displayId) throws RemoteException {
+    }
+
+    @Override
     public void onTaskDisplayChanged(int taskId, int newDisplayId) throws RemoteException {
     }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bf8b006..8212587 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8687,10 +8687,16 @@
          * Whether the notification bubbles are globally enabled
          * The value is boolean (1 or 0).
          * @hide
+         * @deprecated use {@link Global#NOTIFICATION_BUBBLES} instead.
          */
         @TestApi
+        @Deprecated
         public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
 
+        /**
+         * @deprecated use {@link Global#NOTIFICATION_BUBBLES_VALIDATOR} instead.
+         */
+        @Deprecated
         private static final Validator NOTIFICATION_BUBBLES_VALIDATOR = BOOLEAN_VALIDATOR;
 
         /**
@@ -9015,7 +9021,6 @@
             ASSIST_GESTURE_WAKE_ENABLED,
             VR_DISPLAY_MODE,
             NOTIFICATION_BADGING,
-            NOTIFICATION_BUBBLES,
             NOTIFICATION_DISMISS_RTL,
             QS_AUTO_ADDED_TILES,
             SCREENSAVER_ENABLED,
@@ -9386,6 +9391,16 @@
         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global");
 
         /**
+         * Whether the notification bubbles are globally enabled
+         * The value is boolean (1 or 0).
+         * @hide
+         */
+        @TestApi
+        public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
+
+        private static final Validator NOTIFICATION_BUBBLES_VALIDATOR = BOOLEAN_VALIDATOR;
+
+        /**
          * Whether users are allowed to add more users or guest from lockscreen.
          * <p>
          * Type: int
@@ -13910,6 +13925,7 @@
             VALIDATORS.put(AWARE_ALLOWED, AWARE_ALLOWED_VALIDATOR);
             VALIDATORS.put(POWER_BUTTON_LONG_PRESS, POWER_BUTTON_LONG_PRESS_VALIDATOR);
             VALIDATORS.put(POWER_BUTTON_VERY_LONG_PRESS, POWER_BUTTON_VERY_LONG_PRESS_VALIDATOR);
+            VALIDATORS.put(NOTIFICATION_BUBBLES, NOTIFICATION_BUBBLES_VALIDATOR);
         }
 
         /**
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 3c045f4..2313b13 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -84,7 +84,6 @@
     private static native long nativeCopyFromSurfaceControl(long nativeObject);
     private static native void nativeWriteToParcel(long nativeObject, Parcel out);
     private static native void nativeRelease(long nativeObject);
-    private static native void nativeDestroy(long nativeObject);
     private static native void nativeDisconnect(long nativeObject);
 
     private static native ScreenshotGraphicBuffer nativeScreenshot(IBinder displayToken,
@@ -930,19 +929,6 @@
     }
 
     /**
-     * Release the local resources like {@link #release} but also
-     * remove the Surface from the screen.
-     * @hide
-     */
-    public void remove() {
-        if (mNativeObject != 0) {
-            nativeDestroy(mNativeObject);
-            mNativeObject = 0;
-        }
-        mCloseGuard.close();
-    }
-
-    /**
      * Disconnect any client still connected to the surface.
      * @hide
      */
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 254d04e..d11548d 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -23,7 +23,6 @@
 import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.CompatibilityInfo.Translator;
-import android.content.res.Configuration;
 import android.graphics.BlendMode;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -100,10 +99,10 @@
 public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {
     private static final String TAG = "SurfaceView";
     private static final boolean DEBUG = false;
+    private static final boolean DEBUG_POSITION = false;
 
     @UnsupportedAppUsage
-    final ArrayList<SurfaceHolder.Callback> mCallbacks
-            = new ArrayList<SurfaceHolder.Callback>();
+    final ArrayList<SurfaceHolder.Callback> mCallbacks = new ArrayList<>();
 
     final int[] mLocation = new int[2];
 
@@ -126,8 +125,8 @@
     // we need to preserve the old one until the new one has drawn.
     SurfaceControl mDeferredDestroySurfaceControl;
     SurfaceControl mBackgroundControl;
+    final Object mSurfaceControlLock = new Object();
     final Rect mTmpRect = new Rect();
-    final Configuration mConfiguration = new Configuration();
 
     Paint mRoundedViewportPaint;
 
@@ -137,25 +136,16 @@
     boolean mIsCreating = false;
     private volatile boolean mRtHandlingPositionUpdates = false;
 
-    private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
-            = new ViewTreeObserver.OnScrollChangedListener() {
-                    @Override
-                    public void onScrollChanged() {
-                        updateSurface();
-                    }
-            };
+    private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener =
+            this::updateSurface;
 
     @UnsupportedAppUsage
-    private final ViewTreeObserver.OnPreDrawListener mDrawListener =
-            new ViewTreeObserver.OnPreDrawListener() {
-                @Override
-                public boolean onPreDraw() {
-                    // reposition ourselves where the surface is
-                    mHaveFrame = getWidth() > 0 && getHeight() > 0;
-                    updateSurface();
-                    return true;
-                }
-            };
+    private final ViewTreeObserver.OnPreDrawListener mDrawListener = () -> {
+        // reposition ourselves where the surface is
+        mHaveFrame = getWidth() > 0 && getHeight() > 0;
+        updateSurface();
+        return true;
+    };
 
     boolean mRequestedVisible = false;
     boolean mWindowVisibility = false;
@@ -173,6 +163,9 @@
     @UnsupportedAppUsage
     int mRequestedFormat = PixelFormat.RGB_565;
 
+    boolean mUseAlpha = false;
+    float mSurfaceAlpha = 1f;
+
     @UnsupportedAppUsage
     boolean mHaveFrame = false;
     boolean mSurfaceCreated = false;
@@ -190,7 +183,6 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     final Rect mSurfaceFrame = new Rect();
     int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
-    private Translator mTranslator;
 
     private boolean mGlobalListenersAdded;
     private boolean mAttachedToWindow;
@@ -200,6 +192,7 @@
     private int mPendingReportDraws;
 
     private SurfaceControl.Transaction mRtTransaction = new SurfaceControl.Transaction();
+    private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
 
     public SurfaceView(Context context) {
         this(context, null);
@@ -288,6 +281,152 @@
         updateSurface();
     }
 
+    /**
+     * Make alpha value of this view reflect onto the surface. This can only be called from at most
+     * one SurfaceView within a view tree.
+     *
+     * <p class="note"><strong>Note:</strong> Alpha value of the view is ignored and the underlying
+     * surface is rendered opaque by default.</p>
+     *
+     * @hide
+     */
+    public void setUseAlpha() {
+        if (!mUseAlpha) {
+            mUseAlpha = true;
+            updateSurfaceAlpha();
+        }
+    }
+
+    @Override
+    public void setAlpha(float alpha) {
+        // Sets the opacity of the view to a value, where 0 means the view is completely transparent
+        // and 1 means the view is completely opaque.
+        //
+        // Note: Alpha value of this view is ignored by default. To enable alpha blending, you need
+        // to call setUseAlpha() as well.
+        // This view doesn't support translucent opacity if the view is located z-below, since the
+        // logic to punch a hole in the view hierarchy cannot handle such case. See also
+        // #clearSurfaceViewPort(Canvas)
+        if (DEBUG) {
+            Log.d(TAG, System.identityHashCode(this)
+                    + " setAlpha: mUseAlpha = " + mUseAlpha + " alpha=" + alpha);
+        }
+        super.setAlpha(alpha);
+        updateSurfaceAlpha();
+    }
+
+    private float getFixedAlpha() {
+        // Compute alpha value to be set on the underlying surface.
+        final float alpha = getAlpha();
+        return mUseAlpha && (mSubLayer > 0 || alpha == 0f) ? alpha : 1f;
+    }
+
+    private void updateSurfaceAlpha() {
+        if (!mUseAlpha) {
+            if (DEBUG) {
+                Log.d(TAG, System.identityHashCode(this)
+                        + " updateSurfaceAlpha: setUseAlpha() is not called, ignored.");
+            }
+            return;
+        }
+        final float viewAlpha = getAlpha();
+        if (mSubLayer < 0 && 0f < viewAlpha && viewAlpha < 1f) {
+            Log.w(TAG, System.identityHashCode(this)
+                    + " updateSurfaceAlpha:"
+                    + " translucent color is not supported for a surface placed z-below.");
+        }
+        if (!mHaveFrame) {
+            if (DEBUG) {
+                Log.d(TAG, System.identityHashCode(this)
+                        + " updateSurfaceAlpha: has no surface.");
+            }
+            return;
+        }
+        final ViewRootImpl viewRoot = getViewRootImpl();
+        if (viewRoot == null) {
+            if (DEBUG) {
+                Log.d(TAG, System.identityHashCode(this)
+                        + " updateSurfaceAlpha: ViewRootImpl not available.");
+            }
+            return;
+        }
+        if (mSurfaceControl == null) {
+            if (DEBUG) {
+                Log.d(TAG, System.identityHashCode(this)
+                        + "updateSurfaceAlpha:"
+                        + " surface is not yet created, or already released.");
+            }
+            return;
+        }
+        final Surface parent = viewRoot.mSurface;
+        if (parent == null || !parent.isValid()) {
+            if (DEBUG) {
+                Log.d(TAG, System.identityHashCode(this)
+                        + " updateSurfaceAlpha: ViewRootImpl has no valid surface");
+            }
+            return;
+        }
+        final float alpha = getFixedAlpha();
+        if (alpha != mSurfaceAlpha) {
+            if (isHardwareAccelerated()) {
+                /*
+                 * Schedule a callback that reflects an alpha value onto the underlying surfaces.
+                 * This gets called on a RenderThread worker thread, so members accessed here must
+                 * be protected by a lock.
+                 */
+                viewRoot.registerRtFrameCallback(frame -> {
+                    try {
+                        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                        synchronized (mSurfaceControlLock) {
+                            if (!parent.isValid()) {
+                                if (DEBUG) {
+                                    Log.d(TAG, System.identityHashCode(this)
+                                            + " updateSurfaceAlpha RT:"
+                                            + " ViewRootImpl has no valid surface");
+                                }
+                                return;
+                            }
+                            if (mSurfaceControl == null) {
+                                if (DEBUG) {
+                                    Log.d(TAG, System.identityHashCode(this)
+                                            + "updateSurfaceAlpha RT:"
+                                            + " mSurfaceControl has already released");
+                                }
+                                return;
+                            }
+                            if (DEBUG) {
+                                Log.d(TAG, System.identityHashCode(this)
+                                        + " updateSurfaceAlpha RT: set alpha=" + alpha);
+                            }
+                            t.setAlpha(mSurfaceControl, alpha);
+                            t.deferTransactionUntilSurface(mSurfaceControl, parent, frame);
+                        }
+                        // It's possible that mSurfaceControl is released in the UI thread before
+                        // the transaction completes. If that happens, an exception is thrown, which
+                        // must be caught immediately.
+                        t.apply();
+                    } catch (Exception e) {
+                        Log.e(TAG, System.identityHashCode(this)
+                                + "updateSurfaceAlpha RT: Exception during surface transaction", e);
+                    }
+                });
+                damageInParent();
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, System.identityHashCode(this)
+                            + " updateSurfaceAlpha: set alpha=" + alpha);
+                }
+                SurfaceControl.openTransaction();
+                try {
+                    mSurfaceControl.setAlpha(alpha);
+                } finally {
+                    SurfaceControl.closeTransaction();
+                }
+            }
+            mSurfaceAlpha = alpha;
+        }
+    }
+
     private void performDrawFinished() {
         if (mPendingReportDraws > 0) {
             mDrawFinished = true;
@@ -337,11 +476,7 @@
         mRequestedVisible = false;
 
         updateSurface();
-        if (mSurfaceControl != null) {
-            mSurfaceControl.remove();
-        }
-        mSurfaceControl = null;
-
+        releaseSurfaces();
         mHaveFrame = false;
 
         super.onDetachedFromWindow();
@@ -510,15 +645,6 @@
         }
     }
 
-    private Rect getParentSurfaceInsets() {
-        final ViewRootImpl root = getViewRootImpl();
-        if (root == null) {
-            return null;
-        } else {
-            return root.mWindowAttributes.surfaceInsets;
-        }
-    }
-
     private void updateBackgroundVisibilityInTransaction(SurfaceControl viewRoot) {
         if (mBackgroundControl == null) {
             return;
@@ -532,29 +658,40 @@
     }
 
     private void releaseSurfaces() {
-        if (mSurfaceControl != null) {
-            mSurfaceControl.remove();
-            mSurfaceControl = null;
+        synchronized (mSurfaceControlLock) {
+            if (mSurfaceControl != null) {
+                mTmpTransaction.remove(mSurfaceControl);
+                mSurfaceControl = null;
+            }
+            if (mBackgroundControl != null) {
+                mTmpTransaction.remove(mBackgroundControl);
+                mBackgroundControl = null;
+            }
+            mTmpTransaction.apply();
         }
-        if (mBackgroundControl != null) {
-            mBackgroundControl.remove();
-            mBackgroundControl = null;
-        }
+        mSurfaceAlpha = 1f;
     }
 
     /** @hide */
     protected void updateSurface() {
         if (!mHaveFrame) {
+            if (DEBUG) {
+                Log.d(TAG, System.identityHashCode(this) + " updateSurface: has no frame");
+            }
             return;
         }
         ViewRootImpl viewRoot = getViewRootImpl();
         if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
+            if (DEBUG) {
+                Log.d(TAG, System.identityHashCode(this)
+                        + " updateSurface: no valid surface");
+            }
             return;
         }
 
-        mTranslator = viewRoot.mTranslator;
-        if (mTranslator != null) {
-            mSurface.setCompatibilityTranslator(mTranslator);
+        final Translator translator = viewRoot.mTranslator;
+        if (translator != null) {
+            mSurface.setCompatibilityTranslator(translator);
         }
 
         int myWidth = mRequestedWidth;
@@ -562,20 +699,25 @@
         int myHeight = mRequestedHeight;
         if (myHeight <= 0) myHeight = getHeight();
 
+        final float alpha = getFixedAlpha();
         final boolean formatChanged = mFormat != mRequestedFormat;
         final boolean visibleChanged = mVisible != mRequestedVisible;
+        final boolean alphaChanged = mSurfaceAlpha != alpha;
         final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
                 && mRequestedVisible;
         final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
         final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
         boolean redrawNeeded = false;
 
-        if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
+        if (creating || formatChanged || sizeChanged || visibleChanged || (mUseAlpha
+                && alphaChanged) || windowVisibleChanged) {
             getLocationInWindow(mLocation);
 
             if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                     + "Changes: creating=" + creating
                     + " format=" + formatChanged + " size=" + sizeChanged
+                    + " visible=" + visibleChanged + " alpha=" + alphaChanged
+                    + " mUseAlpha=" + mUseAlpha
                     + " visible=" + visibleChanged
                     + " left=" + (mWindowSpaceLeft != mLocation[0])
                     + " top=" + (mWindowSpaceTop != mLocation[1]));
@@ -593,11 +735,11 @@
                 mScreenRect.top = mWindowSpaceTop;
                 mScreenRect.right = mWindowSpaceLeft + getWidth();
                 mScreenRect.bottom = mWindowSpaceTop + getHeight();
-                if (mTranslator != null) {
-                    mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+                if (translator != null) {
+                    translator.translateRectInAppWindowToScreen(mScreenRect);
                 }
 
-                final Rect surfaceInsets = getParentSurfaceInsets();
+                final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
                 mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
 
                 if (creating) {
@@ -646,6 +788,10 @@
                             mSurfaceControl.hide();
                         }
                         updateBackgroundVisibilityInTransaction(viewRoot.getSurfaceControl());
+                        if (mUseAlpha) {
+                            mSurfaceControl.setAlpha(alpha);
+                            mSurfaceAlpha = alpha;
+                        }
 
                         // While creating the surface, we will set it's initial
                         // geometry. Outside of that though, we should generally
@@ -680,11 +826,11 @@
 
                     mSurfaceFrame.left = 0;
                     mSurfaceFrame.top = 0;
-                    if (mTranslator == null) {
+                    if (translator == null) {
                         mSurfaceFrame.right = mSurfaceWidth;
                         mSurfaceFrame.bottom = mSurfaceHeight;
                     } else {
-                        float appInvertedScale = mTranslator.applicationInvertedScale;
+                        float appInvertedScale = translator.applicationInvertedScale;
                         mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
                         mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
                     }
@@ -702,7 +848,7 @@
                 try {
                     redrawNeeded |= visible && !mDrawFinished;
 
-                    SurfaceHolder.Callback callbacks[] = null;
+                    SurfaceHolder.Callback[] callbacks = null;
 
                     final boolean surfaceChanged = creating;
                     if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
@@ -788,7 +934,6 @@
                     mIsCreating = false;
                     if (mSurfaceControl != null && !mSurfaceCreated) {
                         mSurface.release();
-
                         releaseSurfaces();
                     }
                 }
@@ -818,8 +963,8 @@
                 mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop,
                         mWindowSpaceLeft + mLocation[0], mWindowSpaceTop + mLocation[1]);
 
-                if (mTranslator != null) {
-                    mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+                if (translator != null) {
+                    translator.translateRectInAppWindowToScreen(mScreenRect);
                 }
 
                 if (mSurfaceControl == null) {
@@ -828,10 +973,13 @@
 
                 if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
                     try {
-                        if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
-                                "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
-                                mScreenRect.left, mScreenRect.top,
-                                mScreenRect.right, mScreenRect.bottom));
+                        if (DEBUG_POSITION) {
+                            Log.d(TAG, String.format("%d updateSurfacePosition UI, "
+                                            + "position = [%d, %d, %d, %d]",
+                                    System.identityHashCode(this),
+                                    mScreenRect.left, mScreenRect.top,
+                                    mScreenRect.right, mScreenRect.bottom));
+                        }
                         setParentSpaceRectangle(mScreenRect, -1);
                     } catch (Exception ex) {
                         Log.e(TAG, "Exception configuring surface", ex);
@@ -848,13 +996,11 @@
         }
 
         if (mDeferredDestroySurfaceControl != null) {
-            mDeferredDestroySurfaceControl.remove();
+            mTmpTransaction.remove(mDeferredDestroySurfaceControl).apply();
             mDeferredDestroySurfaceControl = null;
         }
 
-        runOnUiThread(() -> {
-            performDrawFinished();
-        });
+        runOnUiThread(this::performDrawFinished);
     }
 
     /**
@@ -884,7 +1030,6 @@
         if (mViewVisibility) {
             mRtTransaction.show(surface);
         }
-
     }
 
     private void setParentSpaceRectangle(Rect position, long frameNumber) {
@@ -925,10 +1070,10 @@
                 return;
             }
             try {
-                if (DEBUG) {
+                if (DEBUG_POSITION) {
                     Log.d(TAG, String.format(
                             "%d updateSurfacePosition RenderWorker, frameNr = %d, "
-                                    + "postion = [%d, %d, %d, %d]",
+                                    + "position = [%d, %d, %d, %d]",
                             System.identityHashCode(this), frameNumber,
                             left, top, right, bottom));
                 }
@@ -964,7 +1109,7 @@
     };
 
     private SurfaceHolder.Callback[] getSurfaceCallbacks() {
-        SurfaceHolder.Callback callbacks[];
+        SurfaceHolder.Callback[] callbacks;
         synchronized (mCallbacks) {
             callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
             mCallbacks.toArray(callbacks);
@@ -1034,7 +1179,7 @@
             synchronized (mCallbacks) {
                 // This is a linear search, but in practice we'll
                 // have only a couple callbacks, so it doesn't matter.
-                if (mCallbacks.contains(callback) == false) {
+                if (!mCallbacks.contains(callback)) {
                     mCallbacks.add(callback);
                 }
             }
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 3d3d5dc..f18aa81 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.HardwareRenderer;
@@ -35,6 +36,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 
 /**
  * Threaded renderer that proxies the rendering to a render thread. Most calls
@@ -300,7 +302,8 @@
     private boolean mEnabled;
     private boolean mRequested = true;
 
-    private FrameDrawingCallback mNextRtFrameCallback;
+    @Nullable
+    private ArrayList<FrameDrawingCallback> mNextRtFrameCallbacks;
 
     ThreadedRenderer(Context context, boolean translucent, String name) {
         super();
@@ -441,8 +444,11 @@
      *
      * @param callback The callback to register.
      */
-    void registerRtFrameCallback(FrameDrawingCallback callback) {
-        mNextRtFrameCallback = callback;
+    void registerRtFrameCallback(@NonNull FrameDrawingCallback callback) {
+        if (mNextRtFrameCallbacks == null) {
+            mNextRtFrameCallbacks = new ArrayList<>();
+        }
+        mNextRtFrameCallbacks.add(callback);
     }
 
     /**
@@ -583,10 +589,14 @@
         // Consume and set the frame callback after we dispatch draw to the view above, but before
         // onPostDraw below which may reset the callback for the next frame.  This ensures that
         // updates to the frame callback during scroll handling will also apply in this frame.
-        final FrameDrawingCallback callback = mNextRtFrameCallback;
-        mNextRtFrameCallback = null;
-        if (callback != null) {
-            setFrameCallback(callback);
+        if (mNextRtFrameCallbacks != null) {
+            final ArrayList<FrameDrawingCallback> frameCallbacks = mNextRtFrameCallbacks;
+            mNextRtFrameCallbacks = null;
+            setFrameCallback(frame -> {
+                for (int i = 0; i < frameCallbacks.size(); ++i) {
+                    frameCallbacks.get(i).onFrameDraw(frame);
+                }
+            });
         }
 
         if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 440df89..57a01a3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1129,7 +1129,7 @@
      *
      * @param callback The callback to register.
      */
-    public void registerRtFrameCallback(FrameDrawingCallback callback) {
+    public void registerRtFrameCallback(@NonNull FrameDrawingCallback callback) {
         if (mAttachInfo.mThreadedRenderer != null) {
             mAttachInfo.mThreadedRenderer.registerRtFrameCallback(frame -> {
                 try {
@@ -1637,7 +1637,7 @@
         mSurfaceSession = null;
 
         if (mBoundsSurfaceControl != null) {
-            mBoundsSurfaceControl.remove();
+            mTransaction.remove(mBoundsSurfaceControl).apply();
             mBoundsSurface.release();
             mBoundsSurfaceControl = null;
         }
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index c50a3aa..9a5f4c9 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -1074,7 +1074,7 @@
      * be called manually if you are forcing the drawing on a View or a hierarchy of Views
      * that are not attached to a Window or in the GONE state.
      *
-     * @return True if the current draw should be canceled and resceduled, false otherwise.
+     * @return True if the current draw should be canceled and rescheduled, false otherwise.
      */
     @SuppressWarnings("unchecked")
     public final boolean dispatchOnPreDraw() {
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index ccafa64..d3c6972 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -1035,7 +1035,7 @@
             // Destroy the renderer. This will not proceed until pending frame callbacks complete.
             mRenderer.destroy();
             mSurface.destroy();
-            mSurfaceControl.remove();
+            new SurfaceControl.Transaction().remove(mSurfaceControl).apply();
             mSurfaceSession.kill();
             mHandler.removeCallbacks(mMagnifierUpdater);
             if (mBitmap != null) {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 986771d..030d3be 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -217,12 +217,6 @@
     ctrl->decStrong((void *)nativeCreate);
 }
 
-static void nativeDestroy(JNIEnv* env, jclass clazz, jlong nativeObject) {
-    sp<SurfaceControl> ctrl(reinterpret_cast<SurfaceControl *>(nativeObject));
-    ctrl->destroy();
-    ctrl->decStrong((void *)nativeCreate);
-}
-
 static void nativeDisconnect(JNIEnv* env, jclass clazz, jlong nativeObject) {
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
     if (ctrl != NULL) {
@@ -1268,8 +1262,6 @@
             (void*)nativeWriteToParcel },
     {"nativeRelease", "(J)V",
             (void*)nativeRelease },
-    {"nativeDestroy", "(J)V",
-            (void*)nativeDestroy },
     {"nativeDisconnect", "(J)V",
             (void*)nativeDisconnect },
     {"nativeCreateTransaction", "()J",
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 0070694..ca71314 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -688,6 +688,7 @@
         // Configuration options for smart replies and smart actions in notifications. This is
         // encoded as a key=value list separated by commas.
         optional SettingProto smart_suggestions_in_notifications_flags = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto bubbles = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
     optional Notification notification = 82;
 
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 12f24ce..0816cc0 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Stemboodskappe"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi-oproepe"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM-status"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Ander party het TTY-modus VOL versoek"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Ander party het TTY-modus GOD versoek"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Ander party het TTY-modus SOD versoek"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 017ddbf..9ab7a53 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"የድምጽ መልዕክቶች"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"የWi-Fi ጥሪ"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"የሲም ሁኔታ"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"ቢጤ መልዕክት መጻጻፊያ ስልክ ሁነታ FULL ጠይቋል"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"ቢጤ መልዕክት መጻጻፊያ ስልክ ሁነታ HCO ጠይቋል"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"ቢጤ መልዕክት መጻጻፊያ ስልክ ሁነታ VCO ጠይቋል"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index b60fd3b..375592a 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -99,8 +99,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"رسائل البريد الصوتي"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"‏الاتصال عبر Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"‏حالة شريحة SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"‏طلب النظير وضع TTY الكامل"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"‏طلب النظير وضع TTY على HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"‏طلب النظير وضع TTY على VCO"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index e50f32a..6989219 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"ভইচমেইলৰ বাৰ্তাসমূহ"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"ৱাই-ফাই কলিং"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"ছিমৰ স্থিতি"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"নেটৱৰ্ক পীয়েৰে TTY ম\'ড FULLলৈ সলনি কৰিবলৈ অনুৰোধ কৰিছে"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"নেটৱৰ্ক পীয়েৰে TTY ম\'ড HCOলৈ সলনি কৰিবলৈ অনুৰোধ কৰিছে"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"নেটৱৰ্ক পীয়েৰে TTY ম\'ড VCO লৈ সলনি কৰিবলৈ অনুৰোধ কৰিছে"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 0092487..7a3cc7a 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Səsli e-poçt mesajları"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi zəngi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM status"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Eskpert TTY Rejimi FULL-u sorğuladı"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Ekspert TTY Rejimi HCO-nu sorğuladı"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Ekspert TTY Rejimi VCO-nu sorğuladı"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 4309f57..93d87fe 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -96,8 +96,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Poruke govorne pošte"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Pozivanje preko Wi-Fi mreže"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Status SIM-a"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Korisnik zahteva POTPUN režim TTY"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Korisnik zahteva PRENOS ZVUKA za režim TTY"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Korisnik zahteva PRENOS GLASA za režim TTY"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 947e1c4..6823c44 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -97,8 +97,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Паведамленні галасавой пошты"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi-тэлефанія"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Статус SIM-карты"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Аднарангавая прылада запытала рэжым TTY FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Аднарангавая прылада запытала рэжым TTY НСО"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Аднарангавая прылада запытала рэжым TTY VCO"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 7126fb7..b41d69d 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Съобщения в гласовата поща"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Обаждания през Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Състояние на SIM картата"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Отсрещният потребител заяви пълен TTY режим (FULL)"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Отсрещният потребител заяви TTY режим с пренос на слух (HCO)"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Отсрещният потребител заяви TTY режим с пренос на глас (VCО)"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index b7f4d67..1889519 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"ভয়েসমেল মেসেজ"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"ওয়াই-ফাই কলিং"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"সিম কার্ডের স্টাটাস"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"পির TTY মোড FULL অনুরোধ করেছে"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"পির TTY মোড HCO অনুরোধ করেছে"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"পির TTY মোড VCO অনুরোধ করেছে"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 1f68dcb..cd0a1d5 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -96,7 +96,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Poruke govorne pošte"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Pozivanje putem WiFi-ja"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Status SIM-a"</string>
-    <string name="notification_channel_sim_high_prio" msgid="1787666807724243207">"Status SIM-a visokog prioriteta"</string>
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Ravnopravni uređaj zatražio načina rada TTY FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Ravnopravni uređaj zatražio načina rada TTY HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Ravnopravni uređaj zatražio načina rada TTY VCO"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index e774a90..b13f506 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Missatges de veu"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Trucades per Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Estat de la SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"L\'altre dispositiu ha sol·licitat el mode TTY COMPLET."</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"L\'altre dispositiu ha sol·licitat el mode TTY HCO."</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"L\'altre dispositiu ha sol·licitat el mode TTY VCO."</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index f080aaa3..a9413bd 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -97,8 +97,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Hlasové zprávy"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Volání přes Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Stav SIM karty"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Partner požádal o přechod na režim TTY FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Partner požádal o přechod na režim TTY HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Partner požádal o přechod na režim TTY VCO"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index e56c9f1..76dea80 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Talebeskeder"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi-opkald"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM-status"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Den anden enhed har skiftet til FULD TTY-tilstand"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Den anden enhed har skiftet til TTY-tilstanden HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Den anden enhed har skiftet til TTY-tilstanden VCO"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index aed542b..754b019 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Mailboxnachrichten"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"WLAN-Telefonie"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Status der SIM-Karte"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Peer hat TTY-Modus \"Vollständig\" angefordert."</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Peer hat TTY-Modus \"HCO\" angefordert."</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Peer hat TTY-Modus \"VC\" angefordert."</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 54390e7..6d1306f 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Μηνύματα αυτόματου τηλεφωνητή"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Κλήση Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Κατάσταση SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Αίτημα peer για TTY ΠΛΗΡΗΣ Λειτουργία"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Αίτημα peer για TTY Λειτουργία HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Αίτημα peer για TTY Λειτουργία VCO"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 97809c0..c2a22ad 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Voicemail messages"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi Calling"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM status"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Peer requested TTY Mode FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Peer requested TTY Mode HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Peer requested TTY Mode VCO"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 5177a90..6cf2843 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Voicemail messages"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi Calling"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM status"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Peer requested TTY Mode FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Peer requested TTY Mode HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Peer requested TTY Mode VCO"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 97809c0..c2a22ad 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Voicemail messages"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi Calling"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM status"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Peer requested TTY Mode FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Peer requested TTY Mode HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Peer requested TTY Mode VCO"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 97809c0..c2a22ad 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Voicemail messages"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi Calling"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM status"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Peer requested TTY Mode FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Peer requested TTY Mode HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Peer requested TTY Mode VCO"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 95ebcdc..f62b385 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -95,7 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‏‎Voicemail messages‎‏‎‎‏‎"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‏‏‏‎‏‏‎‎‏‎‏‎‎‏‏‎‏‎‎‎‏‎Wi-Fi calling‎‏‎‎‏‎"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‎‎‏‎‎‎‎‏‏‎‏‎‏‎‏‎‎‎SIM status‎‏‎‎‏‎"</string>
-    <string name="notification_channel_sim_high_prio" msgid="1787666807724243207">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‏‎High priority SIM status‎‏‎‎‏‎"</string>
     <string name="peerTtyModeFull" msgid="6165351790010341421">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‏‎‎‏‏‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‏‎‎‎‎‎‏‎‏‏‎‏‎Peer requested TTY Mode FULL‎‏‎‎‏‎"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‎‎‎‎‎‎‎‏‎‎‎‎‎Peer requested TTY Mode HCO‎‏‎‎‏‎"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‎‏‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎Peer requested TTY Mode VCO‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 9a0a929..5128f67 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Mensajes del buzón de voz"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Llamada con Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Estado de SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"El dispositivo del mismo nivel solicitó el modo TTY FULL."</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"El dispositivo del mismo nivel solicitó el modo TTY HCO."</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"El dispositivo del mismo nivel solicitó el modo TTY VCO."</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index c515f32..608a851 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Mensajes de voz"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Llamada por Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Estado de la tarjeta SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Un dispositivo ha solicitado el modo TTY FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Un dispositivo ha solicitado el modo TTY HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Un dispositivo ha solicitado el modo TTY VCO"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 79e4671..a78178c 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Kõnepostisõnumid"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"WiFi-kõned"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM-kaardi olek"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Partner taotles TTY-režiimi TÄIELIK"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Partner taotles TTY-režiimi HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Partner taotles TTY-režiimi VCO"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 84b063a..fb79545 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Erantzungailuko mezuak"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi bidezko deiak"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIMaren egoera"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Beste gailuak TTY osagarria FULL moduan erabiltzea eskatu du"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Beste gailuak TTY osagarria HCO moduan erabiltzea eskatu du"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Beste gailuak TTY osagarria VCO moduan erabiltzea eskatu du"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index a39e2ab..f796287 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"پیام‌های پست صوتی"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"‏تماس ازطریق Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"وضعیت سیم‌کارت"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"‏دستگاه مرتبط درخواست TTY حالت FULL کرد"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"‏دستگاه مرتبط درخواست TTY حالت HCO کرد"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"‏دستگاه مرتبط درخواست TTY حالت VCO کرد"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 19b1f8d..0b86b22 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Vastaajaviestit"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi-puhelut"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM-kortin tila"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Toinen käyttäjä vaihtoi TTY-tilaksi TÄYSI"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Toinen käyttäjä vaihtoi TTY-tilaksi HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Toinen käyttäjä vaihtoi TTY-tilaksi VCO"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 1977dff..ca9121d 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Messages vocaux"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Appels Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"État de la carte SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Mode TTY COMPLET demandé par un pair"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Mode TTY HCO demandé par un pair"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Mode TTY VCO demandé par un pair"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index c1cac17..ab1a928 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -95,7 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Messages vocaux"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Appels Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"État de la carte SIM"</string>
-    <string name="notification_channel_sim_high_prio" msgid="1787666807724243207">"Notifications prioritaires de la carte SIM"</string>
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Mode TTY demandé par l\'interlocuteur : COMPLET"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Mode TTY demandé par l\'interlocuteur : HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Mode TTY demandé par l\'interlocuteur : VCO"</string>
diff --git a/core/res/res/values-gl-watch/strings.xml b/core/res/res/values-gl-watch/strings.xml
index 5e52823..701bda5 100644
--- a/core/res/res/values-gl-watch/strings.xml
+++ b/core/res/res/values-gl-watch/strings.xml
@@ -21,5 +21,5 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="android_upgrading_apk" msgid="1090732262010398759">"Aplicación <xliff:g id="NUMBER_0">%1$d</xliff:g> de <xliff:g id="NUMBER_1">%2$d</xliff:g>."</string>
-    <string name="permgrouplab_sensors" msgid="202675452368612754">"Sensores"</string>
+    <string name="permgrouplab_sensors" msgid="202675452368612754">"sensores"</string>
 </resources>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 6bdf045..7a1b265 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -95,7 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Mensaxes de correo de voz"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Chamadas por wifi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Estado da SIM"</string>
-    <string name="notification_channel_sim_high_prio" msgid="1787666807724243207">"Estado da SIM con prioridade alta"</string>
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Outro dispositivo solicitou o modo TTY COMPLETO"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Outro dispositivo solicitou o modo TTY HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Outro dispositivo solicitou o modo TTY VCO"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index a60f475..880454a 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"વૉઇસમેઇલ સંદેશા"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"વાઇ-ફાઇ કૉલિંગ"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"સિમનું સ્ટેટસ"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"પીઅરે TTY મોડ પૂર્ણની વિનંતી કરી"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"પીઅરે TTY મોડ HCO ની વિનંતી કરી"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"પીઅરે TTY મોડ VCO ની વિનંતી કરી"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 07d9144..60e0d49 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"वॉइसमेल संदेश"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"वाई-फ़ाई कॉलिंग"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"सिम की स्थिति"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"पीयर ने टेलीटाइपराइटर (TTY) मोड फ़ुल का अनुरोध किया"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"पीयर ने टेलीटाइपराइटर (TTY) मोड एचसीओ (HCO) का अनुरोध किया"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"पीयर ने टेलीटाइपराइटर (TTY) मोड वीसीओ (VCO) का अनुरोध किया"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index aca3b2d..35853d6 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -96,7 +96,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Poruke govorne pošte"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi pozivi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Status SIM-a"</string>
-    <string name="notification_channel_sim_high_prio" msgid="1787666807724243207">"Status SIM-a visokog prioriteta"</string>
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Način TTY FULL koji zahtijeva paralelni uređaj"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Način TTY HCO koji zahtijeva paralelni uređaj"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Način TTY VCO koji zahtijeva paralelni uređaj"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 0872b84..1fbc3fe 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Hangpostaüzenetek"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi-hívás"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM-kártya állapota"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Partner által kért TTY-mód: FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Partner által kért TTY-mód: HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Partner által kért TTY-mód: VCO"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 9a3e167a..3377d3b 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Ձայնային փոստի հաղորդագրություններ"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Զանգեր Wi-Fi-ի միջոցով"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM քարտի կարգավիճակը"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Բաժանորդի սարքում ընտրված է հեռատիպի ԲՈԼՈՐԸ ռեժիմը"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Բաժանորդի սարքում ընտրված է հեռատիպի HCO ռեժիմը"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Բաժանորդի սարքում ընտրված է հեռատիպի VCO ռեժիմը"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 4cdff5e..07671b2 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Notifikasi pesan suara"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Panggilan Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Status SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Rekan meminta Mode TTY PENUH"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Rekan meminta Mode TTY HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Rekan meminta Mode TTY VCO"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 38360fb..c796dbf 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Talhólfsskilaboð"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi símtöl"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Staða SIM-korts"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Jafningi bað um FULLA stillingu fjarrita"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Jafningi bað um HCO-stillingu fjarrita"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Jafningi bað um VCO-stillingu fjarrita"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 502ffc5..45efc5d 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Messaggi vocali"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Chiamate Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Stato SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Peer ha richiesto la modalità TTY FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Peer ha richiesto la modalità TTY HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Peer ha richiesto la modalità TTY VCO"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index d631c2e5..d792b5c 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -97,8 +97,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"הודעות קוליות"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"‏שיחות Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"‏סטטוס SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"‏העמית ביקש TTY במצב FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"‏העמית ביקש TTY במצב HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"‏העמית ביקש TTY במצב VCO"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 9ffee72..b3ab20a 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"ボイスメール メッセージ"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi 通話"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM のステータス"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"ピアから、TTY モードを FULL にするようリクエストされました"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"ピアから、TTYモードをHCOにするようリクエストされました"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"ピアから、TTYモードをVCOにするようリクエストされました"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index ae2779e..9404f49f 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"ხმოვანი ფოსტის შეტყობინებები"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"დარეკვა Wi-Fi-ს მეშვეობით"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM სტატუსი"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"მოთხოვნილია კვანძი TTY რეჟიმი FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"მოთხოვნილია კვანძი TTY რეჟიმი HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"მოთხოვნილია კვანძი TTY რეჟიმი VCO"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index d15f07f..c3ea2a9 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Дауыстық пошта хабарлары"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi қоңыраулары"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM күйі"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Пир TTY режимі ТОЛЫҚ сұрады"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Пир TTY режимінің HCO сұрады"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Пир TTY режимінің VCO сұрады"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 4d95df8..28c0970 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"សារ​ជា​សំឡេង"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"ការហៅ​ទូរសព្ទ​តាម Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"ស្ថានភាព​ស៊ីម"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"ម៉ាស៊ីនកូនបានស្នើ TTY Mode FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"ម៉ាស៊ីនកូនបានស្នើ TTY Mode HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"ម៉ាស៊ីនកូនបានស្នើ TTY Mode VCO"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 0a7f6a0..0331f86 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"ಧ್ವನಿಮೇಲ್ ಸಂದೇಶಗಳು"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"ವೈ-ಫೈ ಕರೆ ಮಾಡುವಿಕೆ"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"ಸಿಮ್‌ ಸ್ಥಿತಿ"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"ಪೀರ್ ವಿನಂತಿಸಿಕೊಂಡ TTY ಮೋಡ್ ಪೂರ್ಣಗೊಂಡಿದೆ"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"ಪೀರ್ ವಿನಂತಿಸಿಕೊಂಡ TTY ಮೋಡ್ HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"ಪೀರ್ ವಿನಂತಿಸಿಕೊಂಡ TTY ಮೋಡ್ VCO"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 7809db5..c97f740 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"음성사서함 메시지"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi 통화"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM 상태"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"피어가 TTY 모드 FULL을 요청했습니다."</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"피어가 TTY 모드 HCO를 요청했습니다."</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"피어가 TTY 모드 VCO를 요청했습니다."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 5f5e87b5..e86ceb1 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Үн почтасынын билдирүүлөрү"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi аркылуу чалуу"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM-картанын абалы"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Peer TTY режимин FULL кылууну суранды"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Peer TTY режимин HCO кылууну суранды"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Peer TTY режимин VCO кылууну суранды"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 0388053..17b0154 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"ຂໍ້ຄວາມສຽງ"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"ການ​ໂທ Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"ສະຖານະ SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"ໂໝດ TTY ທີ່​ເພື່ອນ​ຂໍ​ນັ້ນ​ເຕັມ​ແລ້ວ"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"ໂໝດ TTY ທີ່​ເພື່ອນ​ຂໍ HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"ໂໝດ TTY ທີ່​ເພື່ອນ​ຂໍ VCO"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 51509fb..caf8e4f 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -97,8 +97,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Balso pašto pranešimai"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"„Wi-Fi“ skambinimas"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM būsena"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Lygiavertis naudotojas pateikė užklausą dėl TTY režimo FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Lygiavertis naudotojas pateikė užklausą dėl TTY režimo HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Lygiavertis naudotojas pateikė užklausą dėl TTY režimo VCO"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index a33b783..d71234c 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -96,8 +96,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Balss pasta ziņojumi"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi zvani"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM kartes statuss"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Vienādranga ierīce pieprasīja teksta tālruņa režīmu FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Vienādranga ierīce pieprasīja teksta tālruņa režīmu HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Vienādranga ierīce pieprasīja teksta tālruņa režīmu VCO"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 231469b..d89fa9c 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Пораки од говорна пошта"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Повикување преку Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Статус на SIM-картичка"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Рамноправен уред го побара режимот на TTY „FULL“"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Рамноправен уред го побара режимот на TTY „HCO“"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Рамноправен уред го побара режимот на TTY „VCO“"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 0026ddb..819d3f7 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"വോയ്‌സ്‌മെയിൽ സന്ദേശങ്ങൾ"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"വൈഫൈ കോളിംഗ്"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"സിം നില"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"പിയർ അഭ്യർത്ഥിച്ച TTY മോഡ് \'ഫുൾ\'"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"പിയർ അഭ്യർത്ഥിച്ച TTY മോഡ് HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"പിയർ അഭ്യർത്ഥിച്ച TTY മോഡ് VCO"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 62c4c57..dd5dc11 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Дуут шуудангийн мессеж"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi дуудлага"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM статус"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Хандлагын цэгт хүсэлт тавьсан TTY Mode FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Хандлагын цэгт хүсэлт тавьсан TTY Mode HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Хандлагын цэгт хүсэлт тавьсан TTY Mode VCO"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 084b950..b39ac84 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"व्हॉइसमेल मेसेज"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"वाय-फाय कॉलिंग"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"सिम स्थिती"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"समवयस्क व्यक्तीने TTY मोड पूर्ण ची विनंती केली"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"समवयस्क व्यक्तीने TTY मोड HCO ची विनंती केली"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"समवयस्क व्यक्तीने TTY मोड VCO ची विनंती केली"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 09b2650..b5f0ef5 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Mesej mel suara"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Panggilan Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Status SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Rakan meminta Mod TTY PENUH"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Rakan meminta Mod TTY HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Rakan meminta Mod TTY VCO"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 90da77f..1387725 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"အသံမေးလ် မက်ဆေ့ဂျ်များ"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi ခေါ်ဆိုမှု"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"ဆင်းမ်ကဒ် အခြေအနေ"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"အခြားစက်မှ TTY မုဒ် FULL ပြုရန် တောင်းဆို၏"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"အခြားစက်မှ TTY မုဒ် HCO ပြုရန် တောင်းဆို၏"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"TTY မုဒ် VCO ပြုရန် အခြားစက်မှ တောင်းဆို၏"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 91c9373..3a3a999 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Talepostmeldinger"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi-anrop"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM-status"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Motpart ba om TTY-modus FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Motpart ba om TTY-modus HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Motpart ba om TTY-modus VCO"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index e554145..7b0fbfc 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"भ्वाइस मेल सन्देशहरू"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi कल"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM को स्थिति"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"सहकर्मी अनुरोध गरियो। TTY मोड पूर्ण"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"सहकर्मी अनुरोध गरियो। TTY मोड HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"सहकर्मी अनुरोध गरियो। TTY मोड VCO"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index a8ac5cf..cadbbfe 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Voicemailberichten"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Bellen via wifi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Simkaartstatus"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Door peer aangevraagde TTY-modus VOL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Door peer aangevraagde TTY-modus HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Door peer aangevraagde TTY-modus VCO"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index e2d3176..053b1cd 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"ଭଏସମେଲ୍‍ ମେସେଜ୍‍"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"ୱାଇ-ଫାଇ କଲିଙ୍ଗ"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM ଷ୍ଟାଟସ୍"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"ପୀଆର୍‌ ଅନୁରୋଧ କରିଥିବା TTY ମୋଡ୍‍ FULL ଅଟେ"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"ପୀଅର୍‌ ଅନୁରୋଧ କରିଥିବା TTY ମୋଡ୍‍ HCO ଅଟେ"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"ପୀଅର୍‌ ଅନୁରୋଧ କରିଥିବା TTY ମୋଡ୍‍ VCO ଅଟେ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 370db7d7..7262ddd 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"ਵੌਇਸਮੇਲ ਸੁਨੇਹੇ"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"ਵਾਈ-ਫਾਈ ਕਾਲਿੰਗ"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"ਸਿਮ ਅਵਸਥਾ"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"ਪੀਅਰ ਨੇ TTY Mode FULL ਦੀ ਬੇਨਤੀ ਕੀਤੀ"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"ਪੀਅਰ ਨੇ TTY Mode HCO ਦੀ ਬੇਨਤੀ ਕੀਤੀ"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"ਪੀਅਰ ਨੇ TTY Mode VCO ਦੀ ਬੇਨਤੀ ਕੀਤੀ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 2f9e7fc..4937ced 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -97,8 +97,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Wiadomości poczty głosowej"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Połączenia przez Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Stan karty SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Drugie urządzenie zażądało trybu „TTY pełny”"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Drugie urządzenie zażądało trybu „TTY HCO”"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Drugie urządzenie zażądało trybu „TTY VCO”"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index e507c74..a506839 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Mensagens do correio de voz"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Chamadas por Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Status do chip"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"TTD modo COMPLETO solicitado"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"TTD modo HCO solicitado"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"TTD modo VCO solicitado"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index b10d998..26f92c9 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Mensagens de correio de voz"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Chamadas Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Estado do SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"O par solicitou o modo COMPLETO de teletipo"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"O par solicitou o modo HCO de teletipo"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"O par solicitou o modo VCO de teletipo"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index e507c74..a506839 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Mensagens do correio de voz"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Chamadas por Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Status do chip"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"TTD modo COMPLETO solicitado"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"TTD modo HCO solicitado"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"TTD modo VCO solicitado"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 921a691..b29367f 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -96,8 +96,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Mesaje din mesageria vocală"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Apelare prin Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Starea cardului SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Cealaltă persoană a solicitat modul TTY cu setarea COMPLET"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Cealaltă persoană a solicitat modul TTY cu setarea HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Cealaltă persoană a solicitat modul TTY cu setarea VCO"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index d1d5c97..b363b33 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -97,8 +97,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Голосовые сообщения"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Звонки по Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Статус SIM-карты"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"На устройстве абонента выбран режим телетайпа \"ВСЕ\""</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"На устройстве абонента выбран режим телетайпа HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"На устройстве абонента выбран режим телетайпа VCO"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 174b2ce..18a3158 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"හඬ තැපැල් පණිවිඩ"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi ඇමතීම"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM තත්ත්වය"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"සම ඉල්ලීම් කළ TTY ප්‍රකාරය පූර්ණයි"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"සම ඉල්ලීම් කළ TTY ප්‍රකාරය HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"සම ඉල්ලීම් කළ TTY ප්‍රකාරය VCO"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 07683a4..50b591a 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -97,8 +97,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Správy hlasovej schránky"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Volanie cez Wi‑Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Stav SIM karty"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Používateľ, s ktorým komunikujete, požiadal o režim FULL textového telefónu"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Používateľ, s ktorým komunikujete, požiadal o režim HCO textového telefónu"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Používateľ, s ktorým komunikujete, požiadal o režim VCO textového telefónu"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index bf5e88b..af26aae 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -97,7 +97,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Sporočila v odzivniku"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Klicanje prek Wi-Fi-ja"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Stanje kartice SIM"</string>
-    <string name="notification_channel_sim_high_prio" msgid="1787666807724243207">"Stanje kartice SIM z visoko stopnjo prednosti"</string>
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Enakovredna naprava je zahtevala način TTY FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Enakovredna naprava je zahtevala način TTY HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Enakovredna naprava je zahtevala način TTY VCO"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index c463490..fc4fd4f 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Mesazhet e postës zanore"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Telefonata me Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Statusi i kartës SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Homologu yt kërkoi modalitet \"TTY\" të plotë"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Homologu kërkoi modalitet \"TTY\" të llojit \"HCO\""</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Homologu yt kërkoi modalitet \"TTY\" të llojit \"VCO\""</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index f43ba55..a8dc15e 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -96,8 +96,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Поруке говорне поште"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Позивање преко Wi-Fi мреже"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Статус SIM-а"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Корисник захтева ПОТПУН режим TTY"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Корисник захтева ПРЕНОС ЗВУКА за режим TTY"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Корисник захтева ПРЕНОС ГЛАСА за режим TTY"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index c1d0f2b..c728bcc 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Röstmeddelanden"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi-samtal"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Status för SIM-kort"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Peer-enheten begärde texttelefonläget FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Peer-enheten begärde texttelefonläget HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Peer-enheten begärde texttelefonläget VCO"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 3f5413b..9f18c45 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Ujumbe wa sauti"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Kupiga simu kupitia Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Hali ya SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Hali ya TTY iliyoombwa na mtandao mwenza KAMILI"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Hali ya TTY iliyoombwa na mtandao mwenza HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Hali ya TTY iliyoombwa na mtandao mwenza VCO"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index f654c07..cac9afd 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"குரலஞ்சல் செய்திகள்"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"வைஃபை அழைப்பு"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"சிம் நிலை"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"TTY Mode FULLஐ இணைச் செயல்பாடு கோரியது"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"TTY Mode HCOஐ இணைச் செயல்பாடு கோரியது"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"TTY Mode VCOஐ இணைச் செயல்பாடு கோரியது"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 574e3cd..6a1f34c 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"వాయిస్ మెయిల్ సందేశాలు"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi కాలింగ్"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM స్థితి"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"అవతలి వారు FULL TTY మోడ్‌ని అభ్యర్థించారు"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"అవతలి వారు HCO TTY మోడ్‌ని అభ్యర్థించారు"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"అవతలి వారు VCO TTY మోడ్‌ని అభ్యర్థించారు"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index bfda686..3132529b 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"ข้อความเสียง"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"การโทรผ่าน Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"สถานะซิม"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"อีกฝั่งหนึ่งขอโหมด TTY เป็น \"เต็ม\""</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"อีกฝั่งหนึ่งขอโหมด TTY เป็น \"HCO\""</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"อีกฝั่งหนึ่งขอโหมด TTY เป็น \"VCO\""</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 285794b..a56a122 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Mga mensahe sa voicemail"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Pagtawag gamit ang Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Status ng SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Hiniling ng peer ang TTY Mode FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Hiniling ng peer ang TTY Mode HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Hiniling ng peer ang TTY Mode VCO"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index f4b8797..34b6e5a 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Sesli mesajlar"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Kablosuz çağrı"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM durumu"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Karşı taraf TTY Modunu TAM yaptı"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Karşı taraf TTY Modunu HCO yaptı"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Karşı taraf TTY Modunu VCO yaptı"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 9f3d5ac..44058f5 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -97,8 +97,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Повідомлення голосової пошти"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Дзвінки через Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Статус SIM-карти"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Пристрій змінив режим TTY на FULL"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Пристрій змінив режим TTY на HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Пристрій змінив режим TTY на VCO"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 70072d5..7766c76 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"صوتی میل پیغامات"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"‏Wi-Fi کالنگ"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"‏SIM کا اسٹیٹس"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"‏ہمسر نے TTY وضع مکمل کی درخواست کی"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"‏ہمسر نے TTY وضع HCO کی درخواست کی"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"‏ہمسر نے TTY وضع VCO کی درخواست کی"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 5cb3892..ac2c837 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Ovozli xabarlar"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi chaqiruv"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM karta holati"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Teng huquqli ishtirokchi teletayp rejimini FULL (to‘liq) qilib o‘zgartirdi"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Teng huquqli ishtirokchi teletayp rejimini HCO (eshitadi, gapirolmaydi) qilib o‘zgartirdi"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Teng huquqli ishtirokchi teletayp rejimini VCO (gapiradi, eshitolmaydi) qilib o‘zgartirdi"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 35f33f0..429861b 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Thư thoại"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Gọi qua Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Trạng thái SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"TTY theo yêu cầu của thiết bị ngang hàng ở chế độ ĐẦY ĐỦ"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"TTY theo yêu cầu của thiết bị ngang hàng ở chế độ HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"TTY theo yêu cầu của thiết bị ngang hàng ở chế độ VCO"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index da38b22..56b8137 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"语音邮件"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"WLAN 通话"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM 卡状态"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"对方请求使用“TTY 完整”模式"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"对方请求使用“TTY HCO”模式"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"对方请求使用“TTY VCO”模式"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 376263b..cce909a4 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"留言訊息"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi 通話"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM 卡狀態"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"對方曾要求 TTY 完整模式"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"對方曾要求 TTY 模式 (HCO)"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"對方曾要求 TTY 模式 (VCO)"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index bef8efb..da7bc7f 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"語音留言"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Wi-Fi 通話"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"SIM 卡狀態"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"通訊對象要求使用 TTY 的 FULL 模式"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"通訊對象要求使用 TTY 的 HCO 模式"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"通訊對象要求使用 TTY 的 VCO 模式"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index ca7da30..2af3320 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -95,8 +95,6 @@
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Imilayezo yevoyisimeyili"</string>
     <string name="notification_channel_wfc" msgid="2130802501654254801">"Ukushaya kwe-Wi-Fi"</string>
     <string name="notification_channel_sim" msgid="4052095493875188564">"Isimo se-SIM"</string>
-    <!-- no translation found for notification_channel_sim_high_prio (1787666807724243207) -->
-    <skip />
     <string name="peerTtyModeFull" msgid="6165351790010341421">"Umngani ucele imodi ye-TTY ephelele"</string>
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Umngani ucele imodi ye-TTY HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Umngani ucele imodi ye-TTY VCO"</string>
diff --git a/packages/BackupRestoreConfirmation/res/values-eu/strings.xml b/packages/BackupRestoreConfirmation/res/values-eu/strings.xml
index 3905a0b..ca89aa6 100644
--- a/packages/BackupRestoreConfirmation/res/values-eu/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-eu/strings.xml
@@ -18,10 +18,10 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="backup_confirm_title" msgid="827563724209303345">"Babeskopia osoa"</string>
     <string name="restore_confirm_title" msgid="5469365809567486602">"Leheneratze osoa"</string>
-    <string name="backup_confirm_text" msgid="1878021282758896593">"Datu guztien babeskopia egitea eta konektatutako ordenagailu batean gordetzea eskatu da. Horretarako baimena eman nahi duzu?\n\nEz baduzu babeskopia egitea zuk eskatu, ez eman eragiketarekin jarraitzeko baimena."</string>
+    <string name="backup_confirm_text" msgid="1878021282758896593">"Datu guztien babeskopia egitea eta konektatutako ordenagailu batean gordetzea eskatu da. Horretarako baimena eman nahi duzu?\n\nEz baduzu babeskopia egitea zeuk eskatu, ez eman eragiketarekin jarraitzeko baimena."</string>
     <string name="allow_backup_button_label" msgid="4217228747769644068">"Egin datuen babeskopia"</string>
     <string name="deny_backup_button_label" msgid="6009119115581097708">"Ez egin babeskopia"</string>
-    <string name="restore_confirm_text" msgid="7499866728030461776">"Konektatutako ordenagailu bateko datu guztiak leheneratzeko eskatu da. Horretarako baimena eman nahi duzu?\n\nEz baduzu leheneratzea zuk eskatu, ez eman eragiketarekin jarraitzeko baimena. Eragiketa gauzatzen bada, gailuan dituzun datu guztiak ordeztuko dira!"</string>
+    <string name="restore_confirm_text" msgid="7499866728030461776">"Konektatutako ordenagailu bateko datu guztiak leheneratzeko eskatu da. Horretarako baimena eman nahi duzu?\n\nEz baduzu leheneratzea zeuk eskatu, ez eman eragiketarekin jarraitzeko baimena. Eragiketa gauzatzen bada, gailuan dituzun datu guztiak ordeztuko dira!"</string>
     <string name="allow_restore_button_label" msgid="3081286752277127827">"Leheneratu datuak"</string>
     <string name="deny_restore_button_label" msgid="1724367334453104378">"Ez leheneratu"</string>
     <string name="current_password_text" msgid="8268189555578298067">"Idatzi babeskopien uneko pasahitza behean:"</string>
diff --git a/packages/BackupRestoreConfirmation/res/values-fa/strings.xml b/packages/BackupRestoreConfirmation/res/values-fa/strings.xml
index a8c9d9e..d8155a6 100644
--- a/packages/BackupRestoreConfirmation/res/values-fa/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-fa/strings.xml
@@ -18,7 +18,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="backup_confirm_title" msgid="827563724209303345">"پشتیبان‌گیری کامل"</string>
     <string name="restore_confirm_title" msgid="5469365809567486602">"بازیابی کامل"</string>
-    <string name="backup_confirm_text" msgid="1878021282758896593">"درخواست پشتیبان گیری کامل از تمام داده‌ها به یک رایانه دسک‌تاپ متصل داده شده است. آیا می‌خواهید این کار انجام شود؟\n\nاگر شما درخواست تهیهٔ نسخهٔ پشتیبان را نداده‌اید، اجازه‌ ادامه عملیات را ندهید."</string>
+    <string name="backup_confirm_text" msgid="1878021282758896593">"درخواست پشتیبان گیری کامل از تمام داده‌ها به یک رایانه دسک‌تاپ متصل داده شده است. آیا می‌خواهید این عمل انجام شود؟\n\nاگر شما درخواست تهیهٔ نسخهٔ پشتیبان را نداده‌اید، اجازه‌ ادامه عملیات را ندهید."</string>
     <string name="allow_backup_button_label" msgid="4217228747769644068">"پشتیبان‌گیری از داده‌های من"</string>
     <string name="deny_backup_button_label" msgid="6009119115581097708">"نسخهٔ پشتیبان تهیه نشود"</string>
     <string name="restore_confirm_text" msgid="7499866728030461776">"بازیابی کامل تمام داده‌ها از یک رایانه دسک تاپ متصل درخواست شده است. آیا می‌خواهید این اجازه را بدهید؟\n\nاگر خود شما درخواست بازیابی نداده‌اید، اجازه ادامه این عملیات را ندهید. با این کار همه داده‌هایی که اکنون روی دستگاه است جایگزین می‌شود!"</string>
@@ -31,9 +31,9 @@
     <string name="backup_enc_password_optional" msgid="1350137345907579306">"اگر می‌خواهید تمام نسخه پشتیبانی داده را رمزدار کنید، یک گذرواژه در زیر وارد کنید:"</string>
     <string name="backup_enc_password_required" msgid="7889652203371654149">"چون دستگاهتان رمز‌گذاری شده است، باید نسخه پشتیبان خودتان را رمزگذاری کنید. لطفاً گذرواژه‌ای را در زیر وارد کنید:"</string>
     <string name="restore_enc_password_text" msgid="6140898525580710823">"اگر داده بازیابی شده رمزگذاری شده است، لطفاً گذرواژه را در زیر وارد کنید:"</string>
-    <string name="toast_backup_started" msgid="550354281452756121">"شروع پشتیبان‌گیری…"</string>
-    <string name="toast_backup_ended" msgid="3818080769548726424">"پشتیبان‌گیری تمام شد"</string>
-    <string name="toast_restore_started" msgid="7881679218971277385">"شروع بازیابی…"</string>
+    <string name="toast_backup_started" msgid="550354281452756121">"شروع پشتیبان‌گیری..."</string>
+    <string name="toast_backup_ended" msgid="3818080769548726424">"پشتیبان‌گیری پایان یافت"</string>
+    <string name="toast_restore_started" msgid="7881679218971277385">"شروع بازیابی..."</string>
     <string name="toast_restore_ended" msgid="1764041639199696132">"بازیابی پایان یافت"</string>
     <string name="toast_timeout" msgid="5276598587087626877">"مهلت عملیات تمام شد"</string>
 </resources>
diff --git a/packages/BackupRestoreConfirmation/res/values-hi/strings.xml b/packages/BackupRestoreConfirmation/res/values-hi/strings.xml
index 2e53cb6..ae9e7c3 100644
--- a/packages/BackupRestoreConfirmation/res/values-hi/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-hi/strings.xml
@@ -16,8 +16,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="backup_confirm_title" msgid="827563724209303345">"पूरा बैकअप"</string>
-    <string name="restore_confirm_title" msgid="5469365809567486602">"पूरी तरह से पुनर्स्‍थापना"</string>
+    <string name="backup_confirm_title" msgid="827563724209303345">"पूर्ण सुरक्षा"</string>
+    <string name="restore_confirm_title" msgid="5469365809567486602">"पूर्ण पुनर्स्‍थापना"</string>
     <string name="backup_confirm_text" msgid="1878021282758896593">"कनेक्‍ट कि‍ए गए डेस्‍कटॉप कंप्‍यूटर से पूरे डेटा के बैकअप का अनुरोध कि‍या गया है. क्‍या आप इसकी अनुमति‍ देना चाहते हैं?\n\nअगर आपने बैकअप का अनुरोध नहीं कि‍या है, तो इस प्रक्रि‍या को जारी रखने की अनुमति‍ न दें."</string>
     <string name="allow_backup_button_label" msgid="4217228747769644068">"मेरे डेटा का बैकअप लें"</string>
     <string name="deny_backup_button_label" msgid="6009119115581097708">"बैकअप न लें"</string>
@@ -32,7 +32,7 @@
     <string name="backup_enc_password_required" msgid="7889652203371654149">"चूंकि आपका डिवाइस एन्क्रिप्ट किया हुआ है, इसलिए आपको अपने बैकअप को एन्क्रिप्ट करना आवश्यक है. कृपया नीचे पासवर्ड डालें:"</string>
     <string name="restore_enc_password_text" msgid="6140898525580710823">"अगर रिस्टोर किया गया डेटा सुरक्षित कि‍या गया है, तो कृपया नीचे पासवर्ड डालें:"</string>
     <string name="toast_backup_started" msgid="550354281452756121">"सुरक्षित करना शुरु हो रहा है..."</string>
-    <string name="toast_backup_ended" msgid="3818080769548726424">"बैकअप करना पूरा हुआ"</string>
+    <string name="toast_backup_ended" msgid="3818080769548726424">"सुरक्षित करना पूर्ण"</string>
     <string name="toast_restore_started" msgid="7881679218971277385">"पुनर्स्‍थापना प्रारंभ हो रही है..."</string>
     <string name="toast_restore_ended" msgid="1764041639199696132">"पुनर्स्‍थापना खत्म"</string>
     <string name="toast_timeout" msgid="5276598587087626877">"काम नहीं हो सका. टाइम आउट हो गया"</string>
diff --git a/packages/CarrierDefaultApp/res/values-in/strings.xml b/packages/CarrierDefaultApp/res/values-in/strings.xml
index f48d31f..01a9c60 100644
--- a/packages/CarrierDefaultApp/res/values-in/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-in/strings.xml
@@ -5,7 +5,7 @@
     <string name="android_system_label" msgid="2797790869522345065">"Operator Seluler"</string>
     <string name="portal_notification_id" msgid="5155057562457079297">"Data seluler telah habis"</string>
     <string name="no_data_notification_id" msgid="668400731803969521">"Data seluler telah dinonaktifkan"</string>
-    <string name="portal_notification_detail" msgid="2295729385924660881">"Ketuk untuk membuka situs web %s"</string>
+    <string name="portal_notification_detail" msgid="2295729385924660881">"Tap untuk membuka situs web %s"</string>
     <string name="no_data_notification_detail" msgid="3112125343857014825">"Hubungi penyedia layanan %s"</string>
     <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Tidak ada sambungan data seluler"</string>
     <string name="no_mobile_data_connection" msgid="544980465184147010">"Tambahkan paket data atau roaming melalui %s"</string>
diff --git a/packages/ExternalStorageProvider/res/values-pt-rPT/strings.xml b/packages/ExternalStorageProvider/res/values-pt-rPT/strings.xml
index 35de8ef..96046ad 100644
--- a/packages/ExternalStorageProvider/res/values-pt-rPT/strings.xml
+++ b/packages/ExternalStorageProvider/res/values-pt-rPT/strings.xml
@@ -19,5 +19,5 @@
     <string name="app_label" msgid="7123375275748530234">"Armazenamento externo"</string>
     <string name="storage_description" msgid="8541974407321172792">"Armazenamento local"</string>
     <string name="root_internal_storage" msgid="827844243068584127">"Armazenamento interno"</string>
-    <string name="root_documents" msgid="4051252304075469250">"Docs"</string>
+    <string name="root_documents" msgid="4051252304075469250">"Documentos"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-hy/strings.xml b/packages/InputDevices/res/values-hy/strings.xml
index 64ddf7a..c7523e3 100644
--- a/packages/InputDevices/res/values-hy/strings.xml
+++ b/packages/InputDevices/res/values-hy/strings.xml
@@ -40,7 +40,7 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Եբրայերեն"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Լիտվերեն"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Իսպաներեն (Լատինական)"</string>
-    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"լատվիերեն"</string>
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"լատիշերեն"</string>
     <string name="keyboard_layout_persian" msgid="3920643161015888527">"պարսկերեն"</string>
     <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"ադրբեջաներեն"</string>
     <string name="keyboard_layout_polish" msgid="1121588624094925325">"լեհերեն"</string>
diff --git a/packages/PackageInstaller/res/values-bs/strings.xml b/packages/PackageInstaller/res/values-bs/strings.xml
index ebf0685..329e940 100644
--- a/packages/PackageInstaller/res/values-bs/strings.xml
+++ b/packages/PackageInstaller/res/values-bs/strings.xml
@@ -40,7 +40,7 @@
     <string name="unknown_apps_admin_dlg_text" msgid="4456572224020176095">"Vaš administrator ne dozvoljava instaliranje aplikacija iz nepoznatih izvora"</string>
     <string name="unknown_apps_user_restriction_dlg_text" msgid="151020786933988344">"Ovaj korisnik ne može instalirati nepoznate aplikacije"</string>
     <string name="install_apps_user_restriction_dlg_text" msgid="2154119597001074022">"Ovom korisniku nije dozvoljeno instaliranje aplikacija"</string>
-    <string name="ok" msgid="7871959885003339302">"Uredu"</string>
+    <string name="ok" msgid="7871959885003339302">"UREDU"</string>
     <string name="manage_applications" msgid="5400164782453975580">"Uprav. aplik."</string>
     <string name="out_of_space_dlg_title" msgid="4156690013884649502">"Nedostatak prostora"</string>
     <string name="out_of_space_dlg_text" msgid="8727714096031856231">"Ne možete instalirati aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>. Oslobodite prostor u pohrani i pokušajte ponovo."</string>
diff --git a/packages/PackageInstaller/res/values-eu/strings.xml b/packages/PackageInstaller/res/values-eu/strings.xml
index dbcb2bb..9c23097 100644
--- a/packages/PackageInstaller/res/values-eu/strings.xml
+++ b/packages/PackageInstaller/res/values-eu/strings.xml
@@ -83,9 +83,9 @@
     <string name="untrusted_external_source_warning" product="tablet" msgid="6539403649459942547">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak tableta honetan."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="1206648674551321364">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak telebista honetan."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="7279739265754475165">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak telefono honetan."</string>
-    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonoak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartu egingo duzu zu zarela hura erabiltzeagatik telefonoak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
-    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tabletak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartu egingo duzu zu zarela hura erabiltzeagatik tabletak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
-    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Telebistak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartu egingo duzu zu zarela hura erabiltzeagatik telebistak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
+    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonoak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartu egingo duzu zeu zarela hura erabiltzeagatik telefonoak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
+    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tabletak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartu egingo duzu zeu zarela hura erabiltzeagatik tabletak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
+    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Telebistak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartu egingo duzu zeu zarela hura erabiltzeagatik telebistak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
     <string name="anonymous_source_continue" msgid="4375745439457209366">"Egin aurrera"</string>
     <string name="external_sources_settings" msgid="4046964413071713807">"Ezarpenak"</string>
     <string name="wear_app_channel" msgid="1960809674709107850">"Wear aplikazioak instalatzea/desinstalatzea"</string>
diff --git a/packages/PackageInstaller/res/values-mr/strings.xml b/packages/PackageInstaller/res/values-mr/strings.xml
index 70ab75f..200c993 100644
--- a/packages/PackageInstaller/res/values-mr/strings.xml
+++ b/packages/PackageInstaller/res/values-mr/strings.xml
@@ -37,7 +37,7 @@
     <string name="install_failed_msg" product="tv" msgid="1920009940048975221">"<xliff:g id="APP_NAME">%1$s</xliff:g> तुमच्या टीव्हीवर इंस्टॉल केले जाऊ शकत नाही."</string>
     <string name="install_failed_msg" product="default" msgid="6484461562647915707">"<xliff:g id="APP_NAME">%1$s</xliff:g> तुमच्या फोनवर इंस्टॉल केले जाऊ शकत नाही."</string>
     <string name="launch" msgid="3952550563999890101">"उघडा"</string>
-    <string name="unknown_apps_admin_dlg_text" msgid="4456572224020176095">"अज्ञात स्रोतांकडून मिळवलेल्या अ‍ॅप्स इंस्टॉलेशनला तुमचा प्रशासक अनुमती देत नाही"</string>
+    <string name="unknown_apps_admin_dlg_text" msgid="4456572224020176095">"अज्ञात स्रोतांकडून मिळवलेल्या अॅप्स इंस्टॉलेशनला तुमचा प्रशासक अनुमती देत नाही"</string>
     <string name="unknown_apps_user_restriction_dlg_text" msgid="151020786933988344">"या वापरकर्त्याद्वारे अज्ञात अ‍ॅप्स इंस्टॉल केली जाऊ शकत नाहीत"</string>
     <string name="install_apps_user_restriction_dlg_text" msgid="2154119597001074022">"या वापरकर्त्याला अ‍ॅप्स इंस्टॉल करण्याची अनुमती नाही"</string>
     <string name="ok" msgid="7871959885003339302">"ओके"</string>
@@ -67,8 +67,8 @@
     <string name="uninstall_done_app" msgid="4588850984473605768">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> अनइंस्टॉल केले"</string>
     <string name="uninstall_failed" msgid="1847750968168364332">"अनइंस्टॉल करता आले नाही."</string>
     <string name="uninstall_failed_app" msgid="5506028705017601412">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> अनइंस्टॉल करता आले नाही."</string>
-    <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"अ‍ॅक्टिव्ह डिव्हाइस प्रशासक अ‍ॅप अनइंस्टॉल करू शकत नाही"</string>
-    <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"<xliff:g id="USERNAME">%1$s</xliff:g> साठी अ‍ॅक्टिव्ह डिव्हाइस प्रशासक अ‍ॅप अनइंस्टॉल करू शकत नाही"</string>
+    <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"अॅक्टिव्ह डिव्हाइस प्रशासक अ‍ॅप अनइंस्टॉल करू शकत नाही"</string>
+    <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"<xliff:g id="USERNAME">%1$s</xliff:g> साठी अॅक्टिव्ह डिव्हाइस प्रशासक अ‍ॅप अनइंस्टॉल करू शकत नाही"</string>
     <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"हे अ‍ॅप काही वापरकर्ते किंवा प्रोफाइलसाठी आवश्यक आहे आणि इतरांसाठी अनइंस्टॉल करण्यात आले"</string>
     <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"तुमच्या प्रोफाइलसाठी हे अ‍ॅप आवश्यक आहे आणि अनइंस्टॉल केले जाऊ शकत नाही."</string>
     <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"तुमच्या डिव्हाइस प्रशासकास हे अ‍ॅप आवश्यक आहे आणि ते अनइंस्टॉल केले जाऊ शकत नाही."</string>
diff --git a/packages/PackageInstaller/res/values-th/strings.xml b/packages/PackageInstaller/res/values-th/strings.xml
index 14c9d5f..1f6e10a 100644
--- a/packages/PackageInstaller/res/values-th/strings.xml
+++ b/packages/PackageInstaller/res/values-th/strings.xml
@@ -72,7 +72,7 @@
     <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"แอปนี้จำเป็นสำหรับผู้ใช้หรือโปรไฟล์บางส่วน และถอนการติดตั้งไปแล้วสำหรับส่วนอื่น"</string>
     <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"แอปนี้จำเป็นสำหรับโปรไฟล์ของคุณและถอนการติดตั้งไม่ได้"</string>
     <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"ผู้ดูแลระบบอุปกรณ์กำหนดให้ใช้แอปนี้และถอนการติดตั้งไม่ได้"</string>
-    <string name="manage_device_administrators" msgid="3092696419363842816">"จัดการแอปดูแลอุปกรณ์"</string>
+    <string name="manage_device_administrators" msgid="3092696419363842816">"จัดการแอปผู้ดูแลระบบอุปกรณ์"</string>
     <string name="manage_users" msgid="1243995386982560813">"จัดการผู้ใช้"</string>
     <string name="uninstall_failed_msg" msgid="2176744834786696012">"ถอนการติดตั้ง <xliff:g id="APP_NAME">%1$s</xliff:g> ไม่ได้"</string>
     <string name="Parse_error_dlg_text" msgid="1661404001063076789">"พบปัญหาในการแยกวิเคราะห์แพ็กเกจ"</string>
diff --git a/packages/PrintSpooler/res/values-fr/strings.xml b/packages/PrintSpooler/res/values-fr/strings.xml
index f6e901d..3c2aec3 100644
--- a/packages/PrintSpooler/res/values-fr/strings.xml
+++ b/packages/PrintSpooler/res/values-fr/strings.xml
@@ -65,7 +65,7 @@
     <string name="notification_channel_failure" msgid="9042250774797916414">"Tâches d\'impression non abouties"</string>
     <string name="could_not_create_file" msgid="3425025039427448443">"Impossible de créer le fichier"</string>
     <string name="print_services_disabled_toast" msgid="9089060734685174685">"Certains services d\'impression sont désactivés."</string>
-    <string name="print_searching_for_printers" msgid="6550424555079932867">"Recherche d\'imprimantes..."</string>
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Recherche d\'imprimantes en cours"</string>
     <string name="print_no_print_services" msgid="8561247706423327966">"Aucun service d\'impression activé"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Aucune imprimante trouvée"</string>
     <string name="cannot_add_printer" msgid="7840348733668023106">"Impossible d\'ajouter des imprimantes"</string>
diff --git a/packages/PrintSpooler/res/values-hi/strings.xml b/packages/PrintSpooler/res/values-hi/strings.xml
index 2637c3c..6f98070 100644
--- a/packages/PrintSpooler/res/values-hi/strings.xml
+++ b/packages/PrintSpooler/res/values-hi/strings.xml
@@ -34,7 +34,7 @@
     <string name="print_preview" msgid="8010217796057763343">"प्रिंट की झलक"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"झलक देखने के लिए PDF व्यूअर इंस्टॉल करें"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"प्रिंटिंग ऐप बंद हो गया"</string>
-    <string name="generating_print_job" msgid="3119608742651698916">"प्रिंट जॉ जनरेट हो रहा है"</string>
+    <string name="generating_print_job" msgid="3119608742651698916">"प्रिंट कार्य जनरेट हो रहा है"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"पीडीएफ़ के तौर पर सेव करें"</string>
     <string name="all_printers" msgid="5018829726861876202">"सभी प्रिंटर..."</string>
     <string name="print_dialog" msgid="32628687461331979">"प्रिंट डॉयलॉग"</string>
@@ -61,8 +61,8 @@
     </plurals>
     <string name="printer_extended_description_template" msgid="1366699227703381874">"<xliff:g id="PRINT_SERVICE_LABEL">%1$s</xliff:g> - <xliff:g id="PRINTER_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="printer_info_desc" msgid="7181988788991581654">"इस प्रिंटर के बारे में ज़्यादा जानकारी"</string>
-    <string name="notification_channel_progress" msgid="872788690775721436">"चल रहे प्रिंट जॉ"</string>
-    <string name="notification_channel_failure" msgid="9042250774797916414">"असफल रहे प्रिंट जॉ"</string>
+    <string name="notification_channel_progress" msgid="872788690775721436">"चल रहे प्रिंट कार्य"</string>
+    <string name="notification_channel_failure" msgid="9042250774797916414">"असफल रहे प्रिंट कार्य"</string>
     <string name="could_not_create_file" msgid="3425025039427448443">"फ़ाइल नहीं बनाई जा सकी"</string>
     <string name="print_services_disabled_toast" msgid="9089060734685174685">"कुछ प्रिंट सेवाएं अक्षम हैं"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"प्रिंटर सर्च कर रहा है"</string>
diff --git a/packages/PrintSpooler/res/values-my/strings.xml b/packages/PrintSpooler/res/values-my/strings.xml
index fdcdd7c..34297b6 100644
--- a/packages/PrintSpooler/res/values-my/strings.xml
+++ b/packages/PrintSpooler/res/values-my/strings.xml
@@ -86,7 +86,7 @@
     <string name="cancel" msgid="4373674107267141885">"မလုပ်တော့"</string>
     <string name="restart" msgid="2472034227037808749">"ပြန်စရန်"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"စာထုတ်စက်နဲ့ ဆက်သွယ်ထားမှု မရှိပါ"</string>
-    <string name="reason_unknown" msgid="5507940196503246139">"မသိ"</string>
+    <string name="reason_unknown" msgid="5507940196503246139">"မသိပါ"</string>
     <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g>ကိုသုံးမလား။"</string>
     <string name="print_service_security_warning_summary" msgid="1427434625361692006">"သင်၏ စာရွက်စာတမ်းများသည် ပရင်တာထံသို့ သွားစဉ် ဆာဗာ တစ်ခု သို့မဟုတ် ပိုများပြီး ဖြတ်ကျော်နိုင်ရသည်။"</string>
   <string-array name="color_mode_labels">
@@ -99,7 +99,7 @@
     <item msgid="79513688117503758">"အနားသတ် အတို"</item>
   </string-array>
   <string-array name="orientation_labels">
-    <item msgid="4061931020926489228">"ဒေါင်လိုက်"</item>
+    <item msgid="4061931020926489228">"ထောင်လိုက်"</item>
     <item msgid="3199660090246166812">"အလျားလိုက်"</item>
   </string-array>
     <string name="print_write_error_message" msgid="5787642615179572543">"ဖိုင်သို့ မရေးနိုင်ခဲ့"</string>
diff --git a/packages/PrintSpooler/res/values-or/strings.xml b/packages/PrintSpooler/res/values-or/strings.xml
index f385391..7eeac87 100644
--- a/packages/PrintSpooler/res/values-or/strings.xml
+++ b/packages/PrintSpooler/res/values-or/strings.xml
@@ -72,7 +72,7 @@
     <string name="select_to_add_printers" msgid="3800709038689830974">"ପ୍ରିଣ୍ଟର ଯୋଡ଼ିବାକୁ ଚୟନ କରନ୍ତୁ"</string>
     <string name="enable_print_service" msgid="3482815747043533842">"ସକ୍ଷମ କରିବା ପାଇଁ ଚୟନ କରନ୍ତୁ"</string>
     <string name="enabled_services_title" msgid="7036986099096582296">"ସକ୍ଷମ କରାଯାଇଥିବା ସର୍ଭିସ୍‌"</string>
-    <string name="recommended_services_title" msgid="3799434882937956924">"ସୁପାରିଶ କରାଯାଇଥିବା ସର୍ଭିସ୍‌"</string>
+    <string name="recommended_services_title" msgid="3799434882937956924">"ସୁପାରିସ କରାଯାଇଥିବା ସର୍ଭିସ୍‌"</string>
     <string name="disabled_services_title" msgid="7313253167968363211">"ଅକ୍ଷମ କରାଯାଇଥିବା ସର୍ଭିସ୍‌"</string>
     <string name="all_services_title" msgid="5578662754874906455">"ସମସ୍ତ ସର୍ଭିସ୍‌"</string>
     <plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138">
diff --git a/packages/PrintSpooler/res/values-pl/strings.xml b/packages/PrintSpooler/res/values-pl/strings.xml
index 0b310f0..a960fe2 100644
--- a/packages/PrintSpooler/res/values-pl/strings.xml
+++ b/packages/PrintSpooler/res/values-pl/strings.xml
@@ -67,7 +67,7 @@
     <string name="notification_channel_failure" msgid="9042250774797916414">"Nieudane zadania drukowania"</string>
     <string name="could_not_create_file" msgid="3425025039427448443">"Nie udało się utworzyć pliku"</string>
     <string name="print_services_disabled_toast" msgid="9089060734685174685">"Niektóre usługi drukowania są wyłączone"</string>
-    <string name="print_searching_for_printers" msgid="6550424555079932867">"Szukam drukarek"</string>
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Szukanie drukarek"</string>
     <string name="print_no_print_services" msgid="8561247706423327966">"Brak włączonych usług drukowania"</string>
     <string name="print_no_printers" msgid="4869403323900054866">"Nie znaleziono drukarek"</string>
     <string name="cannot_add_printer" msgid="7840348733668023106">"Nie można dodawać drukarek"</string>
diff --git a/packages/PrintSpooler/res/values-ta/strings.xml b/packages/PrintSpooler/res/values-ta/strings.xml
index 4bb167a..7773718 100644
--- a/packages/PrintSpooler/res/values-ta/strings.xml
+++ b/packages/PrintSpooler/res/values-ta/strings.xml
@@ -87,7 +87,7 @@
     <string name="restart" msgid="2472034227037808749">"மீண்டும் தொடங்கு"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"அச்சுப்பொறியுடன் இணைக்கப்படவில்லை"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"அறியப்படாதது"</string>
-    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> ஐப் பயன்படுத்தவா?"</string>
+    <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g>ஐப் பயன்படுத்தவா?"</string>
     <string name="print_service_security_warning_summary" msgid="1427434625361692006">"உங்கள் ஆவணம் பிரிண்டருக்குச் செல்லும் வழியில் ஒன்று அல்லது அதற்கு மேற்பட்ட சேவையகங்களைக் கடந்து செல்லக்கூடும்."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"கருப்பு &amp; வெள்ளை"</item>
diff --git a/packages/SettingsLib/res/values-hi/arrays.xml b/packages/SettingsLib/res/values-hi/arrays.xml
index 3d9a78e..5ad9b01 100644
--- a/packages/SettingsLib/res/values-hi/arrays.xml
+++ b/packages/SettingsLib/res/values-hi/arrays.xml
@@ -76,7 +76,7 @@
     <item msgid="3422726142222090896">"avrcp16"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
-    <item msgid="7065842274271279580">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+    <item msgid="7065842274271279580">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
     <item msgid="7539690996561263909">"SBC"</item>
     <item msgid="686685526567131661">"AAC"</item>
     <item msgid="5254942598247222737">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडियो"</item>
@@ -86,7 +86,7 @@
     <item msgid="3304843301758635896">"वैकल्पिक कोडेक अक्षम करें"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_summaries">
-    <item msgid="5062108632402595000">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+    <item msgid="5062108632402595000">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
     <item msgid="6898329690939802290">"SBC"</item>
     <item msgid="6839647709301342559">"AAC"</item>
     <item msgid="7848030269621918608">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडियो"</item>
@@ -96,38 +96,38 @@
     <item msgid="741805482892725657">"वैकल्पिक कोडेक अक्षम करें"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
-    <item msgid="3093023430402746802">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+    <item msgid="3093023430402746802">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
     <item msgid="8895532488906185219">"44.1 kHz"</item>
     <item msgid="2909915718994807056">"48.0 kHz"</item>
     <item msgid="3347287377354164611">"88.2 kHz"</item>
     <item msgid="1234212100239985373">"96.0 kHz"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
-    <item msgid="3214516120190965356">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+    <item msgid="3214516120190965356">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
     <item msgid="4482862757811638365">"44.1 kHz"</item>
     <item msgid="354495328188724404">"48.0 kHz"</item>
     <item msgid="7329816882213695083">"88.2 kHz"</item>
     <item msgid="6967397666254430476">"96.0 kHz"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
-    <item msgid="2684127272582591429">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+    <item msgid="2684127272582591429">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
     <item msgid="5618929009984956469">"16 बिट/नमूना"</item>
     <item msgid="3412640499234627248">"24 बिट/नमूना"</item>
     <item msgid="121583001492929387">"32 बिट/नमूना"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
-    <item msgid="1081159789834584363">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+    <item msgid="1081159789834584363">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
     <item msgid="4726688794884191540">"16 बिट/नमूना"</item>
     <item msgid="305344756485516870">"24 बिट/नमूना"</item>
     <item msgid="244568657919675099">"32 बिट/नमूना"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
-    <item msgid="5226878858503393706">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+    <item msgid="5226878858503393706">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
     <item msgid="4106832974775067314">"मोनो"</item>
     <item msgid="5571632958424639155">"स्टीरियो"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
-    <item msgid="4118561796005528173">"सिस्टम चुनाव का उपयोग करें (डिफ़ॉल्ट)"</item>
+    <item msgid="4118561796005528173">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
     <item msgid="8900559293912978337">"मोनो"</item>
     <item msgid="8883739882299884241">"स्टीरियो"</item>
   </string-array>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 3a20d04..5d512a8 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -46,7 +46,7 @@
     <string name="wifi_limited_connection" msgid="7717855024753201527">"सीमित कनेक्शन"</string>
     <string name="wifi_status_no_internet" msgid="5784710974669608361">"इंटरनेट कनेक्शन नहीं है"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"साइन इन करना ज़रूरी है"</string>
-    <string name="wifi_ap_unable_to_handle_new_sta" msgid="5348824313514404541">"ऐक्सेस पॉइंट फ़िलहाल भरा हुआ है"</string>
+    <string name="wifi_ap_unable_to_handle_new_sta" msgid="5348824313514404541">"एक्सेस पॉइंट फ़िलहाल भरा हुआ है"</string>
     <string name="connected_via_carrier" msgid="7583780074526041912">"%1$s के ज़रिए कनेक्ट"</string>
     <string name="available_via_carrier" msgid="1469036129740799053">"%1$s के ज़रिए उपलब्ध"</string>
     <string name="osu_opening_provider" msgid="5488997661548640424">"<xliff:g id="PASSPOINTPROVIDER">%1$s</xliff:g> खोला जा रहा है"</string>
@@ -68,7 +68,7 @@
     <string name="bluetooth_pairing" msgid="1426882272690346242">"युग्‍मित कर रहा है…"</string>
     <string name="bluetooth_connected_no_headset" msgid="616068069034994802">"जुड़ गया (फ़ोन के ऑडियो को छोड़कर)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
     <string name="bluetooth_connected_no_a2dp" msgid="3736431800395923868">"जुड़ गया (मीडिया ऑडियो को छोड़कर)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
-    <string name="bluetooth_connected_no_map" msgid="3200033913678466453">"जुड़ गया (मैसेज का ऐक्सेस नहीं)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
+    <string name="bluetooth_connected_no_map" msgid="3200033913678466453">"जुड़ गया (मैसेज का एक्सेस नहीं)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
     <string name="bluetooth_connected_no_headset_no_a2dp" msgid="2047403011284187056">"जुड़ गया (फ़ोन या मीडिया ऑडियो को छोड़कर)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
     <string name="bluetooth_connected_battery_level" msgid="5162924691231307748">"जुड़ गया, बैटरी का लेवल <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string>
     <string name="bluetooth_connected_no_headset_battery_level" msgid="1610296229139400266">"जुड़ गया (फ़ोन के ऑडियो को छोड़कर), बैटरी का लेवल <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string>
@@ -88,7 +88,7 @@
     <string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"संपर्क साझाकरण के लिए उपयोग करें"</string>
     <string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"इंटरनेट कनेक्शन साझाकरण"</string>
     <string name="bluetooth_profile_map" msgid="1019763341565580450">"लेख संदेश"</string>
-    <string name="bluetooth_profile_sap" msgid="5764222021851283125">"सिम ऐक्सेस"</string>
+    <string name="bluetooth_profile_sap" msgid="5764222021851283125">"सिम एक्सेस"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="5444517801472820055">"HD ऑडियो: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="8510588052415438887">"HD ऑडियो"</string>
     <string name="bluetooth_profile_hearing_aid" msgid="6680721080542444257">"सुनने में मदद करने वाले डिवाइस"</string>
@@ -200,7 +200,7 @@
     <string name="development_settings_not_available" msgid="4308569041701535607">"यह उपयोगकर्ता, डेवलपर के लिए सेटिंग और टूल का इस्तेमाल नहीं कर सकता"</string>
     <string name="vpn_settings_not_available" msgid="956841430176985598">"VPN सेटिंग इस उपयोगकर्ता के लिए उपलब्ध नहीं हैं"</string>
     <string name="tethering_settings_not_available" msgid="6765770438438291012">"टेदरिंग सेटिंग इस उपयोगकर्ता के लिए उपलब्ध नहीं हैं"</string>
-    <string name="apn_settings_not_available" msgid="7873729032165324000">"ऐक्सेस पॉइंट के नाम की सेटिंग इस उपयोगकर्ता के लिए मौजूद नहीं हैं"</string>
+    <string name="apn_settings_not_available" msgid="7873729032165324000">"एक्सेस पॉइंट के नाम की सेटिंग इस उपयोगकर्ता के लिए मौजूद नहीं हैं"</string>
     <string name="enable_adb" msgid="7982306934419797485">"USB डीबग करना"</string>
     <string name="enable_adb_summary" msgid="4881186971746056635">"डीबग मोड जब USB कनेक्‍ट किया गया हो"</string>
     <string name="clear_adb_keys" msgid="4038889221503122743">"USB डीबग करने की मंज़ूरी रद्द करें"</string>
@@ -361,7 +361,7 @@
     <string name="runningservices_settings_summary" msgid="854608995821032748">"इस समय चल रही सेवाओं को देखें और नियंत्रित करें"</string>
     <string name="select_webview_provider_title" msgid="4628592979751918907">"वेबव्यू लागू करें"</string>
     <string name="select_webview_provider_dialog_title" msgid="4370551378720004872">"वेबव्यू सेट करें"</string>
-    <string name="select_webview_provider_toast_text" msgid="5466970498308266359">"यह चुनाव अब मान्य नहीं है. दोबारा कोशिश करें."</string>
+    <string name="select_webview_provider_toast_text" msgid="5466970498308266359">"यह चयन अब मान्य नहीं है. पुनः प्रयास करें."</string>
     <string name="convert_to_file_encryption" msgid="3060156730651061223">"फ़ाइल आधारित सुरक्षित करने के तरीके में बदलें"</string>
     <string name="convert_to_file_encryption_enabled" msgid="2861258671151428346">"रूपांतरित करें..."</string>
     <string name="convert_to_file_encryption_done" msgid="7859766358000523953">"फ़ाइल पहले से एन्क्रिप्ट की हुई है"</string>
@@ -414,7 +414,7 @@
     <string name="disabled" msgid="9206776641295849915">"बंद किया गया"</string>
     <string name="external_source_trusted" msgid="2707996266575928037">"अनुमति है"</string>
     <string name="external_source_untrusted" msgid="2677442511837596726">"अनुमति नहीं है"</string>
-    <string name="install_other_apps" msgid="6986686991775883017">"अनजान ऐप्लिकेशन इंस्टॉल करने का ऐक्सेस"</string>
+    <string name="install_other_apps" msgid="6986686991775883017">"अनजान ऐप्लिकेशन इंस्टॉल करने का एक्सेस"</string>
     <string name="home" msgid="3256884684164448244">"सेटिंग का होम पेज"</string>
   <string-array name="battery_labels">
     <item msgid="8494684293649631252">"0%"</item>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index f1934c3..fd45cb0 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -248,7 +248,7 @@
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"ताररहित प्रदर्शन प्रमाणीकरणका लागि विकल्पहरू देखाउनुहोस्"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fi लग स्तर बढाउनुहोस्, Wi-Fi चयनकर्तामा प्रति SSID RSSI देखाइन्छ"</string>
     <string name="wifi_scan_throttling_summary" msgid="4461922728822495763">"ब्याट्रीको खपत कम गरी नेटवर्कको कार्यसम्पादनमा सुधार गर्दछ"</string>
-    <string name="wifi_metered_label" msgid="4514924227256839725">"सशुल्क वाइफाइ"</string>
+    <string name="wifi_metered_label" msgid="4514924227256839725">"मिटर गरिएको जडान भनी चिन्ह लगाइएको"</string>
     <string name="wifi_unmetered_label" msgid="6124098729457992931">"मिटर नगरिएको"</string>
     <string name="select_logd_size_title" msgid="7433137108348553508">"लगर बफर आकारहरू"</string>
     <string name="select_logd_size_dialog_title" msgid="1206769310236476760">"लग बफर प्रति लगर आकार चयन गर्नुहोस्"</string>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index b2c10ec..f8c9bcc 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1057,6 +1057,9 @@
                 Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
                 GlobalSettingsProto.Notification.SNOOZE_OPTIONS);
         dumpSetting(s, p,
+                Settings.Global.NOTIFICATION_BUBBLES,
+                GlobalSettingsProto.Notification.BUBBLES);
+        dumpSetting(s, p,
                 Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
                 GlobalSettingsProto.Notification.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS);
         dumpSetting(s, p,
@@ -2150,7 +2153,7 @@
                 Settings.Secure.NOTIFICATION_BADGING,
                 SecureSettingsProto.Notification.BADGING);
         dumpSetting(s, p,
-                Settings.Secure.NOTIFICATION_BUBBLES,
+                Settings.Global.NOTIFICATION_BUBBLES,
                 SecureSettingsProto.Notification.BUBBLES);
         dumpSetting(s, p,
                 Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 5cd29b7..fbfbe7f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3238,7 +3238,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 182;
+            private static final int SETTINGS_VERSION = 183;
 
             private final int mUserId;
 
@@ -4255,19 +4255,7 @@
 
                 if (currentVersion == 173) {
                     // Version 173: Set the default value for Secure Settings: NOTIFICATION_BUBBLES
-
-                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
-
-                    final Setting bubblesSetting = secureSettings.getSettingLocked(
-                            Secure.NOTIFICATION_BUBBLES);
-
-                    if (bubblesSetting.isNull()) {
-                        secureSettings.insertSettingLocked(Secure.NOTIFICATION_BUBBLES,
-                                getContext().getResources().getBoolean(
-                                        R.bool.def_notification_bubbles) ? "1" : "0", null,
-                                true, SettingsState.SYSTEM_PACKAGE_NAME);
-                    }
-
+                    // Removed. Moved NOTIFICATION_BUBBLES to Global Settings.
                     currentVersion = 174;
                 }
 
@@ -4391,14 +4379,9 @@
                 if (currentVersion == 179) {
                     // Version 178: Reset the default for Secure Settings: NOTIFICATION_BUBBLES
                     // This is originally set in version 173, however, the default value changed
-                    // so this step is to ensure the value is updated to the correct defaulte
-                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
+                    // so this step is to ensure the value is updated to the correct default.
 
-                    secureSettings.insertSettingLocked(Secure.NOTIFICATION_BUBBLES,
-                            getContext().getResources().getBoolean(
-                                    R.bool.def_notification_bubbles) ? "1" : "0", null,
-                                    true, SettingsState.SYSTEM_PACKAGE_NAME);
-
+                    // Removed. Moved NOTIFICATION_BUBBLES to Global Settings.
                     currentVersion = 180;
                 }
 
@@ -4452,6 +4435,20 @@
                     currentVersion = 182;
                 }
 
+                if (currentVersion == 182) {
+                    // Remove secure bubble settings.
+                    getSecureSettingsLocked(userId).deleteSettingLocked(
+                            Secure.NOTIFICATION_BUBBLES);
+
+                    // Add global bubble settings.
+                    getGlobalSettingsLocked().insertSettingLocked(Global.NOTIFICATION_BUBBLES,
+                            getContext().getResources().getBoolean(
+                                    R.bool.def_notification_bubbles) ? "1" : "0", null /* tag */,
+                            true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
+
+                    currentVersion = 183;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/Shell/res/values-in/strings.xml b/packages/Shell/res/values-in/strings.xml
index 5c5ba816..cf2ebe5 100644
--- a/packages/Shell/res/values-in/strings.xml
+++ b/packages/Shell/res/values-in/strings.xml
@@ -24,10 +24,10 @@
     <string name="bugreport_updating_wait" msgid="3322151947853929470">"Harap tunggu..."</string>
     <string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"Laporan bug akan segera muncul di ponsel"</string>
     <string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"Pilih untuk membagikan laporan bug Anda"</string>
-    <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"Ketuk untuk membagikan laporan bug"</string>
+    <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"Tap untuk membagikan laporan bug"</string>
     <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"Pilih untuk membagikan laporan bug tanpa screenshot atau menunggu screenshot selesai"</string>
-    <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Ketuk untuk membagikan laporan bug tanpa screenshot atau menunggu screenshot selesai"</string>
-    <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Ketuk untuk membagikan laporan bug tanpa screenshot atau menunggu screenshot selesai"</string>
+    <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Tap untuk membagikan laporan bug tanpa screenshot atau menunggu screenshot selesai"</string>
+    <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Tap untuk membagikan laporan bug tanpa screenshot atau menunggu screenshot selesai"</string>
     <string name="bugreport_confirm" msgid="5917407234515812495">"Laporan bug berisi data dari berbagai file log sistem, yang mungkin mencakup data yang dianggap sensitif (seperti data penggunaan aplikasi dan lokasi). Hanya bagikan laporan bug dengan aplikasi dan orang yang Anda percaya."</string>
     <string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Jangan tampilkan lagi"</string>
     <string name="bugreport_storage_title" msgid="5332488144740527109">"Laporan bug"</string>
diff --git a/packages/Shell/res/values-ne/strings.xml b/packages/Shell/res/values-ne/strings.xml
index 05ff412..ae0a92f 100644
--- a/packages/Shell/res/values-ne/strings.xml
+++ b/packages/Shell/res/values-ne/strings.xml
@@ -28,7 +28,7 @@
     <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"तपाईंको बग रिपोर्ट स्क्रिनसट बिना आदान प्रदान गर्नाका लागि चयन गर्नुहोस् वा स्क्रिनसट लिने प्रक्रिया पूरा हुने प्रतीक्षा गर्नुहोस्"</string>
     <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"तपाईँको बग रिपोर्टलाई स्क्रिनसट बिना साझेदारी गर्नाका लागि ट्याप गर्नुहोस् वा स्क्रिनसट लिने प्रक्रिया पूरा हुन प्रतीक्षा गर्नुहोस्"</string>
     <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"तपाईँको बग रिपोर्टलाई स्क्रिनसट बिना साझेदारी गर्नाका लागि ट्याप गर्नुहोस् वा स्क्रिनसट लिने प्रक्रिया पूरा हुन प्रतीक्षा गर्नुहोस्"</string>
-    <string name="bugreport_confirm" msgid="5917407234515812495">"बग रिपोर्टहरूमा प्रणालीका विभिन्न लग फाइलहरूको डेटा हुन्छ जसमा तपाईँले संवेदनशील मानेको डेटा समावेश हुन सक्छ (जस्तै अनुप्रयोगको प्रयोग र स्थानसम्बन्धी डेटा)। तपाईँले विश्वास गर्ने व्यक्ति र अनुप्रयोगहरूसँग मात्र बग रिपोर्टहरूलाई साझेदारी गर्नुहोस्।"</string>
+    <string name="bugreport_confirm" msgid="5917407234515812495">"बग रिपोर्टहरूमा प्रणालीका विभिन्न लग फाइलहरूको डेटा हुन्छ जसमा तपाईँले संवेदनशील मानेको डेटा समावेश हुन सक्छ (जस्तै अनुप्रयोगको प्रयोग र स्थान सम्बन्धी डेटा)। तपाईँले विश्वास गर्ने व्यक्ति र अनुप्रयोगहरूसँग मात्र बग रिपोर्टहरूलाई साझेदारी गर्नुहोस्।"</string>
     <string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"फेरि नदेखाउनुहोस्"</string>
     <string name="bugreport_storage_title" msgid="5332488144740527109">"बग रिपोर्टहरू"</string>
     <string name="bugreport_unreadable_text" msgid="586517851044535486">"बग रिपोर्ट फाइल पढ्न सकिएन"</string>
diff --git a/packages/Shell/res/values-pt-rBR/strings.xml b/packages/Shell/res/values-pt-rBR/strings.xml
index d0373b1..71483c8 100644
--- a/packages/Shell/res/values-pt-rBR/strings.xml
+++ b/packages/Shell/res/values-pt-rBR/strings.xml
@@ -19,7 +19,7 @@
     <string name="app_label" msgid="3701846017049540910">"Shell"</string>
     <string name="bugreport_notification_channel" msgid="2574150205913861141">"Relatórios de bugs"</string>
     <string name="bugreport_in_progress_title" msgid="4311705936714972757">"O relatório do bug <xliff:g id="ID">#%d</xliff:g> está sendo gerado"</string>
-    <string name="bugreport_finished_title" msgid="4429132808670114081">"Relatório de bug <xliff:g id="ID">#%d</xliff:g> capturado"</string>
+    <string name="bugreport_finished_title" msgid="4429132808670114081">"Relatório do bug <xliff:g id="ID">#%d</xliff:g> capturado"</string>
     <string name="bugreport_updating_title" msgid="4423539949559634214">"Adicionando detalhes ao relatório do bug"</string>
     <string name="bugreport_updating_wait" msgid="3322151947853929470">"Aguarde…"</string>
     <string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"O relatório de bugs será exibido no smartphone em breve"</string>
diff --git a/packages/Shell/res/values-pt/strings.xml b/packages/Shell/res/values-pt/strings.xml
index d0373b1..71483c8 100644
--- a/packages/Shell/res/values-pt/strings.xml
+++ b/packages/Shell/res/values-pt/strings.xml
@@ -19,7 +19,7 @@
     <string name="app_label" msgid="3701846017049540910">"Shell"</string>
     <string name="bugreport_notification_channel" msgid="2574150205913861141">"Relatórios de bugs"</string>
     <string name="bugreport_in_progress_title" msgid="4311705936714972757">"O relatório do bug <xliff:g id="ID">#%d</xliff:g> está sendo gerado"</string>
-    <string name="bugreport_finished_title" msgid="4429132808670114081">"Relatório de bug <xliff:g id="ID">#%d</xliff:g> capturado"</string>
+    <string name="bugreport_finished_title" msgid="4429132808670114081">"Relatório do bug <xliff:g id="ID">#%d</xliff:g> capturado"</string>
     <string name="bugreport_updating_title" msgid="4423539949559634214">"Adicionando detalhes ao relatório do bug"</string>
     <string name="bugreport_updating_wait" msgid="3322151947853929470">"Aguarde…"</string>
     <string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"O relatório de bugs será exibido no smartphone em breve"</string>
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 4c52b132..3e5d720 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -114,6 +114,7 @@
         "androidx.lifecycle_lifecycle-extensions",
         "androidx.dynamicanimation_dynamicanimation",
         "androidx-constraintlayout_constraintlayout",
+        "iconloader_base",
         "SystemUI-tags",
         "SystemUI-proto",
         "metrics-helper-lib",
diff --git a/packages/SystemUI/docs/physics-animation-layout.md b/packages/SystemUI/docs/physics-animation-layout.md
index 488c465..de2ee9e 100644
--- a/packages/SystemUI/docs/physics-animation-layout.md
+++ b/packages/SystemUI/docs/physics-animation-layout.md
@@ -1,7 +1,11 @@
 # Physics Animation Layout
 
 ## Overview
-**PhysicsAnimationLayout** works with an implementation of **PhysicsAnimationController** to construct and maintain physics animations for each of its child views. During the initial construction of the animations, the layout queries the controller for configuration settings such as which properties to animate, which animations to chain together, and what stiffness or bounciness to use. Once the animations are built to the controller’s specifications, the controller can then ask the layout to start, stop and manipulate them arbitrarily to achieve any desired animation effect. The controller is notified whenever children are added or removed from the layout, so that it can animate their entrance or exit, respectively.
+**PhysicsAnimationLayout** works with implementations of **PhysicsAnimationController** to configure and run physics-based animations for each of its child views. During the initial construction of the animations, the layout queries the controller for basic configuration settings such as which properties to animate, which animations to chain together, and the default physics parameters to use.
+
+Once the animations are built, the controller can access **PhysicsPropertyAnimator** instances to run them. The animator behaves similarly to the familiar `ViewPropertyAnimator`, with the ability to animate `alpha`, `translation`, and `scale` values. It also supports additional functionality such as `followAnimatedTargetAlongPath` for more advanced motion.
+
+The controller is notified whenever children are added or removed from the layout, so that it can animate their entrance or exit, respectively.
 
 An example usage is Bubbles, which uses a PhysicsAnimationLayout for its stack of bubbles. Bubbles has controller subclasses including StackAnimationController and ExpansionAnimationController. StackAnimationController tells the layout to configure the translation animations to be chained (for the ‘following’ drag effect), and has methods such as ```moveStack(x, y)``` to animate the stack to a given point. ExpansionAnimationController asks for no animations to be chained, and exposes methods like ```expandStack()``` and ```collapseStack()```, which animate the bubbles to positions along the bottom of the screen.
 
@@ -27,7 +31,7 @@
 ### Animation Control Methods
 Once the layout has used the controller’s configuration properties to build the animations, the controller can use them to actually run animations. This is done for two reasons - reacting to a view being added or removed, or responding to another class (such as a touch handler or broadcast receiver) requesting an animation. ```onChildAdded```, ```onChildRemoved```, and ```setChildVisibility``` are called automatically by the layout, giving the controller the opportunity to animate the child in/out/visible/gone. Custom methods are called by anyone with access to the controller instance to do things like expand, collapse, or move the child views.
 
-In either case, the controller can use `super.animationForChild` to retrieve a `PhysicsPropertyAnimator` instance. This object behaves similarly to the `ViewPropertyAnimator` object you would receive from `View.animate()`. 
+In either case, the controller can use `super.animationForChild` to retrieve a `PhysicsPropertyAnimator` instance. This object behaves similarly to the `ViewPropertyAnimator` object you would receive from `View.animate()`.
 
 #### PhysicsPropertyAnimator
 
@@ -36,9 +40,14 @@
 - `translationX/Y/Z(float)`
 - `scaleX/Y(float)`
 
+...as well as shortcut methods to reduce the amount of boilerplate code needed for common use cases:
+- `position(float, float, Runnable…)`, which starts translationX and translationY animations, and calls the provided callbacks only when both animations have completed.
+- `followAnimatedTargetAlongPath(Path, int, TimeInterpolator)`, which animates a ‘target’ point along the given path using a traditional Animator. As the target moves, the translationX/Y physics animations are updated to follow the target, similarly to how they might follow a touch event location. This results in the view roughly following the path, but with natural motion that takes momentum into account. For example, if a path makes a 90 degree turn to the right, the physics animations will cause the view to curve naturally towards the new trajectory.
+
 It also provides the following configuration methods:
 - `withStartDelay(int)`, for starting the animation after a given delay.
 - `withStartVelocity(float)`, for starting the animation with the given start velocity.
+- `withStiffness(float)` and `withDampingRatio(float)`, for overriding the default physics param values returned by the controller’s getSpringForce method.
 - `withPositionStartVelocities(float, float)`, for setting specific start velocities for TRANSLATION_X and TRANSLATION_Y, since these typically differ.
 - `start(Runnable)`, to start the animation, with an optional end action to call when the animations for every property (including chained animations) have completed.
 
@@ -61,8 +70,7 @@
 
 - Often, animations will set starting values for properties before the animation begins. Property methods like `translationX` have an overloaded variant: `translationX(from, to)`. When `start()` is called, the animation will set the view's translationX property to `from` before beginning the animation to `to`.
 - We may want to use different end actions for each property. For example, if we're animating a view to the bottom of the screen, and also fading it out, we might want to perform an action as soon as the fade out is complete. We can use `alpha(to, endAction)`, which will call endAction as soon as the alpha animation is finished. A special case is `position(x, y, endAction)`, where the endAction is called when both translationX and translationY animations have completed.
-
-`PhysicsAnimationController` also provides `animationsForChildrenFromIndex(int, ChildAnimationConfigurator)`. This is a convenience method for starting animations on multiple child views, starting at the given index. The `ChildAnimationConfigurator` is called with a `PhysicsPropertyAnimator` for each child, where calls to methods like `translationX` and `withStartVelocity` can be made. `animationsForChildrenFromIndex` returns a `MultiAnimationStarter` with a single method, `startAll(endAction)`, which starts all of the animations and calls the end action when they have all completed.
+- `PhysicsAnimationController` also provides `animationsForChildrenFromIndex(int, ChildAnimationConfigurator)`. This is a convenience method for starting animations on multiple child views, starting at the given index. The `ChildAnimationConfigurator` is called with a `PhysicsPropertyAnimator` for each child, where calls to methods like `translationX` and `withStartVelocity` can be made. `animationsForChildrenFromIndex` returns a `MultiAnimationStarter` with a single method, `startAll(endAction)`, which starts all of the animations and calls the end action when they have all completed.
 
 ##### Examples
 Spring the stack of bubbles (whose animations are chained) to the bottom of the screen, shrinking them to 50% size. Once the first bubble is done shrinking, begin fading them out, and then remove them all from the parent once all bubbles have faded out:
@@ -93,16 +101,22 @@
     .startAll(removeFirstView);
 ```
 
+Move a view up along the left side of the screen, and then to the top right of the screen (assume a view that is currently halfway down the left side of the screen). When the translation animations have finished following the target, call a callback:
+
+```
+Path path = new Path();
+path.moveTo(view.getTranslationX(), view.getTranslationY());
+path.lineTo(view.getTranslationX(), 0);
+path.lineTo(mScreenWidth, 0);
+animationForChild(view)
+    .followAnimatedTargetAlongPath(path, 100, new LinearInterpolator())
+    .start(callbackAfterFollowingFinished);
+```
+
 ## PhysicsAnimationLayout
 The layout itself is a FrameLayout descendant with a few extra methods:
 
-```setController(PhysicsAnimationController controller)```
-Attaches the layout to the controller, so that the controller can access the layout’s protected methods. It also constructs or reconfigures the physics animations according to the new controller’s configuration methods.
+```setActiveController(PhysicsAnimationController controller)```
+Sets the given controller as the active controller for the layout. This causes the layout to construct or reconfigure the physics animations according to the new controller’s configuration methods, and halt any in-progress animations.
 
-```setEndListenerForProperty(ViewProperty property, AnimationEndListener endListener)```
-Sets an end listener that is called when all animations on the given property have ended.
-
-```setMaxRenderedChildren(int max)```
-Child views beyond this limit will be set to GONE, and won't be animated, for performance reasons. Defaults to **5**.
-
-It has one protected method, ```animateValueForChildAtIndex(ViewProperty property, int index, float value)```, which is visible to PhysicsAnimationController descendants. This method dispatches the given value to the appropriate animation.
\ No newline at end of file
+Only the currently active controller is allowed to start animations. If a different controller is set as the active controller, the previous controller will no longer be able to start animations. Attempts to do so will have no effect. This is to ensure that multiple controllers aren’t updating animations at the same time, which can cause undefined behavior.
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/values-de/strings.xml b/packages/SystemUI/legacy/recents/res/values-de/strings.xml
index 4a089bf..97a6366 100644
--- a/packages/SystemUI/legacy/recents/res/values-de/strings.xml
+++ b/packages/SystemUI/legacy/recents/res/values-de/strings.xml
@@ -28,7 +28,7 @@
     <string name="recents_empty_message" msgid="7967713254531861311">"Keine kürzlich verwendeten Elemente"</string>
     <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Du hast alles gelöscht"</string>
     <string name="recents_app_info_button_label" msgid="8732926607391786762">"App-Info"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"Bildschirm anpinnen"</string>
+    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"Bildschirmfixierung"</string>
     <string name="recents_search_bar_label" msgid="638132045925945941">"Suchen"</string>
     <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> konnte nicht gestartet werden."</string>
     <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> ist im abgesicherten Modus deaktiviert."</string>
diff --git a/packages/SystemUI/legacy/recents/res/values-es/strings.xml b/packages/SystemUI/legacy/recents/res/values-es/strings.xml
index 8bcfe84..43fb3ad 100644
--- a/packages/SystemUI/legacy/recents/res/values-es/strings.xml
+++ b/packages/SystemUI/legacy/recents/res/values-es/strings.xml
@@ -28,7 +28,7 @@
     <string name="recents_empty_message" msgid="7967713254531861311">"No hay elementos recientes"</string>
     <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Has borrado todo"</string>
     <string name="recents_app_info_button_label" msgid="8732926607391786762">"Información de la aplicación"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"fijar pantalla"</string>
+    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"fijación de pantalla"</string>
     <string name="recents_search_bar_label" msgid="638132045925945941">"buscar"</string>
     <string name="recents_launch_error_message" msgid="9107963563503438012">"No se ha podido iniciar la aplicación <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="recents_launch_disabled_message" msgid="826461671965217243">"La aplicación <xliff:g id="APP">%s</xliff:g> se ha inhabilitado en modo seguro."</string>
diff --git a/packages/SystemUI/legacy/recents/res/values-fr/strings.xml b/packages/SystemUI/legacy/recents/res/values-fr/strings.xml
index 5b0d611..183b6be 100644
--- a/packages/SystemUI/legacy/recents/res/values-fr/strings.xml
+++ b/packages/SystemUI/legacy/recents/res/values-fr/strings.xml
@@ -32,7 +32,7 @@
     <string name="recents_search_bar_label" msgid="638132045925945941">"rechercher"</string>
     <string name="recents_launch_error_message" msgid="9107963563503438012">"Impossible de lancer l\'application <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="recents_launch_disabled_message" msgid="826461671965217243">"L\'application <xliff:g id="APP">%s</xliff:g> est désactivée en mode sécurisé."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Tout fermer"</string>
+    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Tout effacer"</string>
     <string name="recents_drag_hint_message" msgid="610417221848280136">"Faire glisser ici pour utiliser l\'écran partagé"</string>
     <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Séparation horizontale"</string>
     <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Séparation verticale"</string>
diff --git a/packages/SystemUI/legacy/recents/res/values-my/strings.xml b/packages/SystemUI/legacy/recents/res/values-my/strings.xml
index 7b5870e1..94fc662 100644
--- a/packages/SystemUI/legacy/recents/res/values-my/strings.xml
+++ b/packages/SystemUI/legacy/recents/res/values-my/strings.xml
@@ -35,7 +35,7 @@
     <string name="recents_stack_action_button_label" msgid="1974273390109881497">"အားလုံး ဖယ်ရှားရန်"</string>
     <string name="recents_drag_hint_message" msgid="610417221848280136">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းကို အသုံးပြုရန် ဤနေရာသို့ ဖိဆွဲပါ"</string>
     <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"အလျားလိုက် ခွဲရန်"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"ဒေါင်လိုက် ခွဲရန်"</string>
+    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"ထောင်လိုက် ခွဲရန်"</string>
     <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"စိတ်ကြိုက် ခွဲရန်"</string>
     <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"မျက်နှာပြင်ကို အပေါ်သို့ ခွဲရန်"</string>
     <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"မျက်နှာပြင်ကို ဘယ်ဘက်သို့ ခွဲရန်"</string>
diff --git a/packages/SystemUI/legacy/recents/res/values-pt-rBR/strings.xml b/packages/SystemUI/legacy/recents/res/values-pt-rBR/strings.xml
index b557ad2..589b831 100644
--- a/packages/SystemUI/legacy/recents/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/legacy/recents/res/values-pt-rBR/strings.xml
@@ -28,7 +28,7 @@
     <string name="recents_empty_message" msgid="7967713254531861311">"Nenhum item recente"</string>
     <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Você limpou tudo"</string>
     <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informações do aplicativo"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"Fixar tela"</string>
+    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"fixação de tela"</string>
     <string name="recents_search_bar_label" msgid="638132045925945941">"pesquisar"</string>
     <string name="recents_launch_error_message" msgid="9107963563503438012">"Não foi possível iniciar <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="recents_launch_disabled_message" msgid="826461671965217243">"O app <xliff:g id="APP">%s</xliff:g> fica desativado no modo de segurança."</string>
diff --git a/packages/SystemUI/legacy/recents/res/values-pt/strings.xml b/packages/SystemUI/legacy/recents/res/values-pt/strings.xml
index b557ad2..589b831 100644
--- a/packages/SystemUI/legacy/recents/res/values-pt/strings.xml
+++ b/packages/SystemUI/legacy/recents/res/values-pt/strings.xml
@@ -28,7 +28,7 @@
     <string name="recents_empty_message" msgid="7967713254531861311">"Nenhum item recente"</string>
     <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Você limpou tudo"</string>
     <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informações do aplicativo"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"Fixar tela"</string>
+    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"fixação de tela"</string>
     <string name="recents_search_bar_label" msgid="638132045925945941">"pesquisar"</string>
     <string name="recents_launch_error_message" msgid="9107963563503438012">"Não foi possível iniciar <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="recents_launch_disabled_message" msgid="826461671965217243">"O app <xliff:g id="APP">%s</xliff:g> fica desativado no modo de segurança."</string>
diff --git a/packages/SystemUI/legacy/recents/res/values-sk/strings.xml b/packages/SystemUI/legacy/recents/res/values-sk/strings.xml
index 9c3a857..cbffcaf 100644
--- a/packages/SystemUI/legacy/recents/res/values-sk/strings.xml
+++ b/packages/SystemUI/legacy/recents/res/values-sk/strings.xml
@@ -20,7 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Prehľad"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Zavrieť aplikáciu <xliff:g id="APP">%s</xliff:g>."</string>
+    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Zrušiť aplikáciu <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Aplikácia <xliff:g id="APP">%s</xliff:g> bola zrušená."</string>
     <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Všetky nedávne aplikácie boli zrušené."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Otvoriť informácie o aplikácii <xliff:g id="APP">%s</xliff:g>."</string>
diff --git a/packages/SystemUI/legacy/recents/res/values-ur/strings.xml b/packages/SystemUI/legacy/recents/res/values-ur/strings.xml
index 46033da..32aae85 100644
--- a/packages/SystemUI/legacy/recents/res/values-ur/strings.xml
+++ b/packages/SystemUI/legacy/recents/res/values-ur/strings.xml
@@ -19,7 +19,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"عمومی جائزہ۔"</string>
+    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"مجموعی جائزہ۔"</string>
     <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> کو مسترد کریں۔"</string>
     <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> کو مسترد کر دیا گیا۔"</string>
     <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"سبھی حالیہ ایپلیکیشنز کو مسترد کر دیا گیا۔"</string>
diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml
index 8547bc8..b23824e 100644
--- a/packages/SystemUI/res-keyguard/values-bs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml
@@ -22,7 +22,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="3171996292755059205">"Zaključavanje tastature"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3420548423949593123">"Upišite PIN"</string>
-    <string name="keyguard_password_enter_puk_code" msgid="670683628782925409">"Upišite PUK za SIM i novi PIN kôd"</string>
+    <string name="keyguard_password_enter_puk_code" msgid="670683628782925409">"Upišite PUK kôd za SIM karticu i novi PIN"</string>
     <string name="keyguard_password_enter_puk_prompt" msgid="3747778500166059332">"PUK kôd za SIM karticu"</string>
     <string name="keyguard_password_enter_pin_prompt" msgid="8188243197504453830">"Novi PIN za SIM karticu"</string>
     <string name="keyguard_password_entry_touch_hint" msgid="5790410752696806482"><font size="17">"Dodirnite da upišete lozinku"</font></string>
@@ -54,7 +54,7 @@
     <string name="keyguard_accessibility_pin_area" msgid="703175752097279029">"Prostor za PIN"</string>
     <string name="keyguard_accessibility_password" msgid="7695303207740941101">"Lozinka uređaja"</string>
     <string name="keyguard_accessibility_sim_pin_area" msgid="912702510825058921">"Prostor za PIN za SIM karticu"</string>
-    <string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Prostor za PUK za SIM"</string>
+    <string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Prostor za PUK kôd za SIM karticu"</string>
     <string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Naredni alarm je podešen za <xliff:g id="ALARM">%1$s</xliff:g>"</string>
     <string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Izbriši"</string>
     <string name="disable_carrier_button_text" msgid="6914341927421916114">"Onemogući eSIM karticu"</string>
@@ -116,7 +116,7 @@
       <item quantity="other">PUK kôd za SIM karticu je netačan. Imate još <xliff:g id="NUMBER_1">%d</xliff:g> pokušaja prije nego što SIM kartica postane trajno neupotrebljiva.</item>
     </plurals>
     <string name="kg_password_pin_failed" msgid="8769990811451236223">"Korištenje PIN-a za SIM karticu nije uspjelo!"</string>
-    <string name="kg_password_puk_failed" msgid="1331621440873439974">"Korištenje PUK-a za SIM nije uspjelo!"</string>
+    <string name="kg_password_puk_failed" msgid="1331621440873439974">"Korištenje PUK koda za SIM karticu nije uspjelo!"</string>
     <string name="kg_pin_accepted" msgid="7637293533973802143">"Kôd je prihvaćen"</string>
     <string name="keyguard_carrier_default" msgid="4274828292998453695">"Nema mreže."</string>
     <string name="accessibility_ime_switch_button" msgid="2695096475319405612">"Promjena načina unosa"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sq/strings.xml b/packages/SystemUI/res-keyguard/values-sq/strings.xml
index e4b37d0..1d42f1f 100644
--- a/packages/SystemUI/res-keyguard/values-sq/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sq/strings.xml
@@ -38,7 +38,7 @@
     <string name="keyguard_plugged_in" msgid="3161102098900158923">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Po karikohet"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="3684592786276709342">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Po karikohet me shpejtësi"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="509533586841478405">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Po karikohet ngadalë"</string>
-    <string name="keyguard_low_battery" msgid="9218432555787624490">"Lidh karikuesin."</string>
+    <string name="keyguard_low_battery" msgid="9218432555787624490">"Lidh ngarkuesin."</string>
     <string name="keyguard_instructions_when_pattern_disabled" msgid="8566679946700751371">"Shtyp \"Meny\" për të shkyçur."</string>
     <string name="keyguard_network_locked_message" msgid="6743537524631420759">"Rrjeti është i kyçur"</string>
     <string name="keyguard_missing_sim_message_short" msgid="6327533369959764518">"Nuk ka kartë SIM"</string>
diff --git a/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml b/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
index 1661bb2..8c7e82f 100644
--- a/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
+++ b/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
@@ -24,4 +24,5 @@
         android:width="1dp"
         android:color="#66FFFFFF" />
 
+    <solid android:color="#B3000000" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_dismiss_target.xml b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
index 245177c..ca085b6 100644
--- a/packages/SystemUI/res/layout/bubble_dismiss_target.xml
+++ b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
@@ -20,6 +20,13 @@
     android:layout_height="@dimen/pip_dismiss_gradient_height"
     android:layout_gravity="bottom|center_horizontal">
 
+    <FrameLayout
+        android:id="@+id/bubble_dismiss_circle"
+        android:layout_width="@dimen/bubble_dismiss_encircle_size"
+        android:layout_height="@dimen/bubble_dismiss_encircle_size"
+        android:layout_gravity="center"
+        android:background="@drawable/bubble_dismiss_circle" />
+
     <LinearLayout
         android:id="@+id/bubble_dismiss_icon_container"
         android:layout_width="wrap_content"
@@ -38,29 +45,5 @@
             android:layout_width="24dp"
             android:layout_height="24dp"
             android:src="@drawable/bubble_dismiss_icon" />
-
-        <TextView
-            android:id="@+id/bubble_dismiss_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="9dp"
-            android:layout_marginBottom="9dp"
-            android:layout_marginLeft="8dp"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"
-            android:textColor="@android:color/white"
-            android:shadowColor="@android:color/black"
-            android:shadowDx="-1"
-            android:shadowDy="1"
-            android:shadowRadius="0.01"
-            android:text="@string/bubble_dismiss_text" />
-
     </LinearLayout>
-
-    <FrameLayout
-        android:id="@+id/bubble_dismiss_circle"
-        android:layout_width="@dimen/bubble_dismiss_encircle_size"
-        android:layout_height="@dimen/bubble_dismiss_encircle_size"
-        android:layout_gravity="center"
-        android:alpha="0"
-        android:background="@drawable/bubble_dismiss_circle" />
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml
index a8eb2914..e2dea45 100644
--- a/packages/SystemUI/res/layout/bubble_view.xml
+++ b/packages/SystemUI/res/layout/bubble_view.xml
@@ -24,7 +24,6 @@
         android:id="@+id/bubble_image"
         android:layout_width="@dimen/individual_bubble_size"
         android:layout_height="@dimen/individual_bubble_size"
-        android:padding="@dimen/bubble_view_padding"
         android:clipToPadding="false"/>
 
 </com.android.systemui.bubbles.BubbleView>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index a914930..9716a00 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -44,7 +44,7 @@
     </com.android.systemui.statusbar.BackDropView>
 
     <com.android.systemui.statusbar.ScrimView
-        android:id="@+id/scrim_behind"
+        android:id="@+id/scrim_for_bubble"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:importantForAccessibility="no"
@@ -56,6 +56,14 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
 
+    <com.android.systemui.statusbar.ScrimView
+        android:id="@+id/scrim_behind"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:importantForAccessibility="no"
+        sysui:ignoreRightInset="true"
+        />
+
     <include layout="@layout/status_bar_expanded"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 90ead35..08fb0b3 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -255,8 +255,8 @@
     <string name="accessibility_quick_settings_flashlight_on" msgid="2003479320007841077">"Lommelygten er tændt."</string>
     <string name="accessibility_quick_settings_flashlight_changed_off" msgid="3303701786768224304">"Lommelygten er slukket."</string>
     <string name="accessibility_quick_settings_flashlight_changed_on" msgid="6531793301533894686">"Lommelygten er tændt."</string>
-    <string name="accessibility_quick_settings_color_inversion_changed_off" msgid="4406577213290173911">"Ombytning af farver er slået fra."</string>
-    <string name="accessibility_quick_settings_color_inversion_changed_on" msgid="6897462320184911126">"Ombytning af farver er slået til."</string>
+    <string name="accessibility_quick_settings_color_inversion_changed_off" msgid="4406577213290173911">"Invertering af farver er slået fra."</string>
+    <string name="accessibility_quick_settings_color_inversion_changed_on" msgid="6897462320184911126">"Invertering af farver er slået til."</string>
     <string name="accessibility_quick_settings_hotspot_changed_off" msgid="5004708003447561394">"Mobilhotspot er slået fra."</string>
     <string name="accessibility_quick_settings_hotspot_changed_on" msgid="2890951609226476206">"Mobilhotspot er slået til."</string>
     <string name="accessibility_casting_turned_off" msgid="1430668982271976172">"Casting af din skærm er stoppet."</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 552d12e..a3069a1 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -886,7 +886,7 @@
     <string name="qs_dnd_prompt_app" msgid="7978037419334156034">"Le mode Ne pas déranger a été activé par une application (<xliff:g id="ID_1">%s</xliff:g>)."</string>
     <string name="qs_dnd_prompt_auto_rule_app" msgid="2599343675391111951">"Le mode Ne pas déranger a été activé par une règle automatique ou une application."</string>
     <string name="qs_dnd_until" msgid="3469471136280079874">"Jusqu\'à <xliff:g id="ID_1">%s</xliff:g>"</string>
-    <string name="qs_dnd_keep" msgid="1825009164681928736">"Conserver"</string>
+    <string name="qs_dnd_keep" msgid="1825009164681928736">"Garder"</string>
     <string name="qs_dnd_replace" msgid="8019520786644276623">"Remplacer"</string>
     <string name="running_foreground_services_title" msgid="381024150898615683">"Applications qui fonctionnent en arrière-plan"</string>
     <string name="running_foreground_services_msg" msgid="6326247670075574355">"Touchez pour afficher des détails sur l\'utilisation de la pile et des données"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 3fa2a48..263fe8a 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -46,8 +46,8 @@
     <string name="bluetooth_tethered" msgid="7094101612161133267">"ब्लूटूथ टीदर किया गया"</string>
     <string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"इनपुट का तरीका सेट करें"</string>
     <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"सामान्य कीबोर्ड"</string>
-    <string name="usb_device_permission_prompt" msgid="1825685909587559679">"<xliff:g id="APPLICATION">%1$s</xliff:g> को <xliff:g id="USB_DEVICE">%2$s</xliff:g> के ऐक्सेस की अनुमति दें?"</string>
-    <string name="usb_accessory_permission_prompt" msgid="2465531696941369047">"<xliff:g id="APPLICATION">%1$s</xliff:g> को <xliff:g id="USB_ACCESSORY">%2$s</xliff:g> के ऐक्सेस की अनुमति दें?"</string>
+    <string name="usb_device_permission_prompt" msgid="1825685909587559679">"<xliff:g id="APPLICATION">%1$s</xliff:g> को <xliff:g id="USB_DEVICE">%2$s</xliff:g> के एक्सेस की अनुमति दें?"</string>
+    <string name="usb_accessory_permission_prompt" msgid="2465531696941369047">"<xliff:g id="APPLICATION">%1$s</xliff:g> को <xliff:g id="USB_ACCESSORY">%2$s</xliff:g> के एक्सेस की अनुमति दें?"</string>
     <string name="usb_device_confirm_prompt" msgid="7440562274256843905">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> के लिए <xliff:g id="APPLICATION">%1$s</xliff:g> खोलें?"</string>
     <string name="usb_accessory_confirm_prompt" msgid="4333670517539993561">"<xliff:g id="USB_ACCESSORY">%2$s</xliff:g> के लिए <xliff:g id="APPLICATION">%1$s</xliff:g> खोलें?"</string>
     <string name="usb_accessory_uri_prompt" msgid="513450621413733343">"इस USB सहायक डिवाइस के साथ कोई भी इंस्टॉल ऐप्स  काम नहीं करता. इस सहायक डिवाइस के बारे में यहां ज़्यादा जानें: <xliff:g id="URL">%1$s</xliff:g>"</string>
@@ -205,7 +205,7 @@
     <string name="accessibility_overflow_action" msgid="5681882033274783311">"पूरी सूचनाएं देखें"</string>
     <string name="accessibility_remove_notification" msgid="3603099514902182350">"सूचना साफ़ करें"</string>
     <string name="accessibility_gps_enabled" msgid="3511469499240123019">"GPS सक्षम."</string>
-    <string name="accessibility_gps_acquiring" msgid="8959333351058967158">"GPS पाना."</string>
+    <string name="accessibility_gps_acquiring" msgid="8959333351058967158">"GPS प्राप्त करना."</string>
     <string name="accessibility_tty_enabled" msgid="4613200365379426561">"टेलीटाइपराइटर सक्षम."</string>
     <string name="accessibility_ringer_vibrate" msgid="666585363364155055">"रिंगर कंपन (वाइब्रेशन)."</string>
     <string name="accessibility_ringer_silent" msgid="9061243307939135383">"रिंगर मौन."</string>
@@ -452,8 +452,8 @@
     <string name="battery_saver_notification_title" msgid="8614079794522291840">"बैटरी सेवर चालू है"</string>
     <string name="battery_saver_notification_text" msgid="820318788126672692">"निष्‍पादन और पृष्ठभूमि डेटा को कम करता है"</string>
     <string name="battery_saver_notification_action_text" msgid="132118784269455533">"बैटरी सेवर बंद करें"</string>
-    <string name="media_projection_dialog_text" msgid="8585357687598538511">"रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> आपकी स्क्रीन पर दिखाई जा रही या आपके डिवाइस पर चलाई जा रही संवेदनशील जानकारी ऐक्सेस कर सकता है. इस जानकारी में ऑडियो, पासवर्ड, भुगतान की जानकारी, फ़ोटो और मैसेज शामिल हैं."</string>
-    <string name="media_projection_dialog_service_text" msgid="3075544489835858258">"रिकॉर्ड या कास्ट करते समय, यह सेवा देने वाला ऐप्लिकेशन आपकी स्क्रीन पर दिखाई जा रही या आपके डिवाइस पर चलाई जा रही संवेदनशील जानकारी ऐक्सेस कर सकता है. इस जानकारी में ऑडियो, पासवर्ड, भुगतान की जानकारी, फ़ोटो और मैसेज शामिल हैं."</string>
+    <string name="media_projection_dialog_text" msgid="8585357687598538511">"रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> आपकी स्क्रीन पर दिखाई जा रही या आपके डिवाइस पर चलाई जा रही संवेदनशील जानकारी एक्सेस कर सकता है. इस जानकारी में ऑडियो, पासवर्ड, भुगतान की जानकारी, फ़ोटो और मैसेज शामिल हैं."</string>
+    <string name="media_projection_dialog_service_text" msgid="3075544489835858258">"रिकॉर्ड या कास्ट करते समय, यह सेवा देने वाला ऐप्लिकेशन आपकी स्क्रीन पर दिखाई जा रही या आपके डिवाइस पर चलाई जा रही संवेदनशील जानकारी एक्सेस कर सकता है. इस जानकारी में ऑडियो, पासवर्ड, भुगतान की जानकारी, फ़ोटो और मैसेज शामिल हैं."</string>
     <string name="media_projection_dialog_title" msgid="8124184308671641248">"कास्ट करने/रिकॉर्ड करने के दौरान संवेदनशील जानकारी का सबके सामने आ जाना"</string>
     <string name="media_projection_remember_text" msgid="3103510882172746752">"फिर से न दिखाएं"</string>
     <string name="clear_all_notifications_text" msgid="814192889771462828">"सभी को हटाएं"</string>
diff --git a/packages/SystemUI/res/values-hi/strings_tv.xml b/packages/SystemUI/res/values-hi/strings_tv.xml
index 6e3c178..357f7a6 100644
--- a/packages/SystemUI/res/values-hi/strings_tv.xml
+++ b/packages/SystemUI/res/values-hi/strings_tv.xml
@@ -22,5 +22,5 @@
     <string name="notification_channel_tv_pip" msgid="134047986446577723">"पिक्चर में पिक्चर"</string>
     <string name="pip_notification_unknown_title" msgid="6289156118095849438">"(कोई शीर्षक कार्यक्रम नहीं)"</string>
     <string name="pip_close" msgid="3480680679023423574">"PIP बंद करें"</string>
-    <string name="pip_fullscreen" msgid="8604643018538487816">"फ़ुल स्‍क्रीन"</string>
+    <string name="pip_fullscreen" msgid="8604643018538487816">"पूर्ण स्‍क्रीन"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 676208b..20f638d 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -747,7 +747,7 @@
     <string name="data_saver" msgid="5037565123367048522">"डेटा सेव्हर"</string>
     <string name="accessibility_data_saver_on" msgid="8454111686783887148">"डेटा सेव्हर चालू आहे"</string>
     <string name="accessibility_data_saver_off" msgid="8841582529453005337">"डेटा सेव्हर बंद आहे"</string>
-    <string name="switch_bar_on" msgid="1142437840752794229">"सुरू"</string>
+    <string name="switch_bar_on" msgid="1142437840752794229">"चालू"</string>
     <string name="switch_bar_off" msgid="8803270596930432874">"बंद"</string>
     <string name="nav_bar" msgid="1993221402773877607">"नॅव्हिगेशन बार"</string>
     <string name="nav_bar_layout" msgid="3664072994198772020">"लेआउट"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index bef1fc2..e40ef27 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1110,18 +1110,23 @@
     <dimen name="bubble_flyout_pointer_size">6dp</dimen>
     <!-- How much space to leave between the flyout (tip of the arrow) and the bubble stack. -->
     <dimen name="bubble_flyout_space_from_bubble">8dp</dimen>
-    <!-- Padding around a collapsed bubble -->
-    <dimen name="bubble_view_padding">0dp</dimen>
-    <!-- Padding between bubbles when displayed in expanded state -->
-    <dimen name="bubble_padding">8dp</dimen>
+    <!-- Padding between status bar and bubbles when displayed in expanded state -->
+    <dimen name="bubble_padding_top">16dp</dimen>
     <!-- Size of individual bubbles. -->
-    <dimen name="individual_bubble_size">52dp</dimen>
+    <dimen name="individual_bubble_size">60dp</dimen>
+    <!-- Size of bubble icon bitmap. -->
+    <dimen name="bubble_icon_bitmap_size">52dp</dimen>
+    <!-- Extra padding added to the touchable rect for bubbles so they are easier to grab. -->
+    <dimen name="bubble_touch_padding">12dp</dimen>
     <!-- Size of the circle around the bubbles when they're in the dismiss target. -->
-    <dimen name="bubble_dismiss_encircle_size">56dp</dimen>
+    <dimen name="bubble_dismiss_encircle_size">52dp</dimen>
     <!-- How much to inset the icon in the circle -->
     <dimen name="bubble_icon_inset">16dp</dimen>
     <!-- Padding around the view displayed when the bubble is expanded -->
     <dimen name="bubble_expanded_view_padding">4dp</dimen>
+    <!-- This should be at least the size of bubble_expanded_view_padding; it is used to include
+         a slight touch slop around the expanded view. -->
+    <dimen name="bubble_expanded_view_slop">8dp</dimen>
     <!-- Default (and minimum) height of the expanded view shown when the bubble is expanded -->
     <dimen name="bubble_expanded_default_height">180dp</dimen>
     <!-- Height of the triangle that points to the expanded bubble -->
@@ -1146,10 +1151,10 @@
     <dimen name="bubble_message_padding">4dp</dimen>
     <!-- Offset between bubbles in their stacked position. -->
     <dimen name="bubble_stack_offset">5dp</dimen>
-    <!-- How far offscreen the bubble stack rests. -->
-    <dimen name="bubble_stack_offscreen">5dp</dimen>
+    <!-- How far offscreen the bubble stack rests. Cuts off padding and part of icon bitmap. -->
+    <dimen name="bubble_stack_offscreen">9dp</dimen>
     <!-- How far down the screen the stack starts. -->
-    <dimen name="bubble_stack_starting_offset_y">100dp</dimen>
+    <dimen name="bubble_stack_starting_offset_y">96dp</dimen>
     <!-- Size of image buttons in the bubble header -->
     <dimen name="bubble_header_icon_size">48dp</dimen>
     <!-- Space between the pointer triangle and the bubble expanded view -->
@@ -1160,6 +1165,7 @@
          snap to the dismiss target. -->
     <dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
     <dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
+
     <!-- Size of the RAT type for CellularTile -->
     <dimen name="celltile_rat_type_size">10sp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 66f1949..3727181 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -137,6 +137,7 @@
     <item type="id" name="scale_x_dynamicanimation_tag"/>
     <item type="id" name="scale_y_dynamicanimation_tag"/>
     <item type="id" name="physics_animator_tag"/>
+    <item type="id" name="target_animator_tag" />
 
     <!-- Global Actions Menu -->
     <item type="id" name="global_actions_view" />
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index c215d0f..4c49494 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -72,6 +72,13 @@
      */
     public void onSingleTaskDisplayDrawn(int displayId) { }
 
+    /**
+     * Called when the last task is removed from a display which can only contain one task.
+     *
+     * @param displayId the id of the display from which the window is removed.
+     */
+    public void onSingleTaskDisplayEmpty(int displayId) {}
+
     public void onTaskProfileLocked(int taskId, int userId) { }
     public void onTaskCreated(int taskId, ComponentName componentName) { }
     public void onTaskRemoved(int taskId) { }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index d570a58..4465f04 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -209,6 +209,12 @@
     }
 
     @Override
+    public void onSingleTaskDisplayEmpty(int displayId) throws RemoteException {
+        mHandler.obtainMessage(H.ON_SINGLE_TASK_DISPLAY_EMPTY, displayId,
+                0 /* unused */).sendToTarget();
+    }
+
+    @Override
     public void onTaskDisplayChanged(int taskId, int newDisplayId) throws RemoteException {
         mHandler.obtainMessage(H.ON_TASK_DISPLAY_CHANGED, taskId, newDisplayId).sendToTarget();
     }
@@ -233,7 +239,8 @@
         private static final int ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED = 17;
         private static final int ON_BACK_PRESSED_ON_TASK_ROOT = 18;
         private static final int ON_SINGLE_TASK_DISPLAY_DRAWN = 19;
-        private static final int ON_TASK_DISPLAY_CHANGED = 20;
+        private static final int ON_SINGLE_TASK_DISPLAY_EMPTY = 20;
+        private static final int ON_TASK_DISPLAY_CHANGED = 21;
 
 
         public H(Looper looper) {
@@ -376,6 +383,13 @@
                         }
                         break;
                     }
+                    case ON_SINGLE_TASK_DISPLAY_EMPTY: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onSingleTaskDisplayEmpty(
+                                    msg.arg1);
+                        }
+                        break;
+                    }
                     case ON_TASK_DISPLAY_CHANGED: {
                         for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
                             mTaskStackListeners.get(i).onTaskDisplayChanged(msg.arg1, msg.arg2);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index e87da3c..89babb9 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.ScrimView;
 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.collection.NotificationData;
@@ -141,11 +142,12 @@
     }
 
     public ScrimController createScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+            ScrimView scrimForBubble,
             LockscreenWallpaper lockscreenWallpaper,
             TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
             Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
             AlarmManager alarmManager) {
-        return new ScrimController(scrimBehind, scrimInFront, scrimStateListener,
+        return new ScrimController(scrimBehind, scrimInFront, scrimForBubble, scrimStateListener,
                 scrimVisibleListener, dozeParameters, alarmManager);
     }
 
@@ -236,8 +238,8 @@
     @Singleton
     @Provides
     public NotificationInterruptionStateProvider provideNotificationInterruptionStateProvider(
-            Context context) {
-        return new NotificationInterruptionStateProvider(context);
+            Context context, NotificationFilter filter, StatusBarStateController controller) {
+        return new NotificationInterruptionStateProvider(context, filter, controller);
     }
 
     @Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
deleted file mode 100644
index 74ad0fa..0000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.bubbles;
-
-import static android.graphics.Paint.ANTI_ALIAS_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.Log;
-
-import com.android.systemui.R;
-
-// XXX: Mostly opied from launcher code / can we share?
-/**
- * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
- */
-public class BadgeRenderer {
-
-    private static final String TAG = "BadgeRenderer";
-
-    /** The badge sizes are defined as percentages of the app icon size. */
-    private static final float SIZE_PERCENTAGE = 0.38f;
-
-    /** Extra scale down of the dot. */
-    private static final float DOT_SCALE = 0.6f;
-
-    private final float mDotCenterOffset;
-    private final float mCircleRadius;
-    private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
-
-    public BadgeRenderer(Context context) {
-        mDotCenterOffset = getDotCenterOffset(context);
-        mCircleRadius = getDotRadius(mDotCenterOffset);
-    }
-
-    /** Space between the center of the dot and the top or left of the bubble stack. */
-    static float getDotCenterOffset(Context context) {
-        final int iconSizePx =
-                context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
-        return SIZE_PERCENTAGE * iconSizePx;
-    }
-
-    static float getDotRadius(float dotCenterOffset) {
-        int size = (int) (DOT_SCALE * dotCenterOffset);
-        return size / 2f;
-    }
-
-    /**
-     * Draw a circle in the top right corner of the given bounds.
-     *
-     * @param color The color (based on the icon) to use for the badge.
-     * @param iconBounds The bounds of the icon being badged.
-     * @param badgeScale The progress of the animation, from 0 to 1.
-     * @param spaceForOffset How much space to offset the badge up and to the left or right.
-     * @param onLeft Whether the badge should be draw on left or right side.
-     */
-    public void draw(Canvas canvas, int color, Rect iconBounds, float badgeScale,
-            Point spaceForOffset, boolean onLeft) {
-        if (iconBounds == null) {
-            Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
-            return;
-        }
-        canvas.save();
-        // We draw the badge relative to its center.
-        int x = onLeft ? iconBounds.left : iconBounds.right;
-        float offset = onLeft ? (mDotCenterOffset / 2) : -(mDotCenterOffset / 2);
-        float badgeCenterX = x + offset;
-        float badgeCenterY = iconBounds.top + mDotCenterOffset / 2;
-
-        canvas.translate(badgeCenterX + spaceForOffset.x, badgeCenterY - spaceForOffset.y);
-
-        canvas.scale(badgeScale, badgeScale);
-        mCirclePaint.setColor(color);
-        canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
-        canvas.restore();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
index 783780f..091d48a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -18,12 +18,13 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
-import android.graphics.Point;
+import android.graphics.Path;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.widget.ImageView;
 
 import com.android.internal.graphics.ColorUtils;
+import com.android.launcher3.icons.DotRenderer;
 import com.android.systemui.R;
 
 /**
@@ -31,16 +32,19 @@
  */
 public class BadgedImageView extends ImageView {
 
-    private BadgeRenderer mDotRenderer;
-    private int mIconSize;
     private Rect mTempBounds = new Rect();
-    private Point mTempPoint = new Point();
 
+    private DotRenderer mDotRenderer;
+    private DotRenderer.DrawParams mDrawParams;
+    private int mIconBitmapSize;
+    private int mDotColor;
     private float mDotScale = 0f;
-    private int mUpdateDotColor;
-    private boolean mShowUpdateDot;
+    private boolean mShowDot;
     private boolean mOnLeft;
 
+    /** Same as value in Launcher3 IconShape */
+    static final int DEFAULT_PATH_SIZE = 100;
+
     public BadgedImageView(Context context) {
         this(context, null);
     }
@@ -56,23 +60,34 @@
     public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mIconSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
-        mDotRenderer = new BadgeRenderer(getContext());
+        mIconBitmapSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+        mDrawParams = new DotRenderer.DrawParams();
 
         TypedArray ta = context.obtainStyledAttributes(
-                new int[] {android.R.attr.colorBackgroundFloating});
+                new int[]{android.R.attr.colorBackgroundFloating});
         ta.recycle();
     }
 
     @Override
     public void onDraw(Canvas canvas) {
         super.onDraw(canvas);
-        if (mShowUpdateDot) {
-            getDrawingRect(mTempBounds);
-            mTempPoint.set((getWidth() - mIconSize) / 2, getPaddingTop());
-            mDotRenderer.draw(canvas, mUpdateDotColor, mTempBounds, mDotScale, mTempPoint,
-                    mOnLeft);
+        if (!mShowDot) {
+            return;
         }
+        getDrawingRect(mTempBounds);
+
+        mDrawParams.color = mDotColor;
+        mDrawParams.iconBounds = mTempBounds;
+        mDrawParams.leftAlign = mOnLeft;
+        mDrawParams.scale = mDotScale;
+
+        if (mDotRenderer == null) {
+            Path circlePath = new Path();
+            float radius = DEFAULT_PATH_SIZE * 0.5f;
+            circlePath.addCircle(radius /* x */, radius /* y */, radius, Path.Direction.CW);
+            mDotRenderer = new DotRenderer(mIconBitmapSize, circlePath, DEFAULT_PATH_SIZE);
+        }
+        mDotRenderer.draw(canvas, mDrawParams);
     }
 
     /**
@@ -90,8 +105,8 @@
     /**
      * Set whether the dot should show or not.
      */
-    public void setShowDot(boolean showBadge) {
-        mShowUpdateDot = showBadge;
+    public void setShowDot(boolean showDot) {
+        mShowDot = showDot;
         invalidate();
     }
 
@@ -99,14 +114,22 @@
      * @return whether the dot is being displayed.
      */
     public boolean isShowingDot() {
-        return mShowUpdateDot;
+        return mShowDot;
     }
 
     /**
      * The colour to use for the dot.
      */
     public void setDotColor(int color) {
-        mUpdateDotColor = ColorUtils.setAlphaComponent(color, 255 /* alpha */);
+        mDotColor = ColorUtils.setAlphaComponent(color, 255 /* alpha */);
+        invalidate();
+    }
+
+    /**
+     * @param iconPath The new icon path to use when calculating dot position.
+     */
+    public void drawDot(Path iconPath) {
+        mDotRenderer = new DotRenderer(mIconBitmapSize, iconPath, DEFAULT_PATH_SIZE);
         invalidate();
     }
 
@@ -117,8 +140,4 @@
         mDotScale = fraction;
         invalidate();
     }
-
-    public float getDotScale() {
-        return mDotScale;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 5c6c397..c3cee35 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -20,38 +20,67 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
 import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
 import android.view.LayoutInflater;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
 import java.util.Objects;
 
 /**
  * Encapsulates the data and UI elements of a bubble.
  */
 class Bubble {
-
-    private static final boolean DEBUG = false;
     private static final String TAG = "Bubble";
 
+    private NotificationEntry mEntry;
     private final String mKey;
     private final String mGroupId;
     private String mAppName;
-    private final BubbleExpandedView.OnBubbleBlockedListener mListener;
+    private Drawable mUserBadgedAppIcon;
 
     private boolean mInflated;
-    public NotificationEntry entry;
-    BubbleView iconView;
-    BubbleExpandedView expandedView;
+    private BubbleView mIconView;
+    private BubbleExpandedView mExpandedView;
+
     private long mLastUpdated;
     private long mLastAccessed;
-    private PackageManager mPm;
+    private boolean mIsRemoved;
+
+    /**
+     * Whether this notification should be shown in the shade when it is also displayed as a bubble.
+     *
+     * <p>When a notification is a bubble we don't show it in the shade once the bubble has been
+     * expanded</p>
+     */
+    private boolean mShowInShadeWhenBubble = true;
+
+    /**
+     * Whether the bubble should show a dot for the notification indicating updated content.
+     */
+    private boolean mShowBubbleUpdateDot = true;
+
+    /** Whether flyout text should be suppressed, regardless of any other flags or state. */
+    private boolean mSuppressFlyout;
 
     public static String groupId(NotificationEntry entry) {
         UserHandle user = entry.notification.getUser();
@@ -61,31 +90,27 @@
     /** Used in tests when no UI is required. */
     @VisibleForTesting(visibility = PRIVATE)
     Bubble(Context context, NotificationEntry e) {
-        this (context, e, null);
-    }
-
-    Bubble(Context context, NotificationEntry e,
-            BubbleExpandedView.OnBubbleBlockedListener listener) {
-        entry = e;
+        mEntry = e;
         mKey = e.key;
         mLastUpdated = e.notification.getPostTime();
         mGroupId = groupId(e);
-        mListener = listener;
 
-        mPm = context.getPackageManager();
+        PackageManager pm = context.getPackageManager();
         ApplicationInfo info;
         try {
-            info = mPm.getApplicationInfo(
-                entry.notification.getPackageName(),
+            info = pm.getApplicationInfo(
+                mEntry.notification.getPackageName(),
                 PackageManager.MATCH_UNINSTALLED_PACKAGES
                     | PackageManager.MATCH_DISABLED_COMPONENTS
                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                     | PackageManager.MATCH_DIRECT_BOOT_AWARE);
             if (info != null) {
-                mAppName = String.valueOf(mPm.getApplicationLabel(info));
+                mAppName = String.valueOf(pm.getApplicationLabel(info));
             }
+            Drawable appIcon = pm.getApplicationIcon(mEntry.notification.getPackageName());
+            mUserBadgedAppIcon = pm.getUserBadgedIcon(appIcon, mEntry.notification.getUser());
         } catch (PackageManager.NameNotFoundException unused) {
-            mAppName = entry.notification.getPackageName();
+            mAppName = mEntry.notification.getPackageName();
         }
     }
 
@@ -93,12 +118,16 @@
         return mKey;
     }
 
+    public NotificationEntry getEntry() {
+        return mEntry;
+    }
+
     public String getGroupId() {
         return mGroupId;
     }
 
     public String getPackageName() {
-        return entry.notification.getPackageName();
+        return mEntry.notification.getPackageName();
     }
 
     public String getAppName() {
@@ -109,9 +138,23 @@
         return mInflated;
     }
 
-    public void updateDotVisibility() {
-        if (iconView != null) {
-            iconView.updateDotVisibility(true /* animate */);
+    void updateDotVisibility() {
+        if (mIconView != null) {
+            mIconView.updateDotVisibility(true /* animate */);
+        }
+    }
+
+    BubbleView getIconView() {
+        return mIconView;
+    }
+
+    BubbleExpandedView getExpandedView() {
+        return mExpandedView;
+    }
+
+    void cleanupExpandedState() {
+        if (mExpandedView != null) {
+            mExpandedView.cleanUpExpandedState();
         }
     }
 
@@ -119,14 +162,14 @@
         if (mInflated) {
             return;
         }
-        iconView = (BubbleView) inflater.inflate(
+        mIconView = (BubbleView) inflater.inflate(
                 R.layout.bubble_view, stackView, false /* attachToRoot */);
-        iconView.setNotif(entry);
+        mIconView.setBubble(this);
+        mIconView.setAppIcon(mUserBadgedAppIcon);
 
-        expandedView = (BubbleExpandedView) inflater.inflate(
+        mExpandedView = (BubbleExpandedView) inflater.inflate(
                 R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
-        expandedView.setEntry(entry, stackView, mAppName);
-        expandedView.setOnBlockedListener(mListener);
+        mExpandedView.setBubble(this, stackView, mAppName);
 
         mInflated = true;
     }
@@ -140,46 +183,38 @@
      * and setting {@code false} actually means rendering the expanded view in transparent.
      */
     void setContentVisibility(boolean visibility) {
-        if (expandedView != null) {
-            expandedView.setContentVisibility(visibility);
+        if (mExpandedView != null) {
+            mExpandedView.setContentVisibility(visibility);
         }
     }
 
-    void setDismissed() {
-        entry.setBubbleDismissed(true);
-        // TODO: move this somewhere where it can be guaranteed not to run until safe from flicker
-        if (expandedView != null) {
-            expandedView.cleanUpExpandedState();
-        }
-    }
-
-    void setEntry(NotificationEntry entry) {
-        this.entry = entry;
+    void updateEntry(NotificationEntry entry) {
+        mEntry = entry;
         mLastUpdated = entry.notification.getPostTime();
         if (mInflated) {
-            iconView.update(entry);
-            expandedView.update(entry);
+            mIconView.update(this);
+            mExpandedView.update(this);
         }
     }
 
     /**
      * @return the newer of {@link #getLastUpdateTime()} and {@link #getLastAccessTime()}
      */
-    public long getLastActivity() {
+    long getLastActivity() {
         return Math.max(mLastUpdated, mLastAccessed);
     }
 
     /**
      * @return the timestamp in milliseconds of the most recent notification entry for this bubble
      */
-    public long getLastUpdateTime() {
+    long getLastUpdateTime() {
         return mLastUpdated;
     }
 
     /**
      * @return the timestamp in milliseconds when this bubble was last displayed in expanded state
      */
-    public long getLastAccessTime() {
+    long getLastAccessTime() {
         return mLastAccessed;
     }
 
@@ -187,7 +222,7 @@
      * @return the display id of the virtual display on which bubble contents is drawn.
      */
     int getDisplayId() {
-        return expandedView != null ? expandedView.getVirtualDisplayId() : INVALID_DISPLAY;
+        return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY;
     }
 
     /**
@@ -195,14 +230,204 @@
      */
     void markAsAccessedAt(long lastAccessedMillis) {
         mLastAccessed = lastAccessedMillis;
-        entry.setShowInShadeWhenBubble(false);
+        setShowInShadeWhenBubble(false);
+        setShowBubbleDot(false);
     }
 
     /**
-     * @return whether bubble is from a notification associated with a foreground service.
+     * Whether this notification should be shown in the shade when it is also displayed as a
+     * bubble.
      */
-    public boolean isOngoing() {
-        return entry.isForegroundService();
+    boolean showInShadeWhenBubble() {
+        return !mEntry.isRowDismissed() && !shouldSuppressNotification()
+                && (!mEntry.isClearable() || mShowInShadeWhenBubble);
+    }
+
+    /**
+     * Sets whether this notification should be shown in the shade when it is also displayed as a
+     * bubble.
+     */
+    void setShowInShadeWhenBubble(boolean showInShade) {
+        mShowInShadeWhenBubble = showInShade;
+    }
+
+    /**
+     * Sets whether the bubble for this notification should show a dot indicating updated content.
+     */
+    void setShowBubbleDot(boolean showDot) {
+        mShowBubbleUpdateDot = showDot;
+    }
+
+    /**
+     * Whether the bubble for this notification should show a dot indicating updated content.
+     */
+    boolean showBubbleDot() {
+        return mShowBubbleUpdateDot && !mEntry.shouldSuppressNotificationDot();
+    }
+
+    /**
+     * Whether the flyout for the bubble should be shown.
+     */
+    boolean showFlyoutForBubble() {
+        return !mSuppressFlyout && !mEntry.shouldSuppressPeek()
+                && !mEntry.shouldSuppressNotificationList();
+    }
+
+    /**
+     * Set whether the flyout text for the bubble should be shown when an update is received.
+     *
+     * @param suppressFlyout whether the flyout text is shown
+     */
+    void setSuppressFlyout(boolean suppressFlyout) {
+        mSuppressFlyout = suppressFlyout;
+    }
+
+    /**
+     * Returns whether the notification for this bubble is a foreground service. It shows that this
+     * is an ongoing bubble.
+     */
+    boolean isOngoing() {
+        int flags = mEntry.notification.getNotification().flags;
+        return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
+    }
+
+    float getDesiredHeight(Context context) {
+        Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
+        boolean useRes = data.getDesiredHeightResId() != 0;
+        if (useRes) {
+            return getDimenForPackageUser(context, data.getDesiredHeightResId(),
+                    mEntry.notification.getPackageName(),
+                    mEntry.notification.getUser().getIdentifier());
+        } else {
+            return data.getDesiredHeight()
+                    * context.getResources().getDisplayMetrics().density;
+        }
+    }
+
+    String getDesiredHeightString() {
+        Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
+        boolean useRes = data.getDesiredHeightResId() != 0;
+        if (useRes) {
+            return String.valueOf(data.getDesiredHeightResId());
+        } else {
+            return String.valueOf(data.getDesiredHeight());
+        }
+    }
+
+    @Nullable
+    PendingIntent getBubbleIntent(Context context) {
+        Notification notif = mEntry.notification.getNotification();
+        Notification.BubbleMetadata data = notif.getBubbleMetadata();
+        if (BubbleController.canLaunchInActivityView(context, mEntry) && data != null) {
+            return data.getIntent();
+        }
+        return null;
+    }
+
+    Intent getSettingsIntent() {
+        final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
+        intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
+        intent.putExtra(Settings.EXTRA_APP_UID, mEntry.notification.getUid());
+        intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        return intent;
+    }
+
+    /**
+     * Returns our best guess for the most relevant text summary of the latest update to this
+     * notification, based on its type. Returns null if there should not be an update message.
+     */
+    CharSequence getUpdateMessage(Context context) {
+        final Notification underlyingNotif = mEntry.notification.getNotification();
+        final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
+
+        try {
+            if (Notification.BigTextStyle.class.equals(style)) {
+                // Return the big text, it is big so probably important. If it's not there use the
+                // normal text.
+                CharSequence bigText =
+                        underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
+                return !TextUtils.isEmpty(bigText)
+                        ? bigText
+                        : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+            } else if (Notification.MessagingStyle.class.equals(style)) {
+                final List<Notification.MessagingStyle.Message> messages =
+                        Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+                                (Parcelable[]) underlyingNotif.extras.get(
+                                        Notification.EXTRA_MESSAGES));
+
+                final Notification.MessagingStyle.Message latestMessage =
+                        Notification.MessagingStyle.findLatestIncomingMessage(messages);
+
+                if (latestMessage != null) {
+                    final CharSequence personName = latestMessage.getSenderPerson() != null
+                            ? latestMessage.getSenderPerson().getName()
+                            : null;
+
+                    // Prepend the sender name if available since group chats also use messaging
+                    // style.
+                    if (!TextUtils.isEmpty(personName)) {
+                        return context.getResources().getString(
+                                R.string.notification_summary_message_format,
+                                personName,
+                                latestMessage.getText());
+                    } else {
+                        return latestMessage.getText();
+                    }
+                }
+            } else if (Notification.InboxStyle.class.equals(style)) {
+                CharSequence[] lines =
+                        underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
+
+                // Return the last line since it should be the most recent.
+                if (lines != null && lines.length > 0) {
+                    return lines[lines.length - 1];
+                }
+            } else if (Notification.MediaStyle.class.equals(style)) {
+                // Return nothing, media updates aren't typically useful as a text update.
+                return null;
+            } else {
+                // Default to text extra.
+                return underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+            }
+        } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
+            // No use crashing, we'll just return null and the caller will assume there's no update
+            // message.
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) {
+        PackageManager pm = context.getPackageManager();
+        Resources r;
+        if (pkg != null) {
+            try {
+                if (userId == UserHandle.USER_ALL) {
+                    userId = UserHandle.USER_SYSTEM;
+                }
+                r = pm.getResourcesForApplicationAsUser(pkg, userId);
+                return r.getDimensionPixelSize(resId);
+            } catch (PackageManager.NameNotFoundException ex) {
+                // Uninstalled, don't care
+            } catch (Resources.NotFoundException e) {
+                // Invalid res id, return 0 and user our default
+                Log.e(TAG, "Couldn't find desired height res id", e);
+            }
+        }
+        return 0;
+    }
+
+    private boolean shouldSuppressNotification() {
+        return mEntry.getBubbleMetadata() != null
+                && mEntry.getBubbleMetadata().isNotificationSuppressed();
+    }
+
+    boolean shouldAutoExpand() {
+        Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
+        return metadata != null && metadata.getAutoExpandBubble();
     }
 
     @Override
@@ -210,6 +435,20 @@
         return "Bubble{" + mKey + '}';
     }
 
+    /**
+     * Description of current bubble state.
+     */
+    public void dump(
+            @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.print("key: "); pw.println(mKey);
+        pw.print("  showInShade:   "); pw.println(showInShadeWhenBubble());
+        pw.print("  showDot:       "); pw.println(showBubbleDot());
+        pw.print("  showFlyout:    "); pw.println(showFlyoutForBubble());
+        pw.print("  desiredHeight: "); pw.println(getDesiredHeightString());
+        pw.print("  suppressNotif: "); pw.println(shouldSuppressNotification());
+        pw.print("  autoExpand:    "); pw.println(shouldAutoExpand());
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index a23c99e..7d9bb07 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -16,21 +16,24 @@
 
 package com.android.systemui.bubbles;
 
+import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
 import static android.app.Notification.FLAG_BUBBLE;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
 
@@ -39,9 +42,8 @@
 import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-import android.app.ActivityManager;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -53,10 +55,11 @@
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
+import android.util.SparseSetArray;
 import android.view.Display;
 import android.view.IPinnedStackController;
 import android.view.IPinnedStackListener;
@@ -75,18 +78,23 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 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.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
+import java.util.ArrayList;
 import java.util.List;
 
 import javax.inject.Inject;
@@ -101,12 +109,12 @@
 @Singleton
 public class BubbleController implements ConfigurationController.ConfigurationListener {
 
-    private static final String TAG = "BubbleController";
-    private static final boolean DEBUG = false;
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
 
     @Retention(SOURCE)
     @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
-            DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE})
+            DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
+            DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED})
     @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
     @interface DismissReason {}
 
@@ -117,24 +125,13 @@
     static final int DISMISS_NOTIF_CANCEL = 5;
     static final int DISMISS_ACCESSIBILITY_ACTION = 6;
     static final int DISMISS_NO_LONGER_BUBBLE = 7;
+    static final int DISMISS_USER_CHANGED = 8;
+    static final int DISMISS_GROUP_CANCELLED = 9;
 
     public static final int MAX_BUBBLES = 5; // TODO: actually enforce this
 
-    // Enables some subset of notifs to automatically become bubbles
-    public static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
-
     /** Flag to enable or disable the entire feature */
     private static final String ENABLE_BUBBLES = "experiment_enable_bubbles";
-    /** Auto bubble flags set whether different notif types should be presented as a bubble */
-    private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging";
-    private static final String ENABLE_AUTO_BUBBLE_ONGOING = "experiment_autobubble_ongoing";
-    private static final String ENABLE_AUTO_BUBBLE_ALL = "experiment_autobubble_all";
-
-    /** Use an activityView for an auto-bubbled notifs if it has an appropriate content intent */
-    private static final String ENABLE_BUBBLE_CONTENT_INTENT = "experiment_bubble_content_intent";
-
-    private static final String BUBBLE_STIFFNESS = "experiment_bubble_stiffness";
-    private static final String BUBBLE_BOUNCINESS = "experiment_bubble_bounciness";
 
     private final Context mContext;
     private final NotificationEntryManager mNotificationEntryManager;
@@ -142,10 +139,16 @@
     private BubbleStateChangeListener mStateChangeListener;
     private BubbleExpandListener mExpandListener;
     @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
+    private final NotificationGroupManager mNotificationGroupManager;
 
     private BubbleData mBubbleData;
     @Nullable private BubbleStackView mStackView;
 
+    // Tracks the id of the current (foreground) user.
+    private int mCurrentUserId;
+    // Saves notification keys of active bubbles when users are switched.
+    private final SparseSetArray<String> mSavedBubbleKeysPerUser;
+
     // Bubbles get added to the status bar view
     private final StatusBarWindowController mStatusBarWindowController;
     private final ZenModeController mZenModeController;
@@ -157,6 +160,9 @@
     // Used for determining view rect for touch interaction
     private Rect mTempRect = new Rect();
 
+    // Listens to user switch so bubbles can be saved and restored.
+    private final NotificationLockscreenUserManager mNotifUserManager;
+
     /** Last known orientation, used to detect orientation changes in {@link #onConfigChanged}. */
     private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
 
@@ -211,28 +217,38 @@
     public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
             BubbleData data, ConfigurationController configurationController,
             NotificationInterruptionStateProvider interruptionStateProvider,
-            ZenModeController zenModeController) {
+            ZenModeController zenModeController,
+            NotificationLockscreenUserManager notifUserManager,
+            NotificationGroupManager groupManager) {
         this(context, statusBarWindowController, data, null /* synchronizer */,
-                configurationController, interruptionStateProvider, zenModeController);
+                configurationController, interruptionStateProvider, zenModeController,
+                notifUserManager, groupManager);
     }
 
     public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
             BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
             ConfigurationController configurationController,
             NotificationInterruptionStateProvider interruptionStateProvider,
-            ZenModeController zenModeController) {
+            ZenModeController zenModeController,
+            NotificationLockscreenUserManager notifUserManager,
+            NotificationGroupManager groupManager) {
         mContext = context;
         mNotificationInterruptionStateProvider = interruptionStateProvider;
+        mNotifUserManager = notifUserManager;
         mZenModeController = zenModeController;
         mZenModeController.addCallback(new ZenModeController.Callback() {
             @Override
             public void onZenChanged(int zen) {
-                updateStackViewForZenConfig();
+                if (mStackView != null) {
+                    mStackView.updateDots();
+                }
             }
 
             @Override
             public void onConfigChanged(ZenModeConfig config) {
-                updateStackViewForZenConfig();
+                if (mStackView != null) {
+                    mStackView.updateDots();
+                }
             }
         });
 
@@ -244,6 +260,24 @@
         mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
         mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
         mNotificationEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
+        mNotificationGroupManager = groupManager;
+        mNotificationGroupManager.addOnGroupChangeListener(
+                new NotificationGroupManager.OnGroupChangeListener() {
+                    @Override
+                    public void onGroupSuppressionChanged(
+                            NotificationGroupManager.NotificationGroup group,
+                            boolean suppressed) {
+                        // More notifications could be added causing summary to no longer
+                        // be suppressed -- in this case need to remove the key.
+                        final String groupKey = group.summary != null
+                                ? group.summary.notification.getGroupKey()
+                                : null;
+                        if (!suppressed && groupKey != null
+                                && mBubbleData.isSummarySuppressed(groupKey)) {
+                            mBubbleData.removeSuppressedSummary(groupKey);
+                        }
+                    }
+                });
 
         mStatusBarWindowController = statusBarWindowController;
         mStatusBarStateListener = new StatusBarStateListener();
@@ -261,6 +295,16 @@
 
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+
+        mSavedBubbleKeysPerUser = new SparseSetArray<>();
+        mCurrentUserId = mNotifUserManager.getCurrentUserId();
+        mNotifUserManager.addUserChangedListener(
+                newUserId -> {
+                    saveBubbles(mCurrentUserId);
+                    mBubbleData.dismissAll(DISMISS_USER_CHANGED);
+                    restoreBubbles(newUserId);
+                    mCurrentUserId = newUserId;
+                });
     }
 
     /**
@@ -271,19 +315,55 @@
         if (mStackView == null) {
             mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer);
             ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
-            // TODO(b/130237686): When you expand the shade on top of expanded bubble, there is no
-            //  scrim between bubble and the shade
-            int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1;
-            sbv.addView(mStackView, bubblePosition,
+            int bubbleScrimIndex = sbv.indexOfChild(sbv.findViewById(R.id.scrim_for_bubble));
+            int stackIndex = bubbleScrimIndex + 1;  // Show stack above bubble scrim.
+            sbv.addView(mStackView, stackIndex,
                     new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
             if (mExpandListener != null) {
                 mStackView.setExpandListener(mExpandListener);
             }
-
-            updateStackViewForZenConfig();
         }
     }
 
+    /**
+     * Records the notification key for any active bubbles. These are used to restore active
+     * bubbles when the user returns to the foreground.
+     *
+     * @param userId the id of the user
+     */
+    private void saveBubbles(@UserIdInt int userId) {
+        // First clear any existing keys that might be stored.
+        mSavedBubbleKeysPerUser.remove(userId);
+        // Add in all active bubbles for the current user.
+        for (Bubble bubble: mBubbleData.getBubbles()) {
+            mSavedBubbleKeysPerUser.add(userId, bubble.getKey());
+        }
+    }
+
+    /**
+     * Promotes existing notifications to Bubbles if they were previously bubbles.
+     *
+     * @param userId the id of the user
+     */
+    private void restoreBubbles(@UserIdInt int userId) {
+        NotificationData notificationData =
+                mNotificationEntryManager.getNotificationData();
+        ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId);
+        if (savedBubbleKeys == null) {
+            // There were no bubbles saved for this used.
+            return;
+        }
+        for (NotificationEntry e : notificationData.getNotificationsForCurrentUser()) {
+            if (savedBubbleKeys.contains(e.key)
+                    && mNotificationInterruptionStateProvider.shouldBubbleUp(e)
+                    && canLaunchInActivityView(mContext, e)) {
+                updateBubble(e, /* suppressFlyout= */ true);
+            }
+        }
+        // Finally, remove the entries for this user now that bubbles are restored.
+        mSavedBubbleKeysPerUser.remove(mCurrentUserId);
+    }
+
     @Override
     public void onUiModeChanged() {
         if (mStackView != null) {
@@ -301,8 +381,8 @@
     @Override
     public void onConfigChanged(Configuration newConfig) {
         if (mStackView != null && newConfig != null && newConfig.orientation != mOrientation) {
-            mStackView.onOrientationChanged();
             mOrientation = newConfig.orientation;
+            mStackView.onOrientationChanged(newConfig.orientation);
         }
     }
 
@@ -360,6 +440,24 @@
         mBubbleData.setExpanded(false /* expanded */);
     }
 
+    /**
+     * True if either:
+     * (1) There is a bubble associated with the provided key and if its notification is hidden
+     *     from the shade.
+     * (2) There is a group summary associated with the provided key that is hidden from the shade
+     *     because it has been dismissed but still has child bubbles active.
+     *
+     * False otherwise.
+     */
+    public boolean isBubbleNotificationSuppressedFromShade(String key) {
+        boolean isBubbleAndSuppressed = mBubbleData.hasBubbleWithKey(key)
+                && !mBubbleData.getBubbleWithKey(key).showInShadeWhenBubble();
+        NotificationEntry entry = mNotificationEntryManager.getNotificationData().get(key);
+        String groupKey = entry != null ? entry.notification.getGroupKey() : null;
+        boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey);
+        return isSuppressedSummary || isBubbleAndSuppressed;
+    }
+
     void selectBubble(Bubble bubble) {
         mBubbleData.setSelectedBubble(bubble);
     }
@@ -406,11 +504,15 @@
      * @param notif the notification associated with this bubble.
      */
     void updateBubble(NotificationEntry notif) {
+        updateBubble(notif, /* supressFlyout */ false);
+    }
+
+    void updateBubble(NotificationEntry notif, boolean suppressFlyout) {
         // If this is an interruptive notif, mark that it's interrupted
         if (notif.importance >= NotificationManager.IMPORTANCE_HIGH) {
             notif.setInterruption();
         }
-        mBubbleData.notificationEntryUpdated(notif);
+        mBubbleData.notificationEntryUpdated(notif, suppressFlyout);
     }
 
     /**
@@ -423,7 +525,7 @@
         // TEMP: refactor to change this to pass entry
         Bubble bubble = mBubbleData.getBubbleWithKey(key);
         if (bubble != null) {
-            mBubbleData.notificationEntryRemoved(bubble.entry, reason);
+            mBubbleData.notificationEntryRemoved(bubble.getEntry(), reason);
         }
     }
 
@@ -432,33 +534,50 @@
             new NotificationRemoveInterceptor() {
             @Override
             public boolean onNotificationRemoveRequested(String key, int reason) {
-                if (!mBubbleData.hasBubbleWithKey(key)) {
+                NotificationEntry entry = mNotificationEntryManager.getNotificationData().get(key);
+                String groupKey = entry != null ? entry.notification.getGroupKey() : null;
+                ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
+
+                boolean inBubbleData = mBubbleData.hasBubbleWithKey(key);
+                boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
+                        && mBubbleData.getSummaryKey(groupKey).equals(key));
+                boolean isSummary = entry != null
+                        && entry.notification.getNotification().isGroupSummary();
+                boolean isSummaryOfBubbles = (isSuppressedSummary || isSummary)
+                        && bubbleChildren != null && !bubbleChildren.isEmpty();
+
+                if (!inBubbleData && !isSummaryOfBubbles) {
                     return false;
                 }
-                NotificationEntry entry = mBubbleData.getBubbleWithKey(key).entry;
 
                 final boolean isClearAll = reason == REASON_CANCEL_ALL;
-                final boolean isUserDimiss = reason == REASON_CANCEL;
+                final boolean isUserDimiss = reason == REASON_CANCEL || reason == REASON_CLICK;
                 final boolean isAppCancel = reason == REASON_APP_CANCEL
                         || reason == REASON_APP_CANCEL_ALL;
+                final boolean isSummaryCancel = reason == REASON_GROUP_SUMMARY_CANCELED;
 
                 // Need to check for !appCancel here because the notification may have
                 // previously been dismissed & entry.isRowDismissed would still be true
                 boolean userRemovedNotif = (entry.isRowDismissed() && !isAppCancel)
-                        || isClearAll || isUserDimiss;
+                        || isClearAll || isUserDimiss || isSummaryCancel;
+
+                if (isSummaryOfBubbles) {
+                    return handleSummaryRemovalInterception(entry, userRemovedNotif);
+                }
 
                 // The bubble notification sticks around in the data as long as the bubble is
                 // not dismissed and the app hasn't cancelled the notification.
-                boolean bubbleExtended = entry.isBubble() && !entry.isBubbleDismissed()
-                        && userRemovedNotif;
+                Bubble bubble = mBubbleData.getBubbleWithKey(key);
+                boolean bubbleExtended = entry.isBubble() && userRemovedNotif;
                 if (bubbleExtended) {
-                    entry.setShowInShadeWhenBubble(false);
+                    bubble.setShowInShadeWhenBubble(false);
+                    bubble.setShowBubbleDot(false);
                     if (mStackView != null) {
                         mStackView.updateDotVisibility(entry.key);
                     }
                     mNotificationEntryManager.updateNotifications();
                     return true;
-                } else if (!userRemovedNotif && !entry.isBubbleDismissed()) {
+                } else if (!userRemovedNotif) {
                     // This wasn't a user removal so we should remove the bubble as well
                     mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
                     return false;
@@ -467,6 +586,52 @@
             }
         };
 
+    private boolean handleSummaryRemovalInterception(NotificationEntry summary,
+            boolean userRemovedNotif) {
+        String groupKey = summary.notification.getGroupKey();
+        ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
+
+        if (userRemovedNotif) {
+            // If it's a user dismiss we mark the children to be hidden from the shade.
+            for (int i = 0; i < bubbleChildren.size(); i++) {
+                Bubble bubbleChild = bubbleChildren.get(i);
+                // As far as group manager is concerned, once a child is no longer shown
+                // in the shade, it is essentially removed.
+                mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
+                bubbleChild.setShowInShadeWhenBubble(false);
+                bubbleChild.setShowBubbleDot(false);
+                if (mStackView != null) {
+                    mStackView.updateDotVisibility(bubbleChild.getKey());
+                }
+            }
+            // And since all children are removed, remove the summary.
+            mNotificationGroupManager.onEntryRemoved(summary);
+
+            // If the summary was auto-generated we don't need to keep that notification around
+            // because apps can't cancel it; so we only intercept & suppress real summaries.
+            boolean isAutogroupSummary = (summary.notification.getNotification().flags
+                    & FLAG_AUTOGROUP_SUMMARY) != 0;
+            if (!isAutogroupSummary) {
+                mBubbleData.addSummaryToSuppress(summary.notification.getGroupKey(),
+                        summary.key);
+                // Tell shade to update for the suppression
+                mNotificationEntryManager.updateNotifications();
+            }
+            return !isAutogroupSummary;
+        } else {
+            // If it's not a user dismiss it's a cancel.
+            mBubbleData.removeSuppressedSummary(groupKey);
+
+            // Remove any associated bubble children.
+            for (int i = 0; i < bubbleChildren.size(); i++) {
+                Bubble bubbleChild = bubbleChildren.get(i);
+                mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(),
+                        DISMISS_GROUP_CANCELLED);
+            }
+            return false;
+        }
+    }
+
     @SuppressWarnings("FieldCanBeLocal")
     private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
         @Override
@@ -476,17 +641,6 @@
             }
             if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                     && canLaunchInActivityView(mContext, entry)) {
-                updateShowInShadeForSuppressNotification(entry);
-            }
-        }
-
-        @Override
-        public void onEntryInflated(NotificationEntry entry, @InflationFlag int inflatedFlags) {
-            if (!areBubblesEnabled(mContext)) {
-                return;
-            }
-            if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
-                    && canLaunchInActivityView(mContext, entry)) {
                 updateBubble(entry);
             }
         }
@@ -502,8 +656,7 @@
                 // It was previously a bubble but no longer a bubble -- lets remove it
                 removeBubble(entry.key, DISMISS_NO_LONGER_BUBBLE);
             } else if (shouldBubble) {
-                updateShowInShadeForSuppressNotification(entry);
-                entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed
+                Bubble b = mBubbleData.getBubbleWithKey(entry.key);
                 updateBubble(entry);
             }
         }
@@ -540,27 +693,62 @@
             }
 
             // Do removals, if any.
-            for (Pair<Bubble, Integer> removed : update.removedBubbles) {
+            ArrayList<Pair<Bubble, Integer>> removedBubbles =
+                    new ArrayList<>(update.removedBubbles);
+            for (Pair<Bubble, Integer> removed : removedBubbles) {
                 final Bubble bubble = removed.first;
                 @DismissReason final int reason = removed.second;
                 mStackView.removeBubble(bubble);
 
-                if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
-                        && !bubble.entry.showInShadeWhenBubble()) {
-                    // The bubble is gone & the notification is gone, time to actually remove it
-                    mNotificationEntryManager.performRemoveNotification(bubble.entry.notification,
-                            UNDEFINED_DISMISS_REASON);
-                } else {
-                    // Update the flag for SysUI
-                    bubble.entry.notification.getNotification().flags &= ~FLAG_BUBBLE;
+                // If the bubble is removed for user switching, leave the notification in place.
+                if (reason != DISMISS_USER_CHANGED) {
+                    if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
+                            && !bubble.showInShadeWhenBubble()) {
+                        // The bubble is gone & the notification is gone, time to actually remove it
+                        mNotificationEntryManager.performRemoveNotification(
+                                bubble.getEntry().notification, UNDEFINED_DISMISS_REASON);
+                    } else {
+                        // Update the flag for SysUI
+                        bubble.getEntry().notification.getNotification().flags &= ~FLAG_BUBBLE;
 
-                    // Make sure NoMan knows it's not a bubble anymore so anyone querying it will
-                    // get right result back
-                    try {
-                        mBarService.onNotificationBubbleChanged(bubble.getKey(),
-                                false /* isBubble */);
-                    } catch (RemoteException e) {
-                        // Bad things have happened
+                        // Make sure NoMan knows it's not a bubble anymore so anyone querying it
+                        // will get right result back
+                        try {
+                            mBarService.onNotificationBubbleChanged(bubble.getKey(),
+                                    false /* isBubble */);
+                        } catch (RemoteException e) {
+                            // Bad things have happened
+                        }
+                    }
+
+                    // Check if removed bubble has an associated suppressed group summary that needs
+                    // to be removed now.
+                    final String groupKey = bubble.getEntry().notification.getGroupKey();
+                    if (mBubbleData.isSummarySuppressed(groupKey)
+                            && mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
+                        // Time to actually remove the summary.
+                        String notifKey = mBubbleData.getSummaryKey(groupKey);
+                        mBubbleData.removeSuppressedSummary(groupKey);
+                        NotificationEntry entry =
+                                mNotificationEntryManager.getNotificationData().get(notifKey);
+                        if (entry == null) {
+                            Log.w("mady", "WTF summary isn't in data... " + notifKey);
+                            return;
+                        }
+                        mNotificationEntryManager.performRemoveNotification(
+                                entry.notification, UNDEFINED_DISMISS_REASON);
+                    }
+
+                    // Check if summary should be removed from NoManGroup
+                    NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary(
+                            bubble.getEntry().notification);
+                    if (summary != null) {
+                        ArrayList<NotificationEntry> summaryChildren =
+                                mNotificationGroupManager.getLogicalChildren(summary.notification);
+                        if (summaryChildren == null || summaryChildren.isEmpty()) {
+                            mNotificationEntryManager.performRemoveNotification(
+                                    summary.notification, UNDEFINED_DISMISS_REASON);
+                        }
                     }
                 }
             }
@@ -575,6 +763,10 @@
 
             if (update.selectionChanged) {
                 mStackView.setSelectedBubble(update.selectedBubble);
+                if (update.selectedBubble != null) {
+                    mNotificationGroupManager.updateSuppression(
+                            update.selectedBubble.getEntry());
+                }
             }
 
             // Expanding? Apply this last.
@@ -585,7 +777,7 @@
             mNotificationEntryManager.updateNotifications();
             updateStack();
 
-            if (DEBUG) {
+            if (DEBUG_BUBBLE_CONTROLLER) {
                 Log.d(TAG, "[BubbleData]");
                 Log.d(TAG, formatBubblesString(mBubbleData.getBubbles(),
                         mBubbleData.getSelectedBubble()));
@@ -600,34 +792,6 @@
     };
 
     /**
-     * Updates the stack view's suppression flags from the latest config from the zen (do not
-     * disturb) controller.
-     */
-    private void updateStackViewForZenConfig() {
-        final ZenModeConfig zenModeConfig = mZenModeController.getConfig();
-
-        if (zenModeConfig == null || mStackView == null) {
-            return;
-        }
-
-        final int suppressedEffects = zenModeConfig.suppressedVisualEffects;
-        final boolean hideNotificationDotsSelected =
-                (suppressedEffects & SUPPRESSED_EFFECT_BADGE) != 0;
-        final boolean dontPopNotifsOnScreenSelected =
-                (suppressedEffects & SUPPRESSED_EFFECT_PEEK) != 0;
-        final boolean hideFromPullDownShadeSelected =
-                (suppressedEffects & SUPPRESSED_EFFECT_NOTIFICATION_LIST) != 0;
-
-        final boolean dndEnabled = mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF;
-
-        mStackView.setSuppressNewDot(
-                dndEnabled && hideNotificationDotsSelected);
-        mStackView.setSuppressFlyout(
-                dndEnabled && (dontPopNotifsOnScreenSelected
-                        || hideFromPullDownShadeSelected));
-    }
-
-    /**
      * Lets any listeners know if bubble state has changed.
      * Updates the visibility of the bubbles based on current state.
      * Does not un-bubble, just hides or un-hides. Notifies any
@@ -697,46 +861,16 @@
     }
 
     /**
-     * Whether the notification should automatically bubble or not. Gated by secure settings flags.
+     * Description of current bubble state.
      */
-    @VisibleForTesting
-    protected boolean shouldAutoBubbleForFlags(Context context, NotificationEntry entry) {
-        if (entry.isBubbleDismissed()) {
-            return false;
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("BubbleController state:");
+        mBubbleData.dump(fd, pw, args);
+        pw.println();
+        if (mStackView != null) {
+            mStackView.dump(fd, pw, args);
         }
-        StatusBarNotification n = entry.notification;
-
-        boolean autoBubbleMessages = shouldAutoBubbleMessages(context) || DEBUG_ENABLE_AUTO_BUBBLE;
-        boolean autoBubbleOngoing = shouldAutoBubbleOngoing(context) || DEBUG_ENABLE_AUTO_BUBBLE;
-        boolean autoBubbleAll = shouldAutoBubbleAll(context) || DEBUG_ENABLE_AUTO_BUBBLE;
-
-        boolean hasRemoteInput = false;
-        if (n.getNotification().actions != null) {
-            for (Notification.Action action : n.getNotification().actions) {
-                if (action.getRemoteInputs() != null) {
-                    hasRemoteInput = true;
-                    break;
-                }
-            }
-        }
-        boolean isCall = Notification.CATEGORY_CALL.equals(n.getNotification().category)
-                && n.isOngoing();
-        boolean isMusic = n.getNotification().hasMediaSession();
-        boolean isImportantOngoing = isMusic || isCall;
-
-        Class<? extends Notification.Style> style = n.getNotification().getNotificationStyle();
-        boolean isMessageType = Notification.CATEGORY_MESSAGE.equals(n.getNotification().category);
-        boolean isMessageStyle = Notification.MessagingStyle.class.equals(style);
-        return (((isMessageType && hasRemoteInput) || isMessageStyle) && autoBubbleMessages)
-                || (isImportantOngoing && autoBubbleOngoing)
-                || autoBubbleAll;
-    }
-
-    private void updateShowInShadeForSuppressNotification(NotificationEntry entry) {
-        boolean suppressNotification = entry.getBubbleMetadata() != null
-                && entry.getBubbleMetadata().isNotificationSuppressed()
-                && isForegroundApp(mContext, entry.notification.getPackageName());
-        entry.setShowInShadeWhenBubble(!suppressNotification);
+        pw.println();
     }
 
     static String formatBubblesString(List<Bubble> bubbles, Bubble selected) {
@@ -757,18 +891,6 @@
     }
 
     /**
-     * Return true if the applications with the package name is running in foreground.
-     *
-     * @param context application context.
-     * @param pkgName application package name.
-     */
-    public static boolean isForegroundApp(Context context, String pkgName) {
-        ActivityManager am = context.getSystemService(ActivityManager.class);
-        List<RunningTaskInfo> tasks = am.getRunningTasks(1 /* maxNum */);
-        return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
-    }
-
-    /**
      * This task stack listener is responsible for responding to tasks moved to the front
      * which are on the default (main) display. When this happens, expanded bubbles must be
      * collapsed so the user may interact with the app which was just moved to the front.
@@ -807,26 +929,18 @@
                 expandedBubble.setContentVisibility(true);
             }
         }
-    }
 
-    private static boolean shouldAutoBubbleMessages(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ENABLE_AUTO_BUBBLE_MESSAGES, 0) != 0;
-    }
-
-    private static boolean shouldAutoBubbleOngoing(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ENABLE_AUTO_BUBBLE_ONGOING, 0) != 0;
-    }
-
-    private static boolean shouldAutoBubbleAll(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ENABLE_AUTO_BUBBLE_ALL, 0) != 0;
-    }
-
-    static boolean shouldUseContentIntent(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0;
+        @Override
+        public void onSingleTaskDisplayEmpty(int displayId) {
+            final Bubble expandedBubble = getExpandedBubble(mContext);
+            if (expandedBubble == null) {
+                return;
+            }
+            if (expandedBubble.getDisplayId() == displayId) {
+                mBubbleData.setExpanded(false);
+                expandedBubble.getExpandedView().notifyDisplayEmpty();
+            }
+        }
     }
 
     private static boolean areBubblesEnabled(Context context) {
@@ -834,20 +948,6 @@
                 ENABLE_BUBBLES, 1) != 0;
     }
 
-    /** Default stiffness to use for bubble physics animations. */
-    public static int getBubbleStiffness(Context context, int defaultStiffness) {
-        return Settings.Secure.getInt(
-                context.getContentResolver(), BUBBLE_STIFFNESS, defaultStiffness);
-    }
-
-    /** Default bounciness/damping ratio to use for bubble physics animations. */
-    public static float getBubbleBounciness(Context context, float defaultBounciness) {
-        return Settings.Secure.getInt(
-                context.getContentResolver(),
-                BUBBLE_BOUNCINESS,
-                (int) (defaultBounciness * 100)) / 100f;
-    }
-
     /**
      * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 5575b35..81c8da8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -16,6 +16,9 @@
 package com.android.systemui.bubbles;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
 import static java.util.stream.Collectors.toList;
 
@@ -33,11 +36,12 @@
 import com.android.systemui.bubbles.BubbleController.DismissReason;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -51,8 +55,7 @@
 @Singleton
 public class BubbleData {
 
-    private static final String TAG = "BubbleData";
-    private static final boolean DEBUG = false;
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleData" : TAG_BUBBLES;
 
     private static final int MAX_BUBBLES = 5;
 
@@ -123,6 +126,19 @@
     @Nullable
     private Listener mListener;
 
+    /**
+     * We track groups with summaries that aren't visibly displayed but still kept around because
+     * the bubble(s) associated with the summary still exist.
+     *
+     * The summary must be kept around so that developers can cancel it (and hence the bubbles
+     * associated with it). This list is used to check if the summary should be hidden from the
+     * shade.
+     *
+     * Key: group key of the NotificationEntry
+     * Value: key of the NotificationEntry
+     */
+    private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
+
     @Inject
     public BubbleData(Context context) {
         mContext = context;
@@ -148,7 +164,7 @@
     }
 
     public void setExpanded(boolean expanded) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "setExpanded: " + expanded);
         }
         setExpandedInternal(expanded);
@@ -156,29 +172,30 @@
     }
 
     public void setSelectedBubble(Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "setSelectedBubble: " + bubble);
         }
         setSelectedBubbleInternal(bubble);
         dispatchPendingChanges();
     }
 
-    public void notificationEntryUpdated(NotificationEntry entry) {
-        if (DEBUG) {
+    void notificationEntryUpdated(NotificationEntry entry, boolean suppressFlyout) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "notificationEntryUpdated: " + entry);
         }
         Bubble bubble = getBubbleWithKey(entry.key);
         if (bubble == null) {
             // Create a new bubble
-            bubble = new Bubble(mContext, entry, this::onBubbleBlocked);
+            bubble = new Bubble(mContext, entry);
+            bubble.setSuppressFlyout(suppressFlyout);
             doAdd(bubble);
             trim();
         } else {
             // Updates an existing bubble
-            bubble.setEntry(entry);
+            bubble.updateEntry(entry);
             doUpdate(bubble);
         }
-        if (shouldAutoExpand(entry)) {
+        if (bubble.shouldAutoExpand()) {
             setSelectedBubbleInternal(bubble);
             if (!mExpanded) {
                 setExpandedInternal(true);
@@ -186,11 +203,14 @@
         } else if (mSelectedBubble == null) {
             setSelectedBubbleInternal(bubble);
         }
+        boolean isBubbleExpandedAndSelected = mExpanded && mSelectedBubble == bubble;
+        bubble.setShowInShadeWhenBubble(!isBubbleExpandedAndSelected);
+        bubble.setShowBubbleDot(!isBubbleExpandedAndSelected);
         dispatchPendingChanges();
     }
 
     public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason);
         }
         doRemove(entry.key, reason);
@@ -222,8 +242,59 @@
         dispatchPendingChanges();
     }
 
+    /**
+     * Adds a group key indicating that the summary for this group should be suppressed.
+     *
+     * @param groupKey the group key of the group whose summary should be suppressed.
+     * @param notifKey the notification entry key of that summary.
+     */
+    void addSummaryToSuppress(String groupKey, String notifKey) {
+        mSuppressedGroupKeys.put(groupKey, notifKey);
+    }
+
+    /**
+     * Retrieves the notif entry key of the summary associated with the provided group key.
+     *
+     * @param groupKey the group to look up
+     * @return the key for the {@link NotificationEntry} that is the summary of this group.
+     */
+    String getSummaryKey(String groupKey) {
+        return mSuppressedGroupKeys.get(groupKey);
+    }
+
+    /**
+     * Removes a group key indicating that summary for this group should no longer be suppressed.
+     */
+    void removeSuppressedSummary(String groupKey) {
+        mSuppressedGroupKeys.remove(groupKey);
+    }
+
+    /**
+     * Whether the summary for the provided group key is suppressed.
+     */
+    boolean isSummarySuppressed(String groupKey) {
+        return mSuppressedGroupKeys.containsKey(groupKey);
+    }
+
+    /**
+     * Retrieves any bubbles that are part of the notification group represented by the provided
+     * group key.
+     */
+    ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey) {
+        ArrayList<Bubble> bubbleChildren = new ArrayList<>();
+        if (groupKey == null) {
+            return bubbleChildren;
+        }
+        for (Bubble b : mBubbles) {
+            if (groupKey.equals(b.getEntry().notification.getGroupKey())) {
+                bubbleChildren.add(b);
+            }
+        }
+        return bubbleChildren;
+    }
+
     private void doAdd(Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "doAdd: " + bubble);
         }
         int minInsertPoint = 0;
@@ -256,7 +327,7 @@
     }
 
     private void doUpdate(Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "doUpdate: " + bubble);
         }
         mStateChange.updatedBubble = bubble;
@@ -301,12 +372,11 @@
             Bubble newSelected = mBubbles.get(newIndex);
             setSelectedBubbleInternal(newSelected);
         }
-        bubbleToRemove.setDismissed();
-        maybeSendDeleteIntent(reason, bubbleToRemove.entry);
+        maybeSendDeleteIntent(reason, bubbleToRemove.getEntry());
     }
 
     public void dismissAll(@DismissReason int reason) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "dismissAll: reason=" + reason);
         }
         if (mBubbles.isEmpty()) {
@@ -316,8 +386,7 @@
         setSelectedBubbleInternal(null);
         while (!mBubbles.isEmpty()) {
             Bubble bubble = mBubbles.remove(0);
-            bubble.setDismissed();
-            maybeSendDeleteIntent(reason, bubble.entry);
+            maybeSendDeleteIntent(reason, bubble.getEntry());
             mStateChange.bubbleRemoved(bubble, reason);
         }
         dispatchPendingChanges();
@@ -336,7 +405,7 @@
      * @param bubble the new selected bubble
      */
     private void setSelectedBubbleInternal(@Nullable Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
         }
         if (Objects.equals(bubble, mSelectedBubble)) {
@@ -361,7 +430,7 @@
      * @param shouldExpand the new requested state
      */
     private void setExpandedInternal(boolean shouldExpand) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "setExpandedInternal: shouldExpand=" + shouldExpand);
         }
         if (mExpanded == shouldExpand) {
@@ -396,7 +465,7 @@
                     // bubble remains on top.
                     mBubbles.remove(mSelectedBubble);
                     mBubbles.add(0, mSelectedBubble);
-                    packGroup(0);
+                    mStateChange.orderChanged |= packGroup(0);
                 }
             }
         }
@@ -466,7 +535,7 @@
      * @return true if the position of any bubbles has changed as a result
      */
     private boolean packGroup(int position) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "packGroup: position=" + position);
         }
         Bubble groupStart = mBubbles.get(position);
@@ -495,7 +564,7 @@
      * @return true if the position of any bubbles changed as a result
      */
     private boolean repackAll() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "repackAll()");
         }
         if (mBubbles.isEmpty()) {
@@ -550,28 +619,6 @@
         }
     }
 
-    private void onBubbleBlocked(NotificationEntry entry) {
-        final String blockedGroupId = Bubble.groupId(entry);
-        int selectedIndex = mBubbles.indexOf(mSelectedBubble);
-        for (Iterator<Bubble> i = mBubbles.iterator(); i.hasNext(); ) {
-            Bubble bubble = i.next();
-            if (bubble.getGroupId().equals(blockedGroupId)) {
-                mStateChange.bubbleRemoved(bubble, BubbleController.DISMISS_BLOCKED);
-                i.remove();
-            }
-        }
-        if (mBubbles.isEmpty()) {
-            setExpandedInternal(false);
-            setSelectedBubbleInternal(null);
-        } else if (!mBubbles.contains(mSelectedBubble)) {
-            // choose a new one
-            int newIndex = Math.min(selectedIndex, mBubbles.size() - 1);
-            Bubble newSelected = mBubbles.get(newIndex);
-            setSelectedBubbleInternal(newSelected);
-        }
-        dispatchPendingChanges();
-    }
-
     private int indexForKey(String key) {
         for (int i = 0; i < mBubbles.size(); i++) {
             Bubble bubble = mBubbles.get(i);
@@ -610,9 +657,21 @@
         mListener = listener;
     }
 
-    boolean shouldAutoExpand(NotificationEntry entry) {
-        Notification.BubbleMetadata metadata = entry.getBubbleMetadata();
-        return metadata != null && metadata.getAutoExpandBubble()
-                && BubbleController.isForegroundApp(mContext, entry.notification.getPackageName());
+    /**
+     * Description of current bubble data state.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.print("selected: "); pw.println(mSelectedBubble != null
+                ? mSelectedBubble.getKey()
+                : "null");
+        pw.print("expanded: "); pw.println(mExpanded);
+        pw.print("count:    "); pw.println(mBubbles.size());
+        for (Bubble bubble : mBubbles) {
+            bubble.dump(fd, pw, args);
+        }
+        pw.print("summaryKeys: "); pw.println(mSuppressedGroupKeys.size());
+        for (String key : mSuppressedGroupKeys.keySet()) {
+            pw.println("   suppressing: " + key);
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
new file mode 100644
index 0000000..b702d06
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+/**
+ * Common class for the various debug {@link android.util.Log} output configuration in the Bubbles
+ * package.
+ */
+public class BubbleDebugConfig {
+
+    // All output logs in the Bubbles package use the {@link #TAG_BUBBLES} string for tagging their
+    // log output. This makes it easy to identify the origin of the log message when sifting
+    // through a large amount of log output from multiple sources. However, it also makes trying
+    // to figure-out the origin of a log message while debugging the Bubbles a little painful. By
+    // setting this constant to true, log messages from the Bubbles package will be tagged with
+    // their class names instead fot the generic tag.
+    static final boolean TAG_WITH_CLASS_NAME = false;
+
+    // Default log tag for the Bubbles package.
+    static final String TAG_BUBBLES = "Bubbles";
+
+    static final boolean DEBUG_BUBBLE_CONTROLLER = false;
+    static final boolean DEBUG_BUBBLE_DATA = false;
+    static final boolean DEBUG_BUBBLE_STACK_VIEW = false;
+    static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java
index 4db1e27..9db371e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java
@@ -16,18 +16,13 @@
 
 package com.android.systemui.bubbles;
 
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringAnimation;
@@ -37,14 +32,13 @@
 
 /** Dismiss view that contains a scrim gradient, as well as a dismiss icon, text, and circle. */
 public class BubbleDismissView extends FrameLayout {
-    /** Duration for animations involving the dismiss target text/icon/gradient. */
+    /** Duration for animations involving the dismiss target text/icon. */
     private static final int DISMISS_TARGET_ANIMATION_BASE_DURATION = 150;
-
-    private View mDismissGradient;
+    private static final float SCALE_FOR_POP = 1.2f;
+    private static final float SCALE_FOR_DISMISS = 0.9f;
 
     private LinearLayout mDismissTarget;
     private ImageView mDismissIcon;
-    private TextView mDismissText;
     private View mDismissCircle;
 
     private SpringAnimation mDismissTargetAlphaSpring;
@@ -54,36 +48,15 @@
         super(context);
         setVisibility(GONE);
 
-        mDismissGradient = new FrameLayout(mContext);
-
-        FrameLayout.LayoutParams gradientParams =
-                new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
-        gradientParams.gravity = Gravity.BOTTOM;
-        mDismissGradient.setLayoutParams(gradientParams);
-
-        Drawable gradient = mContext.getResources().getDrawable(R.drawable.pip_dismiss_scrim);
-        gradient.setAlpha((int) (255 * 0.85f));
-        mDismissGradient.setBackground(gradient);
-
-        mDismissGradient.setVisibility(GONE);
-        addView(mDismissGradient);
-
         LayoutInflater.from(context).inflate(R.layout.bubble_dismiss_target, this, true);
         mDismissTarget = findViewById(R.id.bubble_dismiss_icon_container);
         mDismissIcon = findViewById(R.id.bubble_dismiss_close_icon);
-        mDismissText = findViewById(R.id.bubble_dismiss_text);
         mDismissCircle = findViewById(R.id.bubble_dismiss_circle);
 
         // Set up the basic target area animations. These are very simple animations that don't need
         // fancy interpolators.
         final AccelerateDecelerateInterpolator interpolator =
                 new AccelerateDecelerateInterpolator();
-        mDismissGradient.animate()
-                .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
-                .setInterpolator(interpolator);
-        mDismissText.animate()
-                .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
-                .setInterpolator(interpolator);
         mDismissIcon.animate()
                 .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
                 .setInterpolator(interpolator);
@@ -108,110 +81,58 @@
             // safely assume it was animating out rather than in.
             if (alpha < 0.5f) {
                 // If the alpha spring was animating the view out, set it to GONE when it's done.
-                setVisibility(GONE);
+                setVisibility(INVISIBLE);
             }
         });
     }
 
-    /** Springs in the dismiss target and fades in the gradient. */
+    /** Springs in the dismiss target. */
     void springIn() {
         setVisibility(View.VISIBLE);
 
-        // Fade in the dismiss target (icon + text).
+        // Fade in the dismiss target icon.
+        mDismissIcon.animate()
+                .setDuration(50)
+                .scaleX(1f)
+                .scaleY(1f)
+                .alpha(1f);
         mDismissTarget.setAlpha(0f);
         mDismissTargetAlphaSpring.animateToFinalPosition(1f);
 
-        // Spring up the dismiss target (icon + text).
+        // Spring up the dismiss target.
         mDismissTarget.setTranslationY(mDismissTarget.getHeight() / 2f);
         mDismissTargetVerticalSpring.animateToFinalPosition(0);
 
-        // Fade in the gradient.
-        mDismissGradient.setVisibility(VISIBLE);
-        mDismissGradient.animate().alpha(1f);
+        mDismissCircle.setAlpha(0f);
+        mDismissCircle.setScaleX(SCALE_FOR_POP);
+        mDismissCircle.setScaleY(SCALE_FOR_POP);
 
-        // Make sure the dismiss elements are in the separated position (in case we hid the target
-        // while they were condensed to cover the bubbles being in the target).
-        mDismissIcon.setAlpha(1f);
-        mDismissIcon.setScaleX(1f);
-        mDismissIcon.setScaleY(1f);
-        mDismissIcon.setTranslationX(0f);
-        mDismissText.setAlpha(1f);
-        mDismissText.setTranslationX(0f);
+        // Fade in circle and reduce size.
+        mDismissCircle.animate()
+                .alpha(1f)
+                .scaleX(1f)
+                .scaleY(1f);
     }
 
-    /** Springs out the dismiss target and fades out the gradient. */
+    /** Springs out the dismiss target. */
     void springOut() {
+        // Fade out the target icon.
+        mDismissIcon.animate()
+                .setDuration(50)
+                .scaleX(SCALE_FOR_DISMISS)
+                .scaleY(SCALE_FOR_DISMISS)
+                .alpha(0f);
+
         // Fade out the target.
         mDismissTargetAlphaSpring.animateToFinalPosition(0f);
 
         // Spring the target down a bit.
         mDismissTargetVerticalSpring.animateToFinalPosition(mDismissTarget.getHeight() / 2f);
 
-        // Fade out the gradient and then set it to GONE so it's not in the SBV hierarchy.
-        mDismissGradient.animate().alpha(0f).withEndAction(
-                () -> mDismissGradient.setVisibility(GONE));
-
-        // Pop out the dismiss circle.
-        mDismissCircle.animate().alpha(0f).scaleX(1.2f).scaleY(1.2f);
-    }
-
-    /**
-     * Encircles the center of the dismiss target, pulling the X towards the center and hiding the
-     * text.
-     */
-    void animateEncircleCenterWithX(boolean encircle) {
-        // Pull the text towards the center if we're encircling (it'll be faded out, leaving only
-        // the X icon over the bubbles), or back to normal if we're un-encircling.
-        final float textTranslation = encircle
-                ? -mDismissIcon.getWidth() / 4f
-                : 0f;
-
-        // Center the icon if we're encircling, or put it back to normal if not.
-        final float iconTranslation = encircle
-                ? mDismissTarget.getWidth() / 2f
-                - mDismissIcon.getWidth() / 2f
-                - mDismissIcon.getLeft()
-                : 0f;
-
-        // Fade in/out the text and translate it.
-        mDismissText.animate()
-                .alpha(encircle ? 0f : 1f)
-                .translationX(textTranslation);
-
-        mDismissIcon.animate()
-                .setDuration(150)
-                .translationX(iconTranslation);
-
-        // Fade out the gradient if we're encircling (the bubbles will 'absorb' it by darkening
-        // themselves).
-        mDismissGradient.animate()
-                .alpha(encircle ? 0f : 1f);
-
-        // Prepare the circle to be 'dropped in'.
-        if (encircle) {
-            mDismissCircle.setAlpha(0f);
-            mDismissCircle.setScaleX(1.2f);
-            mDismissCircle.setScaleY(1.2f);
-        }
-
-        // Drop in the circle, or pull it back up.
+        // Pop out the circle.
         mDismissCircle.animate()
-                .alpha(encircle ? 1f : 0f)
-                .scaleX(encircle ? 1f : 0f)
-                .scaleY(encircle ? 1f : 0f);
-    }
-
-    /** Animates the circle and the centered icon out. */
-    void animateEncirclingCircleDisappearance() {
-        // Pop out the dismiss icon and circle.
-        mDismissIcon.animate()
-                .setDuration(50)
-                .scaleX(0.9f)
-                .scaleY(0.9f)
-                .alpha(0f);
-        mDismissCircle.animate()
-                .scaleX(0.9f)
-                .scaleY(0.9f)
+                .scaleX(SCALE_FOR_DISMISS)
+                .scaleY(SCALE_FOR_DISMISS)
                 .alpha(0f);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 923ca20..18d9fe7 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -18,14 +18,15 @@
 
 import static android.view.Display.INVALID_DISPLAY;
 
-import static com.android.systemui.bubbles.BubbleController.DEBUG_ENABLE_AUTO_BUBBLE;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
-import android.annotation.Nullable;
 import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
 import android.app.ActivityView;
-import android.app.INotificationManager;
-import android.app.Notification;
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -35,18 +36,17 @@
 import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.ShapeDrawable;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.provider.Settings;
+import android.os.RemoteException;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.StatsLog;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.WindowInsets;
+import android.view.WindowManager;
 import android.widget.LinearLayout;
 
 import com.android.internal.policy.ScreenDecorationsUtils;
@@ -54,15 +54,23 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.TriangleShape;
 import com.android.systemui.statusbar.AlphaOptimizedButton;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 
 /**
  * Container for the expanded bubble view, handles rendering the caret and settings icon.
  */
 public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
-    private static final String TAG = "BubbleExpandedView";
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
+
+    private enum ActivityViewStatus {
+        // ActivityView is being initialized, cannot start an activity yet.
+        INITIALIZING,
+        // ActivityView is initialized, and ready to start an activity.
+        INITIALIZED,
+        // Activity runs in the ActivityView.
+        ACTIVITY_STARTED,
+        // ActivityView is released, so activity launching will no longer be permitted.
+        RELEASED,
+    }
 
     // The triangle pointing to the expanded view
     private View mPointerView;
@@ -71,50 +79,79 @@
     private AlphaOptimizedButton mSettingsIcon;
 
     // Views for expanded state
-    private ExpandableNotificationRow mNotifRow;
     private ActivityView mActivityView;
 
-    private boolean mActivityViewReady = false;
+    private ActivityViewStatus mActivityViewStatus = ActivityViewStatus.INITIALIZING;
+    private int mTaskId = -1;
+
     private PendingIntent mBubbleIntent;
 
     private boolean mKeyboardVisible;
     private boolean mNeedsNewHeight;
 
+    private Point mDisplaySize;
     private int mMinHeight;
     private int mSettingsIconHeight;
-    private int mBubbleHeight;
     private int mPointerWidth;
     private int mPointerHeight;
     private ShapeDrawable mPointerDrawable;
+    private Rect mTempRect = new Rect();
+    private int[] mTempLoc = new int[2];
+    private int mExpandedViewTouchSlop;
 
-    private NotificationEntry mEntry;
+    private Bubble mBubble;
     private PackageManager mPm;
     private String mAppName;
     private Drawable mAppIcon;
 
-    private INotificationManager mNotificationManagerService;
     private BubbleController mBubbleController = Dependency.get(BubbleController.class);
 
     private BubbleStackView mStackView;
 
-    private BubbleExpandedView.OnBubbleBlockedListener mOnBubbleBlockedListener;
-
     private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
         @Override
         public void onActivityViewReady(ActivityView view) {
-            if (!mActivityViewReady) {
-                mActivityViewReady = true;
-                // Custom options so there is no activity transition animation
-                ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
-                        0 /* enterResId */, 0 /* exitResId */);
-                // Post to keep the lifecycle normal
-                post(() -> mActivityView.startActivity(mBubbleIntent, options));
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "onActivityViewReady: mActivityViewStatus=" + mActivityViewStatus
+                        + " bubble=" + getBubbleKey());
+            }
+            switch (mActivityViewStatus) {
+                case INITIALIZING:
+                case INITIALIZED:
+                    // Custom options so there is no activity transition animation
+                    ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
+                            0 /* enterResId */, 0 /* exitResId */);
+                    // Post to keep the lifecycle normal
+                    post(() -> {
+                        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                            Log.d(TAG, "onActivityViewReady: calling startActivity, "
+                                    + "bubble=" + getBubbleKey());
+                        }
+                        mActivityView.startActivity(mBubbleIntent, options);
+                    });
+                    mActivityViewStatus = ActivityViewStatus.ACTIVITY_STARTED;
             }
         }
 
         @Override
         public void onActivityViewDestroyed(ActivityView view) {
-            mActivityViewReady = false;
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "onActivityViewDestroyed: mActivityViewStatus=" + mActivityViewStatus
+                        + " bubble=" + getBubbleKey());
+            }
+            mActivityViewStatus = ActivityViewStatus.RELEASED;
+        }
+
+        @Override
+        public void onTaskCreated(int taskId, ComponentName componentName) {
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "onTaskCreated: taskId=" + taskId
+                        + " bubble=" + getBubbleKey());
+            }
+            // Since Bubble ActivityView applies singleTaskDisplay this is
+            // guaranteed to only be called once per ActivityView. The taskId is
+            // saved to use for removeTask, preventing appearance in recent tasks.
+            mTaskId = taskId;
         }
 
         /**
@@ -125,9 +162,14 @@
          */
         @Override
         public void onTaskRemovalStarted(int taskId) {
-            if (mEntry != null) {
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
+                        + " mActivityViewStatus=" + mActivityViewStatus
+                        + " bubble=" + getBubbleKey());
+            }
+            if (mBubble != null) {
                 // Must post because this is called from a binder thread.
-                post(() -> mBubbleController.removeBubble(mEntry.key,
+                post(() -> mBubbleController.removeBubble(mBubble.getKey(),
                         BubbleController.DISMISS_TASK_FINISHED));
             }
         }
@@ -149,20 +191,21 @@
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mPm = context.getPackageManager();
-        mMinHeight = getResources().getDimensionPixelSize(
-                R.dimen.bubble_expanded_default_height);
-        mPointerMargin = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_margin);
-        try {
-            mNotificationManagerService = INotificationManager.Stub.asInterface(
-                    ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
-        } catch (ServiceManager.ServiceNotFoundException e) {
-            Log.w(TAG, e);
-        }
+        mDisplaySize = new Point();
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        wm.getDefaultDisplay().getSize(mDisplaySize);
+        Resources res = getResources();
+        mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
+        mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
+        mExpandedViewTouchSlop = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_slop);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "onFinishInflate: bubble=" + getBubbleKey());
+        }
 
         Resources res = getResources();
         mPointerView = findViewById(R.id.pointer_view);
@@ -173,7 +216,7 @@
         mPointerDrawable = new ShapeDrawable(TriangleShape.create(
                 mPointerWidth, mPointerHeight, true /* pointUp */));
         mPointerView.setBackground(mPointerDrawable);
-        mPointerView.setVisibility(GONE);
+        mPointerView.setVisibility(INVISIBLE);
 
         mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
                 R.dimen.bubble_expanded_header_height);
@@ -210,6 +253,10 @@
         });
     }
 
+    private String getBubbleKey() {
+        return mBubble != null ? mBubble.getKey() : "null";
+    }
+
     void applyThemeAttrs() {
         TypedArray ta = getContext().obtainStyledAttributes(R.styleable.BubbleExpandedView);
         int bgColor = ta.getColor(
@@ -235,6 +282,9 @@
         if (mActivityView != null) {
             mActivityView.setForwardedInsets(Insets.of(0, 0, 0, 0));
         }
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey());
+        }
     }
 
     /**
@@ -246,6 +296,10 @@
      * and setting {@code false} actually means rendering the contents in transparent.
      */
     void setContentVisibility(boolean visibility) {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "setContentVisibility: visibility=" + visibility
+                    + " bubble=" + getBubbleKey());
+        }
         final float alpha = visibility ? 1f : 0f;
         mPointerView.setAlpha(alpha);
         if (mActivityView != null) {
@@ -259,44 +313,36 @@
      */
     void updateInsets(WindowInsets insets) {
         if (usingActivityView()) {
-            Point displaySize = new Point();
-            mActivityView.getContext().getDisplay().getSize(displaySize);
             int[] windowLocation = mActivityView.getLocationOnScreen();
             final int windowBottom = windowLocation[1] + mActivityView.getHeight();
             final int keyboardHeight = insets.getSystemWindowInsetBottom()
                     - insets.getStableInsetBottom();
             final int insetsBottom = Math.max(0,
-                    windowBottom + keyboardHeight - displaySize.y);
+                    windowBottom + keyboardHeight - mDisplaySize.y);
             mActivityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
         }
     }
 
     /**
-     * Sets the listener to notify when a bubble has been blocked.
+     * Sets the bubble used to populate this view.
      */
-    public void setOnBlockedListener(OnBubbleBlockedListener listener) {
-        mOnBubbleBlockedListener = listener;
-    }
+    public void setBubble(Bubble bubble, BubbleStackView stackView, String appName) {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "setBubble: bubble=" + (bubble != null ? bubble.getKey() : "null"));
+        }
 
-    /**
-     * Sets the notification entry used to populate this view.
-     */
-    public void setEntry(NotificationEntry entry, BubbleStackView stackView, String appName) {
         mStackView = stackView;
-        mEntry = entry;
+        mBubble = bubble;
         mAppName = appName;
 
-        ApplicationInfo info;
         try {
-            info = mPm.getApplicationInfo(
-                    entry.notification.getPackageName(),
+            ApplicationInfo info = mPm.getApplicationInfo(
+                    bubble.getPackageName(),
                     PackageManager.MATCH_UNINSTALLED_PACKAGES
                             | PackageManager.MATCH_DISABLED_COMPONENTS
                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                             | PackageManager.MATCH_DIRECT_BOOT_AWARE);
-            if (info != null) {
-                mAppIcon = mPm.getApplicationIcon(info);
-            }
+            mAppIcon = mPm.getApplicationIcon(info);
         } catch (PackageManager.NameNotFoundException e) {
             // Do nothing.
         }
@@ -312,52 +358,46 @@
      * Lets activity view know it should be shown / populated.
      */
     public void populateExpandedView() {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "populateExpandedView: "
+                    + "bubble=" + getBubbleKey());
+        }
+
         if (usingActivityView()) {
             mActivityView.setCallback(mStateCallback);
         } else {
-            // We're using notification template
-            ViewGroup parent = (ViewGroup) mNotifRow.getParent();
-            if (parent == this) {
-                // Already added
-                return;
-            } else if (parent != null) {
-                // Still in the shade... remove it
-                parent.removeView(mNotifRow);
-            }
-            addView(mNotifRow, 1 /* index */);
-            mPointerView.setAlpha(1f);
+            Log.e(TAG, "Cannot populate expanded view.");
         }
     }
 
     /**
-     * Updates the entry backing this view. This will not re-populate ActivityView, it will
+     * Updates the bubble backing this view. This will not re-populate ActivityView, it will
      * only update the deep-links in the title, and the height of the view.
      */
-    public void update(NotificationEntry entry) {
-        if (entry.key.equals(mEntry.key)) {
-            mEntry = entry;
+    public void update(Bubble bubble) {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "update: bubble=" + (bubble != null ? bubble.getKey() : "null"));
+        }
+        if (bubble.getKey().equals(mBubble.getKey())) {
+            mBubble = bubble;
             updateSettingsContentDescription();
             updateHeight();
         } else {
-            Log.w(TAG, "Trying to update entry with different key, new entry: "
-                    + entry.key + " old entry: " + mEntry.key);
+            Log.w(TAG, "Trying to update entry with different key, new bubble: "
+                    + bubble.getKey() + " old bubble: " + bubble.getKey());
         }
     }
 
     private void updateExpandedView() {
-        mBubbleIntent = getBubbleIntent(mEntry);
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "updateExpandedView: bubble="
+                    + getBubbleKey());
+        }
+
+        mBubbleIntent = mBubble.getBubbleIntent(mContext);
         if (mBubbleIntent != null) {
-            if (mNotifRow != null) {
-                // Clear out the row if we had it previously
-                removeView(mNotifRow);
-                mNotifRow = null;
-            }
             setContentVisibility(false);
             mActivityView.setVisibility(VISIBLE);
-        } else if (DEBUG_ENABLE_AUTO_BUBBLE) {
-            // Hide activity view if we had it previously
-            mActivityView.setVisibility(GONE);
-            mNotifRow = mEntry.getRow();
         }
         updateView();
     }
@@ -371,28 +411,13 @@
     }
 
     void updateHeight() {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "updateHeight: bubble=" + getBubbleKey());
+        }
         if (usingActivityView()) {
-            Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
-            float desiredHeight;
-            if (data == null) {
-                // This is a contentIntent based bubble, lets allow it to be the max height
-                // as it was forced into this mode and not prepared to be small
-                desiredHeight = mStackView.getMaxExpandedHeight();
-            } else {
-                boolean useRes = data.getDesiredHeightResId() != 0;
-                float desiredPx;
-                if (useRes) {
-                    desiredPx = getDimenForPackageUser(data.getDesiredHeightResId(),
-                            mEntry.notification.getPackageName(),
-                            mEntry.notification.getUser().getIdentifier());
-                } else {
-                    desiredPx = data.getDesiredHeight()
-                            * getContext().getResources().getDisplayMetrics().density;
-                }
-                desiredHeight = desiredPx > 0 ? desiredPx : mMinHeight;
-            }
-            int max = mStackView.getMaxExpandedHeight() - mSettingsIconHeight - mPointerHeight
+            int max = getMaxExpandedHeight() - mSettingsIconHeight - mPointerHeight
                     - mPointerMargin;
+            float desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight);
             float height = Math.min(desiredHeight, max);
             height = Math.max(height, mMinHeight);
             LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
@@ -401,28 +426,61 @@
                 // If the keyboard is visible... don't adjust the height because that will cause
                 // a configuration change and the keyboard will be lost.
                 lp.height = (int) height;
-                mBubbleHeight = (int) height;
                 mActivityView.setLayoutParams(lp);
                 mNeedsNewHeight = false;
             }
-        } else {
-            mBubbleHeight = mNotifRow != null ? mNotifRow.getIntrinsicHeight() : mMinHeight;
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "updateHeight: bubble=" + getBubbleKey() + " height=" + height
+                        + " mNeedsNewHeight=" + mNeedsNewHeight);
+            }
         }
     }
 
+    private int getMaxExpandedHeight() {
+        int[] windowLocation = mActivityView.getLocationOnScreen();
+        return mDisplaySize.y - windowLocation[1] - mSettingsIconHeight;
+    }
+
+    /**
+     * Whether the provided x, y values (in raw coordinates) are in a touchable area of the
+     * expanded view.
+     *
+     * The touchable areas are the ActivityView (plus some slop around it) and the manage button.
+     */
+    boolean intersectingTouchableContent(int rawX, int rawY) {
+        mTempRect.setEmpty();
+        if (mActivityView != null) {
+            mTempLoc = mActivityView.getLocationOnScreen();
+            mTempRect.set(mTempLoc[0] - mExpandedViewTouchSlop,
+                    mTempLoc[1] - mExpandedViewTouchSlop,
+                    mTempLoc[0] + mActivityView.getWidth() + mExpandedViewTouchSlop,
+                    mTempLoc[1] + mActivityView.getHeight() + mExpandedViewTouchSlop);
+        }
+        if (mTempRect.contains(rawX, rawY)) {
+            return true;
+        }
+        mTempLoc = mSettingsIcon.getLocationOnScreen();
+        mTempRect.set(mTempLoc[0],
+                mTempLoc[1],
+                mTempLoc[0] + mSettingsIcon.getWidth(),
+                mTempLoc[1] + mSettingsIcon.getHeight());
+        if (mTempRect.contains(rawX, rawY)) {
+            return true;
+        }
+        return false;
+    }
+
     @Override
     public void onClick(View view) {
-        if (mEntry == null) {
+        if (mBubble == null) {
             return;
         }
-        Notification n = mEntry.notification.getNotification();
         int id = view.getId();
         if (id == R.id.settings_button) {
-            Intent intent = getSettingsIntent(mEntry.notification.getPackageName(),
-                    mEntry.notification.getUid());
+            Intent intent = mBubble.getSettingsIntent();
             mStackView.collapseStack(() -> {
-                mContext.startActivityAsUser(intent, mEntry.notification.getUser());
-                logBubbleClickEvent(mEntry,
+                mContext.startActivityAsUser(intent, mBubble.getEntry().notification.getUser());
+                logBubbleClickEvent(mBubble,
                         StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
             });
         }
@@ -442,13 +500,14 @@
      * Update appearance of the expanded view being displayed.
      */
     public void updateView() {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "updateView: bubble="
+                    + getBubbleKey());
+        }
         if (usingActivityView()
                 && mActivityView.getVisibility() == VISIBLE
                 && mActivityView.isAttachedToWindow()) {
             mActivityView.onLocationChanged();
-        } else if (mNotifRow != null) {
-            applyRowState(mNotifRow);
-            mPointerView.setAlpha(1f);
         }
         updateHeight();
     }
@@ -467,17 +526,44 @@
      * Removes and releases an ActivityView if one was previously created for this bubble.
      */
     public void cleanUpExpandedState() {
-        removeView(mNotifRow);
-
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "cleanUpExpandedState: mActivityViewStatus=" + mActivityViewStatus
+                    + ", bubble=" + getBubbleKey());
+        }
         if (mActivityView == null) {
             return;
         }
-        if (mActivityViewReady) {
-            mActivityView.release();
+        switch (mActivityViewStatus) {
+            case INITIALIZED:
+            case ACTIVITY_STARTED:
+                mActivityView.release();
+        }
+        if (mTaskId != -1) {
+            try {
+                ActivityTaskManager.getService().removeTask(mTaskId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to remove taskId " + mTaskId);
+            }
+            mTaskId = -1;
         }
         removeView(mActivityView);
+
         mActivityView = null;
-        mActivityViewReady = false;
+    }
+
+    /**
+     * Called when the last task is removed from a {@link android.hardware.display.VirtualDisplay}
+     * which {@link ActivityView} uses.
+     */
+    void notifyDisplayEmpty() {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "notifyDisplayEmpty: bubble="
+                    + getBubbleKey()
+                    + " mActivityViewStatus=" + mActivityViewStatus);
+        }
+        if (mActivityViewStatus == ActivityViewStatus.ACTIVITY_STARTED) {
+            mActivityViewStatus = ActivityViewStatus.INITIALIZED;
+        }
     }
 
     private boolean usingActivityView() {
@@ -494,76 +580,14 @@
         return INVALID_DISPLAY;
     }
 
-    private void applyRowState(ExpandableNotificationRow view) {
-        view.reset();
-        view.setHeadsUp(false);
-        view.resetTranslation();
-        view.setOnKeyguard(false);
-        view.setClipBottomAmount(0);
-        view.setClipTopAmount(0);
-        view.setContentTransformationAmount(0, false);
-        view.setIconsVisible(true);
-
-        // TODO - Need to reset this (and others) when view goes back in shade, leave for now
-        // view.setTopRoundness(1, false);
-        // view.setBottomRoundness(1, false);
-
-        ExpandableViewState viewState = view.getViewState();
-        viewState = viewState == null ? new ExpandableViewState() : viewState;
-        viewState.height = view.getIntrinsicHeight();
-        viewState.gone = false;
-        viewState.hidden = false;
-        viewState.dimmed = false;
-        viewState.alpha = 1f;
-        viewState.notGoneIndex = -1;
-        viewState.xTranslation = 0;
-        viewState.yTranslation = 0;
-        viewState.zTranslation = 0;
-        viewState.scaleX = 1;
-        viewState.scaleY = 1;
-        viewState.inShelf = true;
-        viewState.headsUpIsVisible = false;
-        viewState.applyToView(view);
-    }
-
-    private Intent getSettingsIntent(String packageName, final int appUid) {
-        final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
-        intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
-        intent.putExtra(Settings.EXTRA_APP_UID, appUid);
-        intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
-        return intent;
-    }
-
-    @Nullable
-    private PendingIntent getBubbleIntent(NotificationEntry entry) {
-        Notification notif = entry.notification.getNotification();
-        Notification.BubbleMetadata data = notif.getBubbleMetadata();
-        if (BubbleController.canLaunchInActivityView(mContext, entry) && data != null) {
-            return data.getIntent();
-        }
-        return null;
-    }
-
-    /**
-     * Listener that is notified when a bubble is blocked.
-     */
-    public interface OnBubbleBlockedListener {
-        /**
-         * Called when a bubble is blocked for the provided entry.
-         */
-        void onBubbleBlocked(NotificationEntry entry);
-    }
-
     /**
      * Logs bubble UI click event.
      *
-     * @param entry the bubble notification entry that user is interacting with.
+     * @param bubble the bubble notification entry that user is interacting with.
      * @param action the user interaction enum.
      */
-    private void logBubbleClickEvent(NotificationEntry entry, int action) {
-        StatusBarNotification notification = entry.notification;
+    private void logBubbleClickEvent(Bubble bubble, int action) {
+        StatusBarNotification notification = bubble.getEntry().notification;
         StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
                 notification.getPackageName(),
                 notification.getNotification().getChannelId(),
@@ -573,27 +597,8 @@
                 action,
                 mStackView.getNormalizedXPosition(),
                 mStackView.getNormalizedYPosition(),
-                entry.showInShadeWhenBubble(),
-                entry.isForegroundService(),
-                BubbleController.isForegroundApp(mContext, notification.getPackageName()));
-    }
-
-    private int getDimenForPackageUser(int resId, String pkg, int userId) {
-        Resources r;
-        if (pkg != null) {
-            try {
-                if (userId == UserHandle.USER_ALL) {
-                    userId = UserHandle.USER_SYSTEM;
-                }
-                r = mPm.getResourcesForApplicationAsUser(pkg, userId);
-                return r.getDimensionPixelSize(resId);
-            } catch (PackageManager.NameNotFoundException ex) {
-                // Uninstalled, don't care
-            } catch (Resources.NotFoundException e) {
-                // Invalid res id, return 0 and user our default
-                Log.e(TAG, "Couldn't find desired height res id", e);
-            }
-        }
-        return 0;
+                bubble.showInShadeWhenBubble(),
+                bubble.isOngoing(),
+                false /* isAppForeground (unused) */);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
index 71f68c1..e8f3058 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
@@ -39,8 +39,7 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.annotation.Nullable;
 
 import com.android.systemui.R;
 import com.android.systemui.recents.TriangleShape;
@@ -57,6 +56,9 @@
     private final int mFlyoutSpaceFromBubble;
     private final int mPointerSize;
     private final int mBubbleSize;
+    private final int mBubbleIconBitmapSize;
+    private final float mBubbleIconTopPadding;
+
     private final int mFlyoutElevation;
     private final int mBubbleElevation;
     private final int mFloatingBackgroundColor;
@@ -64,14 +66,11 @@
 
     private final ViewGroup mFlyoutTextContainer;
     private final TextView mFlyoutText;
-    /** Spring animation for the flyout. */
-    private final SpringAnimation mFlyoutSpring =
-            new SpringAnimation(this, DynamicAnimation.TRANSLATION_X);
 
     /** Values related to the 'new' dot which we use to figure out where to collapse the flyout. */
     private final float mNewDotRadius;
     private final float mNewDotSize;
-    private final float mNewDotOffsetFromBubbleBounds;
+    private final float mOriginalDotSize;
 
     /**
      * The paint used to draw the background, whose color changes as the flyout transitions to the
@@ -129,8 +128,16 @@
     /** The flyout's X translation when at rest (not animating or dragging). */
     private float mRestingTranslationX = 0f;
 
+    /** The badge sizes are defined as percentages of the app icon size. Same value as Launcher3. */
+    private static final float SIZE_PERCENTAGE = 0.228f;
+
+    /** Extra scale down of the dot provides room for error in estimating actual dot location.
+     * At the end of the flyout-to-dot animation, wherever the small dot ends up, its disappearance
+     * and the appearance of the larger real dot forms a cohesive animation.*/
+    private static final float DOT_SCALE = 0.8f;
+
     /** Callback to run when the flyout is hidden. */
-    private Runnable mOnHide;
+    @Nullable private Runnable mOnHide;
 
     public BubbleFlyoutView(Context context) {
         super(context);
@@ -143,11 +150,16 @@
         mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
         mFlyoutSpaceFromBubble = res.getDimensionPixelSize(R.dimen.bubble_flyout_space_from_bubble);
         mPointerSize = res.getDimensionPixelSize(R.dimen.bubble_flyout_pointer_size);
+
         mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+        mBubbleIconBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+        mBubbleIconTopPadding  = (mBubbleSize - mBubbleIconBitmapSize) / 2f;
+
         mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
         mFlyoutElevation = res.getDimensionPixelSize(R.dimen.bubble_flyout_elevation);
-        mNewDotOffsetFromBubbleBounds = BadgeRenderer.getDotCenterOffset(context);
-        mNewDotRadius = BadgeRenderer.getDotRadius(mNewDotOffsetFromBubbleBounds);
+
+        mOriginalDotSize = SIZE_PERCENTAGE * mBubbleIconBitmapSize;
+        mNewDotRadius = (DOT_SCALE * mOriginalDotSize) / 2f;
         mNewDotSize = mNewDotRadius * 2f;
 
         final TypedArray ta = mContext.obtainStyledAttributes(
@@ -193,17 +205,16 @@
         super.onDraw(canvas);
     }
 
-    /** Configures the flyout and animates it in. */
-    void showFlyout(
+    /** Configures the flyout, collapsed into to dot form. */
+    void setupFlyoutStartingAsDot(
             CharSequence updateMessage, PointF stackPos, float parentWidth,
-            boolean arrowPointingLeft, int dotColor, Runnable onHide) {
+            boolean arrowPointingLeft, int dotColor, @Nullable Runnable onLayoutComplete,
+            @Nullable Runnable onHide) {
         mArrowPointingLeft = arrowPointingLeft;
         mDotColor = dotColor;
         mOnHide = onHide;
 
-        setCollapsePercent(0f);
-        setAlpha(0f);
-        setVisibility(VISIBLE);
+        setCollapsePercent(1f);
 
         // Set the flyout TextView's max width in terms of percent, and then subtract out the
         // padding so that the entire flyout view will be the desired width (rather than the
@@ -216,7 +227,7 @@
         post(() -> {
             // Multi line flyouts get top-aligned to the bubble.
             if (mFlyoutText.getLineCount() > 1) {
-                setTranslationY(stackPos.y);
+                setTranslationY(stackPos.y + mBubbleIconTopPadding);
             } else {
                 // Single line flyouts are vertically centered with respect to the bubble.
                 setTranslationY(
@@ -229,14 +240,6 @@
                     ? stackPos.x + mBubbleSize + mFlyoutSpaceFromBubble
                     : stackPos.x - getWidth() - mFlyoutSpaceFromBubble;
 
-            // Translate towards the stack slightly.
-            setTranslationX(
-                    mRestingTranslationX + (arrowPointingLeft ? -mBubbleSize : mBubbleSize));
-
-            // Fade in the entire flyout and spring it to its normal position.
-            animate().alpha(1f);
-            mFlyoutSpring.animateToFinalPosition(mRestingTranslationX);
-
             // Calculate the difference in size between the flyout and the 'dot' so that we can
             // transform into the dot later.
             mFlyoutToDotWidthDelta = getWidth() - mNewDotSize;
@@ -244,25 +247,28 @@
 
             // Calculate the translation values needed to be in the correct 'new dot' position.
             final float distanceFromFlyoutLeftToDotCenterX =
-                    mFlyoutSpaceFromBubble + mNewDotOffsetFromBubbleBounds / 2;
+                    mFlyoutSpaceFromBubble + mBubbleIconTopPadding + mOriginalDotSize / 2;
             if (mArrowPointingLeft) {
-                mTranslationXWhenDot = -distanceFromFlyoutLeftToDotCenterX - mNewDotRadius;
+                mTranslationXWhenDot = -distanceFromFlyoutLeftToDotCenterX - (mOriginalDotSize / 2);
             } else {
                 mTranslationXWhenDot =
-                        getWidth() + distanceFromFlyoutLeftToDotCenterX - mNewDotRadius;
+                        getWidth() + distanceFromFlyoutLeftToDotCenterX - (mOriginalDotSize / 2);
             }
-
             mTranslationYWhenDot =
                     getHeight() / 2f
-                            - mNewDotRadius
                             - mBubbleSize / 2f
-                            + mNewDotOffsetFromBubbleBounds / 2;
+                            + mOriginalDotSize / 2;
+
+            if (onLayoutComplete != null) {
+                onLayoutComplete.run();
+            }
         });
     }
 
     /**
-     * Hides the flyout and runs the optional callback passed into showFlyout. The flyout has been
-     * animated into the 'new' dot by the time we call this, so no animations are needed.
+     * Hides the flyout and runs the optional callback passed into setupFlyoutStartingAsDot.
+     * The flyout has been animated into the 'new' dot by the time we call this, so no animations
+     * are needed.
      */
     void hideFlyout() {
         if (mOnHide != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
new file mode 100644
index 0000000..dc38d59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
@@ -0,0 +1,31 @@
+/*
+ * 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.bubbles;
+
+import android.content.Context;
+
+import com.android.launcher3.icons.BaseIconFactory;
+
+/**
+ * Factory for creating normalized bubble icons.
+ * We are not using Launcher's IconFactory because bubbles only runs on the UI thread,
+ * so there is no need to manage a pool across multiple threads.
+ */
+public class BubbleIconFactory extends BaseIconFactory {
+    protected BubbleIconFactory(Context context, int iconBitmapSize) {
+        super(context, context.getResources().getConfiguration().densityDpi, iconBitmapSize);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index f87bcef..7379693 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -19,16 +19,20 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.app.Notification;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -45,7 +49,6 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewOutlineProvider;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -69,6 +72,8 @@
 import com.android.systemui.bubbles.animation.StackAnimationController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.util.ArrayList;
@@ -79,8 +84,7 @@
  * Renders bubbles in a stack and handles animating expanded and collapsed states.
  */
 public class BubbleStackView extends FrameLayout {
-    private static final String TAG = "BubbleStackView";
-    private static final boolean DEBUG = false;
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
 
     /** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
     static final float FLYOUT_DRAG_PERCENT_DISMISS = 0.25f;
@@ -160,9 +164,14 @@
     private BubbleFlyoutView mFlyout;
     /** Runnable that fades out the flyout and then sets it to GONE. */
     private Runnable mHideFlyout = () -> animateFlyoutCollapsed(true, 0 /* velX */);
+    /**
+     * Callback to run after the flyout hides. Also called if a new flyout is shown before the
+     * previous one animates out.
+     */
+    private Runnable mAfterFlyoutHides;
 
     /** Layout change listener that moves the stack to the nearest valid position on rotation. */
-    private OnLayoutChangeListener mMoveStackToValidPositionOnLayoutListener;
+    private OnLayoutChangeListener mOrientationChangedListener;
     /** Whether the stack was on the left side of the screen prior to rotation. */
     private boolean mWasOnLeftBeforeRotation = false;
     /**
@@ -172,18 +181,18 @@
     private float mVerticalPosPercentBeforeRotation = -1;
 
     private int mBubbleSize;
-    private int mBubblePadding;
+    private int mIconBitmapSize;
+    private int mBubblePaddingTop;
+    private int mBubbleTouchPadding;
     private int mExpandedViewPadding;
     private int mExpandedAnimateXDistance;
     private int mExpandedAnimateYDistance;
     private int mPointerHeight;
     private int mStatusBarHeight;
-    private int mPipDismissHeight;
     private int mImeOffset;
-
+    private BubbleIconFactory mBubbleIconFactory;
     private Bubble mExpandedBubble;
     private boolean mIsExpanded;
-    private boolean mImeVisible;
 
     /** Whether the stack is currently on the left side of the screen, or animating there. */
     private boolean mStackOnLeftOrWillBe = false;
@@ -191,9 +200,20 @@
     /** Whether a touch gesture, such as a stack/bubble drag or flyout drag, is in progress. */
     private boolean mIsGestureInProgress = false;
 
+    /** Description of current animation controller state. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Stack view state:");
+        pw.print("  gestureInProgress:    "); pw.println(mIsGestureInProgress);
+        pw.print("  showingDismiss:       "); pw.println(mShowingDismiss);
+        pw.print("  isExpansionAnimating: "); pw.println(mIsExpansionAnimating);
+        pw.print("  draggingInDismiss:    "); pw.println(mDraggingInDismissTarget);
+        pw.print("  animatingMagnet:      "); pw.println(mAnimatingMagnet);
+        mStackAnimationController.dump(fd, pw, args);
+        mExpandedAnimationController.dump(fd, pw, args);
+    }
+
     private BubbleTouchHandler mTouchHandler;
     private BubbleController.BubbleExpandListener mExpandListener;
-    private BubbleExpandedView.OnBubbleBlockedListener mBlockedListener;
 
     private boolean mViewUpdatedRequested = false;
     private boolean mIsExpansionAnimating = false;
@@ -225,7 +245,7 @@
                 @Override
                 public boolean onPreDraw() {
                     getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
-                    applyCurrentState();
+                    updateExpandedView();
                     mViewUpdatedRequested = false;
                     return true;
                 }
@@ -270,6 +290,11 @@
     private float mFlyoutDragDeltaX = 0f;
 
     /**
+     * Runnable that animates in the flyout. This reference is needed to cancel delayed postings.
+     */
+    private Runnable mAnimateInFlyout;
+
+    /**
      * End listener for the flyout spring that either posts a runnable to hide the flyout, or hides
      * it immediately.
      */
@@ -282,13 +307,13 @@
                 }
             };
 
-    @NonNull private final SurfaceSynchronizer mSurfaceSynchronizer;
+    @NonNull
+    private final SurfaceSynchronizer mSurfaceSynchronizer;
 
     private BubbleDismissView mDismissContainer;
     private Runnable mAfterMagnet;
 
-    private boolean mSuppressNewDot = false;
-    private boolean mSuppressFlyout = false;
+    private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
 
     public BubbleStackView(Context context, BubbleData data,
             @Nullable SurfaceSynchronizer synchronizer) {
@@ -302,7 +327,8 @@
 
         Resources res = getResources();
         mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
-        mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+        mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
         mExpandedAnimateXDistance =
                 res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance);
         mExpandedAnimateYDistance =
@@ -311,8 +337,6 @@
 
         mStatusBarHeight =
                 res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
-        mPipDismissHeight = mContext.getResources().getDimensionPixelSize(
-                R.dimen.pip_dismiss_gradient_height);
         mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
 
         mDisplaySize = new Point();
@@ -325,8 +349,9 @@
         int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
 
         mStackAnimationController = new StackAnimationController();
+
         mExpandedAnimationController = new ExpandedAnimationController(
-                mDisplaySize, mExpandedViewPadding);
+                mDisplaySize, mExpandedViewPadding, res.getConfiguration().orientation);
         mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
 
         mBubbleContainer = new PhysicsAnimationLayout(context);
@@ -335,6 +360,9 @@
         mBubbleContainer.setClipChildren(false);
         addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
 
+        mIconBitmapSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+        mBubbleIconFactory = new BubbleIconFactory(context, mIconBitmapSize);
+
         mExpandedViewContainer = new FrameLayout(context);
         mExpandedViewContainer.setElevation(elevation);
         mExpandedViewContainer.setPadding(mExpandedViewPadding, mExpandedViewPadding,
@@ -350,7 +378,7 @@
         addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
 
         mFlyoutTransitionSpring.setSpring(new SpringForce()
-                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+                .setStiffness(SpringForce.STIFFNESS_LOW)
                 .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
         mFlyoutTransitionSpring.addEndListener(mAfterFlyoutTransitionSpring);
 
@@ -361,13 +389,6 @@
                 Gravity.BOTTOM));
         addView(mDismissContainer);
 
-        mDismissContainer = new BubbleDismissView(mContext);
-        mDismissContainer.setLayoutParams(new FrameLayout.LayoutParams(
-                MATCH_PARENT,
-                getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height),
-                Gravity.BOTTOM));
-        addView(mDismissContainer);
-
         mExpandedViewXAnim =
                 new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
         mExpandedViewXAnim.setSpring(
@@ -383,7 +404,7 @@
                         .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
         mExpandedViewYAnim.addEndListener((anim, cancelled, value, velocity) -> {
             if (mIsExpanded && mExpandedBubble != null) {
-                mExpandedBubble.expandedView.updateView();
+                mExpandedBubble.getExpandedView().updateView();
             }
         });
 
@@ -392,14 +413,11 @@
         mBubbleContainer.bringToFront();
 
         setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
-            final int keyboardHeight = insets.getSystemWindowInsetBottom()
-                    - insets.getStableInsetBottom();
             if (!mIsExpanded || mIsExpansionAnimating) {
                 return view.onApplyWindowInsets(insets);
             }
-            mImeVisible = keyboardHeight != 0;
 
-            float newY = getYPositionForExpandedView();
+            float newY = getExpandedViewY();
             if (newY < 0) {
                 // TODO: This means our expanded content is too big to fit on screen. Right now
                 // we'll let it translate off but we should be clipping it & pushing the header
@@ -409,17 +427,24 @@
             mExpandedAnimationController.updateYPosition(
                     // Update the insets after we're done translating otherwise position
                     // calculation for them won't be correct.
-                    () -> mExpandedBubble.expandedView.updateInsets(insets));
+                    () -> mExpandedBubble.getExpandedView().updateInsets(insets));
             return view.onApplyWindowInsets(insets);
         });
 
-        mMoveStackToValidPositionOnLayoutListener =
+        mOrientationChangedListener =
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                    mExpandedAnimationController.updateOrientation(mOrientation);
+                    if (mIsExpanded) {
+                        // Re-draw bubble row and pointer for new orientation.
+                        mExpandedAnimationController.expandFromStack(() -> {
+                            updatePointerPosition();
+                        } /* after */);
+                    }
                     if (mVerticalPosPercentBeforeRotation >= 0) {
                         mStackAnimationController.moveStackToSimilarPositionAfterRotation(
                                 mWasOnLeftBeforeRotation, mVerticalPosPercentBeforeRotation);
                     }
-                    removeOnLayoutChangeListener(mMoveStackToValidPositionOnLayoutListener);
+                    removeOnLayoutChangeListener(mOrientationChangedListener);
                 };
 
         // This must be a separate OnDrawListener since it should be called for every draw.
@@ -453,21 +478,25 @@
      * Handle theme changes.
      */
     public void onThemeChanged() {
+        // Recreate icon factory to update default adaptive icon scale.
+        mBubbleIconFactory = new BubbleIconFactory(mContext, mIconBitmapSize);
         for (Bubble b: mBubbleData.getBubbles()) {
-            b.iconView.updateViews();
-            b.expandedView.applyThemeAttrs();
+            b.getIconView().setBubbleIconFactory(mBubbleIconFactory);
+            b.getIconView().updateViews();
+            b.getExpandedView().applyThemeAttrs();
         }
     }
 
     /** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
-    public void onOrientationChanged() {
+    public void onOrientationChanged(int orientation) {
+        mOrientation = orientation;
+
         final RectF allowablePos = mStackAnimationController.getAllowableStackPositionRegion();
         mWasOnLeftBeforeRotation = mStackAnimationController.isStackOnLeftSide();
         mVerticalPosPercentBeforeRotation =
                 (mStackAnimationController.getStackPosition().y - allowablePos.top)
                         / (allowablePos.bottom - allowablePos.top);
-        addOnLayoutChangeListener(mMoveStackToValidPositionOnLayoutListener);
-
+        addOnLayoutChangeListener(mOrientationChangedListener);
         hideFlyoutImmediate();
     }
 
@@ -483,18 +512,6 @@
     }
 
     @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        float x = ev.getRawX();
-        float y = ev.getRawY();
-        // If we're expanded only intercept if the tap is outside of the widget container
-        if (mIsExpanded && isIntersecting(mExpandedViewContainer, x, y)) {
-            return false;
-        } else {
-            return isIntersecting(mBubbleContainer, x, y);
-        }
-    }
-
-    @Override
     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfoInternal(info);
 
@@ -570,7 +587,7 @@
         }
         Bubble topBubble = mBubbleData.getBubbles().get(0);
         String appName = topBubble.getAppName();
-        Notification notification = topBubble.entry.notification.getNotification();
+        Notification notification = topBubble.getEntry().notification.getNotification();
         CharSequence titleCharSeq = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
         String titleStr = getResources().getString(R.string.stream_notification);
         if (titleCharSeq != null) {
@@ -616,6 +633,7 @@
 
     /**
      * Updates the visibility of the 'dot' indicating an update on the bubble.
+     *
      * @param key the {@link NotificationEntry#key} associated with the bubble.
      */
     public void updateDotVisibility(String key) {
@@ -643,7 +661,7 @@
      * The {@link BubbleView} that is expanded, null if one does not exist.
      */
     BubbleView getExpandedBubbleView() {
-        return mExpandedBubble != null ? mExpandedBubble.iconView : null;
+        return mExpandedBubble != null ? mExpandedBubble.getIconView() : null;
     }
 
     /**
@@ -664,36 +682,33 @@
         Bubble bubbleToExpand = mBubbleData.getBubbleWithKey(key);
         if (bubbleToExpand != null) {
             setSelectedBubble(bubbleToExpand);
-            bubbleToExpand.entry.setShowInShadeWhenBubble(false);
+            bubbleToExpand.setShowInShadeWhenBubble(false);
             setExpanded(true);
         }
     }
 
-    /**
-     * Sets the entry that should be expanded and expands if needed.
-     */
-    @VisibleForTesting
-    void setExpandedBubble(NotificationEntry entry) {
-        for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
-            BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
-            if (entry.equals(bv.getEntry())) {
-                setExpandedBubble(entry.key);
-            }
-        }
-    }
-
     // via BubbleData.Listener
     void addBubble(Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "addBubble: " + bubble);
         }
-        bubble.inflate(mInflater, this);
-        mBubbleContainer.addView(bubble.iconView, 0,
-                new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
-        ViewClippingUtil.setClippingDeactivated(bubble.iconView, true, mClippingParameters);
-        if (bubble.iconView != null) {
-            bubble.iconView.setSuppressDot(mSuppressNewDot, false /* animate */);
+
+        if (mBubbleContainer.getChildCount() == 0) {
+            mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
         }
+
+        bubble.inflate(mInflater, this);
+        bubble.getIconView().setBubbleIconFactory(mBubbleIconFactory);
+        bubble.getIconView().updateViews();
+
+        // Set the dot position to the opposite of the side the stack is resting on, since the stack
+        // resting slightly off-screen would result in the dot also being off-screen.
+        bubble.getIconView().setDotPosition(
+                !mStackOnLeftOrWillBe /* onLeft */, false /* animate */);
+
+        mBubbleContainer.addView(bubble.getIconView(), 0,
+                new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+        ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters);
         animateInFlyoutForBubble(bubble);
         requestUpdate();
         logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
@@ -702,13 +717,14 @@
 
     // via BubbleData.Listener
     void removeBubble(Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "removeBubble: " + bubble);
         }
         // Remove it from the views
-        int removedIndex = mBubbleContainer.indexOfChild(bubble.iconView);
+        int removedIndex = mBubbleContainer.indexOfChild(bubble.getIconView());
         if (removedIndex >= 0) {
             mBubbleContainer.removeViewAt(removedIndex);
+            bubble.cleanupExpandedState();
             logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
         } else {
             Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
@@ -726,7 +742,7 @@
     public void updateBubbleOrder(List<Bubble> bubbles) {
         for (int i = 0; i < bubbles.size(); i++) {
             Bubble bubble = bubbles.get(i);
-            mBubbleContainer.reorderView(bubble.iconView, i);
+            mBubbleContainer.reorderView(bubble.getIconView(), i);
         }
     }
 
@@ -737,7 +753,7 @@
      */
     // via BubbleData.Listener
     public void setSelectedBubble(@Nullable Bubble bubbleToSelect) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
         }
         if (mExpandedBubble != null && mExpandedBubble.equals(bubbleToSelect)) {
@@ -760,9 +776,8 @@
                 requestUpdate();
                 logBubbleEvent(previouslySelected, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
                 logBubbleEvent(bubbleToSelect, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
-                notifyExpansionChanged(previouslySelected.entry, false /* expanded */);
-                notifyExpansionChanged(bubbleToSelect == null ? null : bubbleToSelect.entry,
-                        true /* expanded */);
+                notifyExpansionChanged(previouslySelected, false /* expanded */);
+                notifyExpansionChanged(bubbleToSelect, true /* expanded */);
             });
         }
     }
@@ -774,42 +789,32 @@
      */
     // via BubbleData.Listener
     public void setExpanded(boolean shouldExpand) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "setExpanded: " + shouldExpand);
         }
-        boolean wasExpanded = mIsExpanded;
-        if (shouldExpand == wasExpanded) {
+        if (shouldExpand == mIsExpanded) {
             return;
         }
-        if (wasExpanded) {
-            // Collapse the stack
-            mExpandedViewContainer.setAlpha(0.0f);
-            // TODO: In order to prevent flicker, code below should be executed after the alpha
-            // value set on the mExpandedViewContainer is reflected on the screen. However, we
-            // cannot just postpone the execution like #setSelectedBubble(), since some of member
-            // variables referred by the code are overridden before the execution.
-            if (mExpandedBubble != null) {
-                mExpandedBubble.setContentVisibility(false);
-            }
-            animateExpansion(false /* expand */);
+        if (mIsExpanded) {
+            animateCollapse();
             logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
         } else {
-            // Expand the stack
-            animateExpansion(true /* expand */);
+            animateExpansion();
             // TODO: move next line to BubbleData
             logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
             logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
         }
-        notifyExpansionChanged(mExpandedBubble.entry, mIsExpanded);
+        notifyExpansionChanged(mExpandedBubble, mIsExpanded);
     }
 
     /**
      * Dismiss the stack of bubbles.
+     *
      * @deprecated
      */
     @Deprecated
     void stackDismissed(int reason) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "stackDismissed: reason=" + reason);
         }
         mBubbleData.dismissAll(reason);
@@ -826,21 +831,23 @@
         float y = event.getRawY();
         if (mIsExpanded) {
             if (isIntersecting(mBubbleContainer, x, y)) {
+                // Could be tapping or dragging a bubble while expanded
                 for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
                     BubbleView view = (BubbleView) mBubbleContainer.getChildAt(i);
                     if (isIntersecting(view, x, y)) {
                         return view;
                     }
                 }
-            } else if (isIntersecting(mExpandedViewContainer, x, y)) {
-                return mExpandedViewContainer;
             }
-            // Outside parts of view we care about.
+            BubbleExpandedView bev = (BubbleExpandedView) mExpandedViewContainer.getChildAt(0);
+            if (bev.intersectingTouchableContent((int) x, (int) y)) {
+                return bev;
+            }
+            // Outside of the parts we care about.
             return null;
         } else if (mFlyout.getVisibility() == VISIBLE && isIntersecting(mFlyout, x, y)) {
             return mFlyout;
         }
-
         // If it wasn't an individual bubble in the expanded state, or the flyout, it's the stack.
         return this;
     }
@@ -859,7 +866,7 @@
     @Deprecated
     @MainThread
     void collapseStack() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "collapseStack()");
         }
         mBubbleData.setExpanded(false);
@@ -871,7 +878,7 @@
     @Deprecated
     @MainThread
     void collapseStack(Runnable endRunnable) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "collapseStack(endRunnable)");
         }
         collapseStack();
@@ -889,73 +896,83 @@
     @Deprecated
     @MainThread
     void expandStack() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "expandStack()");
         }
         mBubbleData.setExpanded(true);
     }
 
-    /**
-     * Tell the stack to animate to collapsed or expanded state.
-     */
-    private void animateExpansion(boolean shouldExpand) {
-        if (DEBUG) {
-            Log.d(TAG, "animateExpansion: shouldExpand=" + shouldExpand);
-        }
-        if (mIsExpanded != shouldExpand) {
-            hideFlyoutImmediate();
-
-            mIsExpanded = shouldExpand;
-            updateExpandedBubble();
-            applyCurrentState();
-
-            mIsExpansionAnimating = true;
-
-            Runnable updateAfter = () -> {
-                applyCurrentState();
-                mIsExpansionAnimating = false;
-                requestUpdate();
-            };
-
-            if (shouldExpand) {
-                mBubbleContainer.setActiveController(mExpandedAnimationController);
-                mExpandedAnimationController.expandFromStack(() -> {
-                    updatePointerPosition();
-                    updateAfter.run();
-                } /* after */);
-            } else {
-                mBubbleContainer.cancelAllAnimations();
-                mExpandedAnimationController.collapseBackToStack(
-                        mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(),
-                        () -> {
-                            mBubbleContainer.setActiveController(mStackAnimationController);
-                            updateAfter.run();
-                        });
-            }
-
-            final float xStart =
-                    mStackAnimationController.getStackPosition().x < getWidth() / 2
-                            ? -mExpandedAnimateXDistance
-                            : mExpandedAnimateXDistance;
-
-            final float yStart = Math.min(
-                    mStackAnimationController.getStackPosition().y,
-                    mExpandedAnimateYDistance);
-            final float yDest = getYPositionForExpandedView();
-
-            if (shouldExpand) {
-                mExpandedViewContainer.setTranslationX(xStart);
-                mExpandedViewContainer.setTranslationY(yStart);
-            }
-
-            mExpandedViewXAnim.animateToFinalPosition(shouldExpand ? 0f : xStart);
-            mExpandedViewYAnim.animateToFinalPosition(shouldExpand ? yDest : yStart);
-        }
+    private void beforeExpandedViewAnimation() {
+        hideFlyoutImmediate();
+        updateExpandedBubble();
+        updateExpandedView();
+        mIsExpansionAnimating = true;
     }
 
-    private void notifyExpansionChanged(NotificationEntry entry, boolean expanded) {
-        if (mExpandListener != null) {
-            mExpandListener.onBubbleExpandChanged(expanded, entry != null ? entry.key : null);
+    private void afterExpandedViewAnimation() {
+        updateExpandedView();
+        mIsExpansionAnimating = false;
+        requestUpdate();
+    }
+
+    private void animateCollapse() {
+        mIsExpanded = false;
+        final Bubble previouslySelected = mExpandedBubble;
+        beforeExpandedViewAnimation();
+
+        mBubbleContainer.cancelAllAnimations();
+        mExpandedAnimationController.collapseBackToStack(
+                mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
+                /* collapseTo */,
+                () -> {
+                    mBubbleContainer.setActiveController(mStackAnimationController);
+                    afterExpandedViewAnimation();
+                    previouslySelected.setContentVisibility(false);
+                });
+
+        mExpandedViewXAnim.animateToFinalPosition(getCollapsedX());
+        mExpandedViewYAnim.animateToFinalPosition(getCollapsedY());
+        mExpandedViewContainer.animate()
+                .setDuration(100)
+                .alpha(0f);
+    }
+
+    private void animateExpansion() {
+        mIsExpanded = true;
+        beforeExpandedViewAnimation();
+
+        mBubbleContainer.setActiveController(mExpandedAnimationController);
+        mExpandedAnimationController.expandFromStack(() -> {
+            updatePointerPosition();
+            afterExpandedViewAnimation();
+        } /* after */);
+
+
+        mExpandedViewContainer.setTranslationX(getCollapsedX());
+        mExpandedViewContainer.setTranslationY(getCollapsedY());
+        mExpandedViewContainer.setAlpha(0f);
+
+        mExpandedViewXAnim.animateToFinalPosition(0f);
+        mExpandedViewYAnim.animateToFinalPosition(getExpandedViewY());
+        mExpandedViewContainer.animate()
+                .setDuration(100)
+                .alpha(1f);
+    }
+
+    private float getCollapsedX() {
+        return mStackAnimationController.getStackPosition().x < getWidth() / 2
+                ? -mExpandedAnimateXDistance
+                : mExpandedAnimateXDistance;
+    }
+
+    private float getCollapsedY() {
+        return Math.min(mStackAnimationController.getStackPosition().y,
+                mExpandedAnimateYDistance);
+    }
+
+    private void notifyExpansionChanged(Bubble bubble, boolean expanded) {
+        if (mExpandListener != null && bubble != null) {
+            mExpandListener.onBubbleExpandChanged(expanded, bubble.getKey());
         }
     }
 
@@ -977,7 +994,7 @@
 
     /** Called when a drag operation on an individual bubble has started. */
     public void onBubbleDragStart(View bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "onBubbleDragStart: bubble=" + bubble);
         }
         mExpandedAnimationController.prepareForBubbleDrag(bubble);
@@ -996,7 +1013,7 @@
     /** Called when a drag operation on an individual bubble has finished. */
     public void onBubbleDragFinish(
             View bubble, float x, float y, float velX, float velY) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "onBubbleDragFinish: bubble=" + bubble);
         }
 
@@ -1005,11 +1022,11 @@
         }
 
         mExpandedAnimationController.snapBubbleBack(bubble, velX, velY);
-        springOutDismissTargetAndHideCircle();
+        hideDismissTarget();
     }
 
     void onDragStart() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "onDragStart()");
         }
         if (mIsExpanded || mIsExpansionAnimating) {
@@ -1033,7 +1050,7 @@
     }
 
     void onDragFinish(float x, float y, float velX, float velY) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "onDragFinish");
         }
 
@@ -1047,7 +1064,7 @@
 
         mStackOnLeftOrWillBe = newStackX <= 0;
         updateBubbleShadowsAndDotPosition(true /* animate */);
-        springOutDismissTargetAndHideCircle();
+        hideDismissTarget();
     }
 
     void onFlyoutDragStart() {
@@ -1086,6 +1103,19 @@
     }
 
     /**
+     * Set when the flyout is tapped, so that we can expand the bubble associated with the flyout
+     * once it collapses.
+     */
+    @Nullable private Bubble mBubbleToExpandAfterFlyoutCollapse = null;
+
+    void onFlyoutTapped() {
+        mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
+
+        mFlyout.removeCallbacks(mHideFlyout);
+        mHideFlyout.run();
+    }
+
+    /**
      * Called when the flyout drag has finished, and returns true if the gesture successfully
      * dismissed the flyout.
      */
@@ -1181,9 +1211,6 @@
 
                 animateDesaturateAndDarken(magnetView, true);
             }
-
-            mDismissContainer.animateEncircleCenterWithX(true);
-
         } else {
             mAnimatingMagnet = false;
 
@@ -1194,8 +1221,6 @@
                 mExpandedAnimationController.demagnetizeBubbleTo(x, y, velX, velY);
                 animateDesaturateAndDarken(magnetView, false);
             }
-
-            mDismissContainer.animateEncircleCenterWithX(false);
         }
 
         mVibrator.vibrate(VibrationEffect.get(toTarget
@@ -1214,7 +1239,7 @@
             mAfterMagnet = null;
 
             mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
-            mDismissContainer.animateEncirclingCircleDisappearance();
+            mDismissContainer.springOut();
 
             // 'Implode' the stack and then hide the dismiss target.
             if (touchedView == this) {
@@ -1252,7 +1277,7 @@
         }
     }
 
-    /** Animates in the dismiss target, including the gradient behind it. */
+    /** Animates in the dismiss target. */
     private void springInDismissTarget() {
         if (mShowingDismiss) {
             return;
@@ -1270,7 +1295,7 @@
      * Animates the dismiss target out, as well as the circle that encircles the bubbles, if they
      * were dragged into the target and encircled.
      */
-    private void springOutDismissTargetAndHideCircle() {
+    private void hideDismissTarget() {
         if (!mShowingDismiss) {
             return;
         }
@@ -1287,6 +1312,12 @@
     /** Animates the flyout collapsed (to dot), or the reverse, starting with the given velocity. */
     private void animateFlyoutCollapsed(boolean collapsed, float velX) {
         final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
+        // If the flyout was tapped, we want a higher stiffness for the collapse animation so it's
+        // faster.
+        mFlyoutTransitionSpring.getSpring().setStiffness(
+                (mBubbleToExpandAfterFlyoutCollapse != null)
+                        ? SpringForce.STIFFNESS_MEDIUM
+                        : SpringForce.STIFFNESS_LOW);
         mFlyoutTransitionSpring
                 .setStartValue(mFlyoutDragDeltaX)
                 .setStartVelocity(velX)
@@ -1295,123 +1326,120 @@
                         : 0f);
     }
 
-    /**
-     * Calculates how large the expanded view of the bubble can be. This takes into account the
-     * y position when the bubbles are expanded as well as the bounds of the dismiss target.
-     */
-    int getMaxExpandedHeight() {
-        int expandedY = (int) mExpandedAnimationController.getExpandedY();
-        // PIP dismiss view uses FLAG_LAYOUT_IN_SCREEN so we need to subtract the bottom inset
-        int pipDismissHeight = mPipDismissHeight - getBottomInset();
-        return mDisplaySize.y - expandedY - mBubbleSize - pipDismissHeight;
+    /** Updates the dot visibility, this is used in response to a zen mode config change. */
+    void updateDots() {
+        int bubbsCount = mBubbleContainer.getChildCount();
+        for (int i = 0; i < bubbsCount; i++) {
+            BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
+            // If nothing changed the animation won't happen
+            bv.updateDotVisibility(true /* animate */);
+        }
     }
 
     /**
      * Calculates the y position of the expanded view when it is expanded.
      */
-    float getYPositionForExpandedView() {
-        return getStatusBarHeight() + mBubbleSize + mBubblePadding + mPointerHeight;
+    float getExpandedViewY() {
+        return getStatusBarHeight() + mBubbleSize + mBubblePaddingTop + mPointerHeight;
     }
 
     /**
-     * Called when the height of the currently expanded view has changed (not via an
-     * update to the bubble's desired height but for some other reason, e.g. permission view
-     * goes away).
-     */
-    void onExpandedHeightChanged() {
-        if (mIsExpanded) {
-            requestUpdate();
-        }
-    }
-
-    /** Sets whether all bubbles in the stack should not show the 'new' dot. */
-    void setSuppressNewDot(boolean suppressNewDot) {
-        mSuppressNewDot = suppressNewDot;
-
-        for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
-            BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
-            bv.setSuppressDot(suppressNewDot, true /* animate */);
-        }
-    }
-
-    /**
-     * Sets whether the flyout should not appear, even if the notif otherwise would generate one.
-     */
-    void setSuppressFlyout(boolean suppressFlyout) {
-        mSuppressFlyout = suppressFlyout;
-    }
-
-    /**
-     * Callback to run after the flyout hides. Also called if a new flyout is shown before the
-     * previous one animates out.
-     */
-    private Runnable mAfterFlyoutHides;
-
-    /**
      * Animates in the flyout for the given bubble, if available, and then hides it after some time.
      */
     @VisibleForTesting
     void animateInFlyoutForBubble(Bubble bubble) {
-        final CharSequence updateMessage = bubble.entry.getUpdateMessage(getContext());
+        final CharSequence updateMessage = bubble.getUpdateMessage(getContext());
 
-        // Show the message if one exists, and we're not expanded or animating expansion.
-        if (updateMessage != null
-                && !isExpanded()
-                && !mIsExpansionAnimating
-                && !mIsGestureInProgress
-                && !mSuppressFlyout) {
-            if (bubble.iconView != null) {
-                // Temporarily suppress the dot while the flyout is visible.
-                bubble.iconView.setSuppressDot(
-                        true /* suppressDot */, false /* animate */);
+        if (!bubble.showFlyoutForBubble()) {
+            // In case flyout was suppressed for this update, reset now.
+            bubble.setSuppressFlyout(false);
+            return;
+        }
 
-                mFlyoutDragDeltaX = 0f;
-                mFlyout.setAlpha(0f);
+        if (updateMessage == null
+                || isExpanded()
+                || mIsExpansionAnimating
+                || mIsGestureInProgress
+                || mBubbleToExpandAfterFlyoutCollapse != null) {
+            // Skip the message if none exists, we're expanded or animating expansion, or we're
+            // about to expand a bubble from the previous tapped flyout.
+            return;
+        }
 
-                if (mAfterFlyoutHides != null) {
-                    mAfterFlyoutHides.run();
-                }
+        if (bubble.getIconView() != null) {
+            // Temporarily suppress the dot while the flyout is visible.
+            bubble.getIconView().setSuppressDot(
+                    true /* suppressDot */, false /* animate */);
 
-                mAfterFlyoutHides = () -> {
-                    if (bubble.iconView == null) {
-                        return;
-                    }
+            mFlyout.removeCallbacks(mAnimateInFlyout);
+            mFlyoutDragDeltaX = 0f;
 
-                    // If we're going to suppress the dot, make it visible first so it'll
-                    // visibly animate away.
-                    if (mSuppressNewDot) {
-                        bubble.iconView.setSuppressDot(
-                                false /* suppressDot */, false /* animate */);
-                    }
-
-                    // Reset dot suppression. If we're not suppressing due to DND, then
-                    // stop suppressing it with no animation (since the flyout has
-                    // transformed into the dot). If we are suppressing due to DND, animate
-                    // it away.
-                    bubble.iconView.setSuppressDot(
-                            mSuppressNewDot /* suppressDot */,
-                            mSuppressNewDot /* animate */);
-                };
-
-                // Post in case layout isn't complete and getWidth returns 0.
-                post(() -> {
-                    // An auto-expanding bubble could have been posted during the time it takes to
-                    // layout.
-                    if (isExpanded()) {
-                        return;
-                    }
-
-                    mFlyout.showFlyout(
-                            updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
-                            mStackAnimationController.isStackOnLeftSide(),
-                            bubble.iconView.getBadgeColor(), mAfterFlyoutHides);
-                });
+            if (mAfterFlyoutHides != null) {
+                mAfterFlyoutHides.run();
             }
 
-            mFlyout.removeCallbacks(mHideFlyout);
-            mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
-            logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
+            mAfterFlyoutHides = () -> {
+                final boolean suppressDot = !bubble.showBubbleDot();
+                // If we're going to suppress the dot, make it visible first so it'll
+                // visibly animate away.
+                if (suppressDot) {
+                    bubble.getIconView().setSuppressDot(
+                            false /* suppressDot */, false /* animate */);
+                }
+                // Reset dot suppression. If we're not suppressing due to DND, then
+                // stop suppressing it with no animation (since the flyout has
+                // transformed into the dot). If we are suppressing due to DND, animate
+                // it away.
+                bubble.getIconView().setSuppressDot(
+                        suppressDot /* suppressDot */,
+                        suppressDot /* animate */);
+
+                if (mBubbleToExpandAfterFlyoutCollapse != null) {
+                    mBubbleData.setSelectedBubble(mBubbleToExpandAfterFlyoutCollapse);
+                    mBubbleData.setExpanded(true);
+                    mBubbleToExpandAfterFlyoutCollapse = null;
+                }
+            };
+
+            mFlyout.setVisibility(INVISIBLE);
+
+            // Post in case layout isn't complete and getWidth returns 0.
+            post(() -> {
+                // An auto-expanding bubble could have been posted during the time it takes to
+                // layout.
+                if (isExpanded()) {
+                    return;
+                }
+
+                final Runnable afterShow = () -> {
+                    mAnimateInFlyout = () -> {
+                        mFlyout.setVisibility(VISIBLE);
+                        bubble.getIconView().setSuppressDot(
+                                true /* suppressDot */, false /* animate */);
+                        mFlyoutDragDeltaX =
+                                mStackAnimationController.isStackOnLeftSide()
+                                        ? -mFlyout.getWidth()
+                                        : mFlyout.getWidth();
+                        animateFlyoutCollapsed(false /* collapsed */, 0 /* velX */);
+                        mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
+                    };
+
+                    mFlyout.postDelayed(mAnimateInFlyout, 200);
+                };
+
+                mFlyout.setupFlyoutStartingAsDot(
+                        updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
+                        mStackAnimationController.isStackOnLeftSide(),
+                        bubble.getIconView().getBadgeColor(),
+                        afterShow,
+                        mAfterFlyoutHides);
+                mFlyout.bringToFront();
+            });
         }
+
+        mFlyout.removeCallbacks(mHideFlyout);
+        mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
+        logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
     }
 
     /** Hide the flyout immediately and cancel any pending hide runnables. */
@@ -1420,6 +1448,7 @@
             mAfterFlyoutHides.run();
         }
 
+        mFlyout.removeCallbacks(mAnimateInFlyout);
         mFlyout.removeCallbacks(mHideFlyout);
         mFlyout.hideFlyout();
     }
@@ -1430,6 +1459,11 @@
             if (mBubbleContainer.getChildCount() > 0) {
                 mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
             }
+            // Increase the touch target size of the bubble
+            outRect.top -= mBubbleTouchPadding;
+            outRect.left -= mBubbleTouchPadding;
+            outRect.right += mBubbleTouchPadding;
+            outRect.bottom += mBubbleTouchPadding;
         } else {
             mBubbleContainer.getBoundsOnScreen(outRect);
         }
@@ -1454,14 +1488,6 @@
         return 0;
     }
 
-    private int getBottomInset() {
-        if (getRootWindowInsets() != null) {
-            WindowInsets insets = getRootWindowInsets();
-            return insets.getSystemWindowInsetBottom();
-        }
-        return 0;
-    }
-
     private boolean isIntersecting(View view, float x, float y) {
         mTempLoc = view.getLocationOnScreen();
         mTempRect.set(mTempLoc[0], mTempLoc[1], mTempLoc[0] + view.getWidth(),
@@ -1479,33 +1505,33 @@
     }
 
     private void updateExpandedBubble() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "updateExpandedBubble()");
         }
         mExpandedViewContainer.removeAllViews();
         if (mExpandedBubble != null && mIsExpanded) {
-            mExpandedViewContainer.addView(mExpandedBubble.expandedView);
-            mExpandedBubble.expandedView.populateExpandedView();
+            mExpandedViewContainer.addView(mExpandedBubble.getExpandedView());
+            mExpandedBubble.getExpandedView().populateExpandedView();
             mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
             mExpandedViewContainer.setAlpha(1.0f);
         }
     }
 
-    private void applyCurrentState() {
-        if (DEBUG) {
-            Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded);
+    private void updateExpandedView() {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
+            Log.d(TAG, "updateExpandedView: mIsExpanded=" + mIsExpanded);
         }
 
         mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
         if (mIsExpanded) {
             // First update the view so that it calculates a new height (ensuring the y position
             // calculation is correct)
-            mExpandedBubble.expandedView.updateView();
-            final float y = getYPositionForExpandedView();
+            mExpandedBubble.getExpandedView().updateView();
+            final float y = getExpandedViewY();
             if (!mExpandedViewYAnim.isRunning()) {
                 // We're not animating so set the value
                 mExpandedViewContainer.setTranslationY(y);
-                mExpandedBubble.expandedView.updateView();
+                mExpandedBubble.getExpandedView().updateView();
             } else {
                 // We are animating so update the value; there is an end listener on the animator
                 // that will ensure expandedeView.updateView gets called.
@@ -1519,24 +1545,12 @@
 
     /** Sets the appropriate Z-order and dot position for each bubble in the stack. */
     private void updateBubbleShadowsAndDotPosition(boolean animate) {
-        int bubbsCount = mBubbleContainer.getChildCount();
-        for (int i = 0; i < bubbsCount; i++) {
+        int bubbleCount = mBubbleContainer.getChildCount();
+        for (int i = 0; i < bubbleCount; i++) {
             BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
             bv.updateDotVisibility(true /* animate */);
             bv.setZ((BubbleController.MAX_BUBBLES
                     * getResources().getDimensionPixelSize(R.dimen.bubble_elevation)) - i);
-
-            // Draw the shadow around the circle inscribed within the bubble's bounds. This
-            // (intentionally) does not draw a shadow behind the update dot, which should be drawing
-            // its own shadow since it's on a different (higher) plane.
-            bv.setOutlineProvider(new ViewOutlineProvider() {
-                @Override
-                public void getOutline(View view, Outline outline) {
-                    outline.setOval(0, 0, mBubbleSize, mBubbleSize);
-                }
-            });
-            bv.setClipToOutline(false);
-
             // If the dot is on the left, and so is the stack, we need to change the dot position.
             if (bv.getDotPositionOnLeft() == mStackOnLeftOrWillBe) {
                 bv.setDotPosition(!mStackOnLeftOrWillBe, animate);
@@ -1545,7 +1559,7 @@
     }
 
     private void updatePointerPosition() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "updatePointerPosition()");
         }
 
@@ -1563,7 +1577,7 @@
         // Remove padding when deriving pointer location from bubbles.
         float bubbleCenter = bubbleLeftFromScreenLeft + halfBubble - mExpandedViewPadding;
 
-        expandedBubble.expandedView.setPointerPosition(bubbleCenter);
+        expandedBubble.getExpandedView().setPointerPosition(bubbleCenter);
     }
 
     /**
@@ -1584,7 +1598,7 @@
         if (bubble == null) {
             return 0;
         }
-        return mBubbleContainer.indexOfChild(bubble.iconView);
+        return mBubbleContainer.indexOfChild(bubble.getIconView());
     }
 
     /**
@@ -1617,8 +1631,8 @@
      * @param action the user interaction enum.
      */
     private void logBubbleEvent(@Nullable Bubble bubble, int action) {
-        if (bubble == null || bubble.entry == null
-                || bubble.entry.notification == null) {
+        if (bubble == null || bubble.getEntry() == null
+                || bubble.getEntry().notification == null) {
             StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
                     null /* package name */,
                     null /* notification channel */,
@@ -1630,9 +1644,9 @@
                     getNormalizedYPosition(),
                     false /* unread bubble */,
                     false /* on-going bubble */,
-                    false /* foreground bubble */);
+                    false /* isAppForeground (unused) */);
         } else {
-            StatusBarNotification notification = bubble.entry.notification;
+            StatusBarNotification notification = bubble.getEntry().notification;
             StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
                     notification.getPackageName(),
                     notification.getNotification().getChannelId(),
@@ -1642,9 +1656,9 @@
                     action,
                     getNormalizedXPosition(),
                     getNormalizedYPosition(),
-                    bubble.entry.showInShadeWhenBubble(),
-                    bubble.entry.isForegroundService(),
-                    BubbleController.isForegroundApp(mContext, notification.getPackageName()));
+                    bubble.showInShadeWhenBubble(),
+                    bubble.isOngoing(),
+                    false /* isAppForeground (unused) */);
         }
     }
 
@@ -1656,7 +1670,7 @@
         if (!isExpanded()) {
             return false;
         }
-        return mExpandedBubble.expandedView.performBackPressIfNeeded();
+        return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
     }
 
     /** For debugging only */
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 8fe8bd3..4240e06 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.graphics.PointF;
-import android.os.Handler;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -45,7 +44,6 @@
      */
     private static final float INDIVIDUAL_BUBBLE_DISMISS_MIN_VELOCITY = 6000f;
 
-    private static final String TAG = "BubbleTouchHandler";
     /**
      * When the stack is flung towards the bottom of the screen, it'll be dismissed if it's flung
      * towards the center of the screen (where the dismiss target is). This value is the width of
@@ -66,11 +64,10 @@
     private int mTouchSlopSquared;
     private VelocityTracker mVelocityTracker;
 
-    private boolean mInDismissTarget;
-    private Handler mHandler = new Handler();
-
     /** View that was initially touched, when we received the first ACTION_DOWN event. */
     private View mTouchedView;
+    /** Whether the current touched view is in the dismiss target. */
+    private boolean mInDismissTarget;
 
     BubbleTouchHandler(BubbleStackView stackView,
             BubbleData bubbleData, Context context) {
@@ -98,6 +95,15 @@
             return false;
         }
 
+        if (!(mTouchedView instanceof BubbleView)
+                && !(mTouchedView instanceof BubbleStackView)
+                && !(mTouchedView instanceof BubbleFlyoutView)) {
+            // Not touching anything touchable, but we shouldn't collapse (e.g. touching edge
+            // of expanded view).
+            resetForNextGesture();
+            return false;
+        }
+
         final boolean isStack = mStack.equals(mTouchedView);
         final boolean isFlyout = mStack.getFlyoutView().equals(mTouchedView);
         final float rawX = event.getRawX();
@@ -193,9 +199,8 @@
                                 }
                             });
                 } else if (isFlyout) {
-                    // TODO(b/129768381): Expand if tapped, dismiss if swiped away.
                     if (!mBubbleData.isExpanded() && !mMovedEnough) {
-                        mBubbleData.setExpanded(true);
+                        mStack.onFlyoutTapped();
                     }
                 } else if (mMovedEnough) {
                     if (isStack) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 6f1ed28..4176738 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -19,13 +19,17 @@
 import android.annotation.Nullable;
 import android.app.Notification;
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Path;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.graphics.drawable.InsetDrawable;
 import android.util.AttributeSet;
+import android.util.PathParser;
 import android.widget.FrameLayout;
 
 import com.android.internal.graphics.ColorUtils;
@@ -38,23 +42,25 @@
  * A floating object on the screen that can post message updates.
  */
 public class BubbleView extends FrameLayout {
-    private static final String TAG = "BubbleView";
 
     private static final int DARK_ICON_ALPHA = 180;
     private static final double ICON_MIN_CONTRAST = 4.1;
-    private static final int DEFAULT_BACKGROUND_COLOR =  Color.LTGRAY;
+    private static final int DEFAULT_BACKGROUND_COLOR = Color.LTGRAY;
     // Same value as Launcher3 badge code
     private static final float WHITE_SCRIM_ALPHA = 0.54f;
     private Context mContext;
 
     private BadgedImageView mBadgedImageView;
     private int mBadgeColor;
-    private int mPadding;
     private int mIconInset;
+    private Drawable mUserBadgedAppIcon;
+
+    // mBubbleIconFactory cannot be static because it depends on Context.
+    private BubbleIconFactory mBubbleIconFactory;
 
     private boolean mSuppressDot = false;
 
-    private NotificationEntry mEntry;
+    private Bubble mBubble;
 
     public BubbleView(Context context) {
         this(context, null);
@@ -71,8 +77,6 @@
     public BubbleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mContext = context;
-        // XXX: can this padding just be on the view and we look it up?
-        mPadding = getResources().getDimensionPixelSize(R.dimen.bubble_view_padding);
         mIconInset = getResources().getDimensionPixelSize(R.dimen.bubble_icon_inset);
     }
 
@@ -88,16 +92,15 @@
     }
 
     /**
-     * Populates this view with a notification.
+     * Populates this view with a bubble.
      * <p>
-     * This should only be called when a new notification is being set on the view, updates to the
-     * current notification should use {@link #update(NotificationEntry)}.
+     * This should only be called when a new bubble is being set on the view, updates to the
+     * current bubble should use {@link #update(Bubble)}.
      *
-     * @param entry the notification to display as a bubble.
+     * @param bubble the bubble to display in this view.
      */
-    public void setNotif(NotificationEntry entry) {
-        mEntry = entry;
-        updateViews();
+    public void setBubble(Bubble bubble) {
+        mBubble = bubble;
     }
 
     /**
@@ -105,7 +108,7 @@
      */
     @Nullable
     public NotificationEntry getEntry() {
-        return mEntry;
+        return mBubble != null ? mBubble.getEntry() : null;
     }
 
     /**
@@ -113,24 +116,34 @@
      */
     @Nullable
     public String getKey() {
-        return (mEntry != null) ? mEntry.key : null;
+        return (mBubble != null) ? mBubble.getKey() : null;
     }
 
     /**
-     * Updates the UI based on the entry, updates badge and animates messages as needed.
+     * Updates the UI based on the bubble, updates badge and animates messages as needed.
      */
-    public void update(NotificationEntry entry) {
-        mEntry = entry;
+    public void update(Bubble bubble) {
+        mBubble = bubble;
         updateViews();
     }
 
     /**
+     * @param factory Factory for creating normalized bubble icons.
+     */
+    public void setBubbleIconFactory(BubbleIconFactory factory) {
+        mBubbleIconFactory = factory;
+    }
+
+    public void setAppIcon(Drawable appIcon) {
+        mUserBadgedAppIcon = appIcon;
+    }
+    /**
      * @return the {@link ExpandableNotificationRow} view to display notification content when the
      * bubble is expanded.
      */
     @Nullable
     public ExpandableNotificationRow getRowView() {
-        return (mEntry != null) ? mEntry.getRow() : null;
+        return (mBubble != null) ? mBubble.getEntry().getRow() : null;
     }
 
     /** Changes the dot's visibility to match the bubble view's state. */
@@ -169,7 +182,7 @@
      * after animation if requested.
      */
     private void updateDotVisibility(boolean animate, Runnable after) {
-        boolean showDot = getEntry().showInShadeWhenBubble() && !mSuppressDot;
+        boolean showDot = mBubble.showBubbleDot() && !mSuppressDot;
 
         if (animate) {
             animateDot(showDot, after);
@@ -186,7 +199,6 @@
             if (showDot) {
                 mBadgedImageView.setShowDot(true);
             }
-
             mBadgedImageView.clearAnimation();
             mBadgedImageView.animate().setDuration(200)
                     .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
@@ -207,37 +219,50 @@
     }
 
     void updateViews() {
-        if (mEntry == null) {
+        if (mBubble == null || mBubbleIconFactory == null) {
             return;
         }
-        Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
-        Notification n = mEntry.notification.getNotification();
-        Icon ic;
-        boolean needsTint;
-        if (metadata != null) {
-            ic = metadata.getIcon();
-            needsTint = ic.getType() != Icon.TYPE_ADAPTIVE_BITMAP;
-        } else {
-            needsTint = n.getLargeIcon() == null;
-            ic = needsTint ? n.getSmallIcon() : n.getLargeIcon();
-        }
+        // Update icon.
+        Notification.BubbleMetadata metadata = mBubble.getEntry().getBubbleMetadata();
+        Notification n = mBubble.getEntry().notification.getNotification();
+        Icon ic = metadata.getIcon();
+        boolean needsTint = ic.getType() != Icon.TYPE_ADAPTIVE_BITMAP;
+
         Drawable iconDrawable = ic.loadDrawable(mContext);
         if (needsTint) {
-            mBadgedImageView.setImageDrawable(buildIconWithTint(iconDrawable, n.color));
-        } else {
-            mBadgedImageView.setImageDrawable(iconDrawable);
+            iconDrawable = buildIconWithTint(iconDrawable, n.color);
         }
+        Bitmap bubbleIcon = mBubbleIconFactory.createBadgedIconBitmap(iconDrawable,
+                null /* user */,
+                true /* shrinkNonAdaptiveIcons */).icon;
+        mBubbleIconFactory.badgeWithDrawable(bubbleIcon, mUserBadgedAppIcon);
+        mBadgedImageView.setImageBitmap(bubbleIcon);
+
+        // Update badge.
         int badgeColor = determineDominateColor(iconDrawable, n.color);
         mBadgeColor = badgeColor;
         mBadgedImageView.setDotColor(badgeColor);
-        animateDot(mEntry.showInShadeWhenBubble() /* showDot */, null /* after */);
+
+        // Update dot.
+        Path iconPath = PathParser.createPathFromPathData(
+                getResources().getString(com.android.internal.R.string.config_icon_mask));
+        Matrix matrix = new Matrix();
+        float scale = mBubbleIconFactory.getNormalizer().getScale(iconDrawable,
+                null /* outBounds */, null /* path */, null /* outMaskShape */);
+        float radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f;
+        matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
+                radius /* pivot y */);
+        iconPath.transform(matrix);
+        mBadgedImageView.drawDot(iconPath);
+
+        animateDot(mBubble.showInShadeWhenBubble() /* showDot */, null /* after */);
     }
 
     int getBadgeColor() {
         return mBadgeColor;
     }
 
-    private Drawable buildIconWithTint(Drawable iconDrawable, int backgroundColor) {
+    private AdaptiveIconDrawable buildIconWithTint(Drawable iconDrawable, int backgroundColor) {
         iconDrawable = checkTint(iconDrawable, backgroundColor);
         InsetDrawable foreground = new InsetDrawable(iconDrawable, mIconInset);
         ColorDrawable background = new ColorDrawable(backgroundColor);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 1fa0e12..20a05d2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.bubbles.animation;
 
+import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Path;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.view.View;
@@ -26,10 +28,13 @@
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 
 import com.google.android.collect.Sets;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Set;
 
 /**
@@ -46,24 +51,26 @@
      */
     private static final int ANIMATE_TRANSLATION_FACTOR = 4;
 
-    /** How much to scale down bubbles when they're animating in/out. */
-    private static final float ANIMATE_SCALE_PERCENT = 0.5f;
+    /** Duration of the expand/collapse target path animation. */
+    private static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175;
 
-    /** The stack position to collapse back to in {@link #collapseBackToStack}. */
-    private PointF mCollapseToPoint;
+    /** Stiffness for the expand/collapse path-following animation. */
+    private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
 
     /** Horizontal offset between bubbles, which we need to know to re-stack them. */
     private float mStackOffsetPx;
-    /** Spacing between bubbles in the expanded state. */
-    private float mBubblePaddingPx;
+    /** Space between status bar and bubbles in the expanded state. */
+    private float mBubblePaddingTop;
     /** Size of each bubble. */
     private float mBubbleSizePx;
     /** Height of the status bar. */
     private float mStatusBarHeight;
     /** Size of display. */
     private Point mDisplaySize;
-    /** Size of dismiss target at bottom of screen. */
-    private float mPipDismissHeight;
+    /** Max number of bubbles shown in row above expanded view.*/
+    private int mBubblesMaxRendered;
+    /** Width of current screen orientation. */
+    private float mScreenWidth;
 
     /** Whether the dragged-out bubble is in the dismiss target. */
     private boolean mIndividualBubbleWithinDismissTarget = false;
@@ -86,10 +93,14 @@
     private boolean mSpringingBubbleToTouch = false;
 
     private int mExpandedViewPadding;
+    private float mLauncherGridDiff;
 
-    public ExpandedAnimationController(Point displaySize, int expandedViewPadding) {
+    public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
+            int orientation) {
         mDisplaySize = displaySize;
+        updateOrientation(orientation);
         mExpandedViewPadding = expandedViewPadding;
+        mLauncherGridDiff = 30f;
     }
 
     /**
@@ -109,7 +120,7 @@
         mAnimatingExpand = true;
         mAfterExpand = after;
 
-        startOrUpdateExpandAnimation();
+        startOrUpdatePathAnimation(true /* expanding */);
     }
 
     /** Animate collapsing the bubbles back to their stacked position. */
@@ -119,43 +130,101 @@
         mAfterCollapse = after;
         mCollapsePoint = collapsePoint;
 
-        startOrUpdateCollapseAnimation();
+        startOrUpdatePathAnimation(false /* expanding */);
     }
 
-    private void startOrUpdateExpandAnimation() {
-        animationsForChildrenFromIndex(
-                0, /* startIndex */
-                (index, animation) -> animation.position(getBubbleLeft(index), getExpandedY()))
-                .startAll(() -> {
-                    mAnimatingExpand = false;
-
-                    if (mAfterExpand != null) {
-                        mAfterExpand.run();
-                    }
-
-                    mAfterExpand = null;
-                });
+    /**
+     * Update effective screen width based on current orientation.
+     * @param orientation Landscape or portrait.
+     */
+    public void updateOrientation(int orientation) {
+        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            mScreenWidth = mDisplaySize.y;
+            return;
+        }
+        mScreenWidth = mDisplaySize.x;
     }
 
-    private void startOrUpdateCollapseAnimation() {
-        // Stack to the left if we're going to the left, or right if not.
-        final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1;
-        animationsForChildrenFromIndex(
-                0, /* startIndex */
-                (index, animation) -> {
-                    animation.position(
-                            mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx),
-                            mCollapsePoint.y);
-                })
-                .startAll(() -> {
-                    mAnimatingCollapse = false;
+    /**
+     * Animates the bubbles along a curved path, either to expand them along the top or collapse
+     * them back into a stack.
+     */
+    private void startOrUpdatePathAnimation(boolean expanding) {
+        Runnable after;
 
-                    if (mAfterCollapse != null) {
-                        mAfterCollapse.run();
-                    }
+        if (expanding) {
+            after = () -> {
+                mAnimatingExpand = false;
 
-                    mAfterCollapse = null;
-                });
+                if (mAfterExpand != null) {
+                    mAfterExpand.run();
+                }
+
+                mAfterExpand = null;
+            };
+        } else {
+            after = () -> {
+                mAnimatingCollapse = false;
+
+                if (mAfterCollapse != null) {
+                    mAfterCollapse.run();
+                }
+
+                mAfterCollapse = null;
+            };
+        }
+
+        // Animate each bubble individually, since each path will end in a different spot.
+        animationsForChildrenFromIndex(0, (index, animation) -> {
+            final View bubble = mLayout.getChildAt(index);
+
+            // Start a path at the bubble's current position.
+            final Path path = new Path();
+            path.moveTo(bubble.getTranslationX(), bubble.getTranslationY());
+
+            final float expandedY = getExpandedY();
+            if (expanding) {
+                // If we're expanding, first draw a line from the bubble's current position to the
+                // top of the screen.
+                path.lineTo(bubble.getTranslationX(), expandedY);
+
+                // Then, draw a line across the screen to the bubble's resting position.
+                path.lineTo(getBubbleLeft(index), expandedY);
+            } else {
+                final float sideMultiplier =
+                        mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1;
+                final float stackedX = mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx);
+
+                // If we're collapsing, draw a line from the bubble's current position to the side
+                // of the screen where the bubble will be stacked.
+                path.lineTo(stackedX, expandedY);
+
+                // Then, draw a line down to the stack position.
+                path.lineTo(stackedX, mCollapsePoint.y);
+            }
+
+            // The lead bubble should be the bubble with the longest distance to travel when we're
+            // expanding, and the bubble with the shortest distance to travel when we're collapsing.
+            // During expansion from the left side, the last bubble has to travel to the far right
+            // side, so we have it lead and 'pull' the rest of the bubbles into place. From the
+            // right side, the first bubble is traveling to the top left, so it leads. During
+            // collapse to the left, the first bubble has the shortest travel time back to the stack
+            // position, so it leads (and vice versa).
+            final boolean firstBubbleLeads =
+                    (expanding && !mLayout.isFirstChildXLeftOfCenter(bubble.getTranslationX()))
+                            || (!expanding && mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x));
+            final int startDelay = firstBubbleLeads
+                    ? (index * 10)
+                    : ((mLayout.getChildCount() - index) * 10);
+
+            animation
+                    .followAnimatedTargetAlongPath(
+                            path,
+                            EXPAND_COLLAPSE_TARGET_ANIM_DURATION /* targetAnimDuration */,
+                            Interpolators.LINEAR /* targetAnimInterpolator */)
+                    .withStartDelay(startDelay)
+                    .withStiffness(EXPAND_COLLAPSE_ANIM_STIFFNESS);
+        }).startAll(after);
     }
 
     /** Prepares the given bubble to be dragged out. */
@@ -265,6 +334,7 @@
     public void onGestureFinished() {
         mBubbleDraggedOutEnough = false;
         mBubbleDraggingOut = null;
+        updateBubblePositions();
     }
 
     /**
@@ -276,41 +346,38 @@
                 0, (i, anim) -> anim.translationY(getExpandedY())).startAll(after);
     }
 
-    /**
-     * Animates the bubbles, starting at the given index, to the left or right by the given number
-     * of bubble widths. Passing zero for numBubbleWidths will animate the bubbles to their normal
-     * positions.
-     */
-    private void animateStackByBubbleWidthsStartingFrom(int numBubbleWidths, int startIndex) {
-        animationsForChildrenFromIndex(
-                startIndex,
-                (index, animation) ->
-                        animation.translationX(getXForChildAtIndex(index + numBubbleWidths)))
-            .startAll();
-    }
-
     /** The Y value of the row of expanded bubbles. */
     public float getExpandedY() {
         if (mLayout == null || mLayout.getRootWindowInsets() == null) {
             return 0;
         }
         final WindowInsets insets = mLayout.getRootWindowInsets();
-        return mBubblePaddingPx + Math.max(
+        return mBubblePaddingTop + Math.max(
             mStatusBarHeight,
             insets.getDisplayCutout() != null
                 ? insets.getDisplayCutout().getSafeInsetTop()
                 : 0);
     }
 
+    /** Description of current animation controller state. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("ExpandedAnimationController state:");
+        pw.print("  isActive:          "); pw.println(isActiveController());
+        pw.print("  animatingExpand:   "); pw.println(mAnimatingExpand);
+        pw.print("  animatingCollapse: "); pw.println(mAnimatingCollapse);
+        pw.print("  bubbleInDismiss:   "); pw.println(mIndividualBubbleWithinDismissTarget);
+        pw.print("  springingBubble:   "); pw.println(mSpringingBubbleToTouch);
+    }
+
     @Override
     void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
         final Resources res = layout.getResources();
         mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
-        mBubblePaddingPx = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
         mStatusBarHeight =
                 res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
-        mPipDismissHeight = res.getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height);
+        mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
 
         // Ensure that all child views are at 1x scale, and visible, in case they were animating
         // in.
@@ -351,11 +418,11 @@
         // If a bubble is added while the expand/collapse animations are playing, update the
         // animation to include the new bubble.
         if (mAnimatingExpand) {
-            startOrUpdateExpandAnimation();
+            startOrUpdatePathAnimation(true /* expanding */);
         } else if (mAnimatingCollapse) {
-            startOrUpdateCollapseAnimation();
+            startOrUpdatePathAnimation(false /* expanding */);
         } else {
-            child.setTranslationX(getXForChildAtIndex(index));
+            child.setTranslationX(getBubbleLeft(index));
             animationForChild(child)
                     .translationY(
                             getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */
@@ -411,35 +478,53 @@
         }
     }
 
-    /** Returns the appropriate X translation value for a bubble at the given index. */
-    private float getXForChildAtIndex(int index) {
-        return mBubblePaddingPx + (mBubbleSizePx + mBubblePaddingPx) * index;
-    }
-
     /**
      * @param index Bubble index in row.
      * @return Bubble left x from left edge of screen.
      */
     public float getBubbleLeft(int index) {
-        float bubbleLeftFromRowLeft = index * (mBubbleSizePx + mBubblePaddingPx);
-        return getRowLeft() + bubbleLeftFromRowLeft;
+        final float bubbleFromRowLeft = index * (mBubbleSizePx + getSpaceBetweenBubbles());
+        return getRowLeft() + bubbleFromRowLeft;
     }
 
     private float getRowLeft() {
         if (mLayout == null) {
             return 0;
         }
+
         int bubbleCount = mLayout.getChildCount();
 
-        // Width calculations.
-        double bubble = bubbleCount * mBubbleSizePx;
-        float gap = (bubbleCount - 1) * mBubblePaddingPx;
-        float row = gap + (float) bubble;
+        final float totalBubbleWidth = bubbleCount * mBubbleSizePx;
+        final float totalGapWidth = (bubbleCount - 1) * getSpaceBetweenBubbles();
+        final float rowWidth = totalGapWidth + totalBubbleWidth;
 
-        float halfRow = row / 2f;
-        float centerScreen = mDisplaySize.x / 2;
-        float rowLeftFromScreenLeft = centerScreen - halfRow;
+        final float centerScreen = mScreenWidth / 2f;
+        final float halfRow = rowWidth / 2f;
+        final float rowLeft = centerScreen - halfRow;
 
-        return rowLeftFromScreenLeft;
+        return rowLeft;
+    }
+
+    /**
+     * @return Space between bubbles in row above expanded view.
+     */
+    private float getSpaceBetweenBubbles() {
+        /**
+         * Ordered left to right:
+         *  Screen edge
+         *      [mExpandedViewPadding]
+         *  Expanded view edge
+         *      [launcherGridDiff] --- arbitrary value until launcher exports widths
+         *  Launcher's app icon grid edge that we must match
+         */
+        final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
+        final float maxRowWidth = mScreenWidth - rowMargins;
+
+        final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx;
+        final float totalGapWidth = maxRowWidth - totalBubbleWidth;
+
+        final int gapCount = mBubblesMaxRendered - 1;
+        final float gapWidth = totalGapWidth / gapCount;
+        return gapWidth;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
index 3a33392..e4c009f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -16,7 +16,14 @@
 
 package com.android.systemui.bubbles.animation;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
 import android.content.Context;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.util.FloatProperty;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -160,7 +167,7 @@
 
         /** Whether this controller is the currently active controller for its associated layout. */
         protected boolean isActiveController() {
-            return this == mLayout.mController;
+            return mLayout != null && this == mLayout.mController;
         }
 
         protected void setLayout(PhysicsAnimationLayout layout) {
@@ -232,7 +239,7 @@
                 }
 
                 if (endActions != null) {
-                    mLayout.setEndActionForMultipleProperties(
+                    setEndActionForMultipleProperties(
                             runAllEndActions,
                             allAnimatedProperties.toArray(
                                     new DynamicAnimation.ViewProperty[0]));
@@ -243,6 +250,44 @@
                 }
             };
         }
+
+        /**
+         * Sets an end action that will be run when all child animations for a given property have
+         * stopped running.
+         */
+        protected void setEndActionForProperty(
+                Runnable action, DynamicAnimation.ViewProperty property) {
+            mLayout.mEndActionForProperty.put(property, action);
+        }
+
+        /**
+         * Sets an end action that will be run when all child animations for all of the given
+         * properties have stopped running.
+         */
+        protected void setEndActionForMultipleProperties(
+                Runnable action, DynamicAnimation.ViewProperty... properties) {
+            final Runnable checkIfAllFinished = () -> {
+                if (!mLayout.arePropertiesAnimating(properties)) {
+                    action.run();
+
+                    for (DynamicAnimation.ViewProperty property : properties) {
+                        removeEndActionForProperty(property);
+                    }
+                }
+            };
+
+            for (DynamicAnimation.ViewProperty property : properties) {
+                setEndActionForProperty(checkIfAllFinished, property);
+            }
+        }
+
+        /**
+         * Removes the end listener that would have been called when all child animations for a
+         * given property stopped running.
+         */
+        protected void removeEndActionForProperty(DynamicAnimation.ViewProperty property) {
+            mLayout.mEndActionForProperty.remove(property);
+        }
     }
 
     /**
@@ -275,43 +320,6 @@
         }
     }
 
-    /**
-     * Sets an end action that will be run when all child animations for a given property have
-     * stopped running.
-     */
-    public void setEndActionForProperty(Runnable action, DynamicAnimation.ViewProperty property) {
-        mEndActionForProperty.put(property, action);
-    }
-
-    /**
-     * Sets an end action that will be run when all child animations for all of the given properties
-     * have stopped running.
-     */
-    public void setEndActionForMultipleProperties(
-            Runnable action, DynamicAnimation.ViewProperty... properties) {
-        final Runnable checkIfAllFinished = () -> {
-            if (!arePropertiesAnimating(properties)) {
-                action.run();
-
-                for (DynamicAnimation.ViewProperty property : properties) {
-                    removeEndActionForProperty(property);
-                }
-            }
-        };
-
-        for (DynamicAnimation.ViewProperty property : properties) {
-            setEndActionForProperty(checkIfAllFinished, property);
-        }
-    }
-
-    /**
-     * Removes the end listener that would have been called when all child animations for a given
-     * property stopped running.
-     */
-    public void removeEndActionForProperty(DynamicAnimation.ViewProperty property) {
-        mEndActionForProperty.remove(property);
-    }
-
     @Override
     public void addView(View child, int index, ViewGroup.LayoutParams params) {
         addViewInternal(child, index, params, false /* isReorder */);
@@ -372,11 +380,22 @@
     /** Checks whether any animations of the given properties are running on the given view. */
     public boolean arePropertiesAnimatingOnView(
             View view, DynamicAnimation.ViewProperty... properties) {
+        final ObjectAnimator targetAnimator = getTargetAnimatorFromView(view);
         for (DynamicAnimation.ViewProperty property : properties) {
             final SpringAnimation animation = getAnimationFromView(property, view);
             if (animation != null && animation.isRunning()) {
                 return true;
             }
+
+            // If the target animator is running, its update listener will trigger the translation
+            // physics animations at some point. We should consider the translation properties to be
+            // be animating in this case, even if the physics animations haven't been started yet.
+            final boolean isTranslation =
+                    property.equals(DynamicAnimation.TRANSLATION_X)
+                            || property.equals(DynamicAnimation.TRANSLATION_Y);
+            if (isTranslation && targetAnimator != null && targetAnimator.isRunning()) {
+                return true;
+            }
         }
 
         return false;
@@ -400,6 +419,14 @@
 
     /** Cancels all of the physics animations running on the given view. */
     public void cancelAnimationsOnView(View view) {
+        // If present, cancel the target animator so it doesn't restart the translation physics
+        // animations.
+        final ObjectAnimator targetAnimator = getTargetAnimatorFromView(view);
+        if (targetAnimator != null) {
+            targetAnimator.cancel();
+        }
+
+        // Cancel physics animations on the view.
         for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
             getAnimationFromView(property, view).cancel();
         }
@@ -470,6 +497,11 @@
         return (SpringAnimation) view.getTag(getTagIdForProperty(property));
     }
 
+    /** Retrieves the target animator from the view via the view tag system. */
+    @Nullable private ObjectAnimator getTargetAnimatorFromView(View view) {
+        return (ObjectAnimator) view.getTag(R.id.target_animator_tag);
+    }
+
     /** Sets up SpringAnimations of the given property for each child view in the layout. */
     private void setUpAnimationsForProperty(DynamicAnimation.ViewProperty property) {
         for (int i = 0; i < getChildCount(); i++) {
@@ -587,7 +619,7 @@
          * End actions to call when both TRANSLATION_X and TRANSLATION_Y animations have completed,
          * if {@link #position} was used to animate TRANSLATION_X and TRANSLATION_Y simultaneously.
          */
-        private Runnable[] mPositionEndActions;
+        @Nullable private Runnable[] mPositionEndActions;
 
         /**
          * All of the properties that have been set and will animate when {@link #start} is called.
@@ -603,6 +635,46 @@
         /** The animation controller that last retrieved this animator instance. */
         private PhysicsAnimationController mAssociatedController;
 
+        /**
+         * Animator used to traverse the path provided to {@link #followAnimatedTargetAlongPath}. As
+         * the path is traversed, the view's translation spring animation final positions are
+         * updated such that the view 'follows' the current position on the path.
+         */
+        @Nullable private ObjectAnimator mPathAnimator;
+
+        /** Current position on the path. This is animated by {@link #mPathAnimator}. */
+        private PointF mCurrentPointOnPath = new PointF();
+
+        /**
+         * FloatProperty instances that can be passed to {@link ObjectAnimator} to animate the value
+         * of {@link #mCurrentPointOnPath}.
+         */
+        private final FloatProperty<PhysicsPropertyAnimator> mCurrentPointOnPathXProperty =
+                new FloatProperty<PhysicsPropertyAnimator>("PathX") {
+            @Override
+            public void setValue(PhysicsPropertyAnimator object, float value) {
+                mCurrentPointOnPath.x = value;
+            }
+
+            @Override
+            public Float get(PhysicsPropertyAnimator object) {
+                return mCurrentPointOnPath.x;
+            }
+        };
+
+        private final FloatProperty<PhysicsPropertyAnimator> mCurrentPointOnPathYProperty =
+                new FloatProperty<PhysicsPropertyAnimator>("PathY") {
+            @Override
+            public void setValue(PhysicsPropertyAnimator object, float value) {
+                mCurrentPointOnPath.y = value;
+            }
+
+            @Override
+            public Float get(PhysicsPropertyAnimator object) {
+                return mCurrentPointOnPath.y;
+            }
+        };
+
         protected PhysicsPropertyAnimator(View view) {
             this.mView = view;
         }
@@ -628,6 +700,7 @@
 
         /** Animate the view's translationX value to the provided value. */
         public PhysicsPropertyAnimator translationX(float translationX, Runnable... endActions) {
+            mPathAnimator = null; // We aren't using the path anymore if we're translating.
             return property(DynamicAnimation.TRANSLATION_X, translationX, endActions);
         }
 
@@ -640,6 +713,7 @@
 
         /** Animate the view's translationY value to the provided value. */
         public PhysicsPropertyAnimator translationY(float translationY, Runnable... endActions) {
+            mPathAnimator = null; // We aren't using the path anymore if we're translating.
             return property(DynamicAnimation.TRANSLATION_Y, translationY, endActions);
         }
 
@@ -661,6 +735,46 @@
             return translationY(translationY);
         }
 
+        /**
+         * Animates a 'target' point that moves along the given path, using the provided duration
+         * and interpolator to animate the target. The view itself is animated using physics-based
+         * animations, whose final positions are updated to the target position as it animates. This
+         * results in the view 'following' the target in a realistic way.
+         *
+         * This method will override earlier calls to {@link #translationX}, {@link #translationY},
+         * or {@link #position}, ultimately animating the view's position to the final point on the
+         * given path.
+         *
+         * Any provided end listeners will be called when the physics-based animations kicked off by
+         * the moving target have completed - not when the target animation completes.
+         */
+        public PhysicsPropertyAnimator followAnimatedTargetAlongPath(
+                Path path,
+                int targetAnimDuration,
+                TimeInterpolator targetAnimInterpolator,
+                Runnable... endActions) {
+            mPathAnimator = ObjectAnimator.ofFloat(
+                    this, mCurrentPointOnPathXProperty, mCurrentPointOnPathYProperty, path);
+            mPathAnimator.setDuration(targetAnimDuration);
+            mPathAnimator.setInterpolator(targetAnimInterpolator);
+
+            mPositionEndActions = endActions;
+
+            // Remove translation related values since we're going to ignore them and follow the
+            // path instead.
+            clearTranslationValues();
+            return this;
+        }
+
+        private void clearTranslationValues() {
+            mAnimatedProperties.remove(DynamicAnimation.TRANSLATION_X);
+            mAnimatedProperties.remove(DynamicAnimation.TRANSLATION_Y);
+            mInitialPropertyValues.remove(DynamicAnimation.TRANSLATION_X);
+            mInitialPropertyValues.remove(DynamicAnimation.TRANSLATION_Y);
+            mEndActionForProperty.remove(DynamicAnimation.TRANSLATION_X);
+            mEndActionForProperty.remove(DynamicAnimation.TRANSLATION_Y);
+        }
+
         /** Animate the view's scaleX value to the provided value. */
         public PhysicsPropertyAnimator scaleX(float scaleX, Runnable... endActions) {
             return property(DynamicAnimation.SCALE_X, scaleX, endActions);
@@ -742,7 +856,7 @@
             if (after != null && after.length > 0) {
                 final DynamicAnimation.ViewProperty[] propertiesArray =
                         properties.toArray(new DynamicAnimation.ViewProperty[0]);
-                setEndActionForMultipleProperties(() -> {
+                mAssociatedController.setEndActionForMultipleProperties(() -> {
                     for (Runnable callback : after) {
                         callback.run();
                     }
@@ -774,8 +888,20 @@
                         new Runnable[]{waitForBothXAndY});
             }
 
+            if (mPathAnimator != null) {
+                startPathAnimation();
+            }
+
             // Actually start the animations.
             for (DynamicAnimation.ViewProperty property : properties) {
+                // Don't start translation animations if we're using a path animator, the update
+                // listeners added to that animator will take care of that.
+                if (mPathAnimator != null
+                        && (property.equals(DynamicAnimation.TRANSLATION_X)
+                            || property.equals(DynamicAnimation.TRANSLATION_Y))) {
+                    return;
+                }
+
                 if (mInitialPropertyValues.containsKey(property)) {
                     property.setValue(mView, mInitialPropertyValues.get(property));
                 }
@@ -797,7 +923,16 @@
 
         /** Returns the set of properties that will animate once {@link #start} is called. */
         protected Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
-            return mAnimatedProperties.keySet();
+            final HashSet<DynamicAnimation.ViewProperty> animatedProperties = new HashSet<>(
+                    mAnimatedProperties.keySet());
+
+            // If we're using a path animator, it'll kick off translation animations.
+            if (mPathAnimator != null) {
+                animatedProperties.add(DynamicAnimation.TRANSLATION_X);
+                animatedProperties.add(DynamicAnimation.TRANSLATION_Y);
+            }
+
+            return animatedProperties;
         }
 
         /**
@@ -812,7 +947,7 @@
                 long startDelay,
                 float stiffness,
                 float dampingRatio,
-                Runnable[] afterCallbacks) {
+                Runnable... afterCallbacks) {
             if (view != null) {
                 final SpringAnimation animation =
                         (SpringAnimation) view.getTag(getTagIdForProperty(property));
@@ -855,6 +990,92 @@
             }
         }
 
+        /**
+         * Updates the final position of a view's animation, without changing any of the animation's
+         * other settings. Calling this before an initial call to {@link #animateValueForChild} will
+         * work, but result in unknown values for stiffness, etc. and is not recommended.
+         */
+        private void updateValueForChild(
+                DynamicAnimation.ViewProperty property, View view, float position) {
+            if (view != null) {
+                final SpringAnimation animation =
+                        (SpringAnimation) view.getTag(getTagIdForProperty(property));
+                final SpringForce animationSpring = animation.getSpring();
+
+                if (animationSpring == null) {
+                    return;
+                }
+
+                animationSpring.setFinalPosition(position);
+                animation.start();
+            }
+        }
+
+        /**
+         * Configures the path animator to respect the settings passed into the animation builder
+         * and adds update listeners that update the translation physics animations. Then, starts
+         * the path animation.
+         */
+        protected void startPathAnimation() {
+            final SpringForce defaultSpringForceX = mController.getSpringForce(
+                    DynamicAnimation.TRANSLATION_X, mView);
+            final SpringForce defaultSpringForceY = mController.getSpringForce(
+                    DynamicAnimation.TRANSLATION_Y, mView);
+
+            if (mStartDelay > 0) {
+                mPathAnimator.setStartDelay(mStartDelay);
+            }
+
+            final Runnable updatePhysicsAnims = () -> {
+                updateValueForChild(
+                        DynamicAnimation.TRANSLATION_X, mView, mCurrentPointOnPath.x);
+                updateValueForChild(
+                        DynamicAnimation.TRANSLATION_Y, mView, mCurrentPointOnPath.y);
+            };
+
+            mPathAnimator.addUpdateListener(pathAnim -> updatePhysicsAnims.run());
+            mPathAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    animateValueForChild(
+                            DynamicAnimation.TRANSLATION_X,
+                            mView,
+                            mCurrentPointOnPath.x,
+                            mDefaultStartVelocity,
+                            0 /* startDelay */,
+                            mStiffness >= 0 ? mStiffness : defaultSpringForceX.getStiffness(),
+                            mDampingRatio >= 0
+                                    ? mDampingRatio
+                                    : defaultSpringForceX.getDampingRatio());
+
+                    animateValueForChild(
+                            DynamicAnimation.TRANSLATION_Y,
+                            mView,
+                            mCurrentPointOnPath.y,
+                            mDefaultStartVelocity,
+                            0 /* startDelay */,
+                            mStiffness >= 0 ? mStiffness : defaultSpringForceY.getStiffness(),
+                            mDampingRatio >= 0
+                                    ? mDampingRatio
+                                    : defaultSpringForceY.getDampingRatio());
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    updatePhysicsAnims.run();
+                }
+            });
+
+            // If there's a target animator saved for the view, make sure it's not running.
+            final ObjectAnimator targetAnimator = getTargetAnimatorFromView(mView);
+            if (targetAnimator != null) {
+                targetAnimator.cancel();
+            }
+
+            mView.setTag(R.id.target_animator_tag, mPathAnimator);
+            mPathAnimator.start();
+        }
+
         private void clearAnimator() {
             mInitialPropertyValues.clear();
             mAnimatedProperties.clear();
@@ -864,6 +1085,8 @@
             mStiffness = -1;
             mDampingRatio = -1;
             mEndActionsForProperty.clear();
+            mPathAnimator = null;
+            mPositionEndActions = null;
         }
 
         /**
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 ab8752e4..94d4f59 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -23,6 +23,7 @@
 import android.view.View;
 import android.view.WindowInsets;
 
+import androidx.annotation.Nullable;
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.FlingAnimation;
 import androidx.dynamicanimation.animation.FloatPropertyCompat;
@@ -33,6 +34,8 @@
 
 import com.google.android.collect.Sets;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.HashMap;
 import java.util.Set;
 
@@ -53,6 +56,10 @@
     /** Translation factor (multiplied by stack offset) to use for bubbles being animated in/out. */
     private static final int ANIMATE_TRANSLATION_FACTOR = 4;
 
+    /** Values to use for animating bubbles in. */
+    private static final float ANIMATE_IN_STIFFNESS = 1000f;
+    private static final int ANIMATE_IN_START_DELAY = 25;
+
     /**
      * Values to use for the default {@link SpringForce} provided to the physics animation layout.
      */
@@ -92,7 +99,7 @@
     private boolean mStackMovedToStartPosition = false;
 
     /** The most recent position in which the stack was resting on the edge of the screen. */
-    private PointF mRestingStackPosition;
+    @Nullable private PointF mRestingStackPosition;
 
     /** The height of the most recently visible IME. */
     private float mImeHeight = 0f;
@@ -139,14 +146,16 @@
 
     /** Horizontal offset of bubbles in the stack. */
     private float mStackOffset;
-    /** Diameter of the bubbles themselves. */
-    private int mIndividualBubbleSize;
+    /** Diameter of the bubble icon. */
+    private int mBubbleIconBitmapSize;
+    /** Width of the bubble (icon and padding). */
+    private int mBubbleSize;
     /**
      * The amount of space to add between the bubbles and certain UI elements, such as the top of
      * the screen or the IME. This does not apply to the left/right sides of the screen since the
      * stack goes offscreen intentionally.
      */
-    private int mBubblePadding;
+    private int mBubblePaddingTop;
     /** How far offscreen the stack rests. */
     private int mBubbleOffscreen;
     /** How far down the screen the stack starts, when there is no pre-existing location. */
@@ -185,7 +194,7 @@
             return false;
         }
 
-        float stackCenter = mStackPosition.x + mIndividualBubbleSize / 2;
+        float stackCenter = mStackPosition.x + mBubbleIconBitmapSize / 2;
         float screenCenter = mLayout.getWidth() / 2;
         return stackCenter < screenCenter;
     }
@@ -197,18 +206,18 @@
      */
     public void springStack(float destinationX, float destinationY) {
         springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X,
-                    new SpringForce()
+                new SpringForce()
                         .setStiffness(SPRING_AFTER_FLING_STIFFNESS)
                         .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
-                    0 /* startXVelocity */,
-                    destinationX);
+                0 /* startXVelocity */,
+                destinationX);
 
         springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_Y,
-                    new SpringForce()
+                new SpringForce()
                         .setStiffness(SPRING_AFTER_FLING_STIFFNESS)
                         .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
-                    0 /* startYVelocity */,
-                    destinationY);
+                0 /* startYVelocity */,
+                destinationY);
     }
 
     /**
@@ -218,7 +227,7 @@
      * @return The X value that the stack will end up at after the fling/spring.
      */
     public float flingStackThenSpringToEdge(float x, float velX, float velY) {
-        final boolean stackOnLeftSide = x - mIndividualBubbleSize / 2 < mLayout.getWidth() / 2;
+        final boolean stackOnLeftSide = x - mBubbleIconBitmapSize / 2 < mLayout.getWidth() / 2;
 
         final boolean stackShouldFlingLeft = stackOnLeftSide
                 ? velX < ESCAPE_VELOCITY
@@ -262,15 +271,6 @@
                         .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
                 /* destination */ null);
 
-        mLayout.setEndActionForMultipleProperties(
-                () -> {
-                    mRestingStackPosition = new PointF();
-                    mRestingStackPosition.set(mStackPosition);
-                    mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
-                    mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
-                },
-                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
         // If we're flinging now, there's no more touch event to catch up to.
         mFirstBubbleSpringingToTouch = false;
         mIsMovingFromFlinging = true;
@@ -304,6 +304,18 @@
         setStackPosition(new PointF(x, y));
     }
 
+    /** Description of current animation controller state. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("StackAnimationController state:");
+        pw.print("  isActive:             "); pw.println(isActiveController());
+        pw.print("  restingStackPos:      ");
+        pw.println(mRestingStackPosition != null ? mRestingStackPosition.toString() : "null");
+        pw.print("  currentStackPos:      "); pw.println(mStackPosition.toString());
+        pw.print("  isMovingFromFlinging: "); pw.println(mIsMovingFromFlinging);
+        pw.print("  withinDismiss:        "); pw.println(mWithinDismissTarget);
+        pw.print("  firstBubbleSpringing: "); pw.println(mFirstBubbleSpringingToTouch);
+    }
+
     /**
      * Flings the first bubble along the given property's axis, using the provided configuration
      * values. When the animation ends - either by hitting the min/max, or by friction sufficiently
@@ -317,7 +329,7 @@
             SpringForce spring,
             Float finalPosition) {
         Log.d(TAG, String.format("Flinging %s.",
-                        PhysicsAnimationLayout.getReadablePropertyName(property)));
+                PhysicsAnimationLayout.getReadablePropertyName(property)));
 
         StackPositionProperty firstBubbleProperty = new StackPositionProperty(property);
         final float currentValue = firstBubbleProperty.getValue(this);
@@ -347,6 +359,9 @@
 
                 .addEndListener((animation, canceled, endValue, endVelocity) -> {
                     if (!canceled) {
+                        mRestingStackPosition = new PointF();
+                        mRestingStackPosition.set(mStackPosition);
+
                         springFirstBubbleWithStackFollowing(property, spring, endVelocity,
                                 finalPosition != null
                                         ? finalPosition
@@ -368,8 +383,8 @@
         cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_X);
         cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_Y);
 
-        mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
-        mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
+        removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
+        removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
     }
 
     /** Save the current IME height so that we know where the stack bounds should be. */
@@ -427,7 +442,7 @@
                                     : 0);
             allowableRegion.right =
                     mLayout.getWidth()
-                            - mIndividualBubbleSize
+                            - mBubbleSize
                             + mBubbleOffscreen
                             - Math.max(
                             insets.getSystemWindowInsetRight(),
@@ -436,7 +451,7 @@
                                     : 0);
 
             allowableRegion.top =
-                    mBubblePadding
+                    mBubblePaddingTop
                             + Math.max(
                             mStatusBarHeight,
                             insets.getDisplayCutout() != null
@@ -444,9 +459,9 @@
                                     : 0);
             allowableRegion.bottom =
                     mLayout.getHeight()
-                            - mIndividualBubbleSize
-                            - mBubblePadding
-                            - (mImeHeight > Float.MIN_VALUE ? mImeHeight + mBubblePadding : 0f)
+                            - mBubbleIconBitmapSize
+                            - mBubblePaddingTop
+                            - (mImeHeight > Float.MIN_VALUE ? mImeHeight + mBubblePaddingTop : 0f)
                             - Math.max(
                             insets.getSystemWindowInsetBottom(),
                             insets.getDisplayCutout() != null
@@ -517,7 +532,7 @@
         mFirstBubbleSpringingToTouch = false;
 
         animationForChildAtIndex(0)
-                .translationX(mLayout.getWidth() / 2f - mIndividualBubbleSize / 2f)
+                .translationX(mLayout.getWidth() / 2f - mBubbleIconBitmapSize / 2f)
                 .translationY(destY, after)
                 .withPositionStartVelocities(velX, velY)
                 .withStiffness(SpringForce.STIFFNESS_MEDIUM)
@@ -626,7 +641,7 @@
         } else if (isStackPositionSet() && mLayout.indexOfChild(child) == 0) {
             // Otherwise, animate the bubble in if it's the newest bubble. If we're adding a bubble
             // to the back of the stack, it'll be largely invisible so don't bother animating it in.
-            animateInBubble(child);
+            animateInBubble(child, index);
         }
     }
 
@@ -643,22 +658,23 @@
 
         if (mLayout.getChildCount() > 0) {
             animationForChildAtIndex(0).translationX(mStackPosition.x).start();
-        } else {
-            // Set the start position back to the default since we're out of bubbles. New bubbles
-            // will then animate in from the start position.
-            mStackPosition = getDefaultStartPosition();
         }
     }
 
     @Override
-    void onChildReordered(View child, int oldIndex, int newIndex) {}
+    void onChildReordered(View child, int oldIndex, int newIndex) {
+        if (isStackPositionSet()) {
+            setStackPosition(mStackPosition);
+        }
+    }
 
     @Override
     void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
         Resources res = layout.getResources();
         mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
-        mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
-        mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+        mBubbleIconBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
         mStackStartingVerticalOffset =
                 res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y);
@@ -679,7 +695,7 @@
 
             // Animate in the top bubble now that we're visible.
             if (mLayout.getChildCount() > 0) {
-                animateInBubble(mLayout.getChildAt(0));
+                animateInBubble(mLayout.getChildAt(0), 0 /* index */);
             }
         });
     }
@@ -742,21 +758,34 @@
     }
 
     /** Animates in the given bubble. */
-    private void animateInBubble(View child) {
+    private void animateInBubble(View child, int index) {
         if (!isActiveController()) {
             return;
         }
 
-        child.setTranslationY(mStackPosition.y);
+        final float xOffset =
+                getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
 
-        float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
+        // Position the new bubble in the correct position, scaled down completely.
+        child.setTranslationX(mStackPosition.x + xOffset * index);
+        child.setTranslationY(mStackPosition.y);
+        child.setScaleX(0f);
+        child.setScaleY(0f);
+
+        // Push the subsequent views out of the way, if there are subsequent views.
+        if (index + 1 < mLayout.getChildCount()) {
+            animationForChildAtIndex(index + 1)
+                    .translationX(mStackPosition.x + xOffset * (index + 1))
+                    .withStiffness(SpringForce.STIFFNESS_LOW)
+                    .start();
+        }
+
+        // Scale in the new bubble, slightly delayed.
         animationForChild(child)
-                .scaleX(ANIMATE_IN_STARTING_SCALE /* from */, 1f /* to */)
-                .scaleY(ANIMATE_IN_STARTING_SCALE /* from */, 1f /* to */)
-                .alpha(0f /* from */, 1f /* to */)
-                .translationX(
-                        mStackPosition.x - ANIMATE_TRANSLATION_FACTOR * xOffset /* from */,
-                        mStackPosition.x /* to */)
+                .scaleX(1f)
+                .scaleY(1f)
+                .withStiffness(ANIMATE_IN_STIFFNESS)
+                .withStartDelay(mLayout.getChildCount() > 1 ? ANIMATE_IN_START_DELAY : 0)
                 .start();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 6e75c03..66a0619 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -28,7 +28,7 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleData;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -85,7 +85,7 @@
      * possible.
      */
     private final boolean mAlwaysExpandNonGroupedNotification;
-    private final BubbleData mBubbleData;
+    private final BubbleController mBubbleController;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final KeyguardBypassController mBypassController;
 
@@ -107,8 +107,8 @@
             StatusBarStateController statusBarStateController,
             NotificationEntryManager notificationEntryManager,
             Lazy<ShadeController> shadeController,
-            BubbleData bubbleData,
             KeyguardBypassController bypassController,
+            BubbleController bubbleController,
             DynamicPrivacyController privacyController) {
         mHandler = mainHandler;
         mLockscreenUserManager = notificationLockscreenUserManager;
@@ -121,7 +121,7 @@
         Resources res = context.getResources();
         mAlwaysExpandNonGroupedNotification =
                 res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
-        mBubbleData = bubbleData;
+        mBubbleController = bubbleController;
         mDynamicPrivacyController = privacyController;
         privacyController.addListener(this);
     }
@@ -147,7 +147,7 @@
         for (int i = 0; i < N; i++) {
             NotificationEntry ent = activeNotifications.get(i);
             if (ent.isRowDismissed() || ent.isRowRemoved()
-                    || (mBubbleData.hasBubbleWithKey(ent.key) && !ent.showInShadeWhenBubble())) {
+                    || mBubbleController.isBubbleNotificationSuppressedFromShade(ent.key)) {
                 // we don't want to update removed notifications because they could
                 // temporarily become children if they were isolated before.
                 continue;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index 154d7b35..f99662e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -133,11 +133,6 @@
                 }
             }
         }
-
-        if (entry.isBubble() && !entry.showInShadeWhenBubble()) {
-            return true;
-        }
-
         return false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
index 5bab0ef3..80b2f39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -23,7 +23,6 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.hardware.display.AmbientDisplayConfiguration;
-import android.os.Bundle;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -32,7 +31,6 @@
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
 import android.service.notification.StatusBarNotification;
-import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -41,9 +39,10 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
+import javax.inject.Inject;
+
 /**
  * Provides heads-up and pulsing state for notification entries.
  */
@@ -54,9 +53,8 @@
     private static final boolean ENABLE_HEADS_UP = true;
     private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
 
-    private final StatusBarStateController mStatusBarStateController =
-            Dependency.get(StatusBarStateController.class);
-    private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
+    private final StatusBarStateController mStatusBarStateController;
+    private final NotificationFilter mNotificationFilter;
     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
 
     private final Context mContext;
@@ -64,7 +62,6 @@
     private final IDreamManager mDreamManager;
 
     private NotificationPresenter mPresenter;
-    private ShadeController mShadeController;
     private HeadsUpManager mHeadsUpManager;
     private HeadsUpSuppressor mHeadsUpSuppressor;
 
@@ -73,12 +70,16 @@
     protected boolean mUseHeadsUp = false;
     private boolean mDisableNotificationAlerts;
 
-    public NotificationInterruptionStateProvider(Context context) {
+    @Inject
+    public NotificationInterruptionStateProvider(Context context, NotificationFilter filter,
+            StatusBarStateController stateController) {
         this(context,
                 (PowerManager) context.getSystemService(Context.POWER_SERVICE),
                 IDreamManager.Stub.asInterface(
                         ServiceManager.checkService(DreamService.DREAM_SERVICE)),
-                new AmbientDisplayConfiguration(context));
+                new AmbientDisplayConfiguration(context),
+                filter,
+                stateController);
     }
 
     @VisibleForTesting
@@ -86,11 +87,15 @@
             Context context,
             PowerManager powerManager,
             IDreamManager dreamManager,
-            AmbientDisplayConfiguration ambientDisplayConfiguration) {
+            AmbientDisplayConfiguration ambientDisplayConfiguration,
+            NotificationFilter notificationFilter,
+            StatusBarStateController statusBarStateController) {
         mContext = context;
         mPowerManager = powerManager;
         mDreamManager = dreamManager;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
+        mNotificationFilter = notificationFilter;
+        mStatusBarStateController = statusBarStateController;
     }
 
     /** Sets up late-binding dependencies for this component. */
@@ -98,29 +103,39 @@
             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 = 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();
-                    }
-                }
-            }
-        };
+        mHeadsUpObserver = observer;
 
         if (ENABLE_HEADS_UP) {
             mContext.getContentResolver().registerContentObserver(
@@ -134,13 +149,6 @@
         mHeadsUpObserver.onChange(true); // set up
     }
 
-    private ShadeController getShadeController() {
-        if (mShadeController == null) {
-            mShadeController = Dependency.get(ShadeController.class);
-        }
-        return mShadeController;
-    }
-
     /**
      * Whether the notification should appear as a bubble with a fly-out on top of the screen.
      *
@@ -149,6 +157,15 @@
      */
     public boolean shouldBubbleUp(NotificationEntry entry) {
         final StatusBarNotification sbn = entry.notification;
+
+        if (!canAlertCommon(entry)) {
+            return false;
+        }
+
+        if (!canAlertAwakeCommon(entry)) {
+            return false;
+        }
+
         if (!entry.canBubble) {
             if (DEBUG) {
                 Log.d(TAG, "No bubble up: not allowed to bubble: " + sbn.getKey());
@@ -173,10 +190,6 @@
             return false;
         }
 
-        if (!canHeadsUpCommon(entry)) {
-            return false;
-        }
-
         return true;
     }
 
@@ -197,6 +210,21 @@
     private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
         StatusBarNotification sbn = entry.notification;
 
+        if (!mUseHeadsUp) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: no huns");
+            }
+            return false;
+        }
+
+        if (!canAlertCommon(entry)) {
+            return false;
+        }
+
+        if (!canAlertAwakeCommon(entry)) {
+            return false;
+        }
+
         boolean inShade = mStatusBarStateController.getState() == SHADE;
         if (entry.isBubble() && inShade) {
             if (DEBUG) {
@@ -206,17 +234,13 @@
             return false;
         }
 
-        if (!canAlertCommon(entry)) {
+        if (entry.shouldSuppressPeek()) {
             if (DEBUG) {
-                Log.d(TAG, "No heads up: notification shouldn't alert: " + sbn.getKey());
+                Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
             }
             return false;
         }
 
-        if (!canHeadsUpCommon(entry)) {
-            return false;
-        }
-
         if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
             if (DEBUG) {
                 Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
@@ -287,16 +311,13 @@
     }
 
     /**
-     * Common checks between regular heads up and when pulsing.  See
-     * {@link #shouldHeadsUp(NotificationEntry)} and
-     * {@link #shouldHeadsUpWhenDozing(NotificationEntry)}.  Notifications that fail any of these
-     * checks
-     * should not alert at all.
+     * Common checks between regular & AOD heads up and bubbles.
      *
      * @param entry the entry to check
      * @return true if these checks pass, false if the notification should not alert
      */
-    protected boolean canAlertCommon(NotificationEntry entry) {
+    @VisibleForTesting
+    public boolean canAlertCommon(NotificationEntry entry) {
         StatusBarNotification sbn = entry.notification;
 
         if (mNotificationFilter.shouldFilterOut(entry)) {
@@ -313,46 +334,36 @@
             }
             return false;
         }
-
         return true;
     }
 
     /**
-     * Common checks between heads up alerting and bubble fly out alerting. See
-     * {@link #shouldHeadsUp(NotificationEntry)} and
-     * {@link #shouldBubbleUp(NotificationEntry)}. Notifications that fail any of these
-     * checks should not interrupt the user on screen.
+     * Common checks between alerts that occur while the device is awake (heads up & bubbles).
      *
      * @param entry the entry to check
-     * @return true if these checks pass, false if the notification should not interrupt on screen
+     * @return true if these checks pass, false if the notification should not alert
      */
-    public boolean canHeadsUpCommon(NotificationEntry entry) {
+    @VisibleForTesting
+    public boolean canAlertAwakeCommon(NotificationEntry entry) {
         StatusBarNotification sbn = entry.notification;
 
-        if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+        if (mPresenter.isDeviceInVrMode()) {
             if (DEBUG) {
-                Log.d(TAG, "No heads up: no huns or vr mode");
-            }
-            return false;
-        }
-
-        if (entry.shouldSuppressPeek()) {
-            if (DEBUG) {
-                Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
+                Log.d(TAG, "No alerting: no huns or vr mode");
             }
             return false;
         }
 
         if (isSnoozedPackage(sbn)) {
             if (DEBUG) {
-                Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
+                Log.d(TAG, "No alerting: snoozed package: " + sbn.getKey());
             }
             return false;
         }
 
         if (entry.hasJustLaunchedFullScreenIntent()) {
             if (DEBUG) {
-                Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
+                Log.d(TAG, "No alerting: recent fullscreen: " + sbn.getKey());
             }
             return false;
         }
@@ -370,6 +381,18 @@
         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;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index fe88541..027e8e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -23,6 +23,7 @@
 import static android.app.Notification.CATEGORY_REMINDER;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
@@ -41,7 +42,6 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
-import android.text.TextUtils;
 import android.util.ArraySet;
 import android.view.View;
 import android.widget.ImageView;
@@ -52,7 +52,6 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.R;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.InflationException;
@@ -156,19 +155,6 @@
     public boolean canBubble;
 
     /**
-     * Whether this notification should be shown in the shade when it is also displayed as a bubble.
-     *
-     * <p>When a notification is a bubble we don't show it in the shade once the bubble has been
-     * expanded</p>
-     */
-    private boolean mShowInShadeWhenBubble;
-
-    /**
-     * Whether the user has dismissed this notification when it was in bubble form.
-     */
-    private boolean mUserDismissedBubble;
-
-    /**
      * Whether this notification is shown to the user as a high priority notification: visible on
      * the lock screen/status bar and in the top section in the shade.
      */
@@ -244,31 +230,6 @@
         return (notification.getNotification().flags & FLAG_BUBBLE) != 0;
     }
 
-    public void setBubbleDismissed(boolean userDismissed) {
-        mUserDismissedBubble = userDismissed;
-    }
-
-    public boolean isBubbleDismissed() {
-        return mUserDismissedBubble;
-    }
-
-    /**
-     * Sets whether this notification should be shown in the shade when it is also displayed as a
-     * bubble.
-     */
-    public void setShowInShadeWhenBubble(boolean showInShade) {
-        mShowInShadeWhenBubble = showInShade;
-    }
-
-    /**
-     * Whether this notification should be shown in the shade when it is also displayed as a
-     * bubble.
-     */
-    public boolean showInShadeWhenBubble() {
-        // We always show it in the shade if non-clearable
-        return !isRowDismissed() && (!isClearable() || mShowInShadeWhenBubble);
-    }
-
     /**
      * Returns the data needed for a bubble for this notification, if it exists.
      */
@@ -463,72 +424,6 @@
     }
 
     /**
-     * Returns our best guess for the most relevant text summary of the latest update to this
-     * notification, based on its type. Returns null if there should not be an update message.
-     */
-    public CharSequence getUpdateMessage(Context context) {
-        final Notification underlyingNotif = notification.getNotification();
-        final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
-
-        try {
-            if (Notification.BigTextStyle.class.equals(style)) {
-                // Return the big text, it is big so probably important. If it's not there use the
-                // normal text.
-                CharSequence bigText =
-                        underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
-                return !TextUtils.isEmpty(bigText)
-                        ? bigText
-                        : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
-            } else if (Notification.MessagingStyle.class.equals(style)) {
-                final List<Notification.MessagingStyle.Message> messages =
-                        Notification.MessagingStyle.Message.getMessagesFromBundleArray(
-                                (Parcelable[]) underlyingNotif.extras.get(
-                                        Notification.EXTRA_MESSAGES));
-
-                final Notification.MessagingStyle.Message latestMessage =
-                        Notification.MessagingStyle.findLatestIncomingMessage(messages);
-
-                if (latestMessage != null) {
-                    final CharSequence personName = latestMessage.getSenderPerson() != null
-                            ? latestMessage.getSenderPerson().getName()
-                            : null;
-
-                    // Prepend the sender name if available since group chats also use messaging
-                    // style.
-                    if (!TextUtils.isEmpty(personName)) {
-                        return context.getResources().getString(
-                                R.string.notification_summary_message_format,
-                                personName,
-                                latestMessage.getText());
-                    } else {
-                        return latestMessage.getText();
-                    }
-                }
-            } else if (Notification.InboxStyle.class.equals(style)) {
-                CharSequence[] lines =
-                        underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
-
-                // Return the last line since it should be the most recent.
-                if (lines != null && lines.length > 0) {
-                    return lines[lines.length - 1];
-                }
-            } else if (Notification.MediaStyle.class.equals(style)) {
-                // Return nothing, media updates aren't typically useful as a text update.
-                return null;
-            } else {
-                // Default to text extra.
-                return underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
-            }
-        } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
-            // No use crashing, we'll just return null and the caller will assume there's no update
-            // message.
-            e.printStackTrace();
-        }
-
-        return null;
-    }
-
-    /**
      * Abort all existing inflation tasks
      */
     public void abortTask() {
@@ -875,6 +770,16 @@
         return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST);
     }
 
+
+    /**
+     * Returns whether {@link Policy#SUPPRESSED_EFFECT_BADGE}
+     * is set for this entry. This badge is not an app badge, but rather an indicator of "unseen"
+     * content. Typically this is referred to as a "dot" internally in Launcher & SysUI code.
+     */
+    public boolean shouldSuppressNotificationDot() {
+        return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_BADGE);
+    }
+
     /**
      * Categories that are explicitly called out on DND settings screens are always blocked, if
      * DND has flagged them, even if they are foreground or system notifications that might
@@ -942,12 +847,4 @@
             this.index = index;
         }
     }
-
-    /**
-     * Returns whether the notification is a foreground service. It shows that this is an ongoing
-     * bubble.
-     */
-    public boolean isForegroundService() {
-        return (notification.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index 8e68227..782aad1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -23,6 +23,7 @@
 import android.app.NotificationChannelGroup
 import android.app.NotificationManager.IMPORTANCE_NONE
 import android.content.Context
+import android.content.DialogInterface
 import android.graphics.Color
 import android.graphics.PixelFormat
 import android.graphics.drawable.Drawable
@@ -171,7 +172,6 @@
     private fun done() {
         resetState()
         dialog.dismiss()
-        onFinishListener?.onChannelEditorDialogFinished()
     }
 
     private fun resetState() {
@@ -261,6 +261,11 @@
         dialog.apply {
             setContentView(R.layout.notif_half_shelf)
             setCanceledOnTouchOutside(true)
+            setOnDismissListener(object : DialogInterface.OnDismissListener {
+                override fun onDismiss(dialog: DialogInterface?) {
+                    onFinishListener?.onChannelEditorDialogFinished()
+                }
+            })
             findViewById<ChannelEditorListView>(R.id.half_shelf_container).apply {
                 controller = this@ChannelEditorDialogController
                 appIcon = this@ChannelEditorDialogController.appIcon
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a8327f6..d1799e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2310,9 +2310,6 @@
 
     @Override
     public int getIntrinsicHeight() {
-        if (isShownAsBubble()) {
-            return getMaxExpandHeight();
-        }
         if (isUserLocked()) {
             return getActualHeight();
         }
@@ -2358,10 +2355,6 @@
         return mStatusbarStateController != null && mStatusbarStateController.isDozing();
     }
 
-    private boolean isShownAsBubble() {
-        return mEntry.isBubble() && !mEntry.showInShadeWhenBubble() && !mEntry.isBubbleDismissed();
-    }
-
     @Override
     public boolean isGroupExpanded() {
         return mGroupManager.isGroupExpanded(mStatusBarNotification);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 06a2225..ecfc45b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -604,7 +604,7 @@
      */
     public void onScrimVisibilityChanged(@ScrimVisibility int scrimsVisible) {
         if (mWakeAndUnlockRunning
-                && scrimsVisible == ScrimController.VISIBILITY_FULLY_TRANSPARENT) {
+                && scrimsVisible == ScrimController.TRANSPARENT) {
             mWakeAndUnlockRunning = false;
             update();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index 0bbfbef..08602e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -22,6 +22,7 @@
 import android.util.Log;
 
 import com.android.systemui.Dependency;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.StatusBarState;
@@ -53,12 +54,20 @@
     private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
     private HeadsUpManager mHeadsUpManager;
     private boolean mIsUpdatingUnchangedGroup;
+    @Nullable private BubbleController mBubbleController = null;
 
     @Inject
     public NotificationGroupManager(StatusBarStateController statusBarStateController) {
         statusBarStateController.addCallback(this);
     }
 
+    private BubbleController getBubbleController() {
+        if (mBubbleController == null) {
+            mBubbleController = Dependency.get(BubbleController.class);
+        }
+        return mBubbleController;
+    }
+
     /**
      * Add a listener for changes to groups.
      *
@@ -187,12 +196,22 @@
         if (group == null) {
             return;
         }
+        int childCount = 0;
+        boolean hasBubbles = false;
+        for (String key : group.children.keySet()) {
+            if (!getBubbleController().isBubbleNotificationSuppressedFromShade(key)) {
+                childCount++;
+            } else {
+                hasBubbles = true;
+            }
+        }
+
         boolean prevSuppressed = group.suppressed;
         group.suppressed = group.summary != null && !group.expanded
-                && (group.children.size() == 1
-                || (group.children.size() == 0
+                && (childCount == 1
+                || (childCount == 0
                         && group.summary.notification.getNotification().isGroupSummary()
-                        && hasIsolatedChildren(group)));
+                        && (hasIsolatedChildren(group) || hasBubbles)));
         if (prevSuppressed != group.suppressed) {
             for (OnGroupChangeListener listener : mListeners) {
                 if (!mIsUpdatingUnchangedGroup) {
@@ -381,6 +400,17 @@
     }
 
     /**
+     * If there is a {@link NotificationGroup} associated with the provided entry, this method
+     * will update the suppression of that group.
+     */
+    public void updateSuppression(NotificationEntry entry) {
+        NotificationGroup group = mGroupMap.get(getGroupKey(entry.notification));
+        if (group != null) {
+            updateSuppression(group);
+        }
+    }
+
+    /**
      * Get the group key. May differ from the one in the notification due to the notification
      * being temporarily isolated.
      *
@@ -565,6 +595,7 @@
                         ? Log.getStackTraceString(child.getDebugThrowable())
                         : "");
             }
+            result += "\n    summary suppressed: " + suppressed;
             return result;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 28a9a0a..a0847b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -1967,7 +1967,8 @@
      * @return Whether we should intercept a gesture to open Quick Settings.
      */
     private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
-        if (!mQsExpansionEnabled || mCollapsedOnDown) {
+        if (!mQsExpansionEnabled || mCollapsedOnDown
+                || (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled())) {
             return false;
         }
         View header = mKeyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index b12bf5c..558aaad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -78,23 +78,24 @@
     /**
      * When both scrims have 0 alpha.
      */
-    public static final int VISIBILITY_FULLY_TRANSPARENT = 0;
+    public static final int TRANSPARENT = 0;
     /**
      * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.)
      */
-    public static final int VISIBILITY_SEMI_TRANSPARENT = 1;
+    public static final int SEMI_TRANSPARENT = 1;
     /**
      * When at least 1 scrim is fully opaque (alpha set to 1.)
      */
-    public static final int VISIBILITY_FULLY_OPAQUE = 2;
+    public static final int OPAQUE = 2;
 
-    @IntDef(prefix = { "VISIBILITY_" }, value = {
-            VISIBILITY_FULLY_TRANSPARENT,
-            VISIBILITY_SEMI_TRANSPARENT,
-            VISIBILITY_FULLY_OPAQUE
+    @IntDef(prefix = {"VISIBILITY_"}, value = {
+            TRANSPARENT,
+            SEMI_TRANSPARENT,
+            OPAQUE
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface ScrimVisibility {}
+    public @interface ScrimVisibility {
+    }
 
     /**
      * Default alpha value for most scrims.
@@ -122,8 +123,11 @@
 
     private ScrimState mState = ScrimState.UNINITIALIZED;
     private final Context mContext;
-    protected final ScrimView mScrimBehind;
+
     protected final ScrimView mScrimInFront;
+    protected final ScrimView mScrimBehind;
+    protected final ScrimView mScrimForBubble;
+
     private final UnlockMethodCache mUnlockMethodCache;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DozeParameters mDozeParameters;
@@ -152,10 +156,15 @@
     private Runnable mOnAnimationFinished;
     private boolean mDeferFinishedListener;
     private final Interpolator mInterpolator = new DecelerateInterpolator();
-    private float mCurrentInFrontAlpha  = NOT_INITIALIZED;
-    private float mCurrentBehindAlpha = NOT_INITIALIZED;
-    private int mCurrentInFrontTint;
-    private int mCurrentBehindTint;
+
+    private float mInFrontAlpha = NOT_INITIALIZED;
+    private float mBehindAlpha = NOT_INITIALIZED;
+    private float mBubbleAlpha = NOT_INITIALIZED;
+
+    private int mInFrontTint;
+    private int mBehindTint;
+    private int mBubbleTint;
+
     private boolean mWallpaperVisibilityTimedOut;
     private int mScrimsVisibility;
     private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
@@ -174,14 +183,18 @@
     private boolean mWakeLockHeld;
     private boolean mKeyguardOccluded;
 
-    public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+    public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, ScrimView scrimForBubble,
             TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
             Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
             AlarmManager alarmManager) {
+
         mScrimBehind = scrimBehind;
         mScrimInFront = scrimInFront;
+        mScrimForBubble = scrimForBubble;
+
         mScrimStateListener = scrimStateListener;
         mScrimVisibleListener = scrimVisibleListener;
+
         mContext = scrimBehind.getContext();
         mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
         mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
@@ -205,12 +218,13 @@
 
         final ScrimState[] states = ScrimState.values();
         for (int i = 0; i < states.length; i++) {
-            states[i].init(mScrimInFront, mScrimBehind, mDozeParameters);
+            states[i].init(mScrimInFront, mScrimBehind, mScrimForBubble, mDozeParameters);
             states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
         }
 
         mScrimBehind.setDefaultFocusHighlightEnabled(false);
         mScrimInFront.setDefaultFocusHighlightEnabled(false);
+        mScrimForBubble.setDefaultFocusHighlightEnabled(false);
 
         updateScrims();
     }
@@ -249,10 +263,14 @@
         mBlankScreen = state.getBlanksScreen();
         mAnimateChange = state.getAnimateChange();
         mAnimationDuration = state.getAnimationDuration();
-        mCurrentInFrontTint = state.getFrontTint();
-        mCurrentBehindTint = state.getBehindTint();
-        mCurrentInFrontAlpha = state.getFrontAlpha();
-        mCurrentBehindAlpha = state.getBehindAlpha();
+
+        mInFrontTint = state.getFrontTint();
+        mBehindTint = state.getBehindTint();
+        mBubbleTint = state.getBubbleTint();
+
+        mInFrontAlpha = state.getFrontAlpha();
+        mBehindAlpha = state.getBehindAlpha();
+        mBubbleAlpha = state.getBubbleAlpha();
         applyExpansionToAlpha();
 
         // Scrim might acquire focus when user is navigating with a D-pad or a keyboard.
@@ -385,21 +403,20 @@
         if (mExpansionFraction != fraction) {
             mExpansionFraction = fraction;
 
-            final boolean keyguardOrUnlocked = mState == ScrimState.UNLOCKED
-                    || mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING;
-            if (!keyguardOrUnlocked || !mExpansionAffectsAlpha) {
+            boolean relevantState = (mState == ScrimState.UNLOCKED
+                    || mState == ScrimState.KEYGUARD
+                    || mState == ScrimState.PULSING
+                    || mState == ScrimState.BUBBLE_EXPANDED);
+            if (!(relevantState && mExpansionAffectsAlpha)) {
                 return;
             }
-
             applyExpansionToAlpha();
-
             if (mUpdatePending) {
                 return;
             }
-
             setOrAdaptCurrentAnimation(mScrimBehind);
             setOrAdaptCurrentAnimation(mScrimInFront);
-
+            setOrAdaptCurrentAnimation(mScrimForBubble);
             dispatchScrimState(mScrimBehind.getViewAlpha());
 
             // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING
@@ -413,11 +430,10 @@
     }
 
     private void setOrAdaptCurrentAnimation(View scrim) {
-        if (!isAnimating(scrim)) {
-            updateScrimColor(scrim, getCurrentScrimAlpha(scrim), getCurrentScrimTint(scrim));
-        } else {
+        float alpha = getCurrentScrimAlpha(scrim);
+        if (isAnimating(scrim)) {
+            // Adapt current animation.
             ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM);
-            float alpha = getCurrentScrimAlpha(scrim);
             float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA);
             float previousStartValue = (Float) scrim.getTag(TAG_START_ALPHA);
             float relativeDiff = alpha - previousEndValue;
@@ -425,6 +441,9 @@
             scrim.setTag(TAG_START_ALPHA, newStartValue);
             scrim.setTag(TAG_END_ALPHA, alpha);
             previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
+        } else {
+            // Set animation.
+            updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
         }
     }
 
@@ -433,27 +452,27 @@
             return;
         }
 
-        if (mState == ScrimState.UNLOCKED) {
+        if (mState == ScrimState.UNLOCKED || mState == ScrimState.BUBBLE_EXPANDED) {
             // Darken scrim as you pull down the shade when unlocked
             float behindFraction = getInterpolatedFraction();
             behindFraction = (float) Math.pow(behindFraction, 0.8f);
-            mCurrentBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
-            mCurrentInFrontAlpha = 0;
+            mBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
+            mInFrontAlpha = 0;
         } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) {
             // Either darken of make the scrim transparent when you
             // pull down the shade
             float interpolatedFract = getInterpolatedFraction();
             float alphaBehind = mState.getBehindAlpha();
             if (mDarkenWhileDragging) {
-                mCurrentBehindAlpha = MathUtils.lerp(GRADIENT_SCRIM_ALPHA_BUSY, alphaBehind,
+                mBehindAlpha = MathUtils.lerp(GRADIENT_SCRIM_ALPHA_BUSY, alphaBehind,
                         interpolatedFract);
-                mCurrentInFrontAlpha = 0;
+                mInFrontAlpha = 0;
             } else {
-                mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind,
+                mBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind,
                         interpolatedFract);
-                mCurrentInFrontAlpha = 0;
+                mInFrontAlpha = 0;
             }
-            mCurrentBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
+            mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
                     mState.getBehindTint(), interpolatedFract);
         }
     }
@@ -478,8 +497,8 @@
      */
     public void setAodFrontScrimAlpha(float alpha) {
         if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()
-                && mCurrentInFrontAlpha != alpha) {
-            mCurrentInFrontAlpha = alpha;
+                && mInFrontAlpha != alpha) {
+            mInFrontAlpha = alpha;
             updateScrims();
         }
 
@@ -491,10 +510,10 @@
      * away once the display turns on.
      */
     public void prepareForGentleWakeUp() {
-        if (mState == ScrimState.AOD) {
-            mCurrentInFrontAlpha = 1f;
-            mCurrentInFrontTint = Color.BLACK;
-            mCurrentBehindTint = Color.BLACK;
+        if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()) {
+            mInFrontAlpha = 1f;
+            mInFrontTint = Color.BLACK;
+            mBehindTint = Color.BLACK;
             mAnimateChange = false;
             updateScrims();
             mAnimateChange = true;
@@ -512,8 +531,8 @@
 
         if (mState == ScrimState.PULSING) {
             float newBehindAlpha = mState.getBehindAlpha();
-            if (mCurrentBehindAlpha != newBehindAlpha) {
-                mCurrentBehindAlpha = newBehindAlpha;
+            if (mBehindAlpha != newBehindAlpha) {
+                mBehindAlpha = newBehindAlpha;
                 updateScrims();
             }
         }
@@ -535,8 +554,11 @@
             // Only animate scrim color if the scrim view is actually visible
             boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen;
             boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen;
+            boolean animateScrimForBubble = mScrimForBubble.getViewAlpha() != 0 && !mBlankScreen;
+
             mScrimInFront.setColors(mColors, animateScrimInFront);
             mScrimBehind.setColors(mColors, animateScrimBehind);
+            mScrimForBubble.setColors(mColors, animateScrimForBubble);
 
             // Calculate minimum scrim opacity for white or black text.
             int textColor = mColors.supportsDarkText() ? Color.BLACK : Color.WHITE;
@@ -555,12 +577,11 @@
         boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
                 && mKeyguardOccluded;
         if (aodWallpaperTimeout || occludedKeyguard) {
-            mCurrentBehindAlpha = 1;
+            mBehindAlpha = 1;
         }
-
-        setScrimInFrontAlpha(mCurrentInFrontAlpha);
-        setScrimBehindAlpha(mCurrentBehindAlpha);
-
+        setScrimAlpha(mScrimInFront, mInFrontAlpha);
+        setScrimAlpha(mScrimBehind, mBehindAlpha);
+        setScrimAlpha(mScrimForBubble, mBubbleAlpha);
         dispatchScrimsVisible();
     }
 
@@ -571,11 +592,11 @@
     private void dispatchScrimsVisible() {
         final int currentScrimVisibility;
         if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) {
-            currentScrimVisibility = VISIBILITY_FULLY_OPAQUE;
+            currentScrimVisibility = OPAQUE;
         } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) {
-            currentScrimVisibility = VISIBILITY_FULLY_TRANSPARENT;
+            currentScrimVisibility = TRANSPARENT;
         } else {
-            currentScrimVisibility = VISIBILITY_SEMI_TRANSPARENT;
+            currentScrimVisibility = SEMI_TRANSPARENT;
         }
 
         if (mScrimsVisibility != currentScrimVisibility) {
@@ -592,18 +613,10 @@
             return 0;
         } else {
             // woo, special effects
-            return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
+            return (float) (1f - 0.5f * (1f - Math.cos(3.14159f * Math.pow(1f - frac, 2f))));
         }
     }
 
-    private void setScrimBehindAlpha(float alpha) {
-        setScrimAlpha(mScrimBehind, alpha);
-    }
-
-    private void setScrimInFrontAlpha(float alpha) {
-        setScrimAlpha(mScrimInFront, alpha);
-    }
-
     private void setScrimAlpha(ScrimView scrim, float alpha) {
         if (alpha == 0f) {
             scrim.setClickable(false);
@@ -614,17 +627,26 @@
         updateScrim(scrim, alpha);
     }
 
+    private String getScrimName(ScrimView scrim) {
+        if (scrim == mScrimInFront) {
+            return "front_scrim";
+        } else if (scrim == mScrimBehind) {
+            return "back_scrim";
+        } else if (scrim == mScrimForBubble) {
+            return "bubble_scrim";
+        }
+        return "unknown_scrim";
+    }
+
     private void updateScrimColor(View scrim, float alpha, int tint) {
         alpha = Math.max(0, Math.min(1.0f, alpha));
         if (scrim instanceof ScrimView) {
             ScrimView scrimView = (ScrimView) scrim;
 
-            Trace.traceCounter(Trace.TRACE_TAG_APP,
-                    scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
+            Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_alpha",
                     (int) (alpha * 255));
 
-            Trace.traceCounter(Trace.TRACE_TAG_APP,
-                    scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
+            Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint",
                     Color.alpha(tint));
 
             scrimView.setTint(tint);
@@ -681,9 +703,11 @@
 
     private float getCurrentScrimAlpha(View scrim) {
         if (scrim == mScrimInFront) {
-            return mCurrentInFrontAlpha;
+            return mInFrontAlpha;
         } else if (scrim == mScrimBehind) {
-            return mCurrentBehindAlpha;
+            return mBehindAlpha;
+        } else if (scrim == mScrimForBubble) {
+            return mBubbleAlpha;
         } else {
             throw new IllegalArgumentException("Unknown scrim view");
         }
@@ -691,9 +715,11 @@
 
     private int getCurrentScrimTint(View scrim) {
         if (scrim == mScrimInFront) {
-            return mCurrentInFrontTint;
+            return mInFrontTint;
         } else if (scrim == mScrimBehind) {
-            return mCurrentBehindTint;
+            return mBehindTint;
+        } else if (scrim == mScrimForBubble) {
+            return mBubbleTint;
         } else {
             throw new IllegalArgumentException("Unknown scrim view");
         }
@@ -736,8 +762,9 @@
         // When unlocking with fingerprint, we'll fade the scrims from black to transparent.
         // At the end of the animation we need to remove the tint.
         if (mState == ScrimState.UNLOCKED) {
-            mCurrentInFrontTint = Color.TRANSPARENT;
-            mCurrentBehindTint = Color.TRANSPARENT;
+            mInFrontTint = Color.TRANSPARENT;
+            mBehindTint = Color.TRANSPARENT;
+            mBubbleTint = Color.TRANSPARENT;
         }
     }
 
@@ -842,6 +869,7 @@
 
     /**
      * Executes a callback after the frame has hit the display.
+     *
      * @param callback What to run.
      */
     @VisibleForTesting
@@ -885,16 +913,35 @@
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println(" ScrimController: ");
-        pw.print("  state: "); pw.println(mState);
-        pw.print("  frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha());
-        pw.print(" alpha="); pw.print(mCurrentInFrontAlpha);
-        pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint()));
+        pw.print("  state: ");
+        pw.println(mState);
 
-        pw.print("  backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha());
-        pw.print(" alpha="); pw.print(mCurrentBehindAlpha);
-        pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint()));
+        pw.print("  frontScrim:");
+        pw.print(" viewAlpha=");
+        pw.print(mScrimInFront.getViewAlpha());
+        pw.print(" alpha=");
+        pw.print(mInFrontAlpha);
+        pw.print(" tint=0x");
+        pw.println(Integer.toHexString(mScrimInFront.getTint()));
 
-        pw.print("   mTracking="); pw.println(mTracking);
+        pw.print("  backScrim:");
+        pw.print(" viewAlpha=");
+        pw.print(mScrimBehind.getViewAlpha());
+        pw.print(" alpha=");
+        pw.print(mBehindAlpha);
+        pw.print(" tint=0x");
+        pw.println(Integer.toHexString(mScrimBehind.getTint()));
+
+        pw.print("  bubbleScrim:");
+        pw.print(" viewAlpha=");
+        pw.print(mScrimForBubble.getViewAlpha());
+        pw.print(" alpha=");
+        pw.print(mBubbleAlpha);
+        pw.print(" tint=0x");
+        pw.println(Integer.toHexString(mScrimForBubble.getTint()));
+
+        pw.print("   mTracking=");
+        pw.println(mTracking);
     }
 
     public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
@@ -941,8 +988,8 @@
         // in this case, back-scrim needs to be re-evaluated
         if (mState == ScrimState.AOD || mState == ScrimState.PULSING) {
             float newBehindAlpha = mState.getBehindAlpha();
-            if (mCurrentBehindAlpha != newBehindAlpha) {
-                mCurrentBehindAlpha = newBehindAlpha;
+            if (mBehindAlpha != newBehindAlpha) {
+                mBehindAlpha = newBehindAlpha;
                 updateScrims();
             }
         }
@@ -963,10 +1010,13 @@
     public interface Callback {
         default void onStart() {
         }
+
         default void onDisplayBlanked() {
         }
+
         default void onFinished() {
         }
+
         default void onCancelled() {
         }
         /** Returns whether to timeout wallpaper or not. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index c706062..f2d5112 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -36,7 +36,6 @@
      * On the lock screen.
      */
     KEYGUARD(0) {
-
         @Override
         public void prepare(ScrimState previousState) {
             mBlankScreen = false;
@@ -53,10 +52,13 @@
             } else {
                 mAnimationDuration = ScrimController.ANIMATION_DURATION;
             }
-            mCurrentInFrontTint = Color.BLACK;
-            mCurrentBehindTint = Color.BLACK;
-            mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
-            mCurrentInFrontAlpha = 0;
+            mFrontTint = Color.BLACK;
+            mBehindTint = Color.BLACK;
+            mBubbleTint = Color.TRANSPARENT;
+
+            mFrontAlpha = 0;
+            mBehindAlpha = mScrimBehindAlphaKeyguard;
+            mBubbleAlpha = 0;
         }
     },
 
@@ -66,8 +68,9 @@
     BOUNCER(1) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
-            mCurrentInFrontAlpha = 0f;
+            mBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+            mFrontAlpha = 0f;
+            mBubbleAlpha = 0f;
         }
     },
 
@@ -77,8 +80,9 @@
     BOUNCER_SCRIMMED(2) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentBehindAlpha = 0;
-            mCurrentInFrontAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+            mBehindAlpha = 0;
+            mBubbleAlpha = 0f;
+            mFrontAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
         }
     },
 
@@ -88,8 +92,9 @@
     BRIGHTNESS_MIRROR(3) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentBehindAlpha = 0;
-            mCurrentInFrontAlpha = 0;
+            mBehindAlpha = 0;
+            mFrontAlpha = 0;
+            mBubbleAlpha = 0;
         }
     },
 
@@ -101,9 +106,16 @@
         public void prepare(ScrimState previousState) {
             final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
             mBlankScreen = mDisplayRequiresBlanking;
-            mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
-            mCurrentInFrontTint = Color.BLACK;
-            mCurrentBehindTint = Color.BLACK;
+
+            mFrontTint = Color.BLACK;
+            mFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
+
+            mBehindTint = Color.BLACK;
+            mBehindAlpha = ScrimController.TRANSPARENT;
+
+            mBubbleTint = Color.TRANSPARENT;
+            mBubbleAlpha = ScrimController.TRANSPARENT;
+
             mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
             // DisplayPowerManager may blank the screen for us,
             // in this case we just need to set our state.
@@ -127,9 +139,10 @@
     PULSING(5) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentInFrontAlpha = 0f;
-            mCurrentBehindTint = Color.BLACK;
-            mCurrentInFrontTint = Color.BLACK;
+            mFrontAlpha = 0f;
+            mBubbleAlpha = 0f;
+            mBehindTint = Color.BLACK;
+            mFrontTint = Color.BLACK;
             mBlankScreen = mDisplayRequiresBlanking;
             mAnimationDuration = mWakeLockScreenSensorActive
                     ? ScrimController.ANIMATION_DURATION_LONG : ScrimController.ANIMATION_DURATION;
@@ -148,25 +161,32 @@
     UNLOCKED(6) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentBehindAlpha = 0;
-            mCurrentInFrontAlpha = 0;
+            // State that UI will sync to.
+            mBehindAlpha = 0;
+            mFrontAlpha = 0;
+            mBubbleAlpha = 0;
+
             mAnimationDuration = mUnlockIsFading
                     ? KeyguardBypassController.BYPASS_PANEL_FADE_DURATION
                     : StatusBar.FADE_KEYGUARD_DURATION;
             mAnimateChange = !mLaunchingAffordanceWithPreview;
 
+            mFrontTint = Color.TRANSPARENT;
+            mBehindTint = Color.TRANSPARENT;
+            mBubbleTint = Color.TRANSPARENT;
+            mBlankScreen = false;
+
             if (previousState == ScrimState.AOD) {
-                // Fade from black to transparent when coming directly from AOD
-                updateScrimColor(mScrimInFront, 1, Color.BLACK);
-                updateScrimColor(mScrimBehind, 1, Color.BLACK);
+                // Set all scrims black, before they fade transparent.
+                updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
+                updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
+                updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
+
                 // Scrims should still be black at the end of the transition.
-                mCurrentInFrontTint = Color.BLACK;
-                mCurrentBehindTint = Color.BLACK;
+                mFrontTint = Color.BLACK;
+                mBehindTint = Color.BLACK;
+                mBubbleTint = Color.BLACK;
                 mBlankScreen = true;
-            } else {
-                mCurrentInFrontTint = Color.TRANSPARENT;
-                mCurrentBehindTint = Color.TRANSPARENT;
-                mBlankScreen = false;
             }
         }
     },
@@ -177,25 +197,36 @@
     BUBBLE_EXPANDED(7) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentInFrontTint = Color.TRANSPARENT;
-            mCurrentBehindTint = Color.TRANSPARENT;
+            mFrontTint = Color.TRANSPARENT;
+            mBehindTint = Color.TRANSPARENT;
+            mBubbleTint = Color.TRANSPARENT;
+
+            mFrontAlpha = ScrimController.TRANSPARENT;
+            mBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+            mBubbleAlpha = ScrimController.GRADIENT_SCRIM_ALPHA;
+
             mAnimationDuration = ScrimController.ANIMATION_DURATION;
-            mCurrentBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
             mBlankScreen = false;
         }
     };
 
     boolean mBlankScreen = false;
     long mAnimationDuration = ScrimController.ANIMATION_DURATION;
-    int mCurrentInFrontTint = Color.TRANSPARENT;
-    int mCurrentBehindTint = Color.TRANSPARENT;
+    int mFrontTint = Color.TRANSPARENT;
+    int mBehindTint = Color.TRANSPARENT;
+    int mBubbleTint = Color.TRANSPARENT;
+
     boolean mAnimateChange = true;
-    float mCurrentInFrontAlpha;
-    float mCurrentBehindAlpha;
     float mAodFrontScrimAlpha;
+    float mFrontAlpha;
+    float mBehindAlpha;
+    float mBubbleAlpha;
+
     float mScrimBehindAlphaKeyguard;
     ScrimView mScrimInFront;
     ScrimView mScrimBehind;
+    ScrimView mScrimForBubble;
+
     DozeParameters mDozeParameters;
     boolean mDisplayRequiresBlanking;
     boolean mWallpaperSupportsAmbientMode;
@@ -209,13 +240,17 @@
         mIndex = index;
     }
 
-    public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
+    public void init(ScrimView scrimInFront, ScrimView scrimBehind, ScrimView scrimForBubble,
+            DozeParameters dozeParameters) {
         mScrimInFront = scrimInFront;
         mScrimBehind = scrimBehind;
+        mScrimForBubble = scrimForBubble;
+
         mDozeParameters = dozeParameters;
         mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking();
     }
 
+    /** Prepare state for transition. */
     public void prepare(ScrimState previousState) {
     }
 
@@ -224,19 +259,27 @@
     }
 
     public float getFrontAlpha() {
-        return mCurrentInFrontAlpha;
+        return mFrontAlpha;
     }
 
     public float getBehindAlpha() {
-        return mCurrentBehindAlpha;
+        return mBehindAlpha;
+    }
+
+    public float getBubbleAlpha() {
+        return mBubbleAlpha;
     }
 
     public int getFrontTint() {
-        return mCurrentInFrontTint;
+        return mFrontTint;
     }
 
     public int getBehindTint() {
-        return mCurrentBehindTint;
+        return mBehindTint;
+    }
+
+    public int getBubbleTint() {
+        return mBubbleTint;
     }
 
     public long getAnimationDuration() {
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 5521f1d..baa9df1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -804,6 +804,7 @@
         // TODO: Deal with the ugliness that comes from having some of the statusbar broken out
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
         mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
+
         mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
         mZenController.addCallback(this);
         NotificationListContainer notifListContainer = (NotificationListContainer) mStackScroller;
@@ -924,8 +925,10 @@
 
         ScrimView scrimBehind = mStatusBarWindow.findViewById(R.id.scrim_behind);
         ScrimView scrimInFront = mStatusBarWindow.findViewById(R.id.scrim_in_front);
+        ScrimView scrimForBubble = mStatusBarWindow.findViewById(R.id.scrim_for_bubble);
+
         mScrimController = SystemUIFactory.getInstance().createScrimController(
-                scrimBehind, scrimInFront, mLockscreenWallpaper,
+                scrimBehind, scrimInFront, scrimForBubble, mLockscreenWallpaper,
                 (state, alpha, color) -> mLightBarController.setScrimState(state, alpha, color),
                 scrimsVisible -> {
                     if (mStatusBarWindowController != null) {
@@ -2394,6 +2397,10 @@
             pw.println("  mGroupManager: null");
         }
 
+        if (mBubbleController != null) {
+            mBubbleController.dump(fd, pw, args);
+        }
+
         if (mLightBarController != null) {
             mLightBarController.dump(fd, pw, args);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index d9a9f7c..3ac5b14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -195,7 +195,7 @@
         }
 
         final boolean scrimsOccludingWallpaper =
-                state.scrimsVisibility == ScrimController.VISIBILITY_FULLY_OPAQUE;
+                state.scrimsVisibility == ScrimController.OPAQUE;
         final boolean keyguardOrAod = state.keyguardShowing
                 || (state.dozing && mDozeParameters.getAlwaysOn());
         if (keyguardOrAod && !state.backdropShowing && !scrimsOccludingWallpaper) {
@@ -269,7 +269,7 @@
         return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
                 || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
                 || state.headsUpShowing || state.bubblesShowing
-                || state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_TRANSPARENT);
+                || state.scrimsVisibility != ScrimController.TRANSPARENT);
     }
 
     private void applyFitsSystemWindows(State state) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index d98ac24..9405418 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -419,7 +419,7 @@
         if (mNotificationPanel.isFullyExpanded()
                 && mStatusBarStateController.getState() == StatusBarState.KEYGUARD
                 && !mService.isBouncerShowing()
-                && (!mBypassController.getBypassEnabled() || mNotificationPanel.isQsExpanded())
+                && !mBypassController.getBypassEnabled()
                 && !mService.isDozing()) {
             intercept = mDragDownHelper.onInterceptTouchEvent(ev);
         }
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 b3f6f4e..8aa5cba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.bubbles;
 
 import static android.app.Notification.FLAG_BUBBLE;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
@@ -28,6 +27,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -42,11 +43,10 @@
 import android.app.IActivityManager;
 import android.app.Notification;
 import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.graphics.drawable.Icon;
+import android.hardware.face.FaceManager;
 import android.service.notification.ZenModeConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -57,16 +57,20 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
 import com.android.systemui.statusbar.NotificationTestHelper;
 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.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -80,20 +84,16 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class BubbleControllerTest extends SysuiTestCase {
 
-    // Some APIs rely on the app being foreground, check is via pkg name
-    private static final String FOREGROUND_TEST_PKG_NAME = "com.android.systemui.tests";
-
     @Mock
     private NotificationEntryManager mNotificationEntryManager;
     @Mock
+    private NotificationGroupManager mNotificationGroupManager;
+    @Mock
     private WindowManager mWindowManager;
     @Mock
     private IActivityManager mActivityManager;
@@ -105,6 +105,10 @@
     private ZenModeController mZenModeController;
     @Mock
     private ZenModeConfig mZenModeConfig;
+    @Mock
+    private FaceManager mFaceManager;
+    @Mock
+    private NotificationLockscreenUserManager mLockscreenUserManager;
 
     private FrameLayout mStatusBarView;
     @Captor
@@ -120,8 +124,6 @@
     private NotificationTestHelper mNotificationTestHelper;
     private ExpandableNotificationRow mRow;
     private ExpandableNotificationRow mRow2;
-    private ExpandableNotificationRow mAutoExpandRow;
-    private ExpandableNotificationRow mSuppressNotifRow;
     private ExpandableNotificationRow mNonBubbleNotifRow;
 
     @Mock
@@ -140,6 +142,7 @@
         MockitoAnnotations.initMocks(this);
         mStatusBarView = new FrameLayout(mContext);
         mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
+        mContext.addMockSystemService(FaceManager.class, mFaceManager);
 
         // Bubbles get added to status bar window view
         mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
@@ -152,35 +155,31 @@
         mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
         mNonBubbleNotifRow = mNotificationTestHelper.createRow();
 
-        // Some bubbles want to auto expand
-        Notification.BubbleMetadata autoExpandMetadata =
-                getBuilder().setAutoExpandBubble(true).build();
-        mAutoExpandRow = mNotificationTestHelper.createBubble(autoExpandMetadata,
-                FOREGROUND_TEST_PKG_NAME);
-
-        // Some bubbles want to suppress notifs
-        Notification.BubbleMetadata suppressNotifMetadata =
-                getBuilder().setSuppressNotification(true).build();
-        mSuppressNotifRow = mNotificationTestHelper.createBubble(suppressNotifMetadata,
-                FOREGROUND_TEST_PKG_NAME);
-
         // Return non-null notification data from the NEM
         when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData);
+        when(mNotificationData.get(mRow.getEntry().key)).thenReturn(mRow.getEntry());
         when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel);
 
         mZenModeConfig.suppressedVisualEffects = 0;
         when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
 
         TestableNotificationInterruptionStateProvider interruptionStateProvider =
-                new TestableNotificationInterruptionStateProvider(mContext);
+                new TestableNotificationInterruptionStateProvider(mContext,
+                        mock(NotificationFilter.class),
+                        mock(StatusBarStateController.class));
         interruptionStateProvider.setUpWithPresenter(
                 mock(NotificationPresenter.class),
                 mock(HeadsUpManager.class),
                 mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class));
         mBubbleData = new BubbleData(mContext);
-        mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController,
-                mBubbleData, mConfigurationController, interruptionStateProvider,
-                mZenModeController);
+        mBubbleController = new TestableBubbleController(mContext,
+                mStatusBarWindowController,
+                mBubbleData,
+                mConfigurationController,
+                interruptionStateProvider,
+                mZenModeController,
+                mLockscreenUserManager,
+                mNotificationGroupManager);
         mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener);
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
@@ -212,13 +211,14 @@
     @Test
     public void testRemoveBubble() {
         mBubbleController.updateBubble(mRow.getEntry());
+        assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
         assertTrue(mBubbleController.hasBubbles());
         verify(mNotificationEntryManager).updateNotifications();
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
 
         mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
         assertFalse(mStatusBarWindowController.getBubblesShowing());
-        assertTrue(mRow.getEntry().isBubbleDismissed());
+        assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
         verify(mNotificationEntryManager, times(2)).updateNotifications();
         verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
     }
@@ -229,10 +229,10 @@
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         // Make it look like dismissed notif
-        mRow.getEntry().setShowInShadeWhenBubble(false);
+        mBubbleData.getBubbleWithKey(mRow.getEntry().key).setShowInShadeWhenBubble(false);
 
         // Now remove the bubble
         mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
@@ -248,15 +248,17 @@
     public void testDismissStack() {
         mBubbleController.updateBubble(mRow.getEntry());
         verify(mNotificationEntryManager, times(1)).updateNotifications();
+        assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
         mBubbleController.updateBubble(mRow2.getEntry());
         verify(mNotificationEntryManager, times(2)).updateNotifications();
+        assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().key));
         assertTrue(mBubbleController.hasBubbles());
 
         mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
         assertFalse(mStatusBarWindowController.getBubblesShowing());
         verify(mNotificationEntryManager, times(3)).updateNotifications();
-        assertTrue(mRow.getEntry().isBubbleDismissed());
-        assertTrue(mRow2.getEntry().isBubbleDismissed());
+        assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
+        assertNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().key));
     }
 
     @Test
@@ -267,9 +269,9 @@
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
-        // We should have bubbles & their notifs should show in the shade
+        // We should have bubbles & their notifs should not be suppressed
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
         assertFalse(mStatusBarWindowController.getBubbleExpanded());
 
         // Expand the stack
@@ -279,8 +281,8 @@
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
         assertTrue(mStatusBarWindowController.getBubbleExpanded());
 
-        // Make sure it's no longer in the shade
-        assertFalse(mRow.getEntry().showInShadeWhenBubble());
+        // Make sure the notif is suppressed
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         // Collapse
         mBubbleController.collapseStack();
@@ -297,10 +299,11 @@
         mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.updateBubble(mRow2.getEntry());
 
-        // We should have bubbles & their notifs should show in the shade
+        // We should have bubbles & their notifs should not be suppressed
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
-        assertTrue(mRow2.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow2.getEntry().key));
 
         // Expand
         BubbleStackView stackView = mBubbleController.getStackView();
@@ -310,13 +313,13 @@
 
         // Last added is the one that is expanded
         assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
-        assertFalse(mRow2.getEntry().showInShadeWhenBubble());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry().key));
 
         // Switch which bubble is expanded
         mBubbleController.selectBubble(mRow.getEntry().key);
-        stackView.setExpandedBubble(mRow.getEntry());
+        stackView.setExpandedBubble(mRow.getEntry().key);
         assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry());
-        assertFalse(mRow.getEntry().showInShadeWhenBubble());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         // collapse for previous bubble
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key);
@@ -329,14 +332,37 @@
     }
 
     @Test
-    public void testExpansionRemovesShowInShade() {
+    public void testExpansionRemovesShowInShadeAndDot() {
         // Mark it as a bubble and add it explicitly
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
-        // We should have bubbles & their notifs should show in the shade
+        // We should have bubbles & their notifs should not be suppressed
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
+
+        // Expand
+        mBubbleController.expandStack();
+        assertTrue(mBubbleController.isStackExpanded());
+        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
+
+        // Notif is suppressed after expansion
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        // Notif shouldn't show dot after expansion
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
+    }
+
+    @Test
+    public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
+        // Mark it as a bubble and add it explicitly
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        // We should have bubbles & their notifs should not be suppressed
+        assertTrue(mBubbleController.hasBubbles());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
 
         // Expand
         BubbleStackView stackView = mBubbleController.getStackView();
@@ -344,8 +370,19 @@
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
 
-        // No longer show shade in notif after expansion
-        assertFalse(mRow.getEntry().showInShadeWhenBubble());
+        // Notif is suppressed after expansion
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        // Notif shouldn't show dot after expansion
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
+
+        // Send update
+        mEntryListener.onPreEntryUpdated(mRow.getEntry());
+
+        // Nothing should have changed
+        // Notif is suppressed after expansion
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        // Notif shouldn't show dot after expansion
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
     }
 
     @Test
@@ -366,7 +403,7 @@
 
         // Last added is the one that is expanded
         assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
-        assertFalse(mRow2.getEntry().showInShadeWhenBubble());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry().key));
 
         // Dismiss currently expanded
         mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(),
@@ -390,14 +427,16 @@
     @Test
     public void testAutoExpand_FailsNotForeground() {
         assertFalse(mBubbleController.isStackExpanded());
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
 
         // Add the auto expand bubble
-        mEntryListener.onPendingEntryAdded(mAutoExpandRow.getEntry());
-        mBubbleController.updateBubble(mAutoExpandRow.getEntry());
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
 
         // Expansion shouldn't change
         verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
-                mAutoExpandRow.getEntry().key);
+                mRow.getEntry().key);
         assertFalse(mBubbleController.isStackExpanded());
 
         // # of bubbles should change
@@ -406,91 +445,51 @@
 
     @Test
     public void testAutoExpand_SucceedsForeground() {
-        final CountDownLatch latch = new CountDownLatch(1);
-        BroadcastReceiver receiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                latch.countDown();
-            }
-        };
-        IntentFilter filter = new IntentFilter(BubblesTestActivity.BUBBLE_ACTIVITY_OPENED);
-        mContext.registerReceiver(receiver, filter);
-
-        assertFalse(mBubbleController.isStackExpanded());
-
-        // Make ourselves foreground
-        Intent i = new Intent(mContext, BubblesTestActivity.class);
-        i.setFlags(FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(i);
-
-        try {
-            latch.await(100, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
 
         // Add the auto expand bubble
-        mEntryListener.onPendingEntryAdded(mAutoExpandRow.getEntry());
-        mBubbleController.updateBubble(mAutoExpandRow.getEntry());
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
 
         // Expansion should change
         verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
-                mAutoExpandRow.getEntry().key);
+                mRow.getEntry().key);
         assertTrue(mBubbleController.isStackExpanded());
 
         // # of bubbles should change
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
-        mContext.unregisterReceiver(receiver);
     }
 
     @Test
     public void testSuppressNotif_FailsNotForeground() {
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, false /* enableFlag */);
+
         // Add the suppress notif bubble
-        mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
-        mBubbleController.updateBubble(mSuppressNotifRow.getEntry());
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
 
-        // Should show in shade because we weren't forground
-        assertTrue(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
-
+        // Should not be suppressed because we weren't forground
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
         // # of bubbles should change
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
     }
 
     @Test
     public void testSuppressNotif_SucceedsForeground() {
-        final CountDownLatch latch = new CountDownLatch(1);
-        BroadcastReceiver receiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                latch.countDown();
-            }
-        };
-        IntentFilter filter = new IntentFilter(BubblesTestActivity.BUBBLE_ACTIVITY_OPENED);
-        mContext.registerReceiver(receiver, filter);
-
-        assertFalse(mBubbleController.isStackExpanded());
-
-        // Make ourselves foreground
-        Intent i = new Intent(mContext, BubblesTestActivity.class);
-        i.setFlags(FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(i);
-
-        try {
-            latch.await(100, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
 
         // Add the suppress notif bubble
-        mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
-        mBubbleController.updateBubble(mSuppressNotifRow.getEntry());
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
 
-        // Should NOT show in shade because we were foreground
-        assertFalse(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
+        // Notif should be suppressed because we were foreground
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         // # of bubbles should change
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
-        mContext.unregisterReceiver(receiver);
     }
 
     @Test
@@ -509,7 +508,8 @@
     @Test
     public void testMarkNewNotificationAsShowInShade() {
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
     }
 
     @Test
@@ -577,7 +577,7 @@
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
                 mRow.getEntry().key, REASON_CANCEL_ALL);
@@ -585,7 +585,7 @@
         // Intercept!
         assertTrue(intercepted);
         // Should update show in shade state
-        assertFalse(mRow.getEntry().showInShadeWhenBubble());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         verify(mNotificationEntryManager, never()).performRemoveNotification(
                 any(), anyInt());
@@ -598,7 +598,7 @@
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
                 mRow.getEntry().key, REASON_CANCEL);
@@ -606,7 +606,7 @@
         // Intercept!
         assertTrue(intercepted);
         // Should update show in shade state
-        assertFalse(mRow.getEntry().showInShadeWhenBubble());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         verify(mNotificationEntryManager, never()).performRemoveNotification(
                 any(), anyInt());
@@ -619,7 +619,7 @@
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         // Dismiss the bubble
         mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
@@ -639,22 +639,21 @@
                 StatusBarWindowController statusBarWindowController, BubbleData data,
                 ConfigurationController configurationController,
                 NotificationInterruptionStateProvider interruptionStateProvider,
-                ZenModeController zenModeController) {
+                ZenModeController zenModeController,
+                NotificationLockscreenUserManager lockscreenUserManager,
+                NotificationGroupManager groupManager) {
             super(context, statusBarWindowController, data, Runnable::run,
-                    configurationController, interruptionStateProvider, zenModeController);
-        }
-
-        @Override
-        public boolean shouldAutoBubbleForFlags(Context c, NotificationEntry entry) {
-            return entry.notification.getNotification().getBubbleMetadata() != null;
+                    configurationController, interruptionStateProvider, zenModeController,
+                    lockscreenUserManager, groupManager);
         }
     }
 
-    public static class TestableNotificationInterruptionStateProvider extends
+    static class TestableNotificationInterruptionStateProvider extends
             NotificationInterruptionStateProvider {
 
-        public TestableNotificationInterruptionStateProvider(Context context) {
-            super(context);
+        TestableNotificationInterruptionStateProvider(Context context,
+                NotificationFilter filter, StatusBarStateController controller) {
+            super(context, filter, controller);
             mUseHeadsUp = true;
         }
     }
@@ -669,4 +668,21 @@
                 .setIntent(bubbleIntent)
                 .setIcon(Icon.createWithResource(mContext, R.drawable.android));
     }
+
+    /**
+     * Sets the bubble metadata flags for this entry. These flags are normally set by
+     * NotificationManagerService when the notification is sent, however, these tests do not
+     * go through that path so we set them explicitly when testing.
+     */
+    private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) {
+        Notification.BubbleMetadata bubbleMetadata =
+                entry.notification.getNotification().getBubbleMetadata();
+        int flags = bubbleMetadata.getFlags();
+        if (enableFlag) {
+            flags |= flag;
+        } else {
+            flags &= ~flag;
+        }
+        bubbleMetadata.setFlags(flags);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 815a70a..f7d2743 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -887,7 +887,7 @@
 
     private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
         setPostTime(entry, postTime);
-        mBubbleData.notificationEntryUpdated(entry);
+        mBubbleData.notificationEntryUpdated(entry, /* suppressFlyout=*/ false);
     }
 
     private void changeExpandedStateAtTime(boolean shouldBeExpanded, long time) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
index 173237f..bd63e76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
@@ -18,7 +18,6 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotSame;
-import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.Mockito.verify;
 
@@ -57,16 +56,19 @@
 
     @Test
     public void testShowFlyout_isVisible() {
-        mFlyout.showFlyout("Hello", new PointF(100, 100), 500, true, Color.WHITE, null);
+        mFlyout.setupFlyoutStartingAsDot(
+                "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, null);
+        mFlyout.setVisibility(View.VISIBLE);
+
         assertEquals("Hello", mFlyoutText.getText());
         assertEquals(View.VISIBLE, mFlyout.getVisibility());
-        assertEquals(1f, mFlyoutText.getAlpha(), .01f);
     }
 
     @Test
     public void testFlyoutHide_runsCallback() {
         Runnable after = Mockito.mock(Runnable.class);
-        mFlyout.showFlyout("Hello", new PointF(100, 100), 500, true, Color.WHITE, after);
+        mFlyout.setupFlyoutStartingAsDot(
+                "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, after);
         mFlyout.hideFlyout();
 
         verify(after).run();
@@ -74,19 +76,16 @@
 
     @Test
     public void testSetCollapsePercent() {
-        mFlyout.showFlyout("Hello", new PointF(100, 100), 500, true, Color.WHITE, null);
-
-        float initialTranslationZ = mFlyout.getTranslationZ();
+        mFlyout.setupFlyoutStartingAsDot(
+                "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, null);
+        mFlyout.setVisibility(View.VISIBLE);
 
         mFlyout.setCollapsePercent(1f);
         assertEquals(0f, mFlyoutText.getAlpha(), 0.01f);
         assertNotSame(0f, mFlyoutText.getTranslationX()); // Should have moved to collapse.
-        assertTrue(mFlyout.getTranslationZ() < initialTranslationZ); // Should be descending.
 
         mFlyout.setCollapsePercent(0f);
         assertEquals(1f, mFlyoutText.getAlpha(), 0.01f);
         assertEquals(0f, mFlyoutText.getTranslationX());
-        assertEquals(initialTranslationZ, mFlyout.getTranslationZ());
-
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
similarity index 83%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
index cca9f28..e98662b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.bubbles;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -23,6 +23,7 @@
 
 import android.app.Notification;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -30,6 +31,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -40,13 +42,14 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class NotificationEntryTest extends SysuiTestCase {
+public class BubbleTest extends SysuiTestCase {
     @Mock
     private StatusBarNotification mStatusBarNotification;
     @Mock
     private Notification mNotif;
 
     private NotificationEntry mEntry;
+    private Bubble mBubble;
     private Bundle mExtras;
 
     @Before
@@ -55,11 +58,12 @@
 
         when(mStatusBarNotification.getKey()).thenReturn("key");
         when(mStatusBarNotification.getNotification()).thenReturn(mNotif);
-
+        when(mStatusBarNotification.getUser()).thenReturn(new UserHandle(0));
         mExtras = new Bundle();
         mNotif.extras = mExtras;
-
         mEntry = new NotificationEntry(mStatusBarNotification);
+
+        mBubble = new Bubble(mContext, mEntry);
     }
 
     @Test
@@ -67,7 +71,7 @@
         final String msg = "Hello there!";
         doReturn(Notification.Style.class).when(mNotif).getNotificationStyle();
         mExtras.putCharSequence(Notification.EXTRA_TEXT, msg);
-        assertEquals(msg, mEntry.getUpdateMessage(mContext));
+        assertEquals(msg, mBubble.getUpdateMessage(mContext));
     }
 
     @Test
@@ -78,7 +82,7 @@
         mExtras.putCharSequence(Notification.EXTRA_BIG_TEXT, msg);
 
         // Should be big text, not the small text.
-        assertEquals(msg, mEntry.getUpdateMessage(mContext));
+        assertEquals(msg, mBubble.getUpdateMessage(mContext));
     }
 
     @Test
@@ -86,7 +90,7 @@
         doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle();
 
         // Media notifs don't get update messages.
-        assertNull(mEntry.getUpdateMessage(mContext));
+        assertNull(mBubble.getUpdateMessage(mContext));
     }
 
     @Test
@@ -101,7 +105,7 @@
                         "Really? I prefer them that way."});
 
         // Should be the last one only.
-        assertEquals("Really? I prefer them that way.", mEntry.getUpdateMessage(mContext));
+        assertEquals("Really? I prefer them that way.", mBubble.getUpdateMessage(mContext));
     }
 
     @Test
@@ -116,6 +120,6 @@
                                 "Oh, hello!", 0, "Mady").toBundle()});
 
         // Should be the last one only.
-        assertEquals("Mady: Oh, hello!", mEntry.getUpdateMessage(mContext));
+        assertEquals("Mady: Oh, hello!", mBubble.getUpdateMessage(mContext));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
index b324235..1fbb443 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.mockito.Mockito.verify;
 
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -45,14 +46,18 @@
 
     private int mDisplayWidth = 500;
     private int mDisplayHeight = 1000;
+    private int mExpandedViewPadding = 10;
+    private int mOrientation = Configuration.ORIENTATION_PORTRAIT;
+    private float mLauncherGridDiff = 30f;
 
     @Spy
     private ExpandedAnimationController mExpandedController =
             new ExpandedAnimationController(
                     new Point(mDisplayWidth, mDisplayHeight) /* displaySize */,
-                    0 /* expandedViewPadding */);
+                    mExpandedViewPadding, mOrientation);
+
     private int mStackOffset;
-    private float mBubblePadding;
+    private float mBubblePaddingTop;
     private float mBubbleSize;
 
     private PointF mExpansionPoint;
@@ -65,7 +70,7 @@
 
         Resources res = mLayout.getResources();
         mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
-        mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
         mExpansionPoint = new PointF(100, 100);
     }
@@ -138,7 +143,6 @@
         assertEquals(500f, draggedBubble.getTranslationX(), 1f);
         assertEquals(500f, draggedBubble.getTranslationY(), 1f);
 
-        // Snap it back and make sure it made it back correctly.
         mLayout.removeView(draggedBubble);
         waitForLayoutMessageQueue();
         waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
@@ -174,7 +178,7 @@
 
         waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
 
-        assertEquals(mBubblePadding, mViews.get(1).getTranslationX(), 1f);
+        assertEquals(mBubblePaddingTop, mViews.get(1).getTranslationX(), 1f);
     }
 
     @Test
@@ -256,8 +260,8 @@
      * @return Bubble left x from left edge of screen.
      */
     public float getBubbleLeft(int index) {
-        float bubbleLeftFromRowLeft = index * (mBubbleSize + mBubblePadding);
-        return getRowLeft() + bubbleLeftFromRowLeft;
+        final float bubbleLeft = index * (mBubbleSize + getSpaceBetweenBubbles());
+        return getRowLeft() + bubbleLeft;
     }
 
     private float getRowLeft() {
@@ -265,16 +269,29 @@
             return 0;
         }
         int bubbleCount = mLayout.getChildCount();
+        final float totalBubbleWidth = bubbleCount * mBubbleSize;
+        final float totalGapWidth = (bubbleCount - 1) * getSpaceBetweenBubbles();
+        final float rowWidth = totalGapWidth + totalBubbleWidth;
 
-        // Width calculations.
-        double bubble = bubbleCount * mBubbleSize;
-        float gap = (bubbleCount - 1) * mBubblePadding;
-        float row = gap + (float) bubble;
+        final float centerScreen = mDisplayWidth / 2f;
+        final float halfRow = rowWidth / 2f;
+        final float rowLeft = centerScreen - halfRow;
 
-        float halfRow = row / 2f;
-        float centerScreen = mDisplayWidth / 2;
-        float rowLeftFromScreenLeft = centerScreen - halfRow;
+        return rowLeft;
+    }
 
-        return rowLeftFromScreenLeft;
+    /**
+     * @return Space between bubbles in row above expanded view.
+     */
+    private float getSpaceBetweenBubbles() {
+        final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
+        final float maxRowWidth = mDisplayWidth - rowMargins;
+
+        final float totalBubbleWidth = mMaxBubbles * mBubbleSize;
+        final float totalGapWidth = maxRowWidth - totalBubbleWidth;
+
+        final int gapCount = mMaxBubbles - 1;
+        final float gapWidth = totalGapWidth / gapCount;
+        return gapWidth;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
index f8b32c2..86554b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
@@ -156,8 +156,8 @@
         });
 
         // Set end listeners for both x and y.
-        mLayout.setEndActionForProperty(xEndAction, DynamicAnimation.TRANSLATION_X);
-        mLayout.setEndActionForProperty(yEndAction, DynamicAnimation.TRANSLATION_Y);
+        mTestableController.setEndActionForProperty(xEndAction, DynamicAnimation.TRANSLATION_X);
+        mTestableController.setEndActionForProperty(yEndAction, DynamicAnimation.TRANSLATION_Y);
 
         // Animate x, and wait for it to finish.
         mTestableController.animationForChildAtIndex(0)
@@ -190,7 +190,7 @@
         });
 
         // Set the end listener.
-        mLayout.setEndActionForProperty(xEndListener, DynamicAnimation.TRANSLATION_X);
+        mTestableController.setEndActionForProperty(xEndListener, DynamicAnimation.TRANSLATION_X);
 
         // Animate x, and wait for it to finish.
         mTestableController.animationForChildAtIndex(0)
@@ -205,7 +205,7 @@
         mTestableController.animationForChildAtIndex(0)
                 .translationX(1000)
                 .start();
-        mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
+        mTestableController.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
         xLatch.await(1, TimeUnit.SECONDS);
 
         // Make sure the end listener was not called.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
index f633f39..a5f2e8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
@@ -68,7 +68,7 @@
     @Mock
     private DisplayCutout mCutout;
 
-    private int mMaxBubbles;
+    protected int mMaxBubbles;
 
     @Before
     public void setUp() throws Exception {
@@ -195,14 +195,13 @@
         private void setTestEndActionForProperty(
                 Runnable action, DynamicAnimation.ViewProperty property) {
             final Runnable realEndAction = mEndActionForProperty.get(property);
-
-            setEndActionForProperty(() -> {
+            mLayout.mEndActionForProperty.put(property, () -> {
                 if (realEndAction != null) {
                     realEndAction.run();
                 }
 
                 action.run();
-            }, property);
+            });
         }
 
         /** PhysicsPropertyAnimator that posts its animations to the main thread. */
@@ -219,6 +218,11 @@
                         property, view, value, startVel, startDelay, stiffness, dampingRatio,
                         afterCallbacks));
             }
+
+            @Override
+            protected void startPathAnimation() {
+                mMainThreadHandler.post(super::startPathAnimation);
+            }
         }
 
         /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
new file mode 100644
index 0000000..a66cf84
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
@@ -0,0 +1,589 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar;
+
+
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.dreams.IDreamManager;
+import android.service.notification.StatusBarNotification;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+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.policy.HeadsUpManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the interruption state provider which understands whether the system & notification
+ * is in a state allowing a particular notification to hun, pulse, or bubble.
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class NotificationInterruptionStateProviderTest extends SysuiTestCase {
+
+    @Mock
+    PowerManager mPowerManager;
+    @Mock
+    IDreamManager mDreamManager;
+    @Mock
+    AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+    @Mock
+    NotificationFilter mNotificationFilter;
+    @Mock
+    StatusBarStateController mStatusBarStateController;
+    @Mock
+    NotificationPresenter mPresenter;
+    @Mock
+    HeadsUpManager mHeadsUpManager;
+    @Mock
+    NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor;
+
+    private NotificationInterruptionStateProvider mNotifInterruptionStateProvider;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mNotifInterruptionStateProvider =
+                new TestableNotificationInterruptionStateProvider(mContext,
+                        mPowerManager,
+                        mDreamManager,
+                        mAmbientDisplayConfiguration,
+                        mNotificationFilter,
+                        mStatusBarStateController);
+
+        mNotifInterruptionStateProvider.setUpWithPresenter(
+                mPresenter,
+                mHeadsUpManager,
+                mHeadsUpSuppressor);
+    }
+
+    /**
+     * Sets up the state such that any requests to
+     * {@link NotificationInterruptionStateProvider#canAlertCommon(NotificationEntry)} will
+     * pass as long its provided NotificationEntry fulfills group suppression check.
+     */
+    private void ensureStateForAlertCommon() {
+        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
+    }
+
+    /**
+     * Sets up the state such that any requests to
+     * {@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 NotificationInterruptionStateProvider#shouldHeadsUp(NotificationEntry)} will
+     * pass as long its provided NotificationEntry fulfills importance & DND checks.
+     */
+    private void ensureStateForHeadsUpWhenAwake() throws RemoteException {
+        ensureStateForAlertCommon();
+        ensureStateForAlertAwakeCommon();
+
+        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 NotificationInterruptionStateProvider#shouldHeadsUp(NotificationEntry)} will
+     * pass as long its provided NotificationEntry fulfills importance & DND checks.
+     */
+    private void ensureStateForHeadsUpWhenDozing() {
+        ensureStateForAlertCommon();
+
+        when(mStatusBarStateController.isDozing()).thenReturn(true);
+        when(mAmbientDisplayConfiguration.pulseOnNotificationEnabled(anyInt())).thenReturn(true);
+    }
+
+    /**
+     * Sets up the state such that any requests to
+     * {@link NotificationInterruptionStateProvider#shouldBubbleUp(NotificationEntry)} will
+     * pass as long its provided NotificationEntry fulfills importance & bubble checks.
+     */
+    private void ensureStateForBubbleUp() {
+        ensureStateForAlertCommon();
+        ensureStateForAlertAwakeCommon();
+    }
+
+    /**
+     * Ensure that the disabled state is set correctly.
+     */
+    @Test
+    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);
+        assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isTrue();
+    }
+
+    /**
+     * Filtered out notifications don't alert.
+     */
+    @Test
+    public void testCanAlertCommon_false_filteredOut() {
+        ensureStateForAlertCommon();
+        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+
+        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 testCanAlertCommon_false_suppressedForGroups() {
+        ensureStateForAlertCommon();
+
+        Notification n = new Notification.Builder(getContext(), "a")
+                .setGroup("a")
+                .setGroupSummary(true)
+                .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
+                .build();
+        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
+                UserHandle.of(0), null, 0);
+        NotificationEntry entry = new NotificationEntry(sbn);
+        entry.importance = IMPORTANCE_DEFAULT;
+
+        assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isFalse();
+    }
+
+    /**
+     * HUNs while dozing can happen.
+     */
+    @Test
+    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 testShouldHeadsUpWhenDozing_false_pulseDisabled() {
+        ensureStateForHeadsUpWhenDozing();
+        when(mAmbientDisplayConfiguration.pulseOnNotificationEnabled(anyInt())).thenReturn(false);
+
+        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 testShouldHeadsUpWhenDozing_false_notDozing() {
+        ensureStateForHeadsUpWhenDozing();
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * In DND ambient effects can be suppressed
+     * {@link android.app.NotificationManager.Policy#SUPPRESSED_EFFECT_AMBIENT}.
+     */
+    @Test
+    public void testShouldHeadsUpWhenDozing_false_suppressingAmbient() {
+        ensureStateForHeadsUpWhenDozing();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        entry.suppressedVisualEffects = SUPPRESSED_EFFECT_AMBIENT;
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * Notifications that are < {@link android.app.NotificationManager#IMPORTANCE_DEFAULT} don't
+     * get to pulse.
+     */
+    @Test
+    public void testShouldHeadsUpWhenDozing_false_lessImportant() {
+        ensureStateForHeadsUpWhenDozing();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_LOW);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * Heads up can happen.
+     */
+    @Test
+    public void testShouldHeadsUp_true() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+    }
+
+    /**
+     * 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 testShouldHeadsUp_false_bubble() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+
+        // Bubble bit only applies to interruption when we're in the shade
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(createBubble())).isFalse();
+    }
+
+    /**
+     * If we're not allowed to alert in general, we shouldn't be shown as heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_alertCommonFalse() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+        // Make canAlertCommon false by saying it's filtered out
+        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * In DND HUN peek effects can be suppressed
+     * {@link android.app.NotificationManager.Policy#SUPPRESSED_EFFECT_PEEK}.
+     */
+    @Test
+    public void testShouldHeadsUp_false_suppressPeek() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        entry.suppressedVisualEffects = SUPPRESSED_EFFECT_PEEK;
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * Notifications that are < {@link android.app.NotificationManager#IMPORTANCE_HIGH} don't get
+     * to show as a heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_lessImportant() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * If the device is not in use then we shouldn't be shown as heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_deviceNotInUse() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+
+        // Device is not in use if screen is not on
+        when(mPowerManager.isScreenOn()).thenReturn(false);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+
+        // Also not in use if screen is on but we're showing screen saver / "dreaming"
+        when(mPowerManager.isDeviceIdleMode()).thenReturn(true);
+        when(mDreamManager.isDreaming()).thenReturn(true);
+        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 testShouldHeadsUp_false_suppressed() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+        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 testCanAlertAwakeCommon__false_vrMode() {
+        ensureStateForAlertAwakeCommon();
+        when(mPresenter.isDeviceInVrMode()).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
+    }
+
+    /**
+     * On screen alerts don't happen when the notification is snoozed.
+     */
+    @Test
+    public void testCanAlertAwakeCommon_false_snoozedPackage() {
+        ensureStateForAlertAwakeCommon();
+        when(mHeadsUpManager.isSnoozed(any())).thenReturn(true);
+
+        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 testCanAlertAwakeCommon_false_justLaunchedFullscreen() {
+        ensureStateForAlertAwakeCommon();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        entry.notifyFullScreenIntentLaunched();
+
+        assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
+    }
+
+    /**
+     * Bubbles can happen.
+     */
+    @Test
+    public void testShouldBubbleUp_true() {
+        ensureStateForBubbleUp();
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isTrue();
+    }
+
+    /**
+     * If the notification doesn't have permission to bubble, it shouldn't bubble.
+     */
+    @Test
+    public void shouldBubbleUp_false_notAllowedToBubble() {
+        ensureStateForBubbleUp();
+
+        NotificationEntry entry = createBubble();
+        entry.canBubble = false;
+
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
+    }
+
+    /**
+     * If the notification isn't a bubble, it should definitely not show as a bubble.
+     */
+    @Test
+    public void shouldBubbleUp_false_notABubble() {
+        ensureStateForBubbleUp();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        entry.canBubble = true;
+
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
+    }
+
+    /**
+     * If the notification doesn't have bubble metadata, it shouldn't bubble.
+     */
+    @Test
+    public void shouldBubbleUp_false_invalidMetadata() {
+        ensureStateForBubbleUp();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        entry.canBubble = true;
+        entry.notification.getNotification().flags |= FLAG_BUBBLE;
+
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
+    }
+
+    /**
+     * If the notification can't heads up in general, it shouldn't bubble.
+     */
+    @Test
+    public void shouldBubbleUp_false_alertAwakeCommonFalse() {
+        ensureStateForBubbleUp();
+
+        // 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 shouldBubbleUp_false_alertCommonFalse() {
+        ensureStateForBubbleUp();
+
+        // Make canAlertCommon false by saying it's filtered out
+        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse();
+    }
+
+    private NotificationEntry createBubble() {
+        Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder()
+                .setIntent(PendingIntent.getActivity(mContext, 0, new Intent(), 0))
+                .setIcon(Icon.createWithResource(mContext.getResources(), R.drawable.android))
+                .build();
+        Notification n = new Notification.Builder(getContext(), "a")
+                .setContentTitle("title")
+                .setContentText("content text")
+                .setBubbleMetadata(data)
+                .build();
+        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
+                UserHandle.of(0), null, 0);
+        NotificationEntry entry = new NotificationEntry(sbn);
+        entry.notification.getNotification().flags |= FLAG_BUBBLE;
+        entry.importance = IMPORTANCE_HIGH;
+        entry.canBubble = true;
+        return entry;
+    }
+
+    private NotificationEntry createNotification(int importance) {
+        Notification n = new Notification.Builder(getContext(), "a")
+                .setContentTitle("title")
+                .setContentText("content text")
+                .build();
+        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
+                UserHandle.of(0), null, 0);
+        NotificationEntry entry = new NotificationEntry(sbn);
+        entry.importance = importance;
+        return entry;
+    }
+
+    /**
+     * Testable class overriding constructor.
+     */
+    public class TestableNotificationInterruptionStateProvider extends
+            NotificationInterruptionStateProvider {
+
+        TestableNotificationInterruptionStateProvider(Context context,
+                PowerManager powerManager, IDreamManager dreamManager,
+                AmbientDisplayConfiguration ambientDisplayConfiguration,
+                NotificationFilter notificationFilter,
+                StatusBarStateController statusBarStateController) {
+            super(context, powerManager, dreamManager, ambientDisplayConfiguration,
+                    notificationFilter,
+                    statusBarStateController);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 58fb53a..0248dc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -41,7 +41,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleData;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -110,8 +110,9 @@
         mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
                 mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
                 mock(StatusBarStateControllerImpl.class), mEntryManager,
-                () -> mShadeController, new BubbleData(mContext),
+                () -> mShadeController,
                 mock(KeyguardBypassController.class),
+                mock(BubbleController.class),
                 mock(DynamicPrivacyController.class));
         Dependency.get(InitController.class).executePostInitTasks();
         mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 0dbf308..baaec45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_OPAQUE;
-import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
+import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -68,6 +68,7 @@
     private SynchronousScrimController mScrimController;
     private ScrimView mScrimBehind;
     private ScrimView mScrimInFront;
+    private ScrimView mScrimForBubble;
     private ScrimState mScrimState;
     private float mScrimBehindAlpha;
     private GradientColors mScrimInFrontColor;
@@ -83,6 +84,7 @@
     public void setup() {
         mScrimBehind = spy(new ScrimView(getContext()));
         mScrimInFront = new ScrimView(getContext());
+        mScrimForBubble = new ScrimView(getContext());
         mWakeLock = mock(WakeLock.class);
         mAlarmManager = mock(AlarmManager.class);
         mAlwaysOnEnabled = true;
@@ -91,6 +93,7 @@
         when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled);
         when(mDozeParamenters.getDisplayNeedsBlanking()).thenReturn(true);
         mScrimController = new SynchronousScrimController(mScrimBehind, mScrimInFront,
+                mScrimForBubble,
                 (scrimState, scrimBehindAlpha, scrimInFrontColor) -> {
                     mScrimState = scrimState;
                     mScrimBehindAlpha = scrimBehindAlpha;
@@ -112,21 +115,28 @@
     public void transitionToKeyguard() {
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be visible without tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
-        assertScrimTint(mScrimBehind, true /* tinted */);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
     }
 
     @Test
     public void transitionToAod_withRegularWallpaper() {
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be visible with tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
-        assertScrimTint(mScrimBehind, true /* tinted */);
-        assertScrimTint(mScrimInFront, true /* tinted */);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
     }
 
     @Test
@@ -134,14 +144,18 @@
         mScrimController.setWallpaperSupportsAmbientMode(true);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be transparent
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
 
         // Pulsing notification should conserve AOD wallpaper.
         mScrimController.transitionTo(ScrimState.PULSING);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
     }
 
     @Test
@@ -150,11 +164,14 @@
         mScrimController.setWallpaperSupportsAmbientMode(true);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be visible with tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
-        assertScrimTint(mScrimBehind, true /* tinted */);
-        assertScrimTint(mScrimInFront, true /* tinted */);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
     }
 
     @Test
@@ -164,11 +181,14 @@
         mScrimController.finishAnimationsImmediately();
         mScrimController.setHasBackdrop(true);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be visible with tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
-        assertScrimTint(mScrimBehind, true /* tinted */);
-        assertScrimTint(mScrimInFront, true /* tinted */);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
     }
 
     @Test
@@ -177,27 +197,32 @@
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         mScrimController.setAodFrontScrimAlpha(0.5f);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be visible without tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
 
         // ... but that it does take effect once we enter the AOD state.
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be semi-transparent
-        // Back scrim should be visible
-        assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(SEMI_TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
 
         // ... and that if we set it while we're in AOD, it does take immediate effect.
         mScrimController.setAodFrontScrimAlpha(1f);
-        assertScrimVisibility(VISIBILITY_FULLY_OPAQUE, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(OPAQUE /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
 
         // ... and make sure we recall the previous front scrim alpha even if we transition away
         // for a bit.
         mScrimController.transitionTo(ScrimState.UNLOCKED);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_OPAQUE, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(OPAQUE /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
 
         // ... and alpha updates should be completely ignored if always_on is off.
         // Passing it forward would mess up the wake-up transition.
@@ -221,20 +246,28 @@
         mScrimController.setWallpaperSupportsAmbientMode(false);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
 
         mScrimController.transitionTo(ScrimState.PULSING);
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent, but tinted
         // Back scrim should be semi-transparent so the user can see the wallpaper
         // Pulse callback should have been invoked
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
-        assertScrimTint(mScrimBehind, true /* tinted */);
-        assertScrimTint(mScrimInFront, true /* tinted */);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
 
         mScrimController.setWakeLockScreenSensorActive(true);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
     }
 
     @Test
@@ -243,8 +276,13 @@
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible without tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
-        assertScrimTint(mScrimBehind, false /* tinted */);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(false /* front */,
+                false /* behind */,
+                false /* bubble */);
     }
 
     @Test
@@ -253,8 +291,12 @@
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible without tint
-        assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
-        assertScrimTint(mScrimBehind, false /* tinted */);
+        assertScrimAlpha(SEMI_TRANSPARENT /* front */,
+                TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
+        assertScrimTint(false /* front */,
+                false /* behind */,
+                false /* bubble */);
     }
 
     @Test
@@ -262,15 +304,19 @@
         mScrimController.setPanelExpansion(0f);
         mScrimController.transitionTo(ScrimState.UNLOCKED);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be transparent
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
-        assertScrimTint(mScrimBehind, false /* tinted */);
-        assertScrimTint(mScrimInFront, false /* tinted */);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(false /* front */,
+                false /* behind */,
+                false /* bubble */);
 
         // Back scrim should be visible after start dragging
         mScrimController.setPanelExpansion(0.5f);
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
     }
 
     @Test
@@ -278,12 +324,19 @@
         mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED);
         mScrimController.finishAnimationsImmediately();
 
+        assertScrimTint(false /* front */,
+                false /* behind */,
+                false /* bubble */);
+
         // Front scrim should be transparent
-        Assert.assertEquals(ScrimController.VISIBILITY_FULLY_TRANSPARENT,
+        Assert.assertEquals(ScrimController.TRANSPARENT,
                 mScrimInFront.getViewAlpha(), 0.0f);
         // Back scrim should be visible
         Assert.assertEquals(ScrimController.GRADIENT_SCRIM_ALPHA_BUSY,
                 mScrimBehind.getViewAlpha(), 0.0f);
+        // Bubble scrim should be visible
+        Assert.assertEquals(ScrimController.GRADIENT_SCRIM_ALPHA_BUSY,
+                mScrimBehind.getViewAlpha(), 0.0f);
     }
 
     @Test
@@ -352,16 +405,22 @@
         mScrimController.setPanelExpansion(0f);
         mScrimController.finishAnimationsImmediately();
         mScrimController.transitionTo(ScrimState.UNLOCKED);
-        // Immediately tinted after the transition starts
-        assertScrimTint(mScrimInFront, true /* tinted */);
-        assertScrimTint(mScrimBehind, true /* tinted */);
+
+        // Immediately tinted black after the transition starts
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                true  /* bubble */);
+
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be transparent
-        // Neither scrims should be tinted anymore after the animation.
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
-        assertScrimTint(mScrimInFront, false /* tinted */);
-        assertScrimTint(mScrimBehind, false /* tinted */);
+
+        // All scrims should be transparent at the end of fade transition.
+        assertScrimAlpha(TRANSPARENT /* front */,
+                TRANSPARENT /* behind */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(false /* front */,
+                false /* behind */,
+                false  /* bubble */);
     }
 
     @Test
@@ -376,9 +435,11 @@
                         // Front scrim should be black in the middle of the transition
                         Assert.assertTrue("Scrim should be visible during transition. Alpha: "
                                 + mScrimInFront.getViewAlpha(), mScrimInFront.getViewAlpha() > 0);
-                        assertScrimTint(mScrimInFront, true /* tinted */);
+                        assertScrimTint(true /* front */,
+                                true /* behind */,
+                                true /* bubble */);
                         Assert.assertSame("Scrim should be visible during transition.",
-                                mScrimVisibility, VISIBILITY_FULLY_OPAQUE);
+                                mScrimVisibility, OPAQUE);
                     }
                 });
         mScrimController.finishAnimationsImmediately();
@@ -586,11 +647,15 @@
         mScrimController.setKeyguardOccluded(true);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* behind */,
+                TRANSPARENT /* bubble */);
 
         mScrimController.transitionTo(ScrimState.PULSING);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* behind */,
+                TRANSPARENT /* bubble */);
     }
 
     @Test
@@ -598,11 +663,15 @@
         mScrimController.setWallpaperSupportsAmbientMode(true);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                TRANSPARENT /* behind */,
+                TRANSPARENT /* bubble */);
 
         mScrimController.setKeyguardOccluded(true);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* behind */,
+                TRANSPARENT /* bubble */);
     }
 
     @Test
@@ -641,33 +710,68 @@
                 mScrimInFront.getDefaultFocusHighlightEnabled());
         Assert.assertFalse("Scrim shouldn't have focus highlight",
                 mScrimBehind.getDefaultFocusHighlightEnabled());
+        Assert.assertFalse("Scrim shouldn't have focus highlight",
+                mScrimForBubble.getDefaultFocusHighlightEnabled());
     }
 
-    private void assertScrimTint(ScrimView scrimView, boolean tinted) {
-        final boolean viewIsTinted = scrimView.getTint() != Color.TRANSPARENT;
-        final String name = scrimView == mScrimInFront ? "front" : "back";
+    private void assertScrimTint(boolean front, boolean behind, boolean bubble) {
         Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
-                +" with scrim: " + name + " and tint: " + Integer.toHexString(scrimView.getTint()),
-                tinted, viewIsTinted);
+                        + " with scrim: " + getScrimName(mScrimInFront) + " and tint: "
+                        + Integer.toHexString(mScrimInFront.getTint()),
+                front, mScrimInFront.getTint() != Color.TRANSPARENT);
+
+        Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
+                        + " with scrim: " + getScrimName(mScrimBehind) + " and tint: "
+                        + Integer.toHexString(mScrimBehind.getTint()),
+                behind, mScrimBehind.getTint() != Color.TRANSPARENT);
+
+        Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
+                        + " with scrim: " + getScrimName(mScrimForBubble) + " and tint: "
+                        + Integer.toHexString(mScrimForBubble.getTint()),
+                bubble, mScrimForBubble.getTint() != Color.TRANSPARENT);
     }
 
-    private void assertScrimVisibility(int inFront, int behind) {
-        boolean inFrontVisible = inFront != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
-        boolean behindVisible = behind != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
-        Assert.assertEquals("Unexpected front scrim visibility. Alpha is "
-                + mScrimInFront.getViewAlpha(), inFrontVisible, mScrimInFront.getViewAlpha() > 0);
-        Assert.assertEquals("Unexpected back scrim visibility. Alpha is "
-                + mScrimBehind.getViewAlpha(), behindVisible, mScrimBehind.getViewAlpha() > 0);
-
-        final int visibility;
-        if (inFront == VISIBILITY_FULLY_OPAQUE || behind == VISIBILITY_FULLY_OPAQUE) {
-            visibility = VISIBILITY_FULLY_OPAQUE;
-        } else if (inFront > VISIBILITY_FULLY_TRANSPARENT || behind > VISIBILITY_FULLY_TRANSPARENT) {
-            visibility = VISIBILITY_SEMI_TRANSPARENT;
-        } else {
-            visibility = VISIBILITY_FULLY_TRANSPARENT;
+    private String getScrimName(ScrimView scrim) {
+        if (scrim == mScrimInFront) {
+            return "front";
+        } else if (scrim == mScrimBehind) {
+            return "back";
+        } else if (scrim == mScrimForBubble) {
+            return "bubble";
         }
-        Assert.assertEquals("Invalid visibility.", visibility, mScrimVisibility);
+        return "unknown_scrim";
+    }
+
+    private void assertScrimAlpha(int front, int behind, int bubble) {
+        // Check single scrim visibility.
+        Assert.assertEquals("Unexpected front scrim alpha: "
+                        + mScrimInFront.getViewAlpha(),
+                front != TRANSPARENT /* expected */,
+                mScrimInFront.getViewAlpha() > TRANSPARENT /* actual */);
+
+        Assert.assertEquals("Unexpected back scrim alpha: "
+                        + mScrimBehind.getViewAlpha(),
+                behind != TRANSPARENT /* expected */,
+                mScrimBehind.getViewAlpha() > TRANSPARENT /* actual */);
+
+        Assert.assertEquals(
+                "Unexpected bubble scrim alpha: "
+                        + mScrimForBubble.getViewAlpha(), /* message */
+                bubble != TRANSPARENT /* expected */,
+                mScrimForBubble.getViewAlpha() > TRANSPARENT /* actual */);
+
+        // Check combined scrim visibility.
+        final int visibility;
+        if (front == OPAQUE || behind == OPAQUE || bubble == OPAQUE) {
+            visibility = OPAQUE;
+        } else if (front > TRANSPARENT || behind > TRANSPARENT || bubble > TRANSPARENT) {
+            visibility = SEMI_TRANSPARENT;
+        } else {
+            visibility = TRANSPARENT;
+        }
+        Assert.assertEquals("Invalid visibility.",
+                visibility /* expected */,
+                mScrimVisibility);
     }
 
     /**
@@ -679,11 +783,12 @@
         boolean mOnPreDrawCalled;
 
         SynchronousScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+                ScrimView scrimForBubble,
                 TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
                 Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
                 AlarmManager alarmManager) {
-            super(scrimBehind, scrimInFront, scrimStateListener, scrimVisibleListener,
-                    dozeParameters, alarmManager);
+            super(scrimBehind, scrimInFront, scrimForBubble, scrimStateListener,
+                    scrimVisibleListener, dozeParameters, alarmManager);
         }
 
         @Override
@@ -694,13 +799,14 @@
 
         void finishAnimationsImmediately() {
             boolean[] animationFinished = {false};
-            setOnAnimationFinished(()-> animationFinished[0] = true);
+            setOnAnimationFinished(() -> animationFinished[0] = true);
             // Execute code that will trigger animations.
             onPreDraw();
             // Force finish all animations.
             mLooper.processAllMessages();
             endAnimation(mScrimBehind, TAG_KEY_ANIM);
             endAnimation(mScrimInFront, TAG_KEY_ANIM);
+            endAnimation(mScrimForBubble, TAG_KEY_ANIM);
 
             if (!animationFinished[0]) {
                 throw new IllegalStateException("Animation never finished");
@@ -738,6 +844,7 @@
 
         /**
          * Do not wait for a frame since we're in a test environment.
+         *
          * @param callback What to execute.
          */
         @Override
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 06d76eb..5a6f27d 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
@@ -153,8 +153,6 @@
         StatusBarNotification bubbleSbn = mBubbleNotificationRow.getStatusBarNotification();
         bubbleSbn.getNotification().contentIntent = mContentIntent;
         bubbleSbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
-        // Do what BubbleController's NotificationEntryListener#onPendingEntryAdded does:
-        mBubbleNotificationRow.getEntry().setShowInShadeWhenBubble(true);
 
         mActiveNotifications = new ArrayList<>();
         mActiveNotifications.add(mNotificationRow.getEntry());
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 cffd57b..ea3a742 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
@@ -208,7 +208,8 @@
 
         mNotificationInterruptionStateProvider =
                 new TestableNotificationInterruptionStateProvider(mContext, mPowerManager,
-                        mDreamManager, mAmbientDisplayConfiguration);
+                        mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter,
+                        mStatusBarStateController);
         mDependency.injectTestDependency(NotificationInterruptionStateProvider.class,
                 mNotificationInterruptionStateProvider);
         mDependency.injectMockDependency(NavigationBarController.class);
@@ -870,8 +871,11 @@
                 Context context,
                 PowerManager powerManager,
                 IDreamManager dreamManager,
-                AmbientDisplayConfiguration ambientDisplayConfiguration) {
-            super(context, powerManager, dreamManager, ambientDisplayConfiguration);
+                AmbientDisplayConfiguration ambientDisplayConfiguration,
+                NotificationFilter filter,
+                StatusBarStateController controller) {
+            super(context, powerManager, dreamManager, ambientDisplayConfiguration, filter,
+                    controller);
             mUseHeadsUp = true;
         }
     }
diff --git a/packages/VpnDialogs/res/values-bn/strings.xml b/packages/VpnDialogs/res/values-bn/strings.xml
index 5e11fd9..2defd81 100644
--- a/packages/VpnDialogs/res/values-bn/strings.xml
+++ b/packages/VpnDialogs/res/values-bn/strings.xml
@@ -17,7 +17,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="prompt" msgid="3183836924226407828">"সংযোগের অনুরোধ"</string>
-    <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> এমন একটি VPN সংযোগ সেট-আপ করতে চাচ্ছে যেটি দিয়ে এটি নেটওয়ার্ক ট্রাফিক নিরীক্ষণ করতে পারবে। আপনি যদি উৎসটিকে বিশ্বাস করেন, তাহলেই কেবল এতে সম্মতি দিন। VPN সক্রিয় থাকলে আপনার স্ক্রীনের উপরে &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; দেখা যাবে।"</string>
+    <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> এমন একটি VPN সংযোগ সেট আপ করতে চাচ্ছে যেটি দিয়ে এটি নেটওয়ার্ক ট্রাফিক নিরীক্ষণ করতে পারবে। আপনি যদি উৎসটিকে বিশ্বাস করেন, তাহলেই কেবল এতে সম্মতি দিন। VPN সক্রিয় থাকলে আপনার স্ক্রীনের উপরে &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; দেখা যাবে।"</string>
     <string name="legacy_title" msgid="192936250066580964">"VPN সংযুক্ত হয়েছে"</string>
     <string name="session" msgid="6470628549473641030">"অধিবেশন:"</string>
     <string name="duration" msgid="3584782459928719435">"সময়কাল:"</string>
diff --git a/packages/VpnDialogs/res/values-hi/strings.xml b/packages/VpnDialogs/res/values-hi/strings.xml
index eed0858..5560a85 100644
--- a/packages/VpnDialogs/res/values-hi/strings.xml
+++ b/packages/VpnDialogs/res/values-hi/strings.xml
@@ -22,7 +22,7 @@
     <string name="session" msgid="6470628549473641030">"सत्र:"</string>
     <string name="duration" msgid="3584782459928719435">"अवधि:"</string>
     <string name="data_transmitted" msgid="7988167672982199061">"भेजे गए:"</string>
-    <string name="data_received" msgid="4062776929376067820">"पाया:"</string>
+    <string name="data_received" msgid="4062776929376067820">"प्राप्त:"</string>
     <string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> बाइट / <xliff:g id="NUMBER_1">%2$s</xliff:g> पैकेट"</string>
     <string name="always_on_disconnected_title" msgid="1906740176262776166">"हमेशा चालू रहने वाले VPN से नहीं जुड़ पा रहा है"</string>
     <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> को हर समय जुड़े रहने के लिए सेट अप किया गया है, लेकिन वह इस समय नहीं जुड़ पा रहा है. जब तक आपका फ़ोन <xliff:g id="VPN_APP_1">%1$s</xliff:g> से नहीं जुड़ जाता, तब तक वह सार्वजनिक नेटवर्क का इस्तेमाल करेगा."</string>
@@ -30,7 +30,7 @@
     <string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
     <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"VPN सेटिंग बदलें"</string>
     <string name="configure" msgid="4905518375574791375">"कॉन्फ़िगर करें"</string>
-    <string name="disconnect" msgid="971412338304200056">"डिसकनेक्ट करें"</string>
+    <string name="disconnect" msgid="971412338304200056">"डिस्‍कनेक्‍ट करें"</string>
     <string name="open_app" msgid="3717639178595958667">"ऐप खोलें"</string>
     <string name="dismiss" msgid="6192859333764711227">"खारिज करें"</string>
 </resources>
diff --git a/packages/VpnDialogs/res/values-sk/strings.xml b/packages/VpnDialogs/res/values-sk/strings.xml
index a08117a..0002964 100644
--- a/packages/VpnDialogs/res/values-sk/strings.xml
+++ b/packages/VpnDialogs/res/values-sk/strings.xml
@@ -32,5 +32,5 @@
     <string name="configure" msgid="4905518375574791375">"Konfigurovať"</string>
     <string name="disconnect" msgid="971412338304200056">"Odpojiť"</string>
     <string name="open_app" msgid="3717639178595958667">"Otvoriť aplikáciu"</string>
-    <string name="dismiss" msgid="6192859333764711227">"Zavrieť"</string>
+    <string name="dismiss" msgid="6192859333764711227">"Zrušiť"</string>
 </resources>
diff --git a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-km/strings.xml b/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-km/strings.xml
index 287817f..fb0d88e 100644
--- a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-km/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-km/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="display_cutout_emulation_overlay" msgid="1677693377327336341">"ក្បាលឆកនៅជ្រុង"</string>
+    <string name="display_cutout_emulation_overlay" msgid="1677693377327336341">"សក់សេះនៅជ្រុង"</string>
 </resources>
diff --git a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-km/strings.xml b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-km/strings.xml
index 312817f..1553594 100644
--- a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-km/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-km/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="display_cutout_emulation_overlay" msgid="5323179900047630217">"ក្បាលឆក​ភ្លោះ"</string>
+    <string name="display_cutout_emulation_overlay" msgid="5323179900047630217">"សក់សេះ​ភ្លោះ"</string>
 </resources>
diff --git a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values-km/strings.xml b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values-km/strings.xml
index 1fe37a7..97e867f 100644
--- a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values-km/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values-km/strings.xml
@@ -19,5 +19,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="display_cutout_emulation_overlay" msgid="3947428012427075896">"ក្បាលឆក​តូច"</string>
+    <string name="display_cutout_emulation_overlay" msgid="3947428012427075896">"សក់សេះ​តូច"</string>
 </resources>
diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-km/strings.xml b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-km/strings.xml
index 38fc08c..5a01a62 100644
--- a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-km/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-km/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="display_cutout_emulation_overlay" msgid="6424539415439220018">"ក្បាលឆកវែង"</string>
+    <string name="display_cutout_emulation_overlay" msgid="6424539415439220018">"សក់សេះវែង"</string>
 </resources>
diff --git a/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values-km/strings.xml b/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values-km/strings.xml
index 3a720fd5..7325a73 100644
--- a/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values-km/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values-km/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="display_cutout_emulation_overlay" msgid="4043478945358357737">"ក្បាលឆក​ធំ"</string>
+    <string name="display_cutout_emulation_overlay" msgid="4043478945358357737">"សក់សេះ​ធំ"</string>
 </resources>
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index f613064..c46fc20 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -649,13 +649,8 @@
         if (mSurfaceControl != null) {
             mSurfaceLayout.dispose();
             mSurfaceLayout = null;
-            SurfaceControl.openTransaction();
-            try {
-                mSurfaceControl.remove();
-                mSurface.release();
-            } finally {
-                SurfaceControl.closeTransaction();
-            }
+            new Transaction().remove(mSurfaceControl).apply();
+            mSurface.release();
             mSurfaceControl = null;
             mSurfaceVisible = false;
             mSurfaceAlpha = 0f;
diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java
index 358bdb9..e59bf16 100644
--- a/services/core/java/com/android/server/notification/BubbleExtractor.java
+++ b/services/core/java/com/android/server/notification/BubbleExtractor.java
@@ -41,10 +41,9 @@
             if (DBG) Slog.d(TAG, "missing config");
             return null;
         }
-        boolean userWantsBubbles = mConfig.bubblesEnabled(record.sbn.getUser());
         boolean appCanShowBubble =
                 mConfig.areBubblesAllowed(record.sbn.getPackageName(), record.sbn.getUid());
-        if (!userWantsBubbles || !appCanShowBubble) {
+        if (!mConfig.bubblesEnabled() || !appCanShowBubble) {
             record.setAllowBubble(false);
         } else {
             if (record.getChannel() != null) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index d358f9a..653d9db 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -91,6 +91,7 @@
 import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static com.android.server.notification.PreferencesHelper.DEFAULT_ALLOW_BUBBLE;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG_CRITICAL;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG_NORMAL;
@@ -1358,7 +1359,9 @@
     private final class SettingsObserver extends ContentObserver {
         private final Uri NOTIFICATION_BADGING_URI
                 = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING);
-        private final Uri NOTIFICATION_BUBBLES_URI
+        private final Uri NOTIFICATION_BUBBLES_URI_GLOBAL
+                = Settings.Global.getUriFor(Settings.Global.NOTIFICATION_BUBBLES);
+        private final Uri NOTIFICATION_BUBBLES_URI_SECURE
                 = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES);
         private final Uri NOTIFICATION_LIGHT_PULSE_URI
                 = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
@@ -1377,7 +1380,9 @@
                     false, this, UserHandle.USER_ALL);
             resolver.registerContentObserver(NOTIFICATION_RATE_LIMIT_URI,
                     false, this, UserHandle.USER_ALL);
-            resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI,
+            resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI_GLOBAL,
+                    false, this, UserHandle.USER_ALL);
+            resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI_SECURE,
                     false, this, UserHandle.USER_ALL);
             update(null);
         }
@@ -1404,9 +1409,41 @@
             if (uri == null || NOTIFICATION_BADGING_URI.equals(uri)) {
                 mPreferencesHelper.updateBadgingEnabled();
             }
-            if (uri == null || NOTIFICATION_BUBBLES_URI.equals(uri)) {
+            // In QPR we moved the setting to Global rather than Secure so that the setting
+            // applied to work profiles. Unfortunately we need to maintain both to pass CTS without
+            // a change to CTS outside of a normal letter release.
+            if (uri == null || NOTIFICATION_BUBBLES_URI_GLOBAL.equals(uri)) {
+                syncBubbleSettings(resolver, NOTIFICATION_BUBBLES_URI_GLOBAL);
                 mPreferencesHelper.updateBubblesEnabled();
             }
+            if (NOTIFICATION_BUBBLES_URI_SECURE.equals(uri)) {
+                syncBubbleSettings(resolver, NOTIFICATION_BUBBLES_URI_SECURE);
+            }
+        }
+
+        private void syncBubbleSettings(ContentResolver resolver, Uri settingToFollow) {
+            boolean followSecureSetting = settingToFollow.equals(NOTIFICATION_BUBBLES_URI_SECURE);
+
+            int secureSettingValue = Settings.Secure.getInt(resolver,
+                    Settings.Secure.NOTIFICATION_BUBBLES, DEFAULT_ALLOW_BUBBLE ? 1 : 0);
+            int globalSettingValue = Settings.Global.getInt(resolver,
+                    Settings.Global.NOTIFICATION_BUBBLES, DEFAULT_ALLOW_BUBBLE ? 1 : 0);
+
+            if (globalSettingValue == secureSettingValue) {
+                return;
+            }
+
+            if (followSecureSetting) {
+                // Global => secure
+                Settings.Global.putInt(resolver,
+                        Settings.Global.NOTIFICATION_BUBBLES,
+                        secureSettingValue);
+            } else {
+                // Secure => Global
+                Settings.Secure.putInt(resolver,
+                        Settings.Secure.NOTIFICATION_BADGING,
+                        globalSettingValue);
+            }
         }
     }
 
@@ -4817,47 +4854,112 @@
         } else {
             notification.flags &= ~FLAG_BUBBLE;
         }
+        // Is the app in the foreground?
+        final boolean appIsForeground =
+                mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
+        Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
+        if (!appIsForeground && metadata != null) {
+            // Remove any flags that only work when foregrounded
+            int flags = metadata.getFlags();
+            flags &= ~Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE;
+            flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
+            metadata.setFlags(flags);
+        }
     }
 
     /**
-     * @return whether the provided notification record is allowed to be represented as a bubble.
+     * @return whether the provided notification record is allowed to be represented as a bubble,
+     * accounting for user choice & policy.
      */
     private boolean isNotificationAppropriateToBubble(NotificationRecord r, String pkg, int userId,
             NotificationRecord oldRecord) {
         Notification notification = r.getNotification();
-        Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
-        boolean intentCanBubble = metadata != null
-                && canLaunchInActivityView(getContext(), metadata.getIntent(), pkg);
+        if (!canBubble(r, pkg, userId)) {
+            // no log: canBubble has its own
+            return false;
+        }
 
-        // Does the app want to bubble & is able to bubble
-        boolean canBubble = intentCanBubble
-                && mPreferencesHelper.areBubblesAllowed(pkg, userId)
-                && mPreferencesHelper.bubblesEnabled(r.sbn.getUser())
-                && r.getChannel().canBubble()
-                && !mActivityManager.isLowRamDevice();
+        if (mActivityManager.isLowRamDevice()) {
+            logBubbleError(r.getKey(), "low ram device");
+            return false;
+        }
 
-        // Is the app in the foreground?
-        final boolean appIsForeground =
-                mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
+        if (mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND) {
+            // If the app is foreground it always gets to bubble
+            return true;
+        }
 
-        // Is the notification something we'd allow to bubble?
-        // A call with a foreground service + person
+        if (oldRecord != null && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0) {
+            // This is an update to an active bubble
+            return true;
+        }
+
+        // At this point the bubble must fulfill communication policy
+
+        // Communication always needs a person
         ArrayList<Person> peopleList = notification.extras != null
                 ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)
                 : null;
-        boolean isForegroundCall = CATEGORY_CALL.equals(notification.category)
-                && (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
-        // OR message style (which always has a person) with any remote input
-        Class<? extends Notification.Style> style = notification.getNotificationStyle();
-        boolean isMessageStyle = Notification.MessagingStyle.class.equals(style);
-        boolean notificationAppropriateToBubble =
-                (isMessageStyle && hasValidRemoteInput(notification))
-                || (peopleList != null && !peopleList.isEmpty() && isForegroundCall);
+        // Message style requires a person & it's not included in the list
+        boolean isMessageStyle = Notification.MessagingStyle.class.equals(
+                notification.getNotificationStyle());
+        if (!isMessageStyle && (peopleList == null || peopleList.isEmpty())) {
+            logBubbleError(r.getKey(), "if not foreground, must have a person and be "
+                    + "Notification.MessageStyle or Notification.CATEGORY_CALL");
+            return false;
+        }
 
-        // OR something that was previously a bubble & still exists
-        boolean bubbleUpdate = oldRecord != null
-                && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0;
-        return canBubble && (notificationAppropriateToBubble || appIsForeground || bubbleUpdate);
+        // Communication is a message or a call
+        boolean isCall = CATEGORY_CALL.equals(notification.category);
+        boolean hasForegroundService = (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
+        if (isMessageStyle) {
+            if (hasValidRemoteInput(notification)) {
+                return true;
+            }
+            logBubbleError(r.getKey(), "messages require valid remote input");
+            return false;
+        } else if (isCall) {
+            if (hasForegroundService) {
+                return true;
+            }
+            logBubbleError(r.getKey(), "calls require foreground service");
+            return false;
+        }
+        logBubbleError(r.getKey(), "if not foreground, must be "
+                + "Notification.MessageStyle or Notification.CATEGORY_CALL");
+        return false;
+    }
+
+    /**
+     * @return whether the user has enabled the provided notification to bubble, does not account
+     * for policy.
+     */
+    private boolean canBubble(NotificationRecord r, String pkg, int userId) {
+        Notification notification = r.getNotification();
+        Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
+        if (metadata == null) {
+            // no log: no need to inform dev if they didn't attach bubble metadata
+            return false;
+        }
+        if (!canLaunchInActivityView(getContext(), metadata.getIntent(), pkg)) {
+            // no log: method has the failure log
+            return false;
+        }
+        if (!mPreferencesHelper.bubblesEnabled()) {
+            logBubbleError(r.getKey(), "bubbles disabled for user: " + userId);
+            return false;
+        }
+        if (!mPreferencesHelper.areBubblesAllowed(pkg, userId)) {
+            logBubbleError(r.getKey(),
+                    "bubbles for package: " + pkg + " disabled for user: " + userId);
+            return false;
+        }
+        if (!r.getChannel().canBubble()) {
+            logBubbleError(r.getKey(),
+                    "bubbles for channel " + r.getChannel().getId() + " disabled");
+            return false;
+        }
+        return true;
     }
 
     private boolean hasValidRemoteInput(Notification n) {
@@ -4876,6 +4978,11 @@
         return false;
     }
 
+    private void logBubbleError(String key, String failureMessage) {
+        if (DBG) {
+            Log.w(TAG, "Bubble notification: " + key + " failed: " + failureMessage);
+        }
+    }
     /**
      * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
      *
@@ -5238,12 +5345,26 @@
                         return;
                     }
 
+                    // Bubbled children get to stick around if the summary was manually cancelled
+                    // (user removed) from systemui.
+                    FlagChecker childrenFlagChecker = null;
+                    if (mReason == REASON_CANCEL
+                            || mReason == REASON_CLICK
+                            || mReason == REASON_CANCEL_ALL) {
+                        childrenFlagChecker = (flags) -> {
+                            if ((flags & FLAG_BUBBLE) != 0) {
+                                return false;
+                            }
+                            return true;
+                        };
+                    }
+
                     // Cancel the notification.
                     boolean wasPosted = removeFromNotificationListsLocked(r);
                     cancelNotificationLocked(
                             r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName);
                     cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
-                            mSendDelete, null);
+                            mSendDelete, childrenFlagChecker);
                     updateLightsLocked();
                 } else {
                     // No notification was found, assume that it is snoozed and cancel it.
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 7b45a1b..f094729 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -100,7 +100,7 @@
     @VisibleForTesting
     static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = false;
     private static final boolean DEFAULT_SHOW_BADGE = true;
-    private static final boolean DEFAULT_ALLOW_BUBBLE = true;
+    static final boolean DEFAULT_ALLOW_BUBBLE = true;
     private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE  = false;
     private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE  = false;
 
@@ -130,7 +130,7 @@
     private final ZenModeHelper mZenModeHelper;
 
     private SparseBooleanArray mBadgingEnabled;
-    private SparseBooleanArray mBubblesEnabled;
+    private boolean mBubblesEnabled;
     private boolean mAreChannelsBypassingDnd;
     private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
 
@@ -1818,40 +1818,19 @@
     }
 
     public void updateBubblesEnabled() {
-        if (mBubblesEnabled == null) {
-            mBubblesEnabled = new SparseBooleanArray();
-        }
-        boolean changed = false;
-        // update the cached values
-        for (int index = 0; index < mBubblesEnabled.size(); index++) {
-            int userId = mBubblesEnabled.keyAt(index);
-            final boolean oldValue = mBubblesEnabled.get(userId);
-            final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                    Settings.Secure.NOTIFICATION_BUBBLES,
-                    DEFAULT_ALLOW_BUBBLE ? 1 : 0, userId) != 0;
-            mBubblesEnabled.put(userId, newValue);
-            changed |= oldValue != newValue;
-        }
-        if (changed) {
+        final boolean newValue = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_BUBBLES,
+                DEFAULT_ALLOW_BUBBLE ? 1 : 0) == 1;
+        if (newValue != mBubblesEnabled) {
+            mBubblesEnabled = newValue;
             updateConfig();
         }
     }
 
-    public boolean bubblesEnabled(UserHandle userHandle) {
-        int userId = userHandle.getIdentifier();
-        if (userId == UserHandle.USER_ALL) {
-            return false;
-        }
-        if (mBubblesEnabled.indexOfKey(userId) < 0) {
-            mBubblesEnabled.put(userId,
-                    Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                            Settings.Secure.NOTIFICATION_BUBBLES,
-                            DEFAULT_ALLOW_BUBBLE ? 1 : 0, userId) != 0);
-        }
-        return mBubblesEnabled.get(userId, DEFAULT_ALLOW_BUBBLE);
+    public boolean bubblesEnabled() {
+        return mBubblesEnabled;
     }
 
-
     public void updateBadgingEnabled() {
         if (mBadgingEnabled == null) {
             mBadgingEnabled = new SparseBooleanArray();
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 5de00e4..7816f36 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -30,7 +30,7 @@
     boolean canShowBadge(String packageName, int uid);
     boolean badgingEnabled(UserHandle userHandle);
     boolean areBubblesAllowed(String packageName, int uid);
-    boolean bubblesEnabled(UserHandle userHandle);
+    boolean bubblesEnabled();
     boolean isGroupBlocked(String packageName, int uid, String groupId);
 
     Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 4cfc1d1..afd6674 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -907,7 +907,7 @@
                 }
 
                 public void releaseSurface() {
-                    mSurfaceControl.remove();
+                    mService.mTransactionFactory.make().remove(mSurfaceControl).apply();
                     mSurface.release();
                 }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index b33f23c..0b3832e 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -5454,6 +5454,7 @@
             task.cleanUpResourcesForDestroy();
         }
 
+        final ActivityDisplay display = getDisplay();
         if (mTaskHistory.isEmpty()) {
             if (DEBUG_STACK) Slog.i(TAG_STACK, "removeTask: removing stack=" + this);
             // We only need to adjust focused stack if this stack is in focus and we are not in the
@@ -5462,11 +5463,11 @@
                     && mRootActivityContainer.isTopDisplayFocusedStack(this)) {
                 String myReason = reason + " leftTaskHistoryEmpty";
                 if (!inMultiWindowMode() || adjustFocusToNextFocusableStack(myReason) == null) {
-                    getDisplay().moveHomeStackToFront(myReason);
+                    display.moveHomeStackToFront(myReason);
                 }
             }
             if (isAttached()) {
-                getDisplay().positionChildAtBottom(this);
+                display.positionChildAtBottom(this);
             }
             if (!isActivityTypeHome() || !isAttached()) {
                 remove();
@@ -5479,6 +5480,9 @@
         if (inPinnedWindowingMode()) {
             mService.getTaskChangeNotificationController().notifyActivityUnpinned();
         }
+        if (display.isSingleTaskInstance()) {
+            mService.notifySingleTaskDisplayEmpty(display.mDisplayId);
+        }
     }
 
     TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8a9dfc7..bbc02c7 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6046,6 +6046,10 @@
         return allUids.contains(uid);
     }
 
+    void notifySingleTaskDisplayEmpty(int displayId) {
+        mTaskChangeNotificationController.notifySingleTaskDisplayEmpty(displayId);
+    }
+
     final class H extends Handler {
         static final int REPORT_TIME_TRACKER_MSG = 1;
 
diff --git a/services/core/java/com/android/server/wm/BlackFrame.java b/services/core/java/com/android/server/wm/BlackFrame.java
index 84ba5ca9..7fc17e1 100644
--- a/services/core/java/com/android/server/wm/BlackFrame.java
+++ b/services/core/java/com/android/server/wm/BlackFrame.java
@@ -97,6 +97,7 @@
     final BlackSurface[] mBlackSurfaces = new BlackSurface[4];
 
     final boolean mForceDefaultOrientation;
+    private final TransactionFactory mTransactionFactory;
 
     public void printTo(String prefix, PrintWriter pw) {
         pw.print(prefix); pw.print("Outer: "); mOuterRect.printShortString(pw);
@@ -111,11 +112,12 @@
         }
     }
 
-    public BlackFrame(SurfaceControl.Transaction t,
-            Rect outer, Rect inner, int layer, DisplayContent dc,
-            boolean forceDefaultOrientation) throws OutOfResourcesException {
+    public BlackFrame(TransactionFactory factory, SurfaceControl.Transaction t, Rect outer,
+            Rect inner, int layer, DisplayContent dc, boolean forceDefaultOrientation)
+            throws OutOfResourcesException {
         boolean success = false;
 
+        mTransactionFactory = factory;
         mForceDefaultOrientation = forceDefaultOrientation;
 
         // TODO: Why do we use 4 surfaces instead of just one big one behind the screenshot?
@@ -149,14 +151,16 @@
 
     public void kill() {
         if (mBlackSurfaces != null) {
+            SurfaceControl.Transaction t = mTransactionFactory.make();
             for (int i=0; i<mBlackSurfaces.length; i++) {
                 if (mBlackSurfaces[i] != null) {
                     if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG_WM,
                             "  BLACK " + mBlackSurfaces[i].surface + ": DESTROY");
-                    mBlackSurfaces[i].surface.remove();
+                    t.remove(mBlackSurfaces[i].surface);
                     mBlackSurfaces[i] = null;
                 }
             }
+            t.apply();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a1b8760..71e794b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4584,13 +4584,15 @@
                         .show(mSplitScreenDividerAnchor);
                 scheduleAnimation();
             } else {
-                mAppAnimationLayer.remove();
+                mWmService.mTransactionFactory.make()
+                        .remove(mAppAnimationLayer)
+                        .remove(mBoostedAppAnimationLayer)
+                        .remove(mHomeAppAnimationLayer)
+                        .remove(mSplitScreenDividerAnchor)
+                        .apply();
                 mAppAnimationLayer = null;
-                mBoostedAppAnimationLayer.remove();
                 mBoostedAppAnimationLayer = null;
-                mHomeAppAnimationLayer.remove();
                 mHomeAppAnimationLayer = null;
-                mSplitScreenDividerAnchor.remove();
                 mSplitScreenDividerAnchor = null;
             }
         }
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index c3ea72f..bb035d5 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -261,7 +261,7 @@
 
         public void remove() {
             if (mSurface != null) {
-                mSurface.remove();
+                new SurfaceControl.Transaction().remove(mSurface).apply();
                 mSurface = null;
             }
             if (mInputInterceptor != null) {
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 0be9736..b94a7dc 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -558,7 +558,7 @@
                 Rect outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1,
                         mOriginalWidth*2, mOriginalHeight*2);
                 Rect inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
-                mCustomBlackFrame = new BlackFrame(t, outer, inner,
+                mCustomBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner,
                         SCREEN_FREEZE_LAYER_CUSTOM, mDisplayContent, false);
                 mCustomBlackFrame.setMatrix(t, mFrameInitialMatrix);
             } catch (OutOfResourcesException e) {
@@ -589,7 +589,7 @@
                             mOriginalWidth*2, mOriginalHeight*2);
                     inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
                 }
-                mExitingBlackFrame = new BlackFrame(t, outer, inner,
+                mExitingBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner,
                         SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation);
                 mExitingBlackFrame.setMatrix(t, mFrameInitialMatrix);
             } catch (OutOfResourcesException e) {
@@ -602,7 +602,7 @@
                 Rect outer = new Rect(-finalWidth*1, -finalHeight*1,
                         finalWidth*2, finalHeight*2);
                 Rect inner = new Rect(0, 0, finalWidth, finalHeight);
-                mEnteringBlackFrame = new BlackFrame(t, outer, inner,
+                mEnteringBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner,
                         SCREEN_FREEZE_LAYER_ENTER, mDisplayContent, false);
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
@@ -640,7 +640,7 @@
             if (SHOW_TRANSACTIONS ||
                     SHOW_SURFACE_ALLOC) Slog.i(TAG_WM,
                             "  FREEZE " + mSurfaceControl + ": DESTROY");
-            mSurfaceControl.remove();
+            mService.mTransactionFactory.make().remove(mSurfaceControl).apply();
             mSurfaceControl = null;
         }
         if (mCustomBlackFrame != null) {
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index f776062..4c50527 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -55,7 +55,8 @@
     private static final int NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG = 20;
     private static final int NOTIFY_BACK_PRESSED_ON_TASK_ROOT = 21;
     private static final int NOTIFY_SINGLE_TASK_DISPLAY_DRAWN = 22;
-    private static final int NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG = 23;
+    private static final int NOTIFY_SINGLE_TASK_DISPLAY_EMPTY = 23;
+    private static final int NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG = 24;
 
     // Delay in notifying task stack change listeners (in millis)
     private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -160,6 +161,10 @@
         l.onSingleTaskDisplayDrawn(m.arg1);
     };
 
+    private final TaskStackConsumer mNotifySingleTaskDisplayEmpty = (l, m) -> {
+        l.onSingleTaskDisplayEmpty(m.arg1);
+    };
+
     private final TaskStackConsumer mNotifyTaskDisplayChanged = (l, m) -> {
         l.onTaskDisplayChanged(m.arg1, m.arg2);
     };
@@ -246,6 +251,9 @@
                 case NOTIFY_SINGLE_TASK_DISPLAY_DRAWN:
                     forAllRemoteListeners(mNotifySingleTaskDisplayDrawn, msg);
                     break;
+                case NOTIFY_SINGLE_TASK_DISPLAY_EMPTY:
+                    forAllRemoteListeners(mNotifySingleTaskDisplayEmpty, msg);
+                    break;
                 case NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG:
                     forAllRemoteListeners(mNotifyTaskDisplayChanged, msg);
                     break;
@@ -505,6 +513,17 @@
     }
 
     /**
+     * Notify listeners that the last task is removed from a single task display.
+     */
+    void notifySingleTaskDisplayEmpty(int displayId) {
+        final Message msg = mHandler.obtainMessage(
+                NOTIFY_SINGLE_TASK_DISPLAY_EMPTY,
+                displayId, 0 /* unused */);
+        forAllLocalListeners(mNotifySingleTaskDisplayEmpty, msg);
+        msg.sendToTarget();
+    }
+
+    /**
      * Notify listeners that a task is reparented to another display.
      */
     void notifyTaskDisplayChanged(int taskId, int newDisplayId) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 114a56f..e410868 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1012,7 +1012,7 @@
         EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
 
         if (mAnimationBackgroundSurface != null) {
-            mAnimationBackgroundSurface.remove();
+            mWmService.mTransactionFactory.make().remove(mAnimationBackgroundSurface).apply();
             mAnimationBackgroundSurface = null;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index bef0f81..8d619d7 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -162,7 +162,7 @@
         }
         try {
             if (mSurfaceControl != null) {
-                mSurfaceControl.remove();
+                mTmpTransaction.remove(mSurfaceControl).apply();
             }
         } catch (RuntimeException e) {
             Slog.w(TAG, "Error destroying surface in: " + this, e);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
index 273a9e6..7459c4b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
@@ -86,7 +86,7 @@
         BubbleExtractor extractor = new BubbleExtractor();
         extractor.setConfig(mConfig);
 
-        when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+        when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
         NotificationRecord r = getNotificationRecord(false, IMPORTANCE_UNSPECIFIED);
 
@@ -100,7 +100,7 @@
         BubbleExtractor extractor = new BubbleExtractor();
         extractor.setConfig(mConfig);
 
-        when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+        when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(false);
         NotificationRecord r = getNotificationRecord(true, IMPORTANCE_HIGH);
 
@@ -114,7 +114,7 @@
         BubbleExtractor extractor = new BubbleExtractor();
         extractor.setConfig(mConfig);
 
-        when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+        when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
         NotificationRecord r = getNotificationRecord(true, IMPORTANCE_UNSPECIFIED);
 
@@ -128,7 +128,7 @@
         BubbleExtractor extractor = new BubbleExtractor();
         extractor.setConfig(mConfig);
 
-        when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+        when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(false);
         NotificationRecord r = getNotificationRecord(false, IMPORTANCE_UNSPECIFIED);
 
@@ -142,7 +142,7 @@
         BubbleExtractor extractor = new BubbleExtractor();
         extractor.setConfig(mConfig);
 
-        when(mConfig.bubblesEnabled(mUser)).thenReturn(false);
+        when(mConfig.bubblesEnabled()).thenReturn(false);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
         NotificationRecord r = getNotificationRecord(true, IMPORTANCE_HIGH);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 5ba1eb2..4bb6ed4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
 import static android.app.Notification.CATEGORY_CALL;
+import static android.app.Notification.FLAG_AUTO_CANCEL;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
 import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
@@ -420,7 +421,7 @@
     private void setUpPrefsForBubbles(boolean globalEnabled, boolean pkgEnabled,
             boolean channelEnabled) {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mPreferencesHelper.bubblesEnabled(any())).thenReturn(globalEnabled);
+        when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled);
         when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled);
         when(mPreferencesHelper.getNotificationChannel(
                 anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
@@ -439,14 +440,22 @@
         return sbn;
     }
 
+
     private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
             String groupKey, boolean isSummary) {
+        return generateNotificationRecord(channel, id, groupKey, isSummary, false /* isBubble */);
+    }
+
+    private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
+            String groupKey, boolean isSummary, boolean isBubble) {
         Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
                 .setGroup(groupKey)
                 .setGroupSummary(isSummary);
-
+        if (isBubble) {
+            nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build());
+        }
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, "tag", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
@@ -542,6 +551,52 @@
                 .setIcon(Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon));
     }
 
+    private NotificationRecord addGroupWithBubblesAndValidateAdded(boolean summaryAutoCancel)
+            throws RemoteException {
+
+        // Notification that has bubble metadata
+        NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel, 1,
+                "BUBBLE_GROUP", false /* isSummary */, true /* isBubble */);
+
+        // Make the package foreground so that we're allowed to be a bubble
+        when(mActivityManager.getPackageImportance(nrBubble.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_FOREGROUND);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId());
+        waitForIdle();
+
+        // Make sure we are a bubble
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+        assertTrue((notifsAfter[0].getNotification().flags & FLAG_BUBBLE) != 0);
+
+        // Plain notification without bubble metadata
+        NotificationRecord nrPlain = generateNotificationRecord(mTestNotificationChannel, 2,
+                "BUBBLE_GROUP", false /* isSummary */, false /* isBubble */);
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nrPlain.sbn.getId(), nrPlain.sbn.getNotification(), nrPlain.sbn.getUserId());
+        waitForIdle();
+
+        notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(2, notifsAfter.length);
+
+        // Summary notification for both of those
+        NotificationRecord nrSummary = generateNotificationRecord(mTestNotificationChannel, 3,
+                "BUBBLE_GROUP", true /* isSummary */, false /* isBubble */);
+        if (summaryAutoCancel) {
+            nrSummary.getNotification().flags |= FLAG_AUTO_CANCEL;
+        }
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nrSummary.sbn.getId(), nrSummary.sbn.getNotification(), nrSummary.sbn.getUserId());
+        waitForIdle();
+
+        notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(3, notifsAfter.length);
+
+        return nrSummary;
+    }
+
     @Test
     public void testCreateNotificationChannels_SingleChannel() throws Exception {
         final NotificationChannel channel =
@@ -5078,4 +5133,162 @@
         assertEquals(1, notifsAfter.length);
         assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0);
     }
+
+    @Test
+    public void testNotificationBubbles_flagAutoExpandForeground_fails_notForeground()
+            throws Exception {
+        // Bubbles are allowed!
+        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+        // Give it bubble metadata
+        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder()
+                .setSuppressNotification(true)
+                .setAutoExpandBubble(true).build();
+        // Give it a person
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        // It needs remote input to be bubble-able
+        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+                inputIntent).addRemoteInput(remoteInput)
+                .build();
+        // Make it messaging style
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .setBubbleMetadata(data)
+                .setStyle(new Notification.MessagingStyle(person)
+                        .setConversationTitle("Bubble Chat")
+                        .addMessage("Hello?",
+                                SystemClock.currentThreadTimeMillis() - 300000, person)
+                        .addMessage("Is it me you're looking for?",
+                                SystemClock.currentThreadTimeMillis(), person)
+                )
+                .setActions(replyAction)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+                nb.build(), new UserHandle(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        // Ensure we're not foreground
+        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_VISIBLE);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // yes allowed, yes messaging, yes bubble
+        Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification();
+        assertTrue(notif.isBubbleNotification());
+
+        // Our flags should have failed since we're not foreground
+        assertFalse(notif.getBubbleMetadata().getAutoExpandBubble());
+        assertFalse(notif.getBubbleMetadata().isNotificationSuppressed());
+    }
+
+    @Test
+    public void testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground()
+            throws RemoteException {
+        // Bubbles are allowed!
+        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+        // Give it bubble metadata
+        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder()
+                .setSuppressNotification(true)
+                .setAutoExpandBubble(true).build();
+        // Give it a person
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        // It needs remote input to be bubble-able
+        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+                inputIntent).addRemoteInput(remoteInput)
+                .build();
+        // Make it messaging style
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .setBubbleMetadata(data)
+                .setStyle(new Notification.MessagingStyle(person)
+                        .setConversationTitle("Bubble Chat")
+                        .addMessage("Hello?",
+                                SystemClock.currentThreadTimeMillis() - 300000, person)
+                        .addMessage("Is it me you're looking for?",
+                                SystemClock.currentThreadTimeMillis(), person)
+                )
+                .setActions(replyAction)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+                nb.build(), new UserHandle(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        // Ensure we are in the foreground
+        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_FOREGROUND);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // yes allowed, yes messaging, yes bubble
+        Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification();
+        assertTrue(notif.isBubbleNotification());
+
+        // Our flags should have failed since we are foreground
+        assertTrue(notif.getBubbleMetadata().getAutoExpandBubble());
+        assertTrue(notif.getBubbleMetadata().isNotificationSuppressed());
+    }
+
+    @Test
+    public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryDismissed()
+            throws Exception {
+        // Bubbles are allowed!
+        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+        NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
+                true /* summaryAutoCancel */);
+
+        // Dismiss summary
+        final NotificationVisibility nv = NotificationVisibility.obtain(nrSummary.getKey(), 1, 2,
+                true);
+        mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, nrSummary.sbn.getTag(),
+                nrSummary.sbn.getId(), nrSummary.getUserId(), nrSummary.getKey(),
+                NotificationStats.DISMISSAL_SHADE,
+                NotificationStats.DISMISS_SENTIMENT_NEUTRAL, nv);
+        waitForIdle();
+
+        // The bubble should still exist
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+    }
+
+    @Test
+    public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryClicked()
+            throws Exception {
+        // Bubbles are allowed!
+        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+        NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
+                true /* summaryAutoCancel */);
+
+        // Click summary
+        final NotificationVisibility nv = NotificationVisibility.obtain(nrSummary.getKey(), 1, 2,
+                true);
+        mService.mNotificationDelegate.onNotificationClick(mUid, Binder.getCallingPid(),
+                nrSummary.getKey(), nv);
+        waitForIdle();
+
+        // The bubble should still exist
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 8f8b746..933552e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -58,7 +58,9 @@
 import android.os.Build;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
+
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableContentResolver;
 import android.util.ArrayMap;
@@ -152,8 +154,7 @@
         contentResolver.setFallbackToExisting(false);
         Secure.putIntForUser(contentResolver,
                 Secure.NOTIFICATION_BADGING, 1, UserHandle.getUserId(UID_N_MR1));
-        Secure.putIntForUser(contentResolver,
-                Secure.NOTIFICATION_BUBBLES, 1, UserHandle.getUserId(UID_N_MR1));
+        Global.putInt(contentResolver, Global.NOTIFICATION_BUBBLES, 1);
 
         ContentProvider testContentProvider = mock(ContentProvider.class);
         when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
@@ -1948,42 +1949,18 @@
 
     @Test
     public void testBubblesOverrideTrue() {
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BUBBLES, 1,
-                USER.getIdentifier());
+        Global.putInt(getContext().getContentResolver(),
+                Global.NOTIFICATION_BUBBLES, 1);
         mHelper.updateBubblesEnabled(); // would be called by settings observer
-        assertTrue(mHelper.bubblesEnabled(USER));
+        assertTrue(mHelper.bubblesEnabled());
     }
 
     @Test
     public void testBubblesOverrideFalse() {
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BUBBLES, 0,
-                USER.getIdentifier());
+        Global.putInt(getContext().getContentResolver(),
+                Global.NOTIFICATION_BUBBLES, 0);
         mHelper.updateBubblesEnabled(); // would be called by settings observer
-        assertFalse(mHelper.bubblesEnabled(USER));
-    }
-
-    @Test
-    public void testBubblesForUserAll() {
-        try {
-            mHelper.bubblesEnabled(UserHandle.ALL);
-        } catch (Exception e) {
-            fail("just don't throw");
-        }
-    }
-
-    @Test
-    public void testBubblesOverrideUserIsolation() {
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BUBBLES, 0,
-                USER.getIdentifier());
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BUBBLES, 1,
-                USER2.getIdentifier());
-        mHelper.updateBubblesEnabled(); // would be called by settings observer
-        assertFalse(mHelper.bubblesEnabled(USER));
-        assertTrue(mHelper.bubblesEnabled(USER2));
+        assertFalse(mHelper.bubblesEnabled());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 292a05b..a98f79c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -169,7 +169,7 @@
 
         mDimmer.updateDims(mTransaction, new Rect());
         verify(mTransaction).show(getDimLayer());
-        verify(dimLayer, never()).remove();
+        verify(mTransaction, never()).remove(dimLayer);
     }
 
     @Test
@@ -231,7 +231,7 @@
 
         mDimmer.updateDims(mTransaction, new Rect());
         verify(mTransaction).show(dimLayer);
-        verify(dimLayer, never()).remove();
+        verify(mTransaction, never()).remove(dimLayer);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index 19fd93fe..6e41118 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -271,6 +271,54 @@
         waitForCallback(singleTaskDisplayDrawnLatch);
     }
 
+    @Test
+    public void testSingleTaskDisplayEmpty() throws Exception {
+        final Instrumentation instrumentation = getInstrumentation();
+
+        final CountDownLatch activityViewReadyLatch = new CountDownLatch(1);
+        final CountDownLatch activityViewDestroyedLatch = new CountDownLatch(1);
+        final CountDownLatch singleTaskDisplayDrawnLatch = new CountDownLatch(1);
+        final CountDownLatch singleTaskDisplayEmptyLatch = new CountDownLatch(1);
+
+        registerTaskStackChangedListener(new TaskStackListener() {
+            @Override
+            public void onSingleTaskDisplayDrawn(int displayId) throws RemoteException {
+                singleTaskDisplayDrawnLatch.countDown();
+            }
+            @Override
+            public void onSingleTaskDisplayEmpty(int displayId)
+                    throws RemoteException {
+                singleTaskDisplayEmptyLatch.countDown();
+            }
+        });
+        final ActivityViewTestActivity activity =
+                (ActivityViewTestActivity) startTestActivity(ActivityViewTestActivity.class);
+        final ActivityView activityView = activity.getActivityView();
+        activityView.setCallback(new ActivityView.StateCallback() {
+            @Override
+            public void onActivityViewReady(ActivityView view) {
+                activityViewReadyLatch.countDown();
+            }
+
+            @Override
+            public void onActivityViewDestroyed(ActivityView view) {
+                activityViewDestroyedLatch.countDown();
+            }
+        });
+        waitForCallback(activityViewReadyLatch);
+
+        final Context context = instrumentation.getContext();
+        Intent intent = new Intent(context, ActivityInActivityView.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        activityView.startActivity(intent);
+        waitForCallback(singleTaskDisplayDrawnLatch);
+        assertEquals(1, singleTaskDisplayEmptyLatch.getCount());
+
+        activityView.release();
+        waitForCallback(activityViewDestroyedLatch);
+        waitForCallback(singleTaskDisplayEmptyLatch);
+    }
+
     /**
      * Starts the provided activity and returns the started instance.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index b93c994..acfc2ea 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -114,16 +114,12 @@
     @Test
     public void testAddChildSetsSurfacePosition() {
         try (MockSurfaceBuildingContainer top = new MockSurfaceBuildingContainer(mWm)) {
-
-            final SurfaceControl.Transaction transaction = mock(SurfaceControl.Transaction.class);
-            mWm.mTransactionFactory = () -> transaction;
-
             WindowContainer child = new WindowContainer(mWm);
             child.setBounds(1, 1, 10, 10);
 
-            verify(transaction, never()).setPosition(any(), anyFloat(), anyFloat());
+            verify(mTransaction, never()).setPosition(any(), anyFloat(), anyFloat());
             top.addChild(child, 0);
-            verify(transaction, times(1)).setPosition(any(), eq(1.f), eq(1.f));
+            verify(mTransaction, times(1)).setPosition(any(), eq(1.f), eq(1.f));
         }
     }