Merge "Adding CUJ types for Launcher"
diff --git a/Android.bp b/Android.bp
index 5953007..5af7756 100644
--- a/Android.bp
+++ b/Android.bp
@@ -474,6 +474,7 @@
         "android.hardware.radio-V1.3-java",
         "android.hardware.radio-V1.4-java",
         "android.hardware.radio-V1.5-java",
+        "android.hardware.radio-V1.6-java",
         "android.hardware.thermal-V1.0-java-constants",
         "android.hardware.thermal-V1.0-java",
         "android.hardware.thermal-V1.1-java",
@@ -1348,7 +1349,7 @@
     },
     libs: [
         "framework-annotations-lib",
-        "android.hardware.radio-V1.5-java",
+        "android.hardware.radio-V1.6-java",
     ],
     check_api: {
         current: {
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp
index 92dbc26..c541963 100644
--- a/apct-tests/perftests/core/Android.bp
+++ b/apct-tests/perftests/core/Android.bp
@@ -1,3 +1,19 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
 android_test {
     name: "CorePerfTests",
 
@@ -23,17 +39,14 @@
 
     libs: ["android.test.base"],
 
+    java_resources: [ ":GoogleFontDancingScript", ],
+
     data: [":perfetto_artifacts"],
 
     platform_apis: true,
 
     jni_libs: ["libperftestscore_jni"],
 
-    // Use google-fonts/dancing-script for the performance metrics
-    // ANDROIDMK TRANSLATION ERROR: Only $(LOCAL_PATH)/.. values are allowed
-    // LOCAL_ASSET_DIR := $(TOP)/external/google-fonts/dancing-script
-
     test_suites: ["device-tests"],
     certificate: "platform",
-
 }
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java
index 8847456..e83c64c 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java
@@ -27,6 +27,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.Preconditions;
 import com.android.perftests.core.R;
 
 import org.junit.Rule;
@@ -73,10 +74,31 @@
         final AssetManager am = context.getAssets();
 
         while (state.keepRunning()) {
-            Typeface face = Typeface.createFromAsset(am, TEST_FONT_NAME);
+            Typeface face = createFromNonAsset(am, TEST_FONT_NAME);
         }
     }
 
+    /**
+     * {@link AssetManager#openNonAsset(String)} variant of
+     * {@link Typeface#createFromAsset(AssetManager, String)}.
+     */
+    private static Typeface createFromNonAsset(AssetManager mgr, String path) {
+        Preconditions.checkNotNull(path); // for backward compatibility
+        Preconditions.checkNotNull(mgr);
+
+        Typeface typeface = new Typeface.Builder(mgr, path).build();
+        if (typeface != null) return typeface;
+        // check if the file exists, and throw an exception for backward compatibility
+        //noinspection EmptyTryBlock
+        try (InputStream inputStream = mgr.openNonAsset(path)) {
+            // Purposely empty
+        } catch (IOException e) {
+            throw new RuntimeException("Font asset not found " + path);
+        }
+
+        return Typeface.DEFAULT;
+    }
+
     @Test
     public void testCreate_fromFile() {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
@@ -90,7 +112,7 @@
             throw new RuntimeException(e);
         }
 
-        try (InputStream in = am.open(TEST_FONT_NAME);
+        try (InputStream in = am.openNonAsset(TEST_FONT_NAME);
                 OutputStream out = new FileOutputStream(outFile)) {
             byte[] buf = new byte[1024];
             int n = 0;
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index d27f5a5..be8ea9c 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -11,7 +11,7 @@
   }
 
   public class StatusBarManager {
-    method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
+    method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
   }
 
 }
diff --git a/api/system-current.txt b/api/system-current.txt
index 118184d..91be5bd 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -11652,11 +11652,11 @@
     method public int getSuggestedRetryTime();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.DataCallResponse> CREATOR;
-    field public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 2; // 0x2
-    field public static final int HANDOVER_FAILURE_MODE_LEGACY = 1; // 0x1
-    field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 3; // 0x3
-    field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 4; // 0x4
-    field public static final int HANDOVER_FAILURE_MODE_UNKNOWN = 0; // 0x0
+    field public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 1; // 0x1
+    field public static final int HANDOVER_FAILURE_MODE_LEGACY = 0; // 0x0
+    field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 2; // 0x2
+    field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 3; // 0x3
+    field public static final int HANDOVER_FAILURE_MODE_UNKNOWN = -1; // 0xffffffff
     field public static final int LINK_STATUS_ACTIVE = 2; // 0x2
     field public static final int LINK_STATUS_DORMANT = 1; // 0x1
     field public static final int LINK_STATUS_INACTIVE = 0; // 0x0
diff --git a/api/test-current.txt b/api/test-current.txt
index de67f40..9383152 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -514,7 +514,7 @@
     method public void expandNotificationsPanel();
     method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo();
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
-    method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
+    method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
   }
 
   public static final class StatusBarManager.DisableInfo {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 0a09801..4ccc7e6 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -3346,6 +3346,7 @@
     optional int32 wallpaper_id_hash = 8;
     optional int32 color_preference = 9;
     optional android.stats.style.LocationPreference location_preference = 10;
+    optional android.stats.style.DatePreference date_preference = 11;
 }
 
 /**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index cf5a7b6..e94fd45 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -187,16 +187,6 @@
     private static final String XATTR_INODE_CODE_CACHE = "user.inode_code_cache";
 
     /**
-     * Special intent extra that critical system apps can use to hide the notification for a
-     * foreground service. This extra should be placed in the intent passed into {@link
-     * #startForegroundService(Intent)}.
-     *
-     * @hide
-     */
-    private static final String EXTRA_HIDDEN_FOREGROUND_SERVICE =
-            "android.intent.extra.HIDDEN_FOREGROUND_SERVICE";
-
-    /**
      * Map from package name, to preference name, to cached preferences.
      */
     @GuardedBy("ContextImpl.class")
@@ -1717,12 +1707,9 @@
         try {
             validateServiceIntent(service);
             service.prepareToLeaveProcess(this);
-            final boolean hideForegroundNotification = requireForeground
-                    && service.getBooleanExtra(EXTRA_HIDDEN_FOREGROUND_SERVICE, false);
             ComponentName cn = ActivityManager.getService().startService(
                     mMainThread.getApplicationThread(), service,
                     service.resolveTypeIfNeeded(getContentResolver()), requireForeground,
-                    hideForegroundNotification,
                     getOpPackageName(), getAttributionTag(), user.getIdentifier());
             if (cn != null) {
                 if (cn.getPackageName().equals("!")) {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index c1e6f52..95bbebe 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -156,8 +156,7 @@
     boolean refContentProvider(in IBinder connection, int stableDelta, int unstableDelta);
     PendingIntent getRunningServiceControlPanel(in ComponentName service);
     ComponentName startService(in IApplicationThread caller, in Intent service,
-            in String resolvedType, boolean requireForeground,
-            boolean hideForegroundNotification, in String callingPackage,
+            in String resolvedType, boolean requireForeground, in String callingPackage,
             in String callingFeatureId, int userId);
     @UnsupportedAppUsage
     int stopService(in IApplicationThread caller, in Intent service,
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 4ff9e8d..99d2127 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -402,7 +402,7 @@
     @TestApi
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.STATUS_BAR)
-    public void setDisabledForSimNetworkLock(boolean disabled) {
+    public void setExpansionDisabledForSimNetworkLock(boolean disabled) {
         try {
             final int userId = Binder.getCallingUserHandle().getIdentifier();
             final IStatusBarService svc = getService();
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index c58b5d2..6d22eb9 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -688,6 +688,31 @@
                         }
                     });
                 }
+
+                /**
+                 * Callback invoked when service changed event is received
+                 * @hide
+                 */
+                @Override
+                public void onServiceChanged(String address) {
+                    if (DBG) {
+                        Log.d(TAG, "onServiceChanged() - Device=" + address);
+                    }
+
+                    if (!address.equals(mDevice.getAddress())) {
+                        return;
+                    }
+
+                    runOrQueueCallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onServiceChanged(BluetoothGatt.this);
+                            }
+                        }
+                    });
+                }
             };
 
     /*package*/ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device,
diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java
index f718c0b..9f6b828 100644
--- a/core/java/android/bluetooth/BluetoothGattCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattCallback.java
@@ -194,4 +194,17 @@
     public void onConnectionUpdated(BluetoothGatt gatt, int interval, int latency, int timeout,
             int status) {
     }
+
+    /**
+     * Callback indicating service changed event is received
+     *
+     * <p>Receiving this event means that the GATT database is out of sync with
+     * the remote device. {@link BluetoothGatt#discoverServices} should be
+     * called to re-discover the services.
+     *
+     * @param gatt GATT client involved
+     * @hide
+     */
+    public void onServiceChanged(BluetoothGatt gatt) {
+    }
 }
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index cc4c456..a6e8c13 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -1245,6 +1245,7 @@
                 return true;
             case TYPE_HINGE_ANGLE:
                 mStringType = STRING_TYPE_HINGE_ANGLE;
+                return true;
             default:
                 return false;
         }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 1db544c..78ba7f0 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -141,8 +141,8 @@
      *
      * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
      * restrictions, and apps are recommended to use resettable identifiers (see <a
-     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
-     * the following requirements is met:
+     * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+     * method can be invoked if one of the following requirements is met:
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
new file mode 100644
index 0000000..e821e31
--- /dev/null
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.VibrationAttributes;
+
+/** {@hide} */
+interface IVibratorManagerService {
+    int[] getVibratorIds();
+}
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 40c291f..91eb2a5 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -3,6 +3,7 @@
 per-file ExternalVibration.java = michaelwr@google.com
 per-file IExternalVibrationController.aidl = michaelwr@google.com
 per-file IExternalVibratorService.aidl = michaelwr@google.com
+per-file IVibratorManagerService.aidl = michaelwr@google.com
 per-file IVibratorService.aidl = michaelwr@google.com
 per-file NullVibrator.java = michaelwr@google.com
 per-file SystemVibrator.java = michaelwr@google.com
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a7055ec0..07867e2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9708,6 +9708,14 @@
         public static final String DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR =
                 "render_shadows_in_compositor";
 
+        /**
+         * If true, submit buffers using blast in SurfaceView.
+         * (0 = false, 1 = true)
+         * @hide
+         */
+        public static final String DEVELOPMENT_USE_BLAST_ADAPTER_SV =
+                "use_blast_adapter_sv";
+
        /**
         * Whether user has enabled development settings.
         */
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 5f8b5e5..c5d0a10 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -458,9 +458,11 @@
         final ArraySet<Integer> result = new ArraySet<>();
         if ((types & Type.STATUS_BARS) != 0) {
             result.add(ITYPE_STATUS_BAR);
+            result.add(ITYPE_CLIMATE_BAR);
         }
         if ((types & Type.NAVIGATION_BARS) != 0) {
             result.add(ITYPE_NAVIGATION_BAR);
+            result.add(ITYPE_EXTRA_NAVIGATION_BAR);
         }
         if ((types & Type.CAPTION_BAR) != 0) {
             result.add(ITYPE_CAPTION_BAR);
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 8cb8e1d..5b0d950 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -24,6 +24,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.res.CompatibilityInfo.Translator;
+import android.graphics.BLASTBufferQueue;
 import android.graphics.Canvas;
 import android.graphics.ColorSpace;
 import android.graphics.HardwareRenderer;
@@ -66,6 +67,8 @@
     private static native long nativeCreateFromSurfaceControl(long surfaceControlNativeObject);
     private static native long nativeGetFromSurfaceControl(long surfaceObject,
             long surfaceControlNativeObject);
+    private static native long nativeGetFromBlastBufferQueue(long surfaceObject,
+                                                             long blastBufferQueueNativeObject);
 
     private static native long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty)
             throws OutOfResourcesException;
@@ -534,6 +537,18 @@
         }
     }
 
+    private void updateNativeObject(long newNativeObject) {
+        synchronized (mLock) {
+            if (newNativeObject == mNativeObject) {
+                return;
+            }
+            if (mNativeObject != 0) {
+                nativeRelease(mNativeObject);
+            }
+            setNativeObjectLocked(newNativeObject);
+        }
+    }
+
     /**
      * Copy another surface to this one.  This surface now holds a reference
      * to the same data as the original surface, and is -not- the owner.
@@ -557,16 +572,27 @@
                     "null SurfaceControl native object. Are you using a released SurfaceControl?");
         }
         long newNativeObject = nativeGetFromSurfaceControl(mNativeObject, surfaceControlPtr);
+        updateNativeObject(newNativeObject);
+    }
 
-        synchronized (mLock) {
-            if (newNativeObject == mNativeObject) {
-                return;
-            }
-            if (mNativeObject != 0) {
-                nativeRelease(mNativeObject);
-            }
-            setNativeObjectLocked(newNativeObject);
+    /**
+     * Update the surface if the BLASTBufferQueue IGraphicBufferProducer is different from this
+     * surface's IGraphicBufferProducer.
+     *
+     * @param queue {@link BLASTBufferQueue} to copy from.
+     * @hide
+     */
+    public void copyFrom(BLASTBufferQueue queue) {
+        if (queue == null) {
+            throw new IllegalArgumentException("queue must not be null");
         }
+
+        long blastBufferQueuePtr = queue.mNativeObject;
+        if (blastBufferQueuePtr == 0) {
+            throw new NullPointerException("Null BLASTBufferQueue native object");
+        }
+        long newNativeObject = nativeGetFromBlastBufferQueue(mNativeObject, blastBufferQueuePtr);
+        updateNativeObject(newNativeObject);
     }
 
     /**
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index abda698..a8ec9ed 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -23,8 +23,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.CompatibilityInfo.Translator;
+import android.graphics.BLASTBufferQueue;
 import android.graphics.BlendMode;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -41,6 +43,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.SurfaceControl.Transaction;
@@ -232,6 +235,19 @@
 
     SurfaceControlViewHost.SurfacePackage mSurfacePackage;
 
+    /**
+     * Returns {@code true} if buffers should be submitted via blast
+     */
+    private static boolean useBlastAdapter(Context context) {
+        ContentResolver contentResolver = context.getContentResolver();
+        return Settings.Global.getInt(contentResolver,
+                Settings.Global.DEVELOPMENT_USE_BLAST_ADAPTER_SV, 0 /* default */) == 1;
+    }
+
+    private final boolean mUseBlastAdapter;
+    private SurfaceControl mBlastSurfaceControl;
+    private BLASTBufferQueue mBlastBufferQueue;
+
     public SurfaceView(Context context) {
         this(context, null);
     }
@@ -252,6 +268,7 @@
     public SurfaceView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
             int defStyleRes, boolean disableBackgroundLayer) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        mUseBlastAdapter = useBlastAdapter(context);
         mRenderNode.addPositionUpdateListener(mPositionListener);
 
         setWillNotDraw(true);
@@ -531,7 +548,7 @@
         mRequestedVisible = false;
 
         updateSurface();
-        releaseSurfaces();
+        tryReleaseSurfaces();
 
         // We don't release this as part of releaseSurfaces as
         // that is also called on transient visibility changes. We can't
@@ -875,7 +892,7 @@
         return t;
     }
 
-    private void releaseSurfaces() {
+    private void tryReleaseSurfaces() {
         mSurfaceAlpha = 1f;
 
         synchronized (mSurfaceControlLock) {
@@ -886,18 +903,30 @@
                 return;
             }
 
-            if (mSurfaceControl != null) {
-                mTmpTransaction.remove(mSurfaceControl);
-                mSurfaceControl = null;
-            }
-            if (mBackgroundControl != null) {
-                mTmpTransaction.remove(mBackgroundControl);
-                mBackgroundControl = null;
-            }
+            releaseSurfaces(mTmpTransaction);
             mTmpTransaction.apply();
         }
     }
 
+    private void releaseSurfaces(Transaction transaction) {
+        if (mSurfaceControl != null) {
+            transaction.remove(mSurfaceControl);
+            mSurfaceControl = null;
+        }
+        if (mBackgroundControl != null) {
+            transaction.remove(mBackgroundControl);
+            mBackgroundControl = null;
+        }
+        if (mBlastSurfaceControl != null) {
+            transaction.remove(mBlastSurfaceControl);
+            mBlastSurfaceControl = null;
+        }
+        if (mBlastBufferQueue != null) {
+            mBlastBufferQueue.destroy();
+            mBlastBufferQueue = null;
+        }
+    }
+
     /** @hide */
     protected void updateSurface() {
         if (!mHaveFrame) {
@@ -914,7 +943,7 @@
 
         if (viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
             notifySurfaceDestroyed();
-            releaseSurfaces();
+            tryReleaseSurfaces();
             return;
         }
 
@@ -979,45 +1008,8 @@
                 mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
 
                 if (creating) {
-                    mDeferredDestroySurfaceControl = mSurfaceControl;
-
                     updateOpaqueFlag();
-                    // SurfaceView hierarchy
-                    // ViewRootImpl surface
-                    //   - bounds layer (crops all child surfaces to parent surface insets)
-                    //     - SurfaceView surface (drawn relative to ViewRootImpl surface)
-                    //     - Background color layer (drawn behind all SurfaceView surfaces)
-                    //
-                    // The bounds layer is used to crop the surface view so it does not draw into
-                    // the parent surface inset region. Since there can be multiple surface views
-                    // below or above the parent surface, one option is to create multiple bounds
-                    // layer for each z order. The other option, the one implement is to create
-                    // a single bounds layer and set z order for each child surface relative to the
-                    // parent surface.
-                    // When creating the surface view, we parent it to the bounds layer and then
-                    // set the relative z order. When the parent surface changes, we have to
-                    // make sure to update the relative z via ViewRootImpl.SurfaceChangedCallback.
-                    final String name = "SurfaceView - " + viewRoot.getTitle().toString();
-
-                    mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
-                        .setName(name)
-                        .setLocalOwnerView(this)
-                        .setOpaque((mSurfaceFlags & SurfaceControl.OPAQUE) != 0)
-                        .setBufferSize(mSurfaceWidth, mSurfaceHeight)
-                        .setFormat(mFormat)
-                        .setParent(viewRoot.getBoundsLayer())
-                        .setFlags(mSurfaceFlags)
-                        .setCallsite("SurfaceView.updateSurface")
-                        .build();
-                    mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession)
-                        .setName("Background for -" + name)
-                        .setLocalOwnerView(this)
-                        .setOpaque(true)
-                        .setColorLayer()
-                        .setParent(mSurfaceControl)
-                        .setCallsite("SurfaceView.updateSurface")
-                        .build();
-
+                    mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot);
                 } else if (mSurfaceControl == null) {
                     return;
                 }
@@ -1090,8 +1082,7 @@
                     }
                     mTmpTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
                     if (sizeChanged && !creating) {
-                        mTmpTransaction.setBufferSize(mSurfaceControl, mSurfaceWidth,
-                                mSurfaceHeight);
+                        setBufferSize(mTmpTransaction);
                     }
 
                     mTmpTransaction.apply();
@@ -1133,19 +1124,7 @@
                         notifySurfaceDestroyed();
                     }
 
-                    if (creating) {
-                        mSurface.copyFrom(mSurfaceControl);
-                    }
-
-                    if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion
-                            < Build.VERSION_CODES.O) {
-                        // Some legacy applications use the underlying native {@link Surface} object
-                        // as a key to whether anything has changed. In these cases, updates to the
-                        // existing {@link Surface} will be ignored when the size changes.
-                        // Therefore, we must explicitly recreate the {@link Surface} in these
-                        // cases.
-                        mSurface.createFrom(mSurfaceControl);
-                    }
+                    copySurface(creating /* surfaceControlCreated */, sizeChanged);
 
                     if (visible && mSurface.isValid()) {
                         if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
@@ -1189,7 +1168,7 @@
                 } finally {
                     mIsCreating = false;
                     if (mSurfaceControl != null && !mSurfaceCreated) {
-                        releaseSurfaces();
+                        tryReleaseSurfaces();
                     }
                 }
             } catch (Exception ex) {
@@ -1202,6 +1181,119 @@
         }
     }
 
+    /**
+     * Copy the Surface from the SurfaceControl or the blast adapter.
+     *
+     * @param surfaceControlCreated true if we created the SurfaceControl and need to update our
+     *                              Surface if needed.
+     * @param bufferSizeChanged true if the BufferSize has changed and we need to recreate the
+     *                          Surface for compatibility reasons.
+     */
+    private void copySurface(boolean surfaceControlCreated, boolean bufferSizeChanged) {
+        if (surfaceControlCreated) {
+            if (mUseBlastAdapter) {
+                mSurface.copyFrom(mBlastBufferQueue);
+            } else {
+                mSurface.copyFrom(mSurfaceControl);
+            }
+        }
+
+        if (bufferSizeChanged && getContext().getApplicationInfo().targetSdkVersion
+                < Build.VERSION_CODES.O) {
+            // Some legacy applications use the underlying native {@link Surface} object
+            // as a key to whether anything has changed. In these cases, updates to the
+            // existing {@link Surface} will be ignored when the size changes.
+            // Therefore, we must explicitly recreate the {@link Surface} in these
+            // cases.
+            if (mUseBlastAdapter) {
+                mSurface.transferFrom(mBlastBufferQueue.createSurface());
+            } else {
+                mSurface.createFrom(mSurfaceControl);
+            }
+        }
+    }
+
+    private void setBufferSize(Transaction transaction) {
+        if (mUseBlastAdapter) {
+            mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth,
+                    mSurfaceHeight);
+        } else {
+            transaction.setBufferSize(mSurfaceControl, mSurfaceWidth,
+                    mSurfaceHeight);
+        }
+    }
+
+    /**
+     * Creates the surface control hierarchy as follows
+     *   ViewRootImpl surface
+     *     bounds layer (crops all child surfaces to parent surface insets)
+     *       * SurfaceView surface (drawn relative to ViewRootImpl surface)
+     *           * Blast surface (if enabled)
+     *       * Background color layer (drawn behind all SurfaceView surfaces)
+     *
+     *  The bounds layer is used to crop the surface view so it does not draw into the parent
+     *  surface inset region. Since there can be multiple surface views below or above the parent
+     *  surface, one option is to create multiple bounds layer for each z order. The other option,
+     *  the one implement is to create a single bounds layer and set z order for each child surface
+     *  relative to the parent surface.
+     *  When creating the surface view, we parent it to the bounds layer and then set the relative z
+     *  order. When the parent surface changes, we have to make sure to update the relative z via
+     *  ViewRootImpl.SurfaceChangedCallback.
+     *
+     * @return previous SurfaceControl where the content was rendered. In the surface is switched
+     * out, the old surface can be persevered until the new one has drawn by keeping the reference
+     * of the old SurfaceControl alive.
+     */
+    private SurfaceControl createSurfaceControls(ViewRootImpl viewRoot) {
+        final String name = "SurfaceView - " + viewRoot.getTitle().toString();
+
+        SurfaceControl.Builder builder = new SurfaceControl.Builder(mSurfaceSession)
+                .setName(name)
+                .setLocalOwnerView(this)
+                .setParent(viewRoot.getBoundsLayer())
+                .setCallsite("SurfaceView.updateSurface");
+
+        final SurfaceControl previousSurfaceControl;
+        if (mUseBlastAdapter) {
+            mSurfaceControl = builder
+                    .setContainerLayer()
+                    .build();
+            previousSurfaceControl = mBlastSurfaceControl;
+            mBlastSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+                    .setName(name + "(BLAST)")
+                    .setLocalOwnerView(this)
+                    .setBufferSize(mSurfaceWidth, mSurfaceHeight)
+                    .setFormat(mFormat)
+                    .setParent(mSurfaceControl)
+                    .setFlags(mSurfaceFlags)
+                    .setHidden(false)
+                    .setBLASTLayer()
+                    .setCallsite("SurfaceView.updateSurface")
+                    .build();
+            mBlastBufferQueue = new BLASTBufferQueue(
+                    mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, true /* TODO */);
+        } else {
+            previousSurfaceControl = mSurfaceControl;
+            mSurfaceControl = builder
+                    .setBufferSize(mSurfaceWidth, mSurfaceHeight)
+                    .setFlags(mSurfaceFlags)
+                    .setFormat(mFormat)
+                    .build();
+            mBlastSurfaceControl = null;
+            mBlastBufferQueue = null;
+        }
+        mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession)
+            .setName("Background for " + name)
+            .setLocalOwnerView(this)
+            .setOpaque(true)
+            .setColorLayer()
+            .setParent(mSurfaceControl)
+            .setCallsite("SurfaceView.updateSurface")
+            .build();
+
+        return previousSurfaceControl;
+    }
+
     private void onDrawFinished() {
         if (DEBUG) {
             Log.i(TAG, System.identityHashCode(this) + " "
@@ -1348,16 +1440,6 @@
             }
         }
 
-        private void releaseSurfaces(Transaction t) {
-            if (mRtReleaseSurfaces) {
-                mRtReleaseSurfaces = false;
-                t.remove(mSurfaceControl);
-                t.remove(mBackgroundControl);
-                mSurfaceControl = null;
-                mBackgroundControl = null;
-            }
-        }
-
         @Override
         public void positionLost(long frameNumber) {
             final ViewRootImpl viewRoot = getViewRootImpl();
@@ -1380,7 +1462,10 @@
                 if (useBLAST) {
                     synchronized (viewRoot.getBlastTransactionLock()) {
                         viewRoot.getBLASTSyncTransaction().hide(mSurfaceControl);
-                        releaseSurfaces(viewRoot.getBLASTSyncTransaction());
+                        if (mRtReleaseSurfaces) {
+                            mRtReleaseSurfaces = false;
+                            releaseSurfaces(viewRoot.getBLASTSyncTransaction());
+                        }
                     }
                 } else {
                     if (frameNumber > 0 && viewRoot != null && viewRoot.mSurface.isValid()) {
@@ -1388,7 +1473,10 @@
                                 viewRoot.getRenderSurfaceControl(), frameNumber);
                     }
                     mRtTransaction.hide(mSurfaceControl);
-                    releaseSurfaces(mRtTransaction);
+                    if (mRtReleaseSurfaces) {
+                        mRtReleaseSurfaces = false;
+                        releaseSurfaces(mRtTransaction);
+                    }
                     // If we aren't using BLAST, we apply the transaction locally, otherwise we let
                     // the ViewRoot apply it for us.
                     // If the ViewRoot is null, we behave as if we aren't using BLAST so we need to
@@ -1716,7 +1804,6 @@
      * @param p The SurfacePackage to embed.
      */
     public void setChildSurfacePackage(@NonNull SurfaceControlViewHost.SurfacePackage p) {
-        final SurfaceControl sc = p != null ? p.getSurfaceControl() : null;
         final SurfaceControl lastSc = mSurfacePackage != null ?
             mSurfacePackage.getSurfaceControl() : null;
         if (mSurfaceControl != null && lastSc != null) {
@@ -1733,7 +1820,14 @@
             SurfaceControlViewHost.SurfacePackage p) {
         initEmbeddedHierarchyForAccessibility(p);
         final SurfaceControl sc = p.getSurfaceControl();
-        t.reparent(sc, mSurfaceControl).show(sc);
+        final SurfaceControl parent;
+        if (mUseBlastAdapter) {
+            parent = mBlastSurfaceControl;
+        } else {
+            parent = mSurfaceControl;
+        }
+
+        t.reparent(sc, parent).show(sc);
     }
 
     /** @hide */
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index d6cf0c4..4176e88 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1810,7 +1810,7 @@
                 mBlastSurfaceControl, width, height, mEnableTripleBuffering);
             // We only return the Surface the first time, as otherwise
             // it hasn't changed and there is no need to update.
-            ret = mBlastBufferQueue.getSurface();
+            ret = mBlastBufferQueue.createSurface();
         } else {
             mBlastBufferQueue.update(mBlastSurfaceControl, width, height);
         }
@@ -2666,7 +2666,7 @@
                     surfaceSizeChanged = true;
                     mLastSurfaceSize.set(mSurfaceSize.x, mSurfaceSize.y);
                 }
-                  final boolean alwaysConsumeSystemBarsChanged =
+                final boolean alwaysConsumeSystemBarsChanged =
                         mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars;
                 updateColorModeIfNeeded(lp.getColorMode());
                 surfaceCreated = !hadSurface && mSurface.isValid();
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 4cfc205b..6a07cf7 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -32,9 +32,10 @@
 #include "android_os_Parcel.h"
 #include <binder/Parcel.h>
 
+#include <gui/BLASTBufferQueue.h>
 #include <gui/Surface.h>
-#include <gui/view/Surface.h>
 #include <gui/SurfaceControl.h>
+#include <gui/view/Surface.h>
 
 #include <ui/GraphicBuffer.h>
 #include <ui/Rect.h>
@@ -300,6 +301,26 @@
     return reinterpret_cast<jlong>(surface.get());
 }
 
+static jlong nativeGetFromBlastBufferQueue(JNIEnv* env, jclass clazz, jlong nativeObject,
+                                           jlong blastBufferQueueNativeObj) {
+    Surface* self(reinterpret_cast<Surface*>(nativeObject));
+    sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(blastBufferQueueNativeObj);
+    const sp<IGraphicBufferProducer>& bufferProducer = queue->getIGraphicBufferProducer();
+    // If the underlying IGBP's are the same, we don't need to do anything.
+    if (self != nullptr &&
+        IInterface::asBinder(self->getIGraphicBufferProducer()) ==
+                IInterface::asBinder(bufferProducer)) {
+        return nativeObject;
+    }
+
+    sp<Surface> surface(new Surface(bufferProducer, true));
+    if (surface != NULL) {
+        surface->incStrong(&sRefBaseOwner);
+    }
+
+    return reinterpret_cast<jlong>(surface.get());
+}
+
 static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz,
         jlong nativeObject, jobject parcelObj) {
     Parcel* parcel = parcelForJavaObject(env, parcelObj);
@@ -428,38 +449,31 @@
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gSurfaceMethods[] = {
-    {"nativeCreateFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)J",
-            (void*)nativeCreateFromSurfaceTexture },
-    {"nativeRelease", "(J)V",
-            (void*)nativeRelease },
-    {"nativeIsValid", "(J)Z",
-            (void*)nativeIsValid },
-    {"nativeIsConsumerRunningBehind", "(J)Z",
-            (void*)nativeIsConsumerRunningBehind },
-    {"nativeLockCanvas", "(JLandroid/graphics/Canvas;Landroid/graphics/Rect;)J",
-            (void*)nativeLockCanvas },
-    {"nativeUnlockCanvasAndPost", "(JLandroid/graphics/Canvas;)V",
-            (void*)nativeUnlockCanvasAndPost },
-    {"nativeAllocateBuffers", "(J)V",
-            (void*)nativeAllocateBuffers },
-    {"nativeCreateFromSurfaceControl", "(J)J",
-            (void*)nativeCreateFromSurfaceControl },
-    {"nativeGetFromSurfaceControl", "(JJ)J",
-            (void*)nativeGetFromSurfaceControl },
-    {"nativeReadFromParcel", "(JLandroid/os/Parcel;)J",
-            (void*)nativeReadFromParcel },
-    {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
-            (void*)nativeWriteToParcel },
-    {"nativeGetWidth", "(J)I", (void*)nativeGetWidth },
-    {"nativeGetHeight", "(J)I", (void*)nativeGetHeight },
-    {"nativeGetNextFrameNumber", "(J)J", (void*)nativeGetNextFrameNumber },
-    {"nativeSetScalingMode", "(JI)I", (void*)nativeSetScalingMode },
-    {"nativeForceScopedDisconnect", "(J)I", (void*)nativeForceScopedDisconnect},
-    {"nativeAttachAndQueueBufferWithColorSpace", "(JLandroid/hardware/HardwareBuffer;I)I",
-            (void*)nativeAttachAndQueueBufferWithColorSpace},
-    {"nativeSetSharedBufferModeEnabled", "(JZ)I", (void*)nativeSetSharedBufferModeEnabled},
-    {"nativeSetAutoRefreshEnabled", "(JZ)I", (void*)nativeSetAutoRefreshEnabled},
-    {"nativeSetFrameRate", "(JFI)I", (void*)nativeSetFrameRate},
+        {"nativeCreateFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)J",
+         (void*)nativeCreateFromSurfaceTexture},
+        {"nativeRelease", "(J)V", (void*)nativeRelease},
+        {"nativeIsValid", "(J)Z", (void*)nativeIsValid},
+        {"nativeIsConsumerRunningBehind", "(J)Z", (void*)nativeIsConsumerRunningBehind},
+        {"nativeLockCanvas", "(JLandroid/graphics/Canvas;Landroid/graphics/Rect;)J",
+         (void*)nativeLockCanvas},
+        {"nativeUnlockCanvasAndPost", "(JLandroid/graphics/Canvas;)V",
+         (void*)nativeUnlockCanvasAndPost},
+        {"nativeAllocateBuffers", "(J)V", (void*)nativeAllocateBuffers},
+        {"nativeCreateFromSurfaceControl", "(J)J", (void*)nativeCreateFromSurfaceControl},
+        {"nativeGetFromSurfaceControl", "(JJ)J", (void*)nativeGetFromSurfaceControl},
+        {"nativeReadFromParcel", "(JLandroid/os/Parcel;)J", (void*)nativeReadFromParcel},
+        {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+        {"nativeGetWidth", "(J)I", (void*)nativeGetWidth},
+        {"nativeGetHeight", "(J)I", (void*)nativeGetHeight},
+        {"nativeGetNextFrameNumber", "(J)J", (void*)nativeGetNextFrameNumber},
+        {"nativeSetScalingMode", "(JI)I", (void*)nativeSetScalingMode},
+        {"nativeForceScopedDisconnect", "(J)I", (void*)nativeForceScopedDisconnect},
+        {"nativeAttachAndQueueBufferWithColorSpace", "(JLandroid/hardware/HardwareBuffer;I)I",
+         (void*)nativeAttachAndQueueBufferWithColorSpace},
+        {"nativeSetSharedBufferModeEnabled", "(JZ)I", (void*)nativeSetSharedBufferModeEnabled},
+        {"nativeSetAutoRefreshEnabled", "(JZ)I", (void*)nativeSetAutoRefreshEnabled},
+        {"nativeSetFrameRate", "(JFI)I", (void*)nativeSetFrameRate},
+        {"nativeGetFromBlastBufferQueue", "(JJ)J", (void*)nativeGetFromBlastBufferQueue},
 };
 
 int register_android_view_Surface(JNIEnv* env)
diff --git a/core/proto/android/stats/style/style_enums.proto b/core/proto/android/stats/style/style_enums.proto
index f3f491f..828e412 100644
--- a/core/proto/android/stats/style/style_enums.proto
+++ b/core/proto/android/stats/style/style_enums.proto
@@ -38,6 +38,9 @@
     LIVE_WALLPAPER_APPLIED = 16;
     LIVE_WALLPAPER_INFO_SELECT = 17;
     LIVE_WALLPAPER_CUSTOMIZE_SELECT = 18;
+    LIVE_WALLPAPER_QUESTIONNAIRE_SELECT = 19;
+    LIVE_WALLPAPER_QUESTIONNAIRE_APPLIED = 20;
+    LIVE_WALLPAPER_EFFECT_SHOW = 21;
 }
 
 enum LocationPreference {
@@ -46,3 +49,9 @@
     LOCATION_CURRENT = 2;
     LOCATION_MANUAL = 3;
 }
+
+enum DatePreference {
+    DATE_PREFERENCE_UNSPECIFIED = 0;
+    DATE_UNAVAILABLE = 1;
+    DATE_MANUAL = 2;
+}
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index afab769..8a000a0 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -124,6 +124,22 @@
     }
 
     @Test
+    public void testCalculateInsets_extraNavRightClimateTop() throws Exception {
+        mState.getSource(ITYPE_CLIMATE_BAR).setFrame(new Rect(0, 0, 100, 100));
+        mState.getSource(ITYPE_CLIMATE_BAR).setVisible(true);
+        mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
+        mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setVisible(true);
+        WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
+                false, DisplayCutout.NO_CUTOUT, 0, 0, 0, TYPE_APPLICATION,
+                WINDOWING_MODE_UNDEFINED, null);
+        // ITYPE_CLIMATE_BAR is a type of status bar and ITYPE_EXTRA_NAVIGATION_BAR is a type
+        // of navigation bar.
+        assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
+        assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars()));
+        assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.navigationBars()));
+    }
+
+    @Test
     public void testCalculateInsets_imeIgnoredWithoutAdjustResize() {
         mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
         mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
@@ -336,6 +352,8 @@
     public void testGetDefaultVisibility() {
         assertTrue(InsetsState.getDefaultVisibility(ITYPE_STATUS_BAR));
         assertTrue(InsetsState.getDefaultVisibility(ITYPE_NAVIGATION_BAR));
+        assertTrue(InsetsState.getDefaultVisibility(ITYPE_CLIMATE_BAR));
+        assertTrue(InsetsState.getDefaultVisibility(ITYPE_EXTRA_NAVIGATION_BAR));
         assertTrue(InsetsState.getDefaultVisibility(ITYPE_CAPTION_BAR));
         assertFalse(InsetsState.getDefaultVisibility(ITYPE_IME));
     }
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
index 5c41087..92fb528 100644
--- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
@@ -17,16 +17,23 @@
 package android.view.inputmethod;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyInt;
 
 import android.annotation.Nullable;
+import android.graphics.BlurMaskFilter;
 import android.os.Parcel;
 import android.os.UserHandle;
+import android.text.Spannable;
+import android.text.SpannableString;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
+import android.text.style.MaskFilterSpan;
+import android.text.style.UnderlineSpan;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -280,6 +287,55 @@
         editorInfo.getInitialTextBeforeCursor(/* length= */ 60, /* flags= */ 1);
     }
 
+    @Test
+    public void testSpanAfterSurroundingTextRetrieval() {
+        final int flags = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE;
+        final SpannableStringBuilder sb =
+                new SpannableStringBuilder("ParcelableSpan and non-ParcelableSpan test");
+        final int parcelableStart = 0;
+        final int parcelableEnd = 14;
+        final int nonParcelableStart = 19;
+        final int nonParcelableEnd = 37;
+        final UnderlineSpan parcelableSpan = new UnderlineSpan();
+        final MaskFilterSpan nonParcelableSpan =
+                new MaskFilterSpan(new BlurMaskFilter(5f, BlurMaskFilter.Blur.NORMAL));
+
+        // Set spans
+        sb.setSpan(parcelableSpan, parcelableStart, parcelableEnd, flags);
+        sb.setSpan(nonParcelableSpan, nonParcelableStart, nonParcelableEnd, flags);
+
+        Object[] spansBefore = sb.getSpans(/* queryStart= */ 0, sb.length(), Object.class);
+        Object[] parcelableSpanBefore = sb.getSpans(parcelableStart, parcelableEnd, Object.class);
+
+        // Verify the original spans length is 2, include ParcelableSpan and non-ParcelableSpan.
+        assertNotNull(spansBefore);
+        assertEquals(2, spansBefore.length);
+
+        // Set initial surrounding text then retrieve the text.
+        EditorInfo editorInfo = new EditorInfo();
+        editorInfo.initialSelStart = sb.length();
+        editorInfo.initialSelEnd = sb.length();
+        editorInfo.inputType = EditorInfo.TYPE_CLASS_TEXT;
+        editorInfo.setInitialSurroundingText(sb);
+        SpannableString textBeforeCursor =
+                (SpannableString) editorInfo.getInitialTextBeforeCursor(
+                        /* length= */ 60, /* flags= */ 1);
+
+        Object[] spansAfter =
+                textBeforeCursor.getSpans(/* queryStart= */ 0, sb.length(), Object.class);
+        Object[] parcelableSpanAfter =
+                textBeforeCursor.getSpans(parcelableStart, parcelableEnd, Object.class);
+        Object[] nonParcelableSpanAfter =
+                textBeforeCursor.getSpans(nonParcelableStart, nonParcelableEnd, Object.class);
+
+        // Verify only remain ParcelableSpan and it's different from the original Span instance.
+        assertNotNull(spansAfter);
+        assertEquals(1, spansAfter.length);
+        assertEquals(1, parcelableSpanAfter.length);
+        assertEquals(0, nonParcelableSpanAfter.length);
+        assertNotEquals(parcelableSpanBefore, parcelableSpanAfter);
+    }
+
     private static void assertExpectedTextLength(EditorInfo editorInfo,
             @Nullable Integer expectBeforeCursorLength, @Nullable Integer expectSelectionLength,
             @Nullable Integer expectAfterCursorLength) {
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 102c933..2779a75 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -168,6 +168,7 @@
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.MANAGE_SUBSCRIPTION_PLANS" />
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.PACKAGE_USAGE_STATS"/>
         <permission name="android.permission.PERFORM_CDMA_PROVISIONING"/>
@@ -440,6 +441,13 @@
         <permission name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/>
     </privapp-permissions>
 
+    <privapp-permissions package="com.android.traceur">
+        <!-- Permissions required to receive BUGREPORT_STARTED intent -->
+        <permission name="android.permission.DUMP"/>
+        <!-- Permissions required for quick settings tile -->
+        <permission name="android.permission.STATUS_BAR"/>
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.tv">
         <permission name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/>
         <permission name="android.permission.DVB_DEVICE"/>
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index 3a3bea4..ee5c5b0 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -17,9 +17,8 @@
     src: "DroidSansMono.ttf",
     required: [
         // Roboto-Regular.ttf provides DroidSans.ttf as a symlink to itself
+        // Roboto-Regular.ttf provides DroidSans-Bold.ttf as a symlink to itself
         "Roboto-Regular.ttf",
-        // Roboto-Bold.ttf provides DroidSans-Bold.ttf as a symlink to itself
-        "Roboto-Bold.ttf",
     ],
 }
 
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index c992bd8..0504cdc 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -18,23 +18,104 @@
     prefer the former when an 800 weight is requested. Since bold spans
     effectively add 300 to the weight, this ensures that 900 is the bold
     paired with the 500 weight, ensuring adequate contrast.
+
+    TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
 -->
 <familyset version="23">
     <!-- first font is default -->
     <family name="sans-serif">
-        <font weight="100" style="normal">Roboto-Thin.ttf</font>
-        <font weight="100" style="italic">Roboto-ThinItalic.ttf</font>
-        <font weight="300" style="normal">Roboto-Light.ttf</font>
-        <font weight="300" style="italic">Roboto-LightItalic.ttf</font>
-        <font weight="400" style="normal">Roboto-Regular.ttf</font>
-        <font weight="400" style="italic">Roboto-Italic.ttf</font>
-        <font weight="500" style="normal">Roboto-Medium.ttf</font>
-        <font weight="500" style="italic">Roboto-MediumItalic.ttf</font>
-        <font weight="900" style="normal">Roboto-Black.ttf</font>
-        <font weight="900" style="italic">Roboto-BlackItalic.ttf</font>
-        <font weight="700" style="normal">Roboto-Bold.ttf</font>
-        <font weight="700" style="italic">Roboto-BoldItalic.ttf</font>
-    </family>
+        <font weight="100" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="100" />
+        </font>
+        <font weight="200" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="200" />
+        </font>
+        <font weight="300" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="300" />
+        </font>
+        <font weight="400" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="400" />
+        </font>
+        <font weight="500" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="500" />
+        </font>
+        <font weight="600" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="600" />
+        </font>
+        <font weight="700" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="700" />
+        </font>
+        <font weight="800" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="800" />
+        </font>
+        <font weight="900" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="900" />
+        </font>
+        <font weight="100" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="100" />
+        </font>
+        <font weight="200" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="200" />
+        </font>
+        <font weight="300" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="300" />
+        </font>
+        <font weight="400" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="400" />
+        </font>
+        <font weight="500" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="500" />
+        </font>
+        <font weight="600" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="600" />
+        </font>
+        <font weight="700" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="700" />
+        </font>
+        <font weight="800" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="800" />
+        </font>
+        <font weight="900" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="900" />
+        </font>
+   </family>
+
 
     <!-- Note that aliases must come after the fonts they reference. -->
     <alias name="sans-serif-thin" to="sans-serif" weight="100" />
@@ -47,14 +128,96 @@
     <alias name="verdana" to="sans-serif" />
 
     <family name="sans-serif-condensed">
-        <font weight="300" style="normal">RobotoCondensed-Light.ttf</font>
-        <font weight="300" style="italic">RobotoCondensed-LightItalic.ttf</font>
-        <font weight="400" style="normal">RobotoCondensed-Regular.ttf</font>
-        <font weight="400" style="italic">RobotoCondensed-Italic.ttf</font>
-        <font weight="500" style="normal">RobotoCondensed-Medium.ttf</font>
-        <font weight="500" style="italic">RobotoCondensed-MediumItalic.ttf</font>
-        <font weight="700" style="normal">RobotoCondensed-Bold.ttf</font>
-        <font weight="700" style="italic">RobotoCondensed-BoldItalic.ttf</font>
+      <font weight="100" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="100" />
+      </font>
+      <font weight="200" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="200" />
+      </font>
+      <font weight="300" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="300" />
+      </font>
+      <font weight="400" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="400" />
+      </font>
+      <font weight="500" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="500" />
+      </font>
+      <font weight="600" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="600" />
+      </font>
+      <font weight="700" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="700" />
+      </font>
+      <font weight="800" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="800" />
+      </font>
+      <font weight="900" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="900" />
+      </font>
+      <font weight="100" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="100" />
+      </font>
+      <font weight="200" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="200" />
+      </font>
+      <font weight="300" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="300" />
+      </font>
+      <font weight="400" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="400" />
+      </font>
+      <font weight="500" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="500" />
+      </font>
+      <font weight="600" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="600" />
+      </font>
+      <font weight="700" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="700" />
+      </font>
+      <font weight="800" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="800" />
+      </font>
+      <font weight="900" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="900" />
+      </font>
     </family>
     <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
     <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 4c7e960..4cd55e8 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -24,7 +24,7 @@
  */
 public final class BLASTBufferQueue {
     // Note: This field is accessed by native code.
-    private long mNativeObject; // BLASTBufferQueue*
+    public long mNativeObject; // BLASTBufferQueue*
 
     private static native long nativeCreate(long surfaceControl, long width, long height,
             boolean tripleBufferingEnabled);
@@ -41,9 +41,13 @@
 
     public void destroy() {
         nativeDestroy(mNativeObject);
+        mNativeObject = 0;
     }
 
-    public Surface getSurface() {
+    /**
+     * @return a new Surface instance from the IGraphicsBufferProducer of the adapter.
+     */
+    public Surface createSurface() {
         return nativeGetSurface(mNativeObject);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java
index 00146e9..edbbd69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java
@@ -1319,10 +1319,6 @@
                 mBackground.getRight(), mBackground.getBottom(), Op.UNION);
     }
 
-    void onDockedFirstAnimationFrame() {
-        saveSnapTargetBeforeMinimized(mSplitLayout.getSnapAlgorithm().getMiddleTarget());
-    }
-
     void onDockedTopTask() {
         mState.animateAfterRecentsDrawn = true;
         startDragging(false /* animate */, false /* touching */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 184342f..58106c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -68,9 +68,6 @@
     /** Called when there's a task undocking. */
     void onUndockingTask();
 
-    /** Called when the first docked animation frame rendered. */
-    void onDockedFirstAnimationFrame();
-
     /** Called when top task docked. */
     void onDockedTopTask();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index eed5092..c8be937 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -426,13 +426,6 @@
         }
     }
 
-    /** Called when the first docked animation frame rendered. */
-    public void onDockedFirstAnimationFrame() {
-        if (mView != null) {
-            mView.onDockedFirstAnimationFrame();
-        }
-    }
-
     /** Called when top task docked. */
     public void onDockedTopTask() {
         if (mView != null) {
@@ -502,6 +495,9 @@
         mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, true /* dismissOrMaximize */);
         updateVisibility(false /* visible */);
         mMinimized = false;
+        // Resets divider bar position to undefined, so new divider bar will apply default position
+        // next time entering split mode.
+        mDividerState.mRatioPositionBeforeMinimized = 0;
         removeDivider();
         mImePositionProcessor.reset();
     }
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 5f6b53a..b802908 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -21,6 +21,7 @@
 #include <log/log.h>
 
 #include <minikin/MeasuredText.h>
+#include <minikin/Measurement.h>
 #include "Paint.h"
 #include "SkPathMeasure.h"
 #include "Typeface.h"
@@ -69,6 +70,18 @@
     }
 }
 
+void MinikinUtils::getBounds(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface,
+                             const uint16_t* buf, size_t bufSize, minikin::MinikinRect* out) {
+    minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
+
+    const minikin::U16StringPiece textBuf(buf, bufSize);
+    const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
+    const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
+
+    minikin::getBounds(textBuf, minikin::Range(0, textBuf.size()), bidiFlags, minikinPaint,
+        startHyphen, endHyphen, out);
+}
+
 float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
                                 const Typeface* typeface, const uint16_t* buf, size_t start,
                                 size_t count, size_t bufSize, float* advances) {
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index 0eacde9..6cde9c5 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -48,6 +48,9 @@
                                                 size_t contextStart, size_t contextCount,
                                                 minikin::MeasuredText* mt);
 
+    static void getBounds(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface,
+                          const uint16_t* buf, size_t bufSize, minikin::MinikinRect* out);
+
     static float measureText(const Paint* paint, minikin::Bidi bidiFlags,
                                          const Typeface* typeface, const uint16_t* buf,
                                          size_t start, size_t count, size_t bufSize,
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index d275659..f6c8496 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -342,18 +342,13 @@
     }
 
     static void doTextBounds(JNIEnv* env, const jchar* text, int count, jobject bounds,
-            const Paint& paint, const Typeface* typeface, jint bidiFlags) {
+            const Paint& paint, const Typeface* typeface, jint bidiFlagsInt) {
         SkRect  r;
         SkIRect ir;
 
-        minikin::Layout layout = MinikinUtils::doLayout(&paint,
-                static_cast<minikin::Bidi>(bidiFlags), typeface,
-                text, count,  // text buffer
-                0, count,  // draw range
-                0, count,  // context range
-                nullptr);
         minikin::MinikinRect rect;
-        layout.getBounds(&rect);
+        minikin::Bidi bidiFlags = static_cast<minikin::Bidi>(bidiFlagsInt);
+        MinikinUtils::getBounds(&paint, bidiFlags, typeface, text, count, &rect);
         r.fLeft = rect.mLeft;
         r.fTop = rect.mTop;
         r.fRight = rect.mRight;
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 1a09b1c..b5baafd 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -31,10 +31,12 @@
 
 namespace {
 
-constexpr char kRobotoRegular[] = "/system/fonts/Roboto-Regular.ttf";
-constexpr char kRobotoBold[] = "/system/fonts/Roboto-Bold.ttf";
-constexpr char kRobotoItalic[] = "/system/fonts/Roboto-Italic.ttf";
-constexpr char kRobotoBoldItalic[] = "/system/fonts/Roboto-BoldItalic.ttf";
+constexpr char kRobotoVariable[] = "/system/fonts/Roboto-Regular.ttf";
+
+constexpr char kRegularFont[] = "/system/fonts/NotoSerif-Regular.ttf";
+constexpr char kBoldFont[] = "/system/fonts/NotoSerif-Bold.ttf";
+constexpr char kItalicFont[] = "/system/fonts/NotoSerif-Italic.ttf";
+constexpr char kBoldItalicFont[] = "/system/fonts/NotoSerif-BoldItalic.ttf";
 
 void unmap(const void* ptr, void* context) {
     void* p = const_cast<void*>(ptr);
@@ -68,7 +70,7 @@
 
 TEST(TypefaceTest, resolveDefault_and_setDefaultTest) {
     std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
-            makeSingleFamlyVector(kRobotoRegular), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+            makeSingleFamlyVector(kRobotoVariable), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
     EXPECT_EQ(regular.get(), Typeface::resolveDefault(regular.get()));
 
     // Keep the original to restore it later.
@@ -347,71 +349,71 @@
     // In Java, new
     // Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(false).build();
     std::unique_ptr<Typeface> regular(
-            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoRegular), 400, false));
+            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, false));
     EXPECT_EQ(400, regular->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
     EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
 
     // In Java, new
-    // Typeface.Builder("Roboto-Bold.ttf").setWeight(700).setItalic(false).build();
+    // Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(false).build();
     std::unique_ptr<Typeface> bold(
-            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBold), 700, false));
+            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, false));
     EXPECT_EQ(700, bold->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, new
-    // Typeface.Builder("Roboto-Italic.ttf").setWeight(400).setItalic(true).build();
+    // Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(true).build();
     std::unique_ptr<Typeface> italic(
-            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoItalic), 400, true));
+            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, true));
     EXPECT_EQ(400, italic->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
     // new
-    // Typeface.Builder("Roboto-BoldItalic.ttf").setWeight(700).setItalic(true).build();
+    // Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(true).build();
     std::unique_ptr<Typeface> boldItalic(
-            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBoldItalic), 700, true));
+            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, true));
     EXPECT_EQ(700, boldItalic->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
     // new
-    // Typeface.Builder("Roboto-BoldItalic.ttf").setWeight(1100).setItalic(false).build();
+    // Typeface.Builder("Roboto-Regular.ttf").setWeight(1100).setItalic(false).build();
     std::unique_ptr<Typeface> over1000(
-            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBold), 1100, false));
+            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 1100, false));
     EXPECT_EQ(1000, over1000->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
 }
 
 TEST(TypefaceTest, createFromFamilies_Single_resolveByTable) {
-    // In Java, new Typeface.Builder("Roboto-Regular.ttf").build();
+    // In Java, new Typeface.Builder("Family-Regular.ttf").build();
     std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
-            makeSingleFamlyVector(kRobotoRegular), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+            makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
     EXPECT_EQ(400, regular->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
     EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
 
-    // In Java, new Typeface.Builder("Roboto-Bold.ttf").build();
+    // In Java, new Typeface.Builder("Family-Bold.ttf").build();
     std::unique_ptr<Typeface> bold(Typeface::createFromFamilies(
-            makeSingleFamlyVector(kRobotoBold), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+            makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
     EXPECT_EQ(700, bold->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
-    // In Java, new Typeface.Builder("Roboto-Italic.ttf").build();
+    // In Java, new Typeface.Builder("Family-Italic.ttf").build();
     std::unique_ptr<Typeface> italic(Typeface::createFromFamilies(
-            makeSingleFamlyVector(kRobotoItalic), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+            makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
     EXPECT_EQ(400, italic->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
-    // In Java, new Typeface.Builder("Roboto-BoldItalic.ttf").build();
+    // In Java, new Typeface.Builder("Family-BoldItalic.ttf").build();
     std::unique_ptr<Typeface> boldItalic(
-            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBoldItalic),
+            Typeface::createFromFamilies(makeSingleFamlyVector(kBoldItalicFont),
                                          RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
     EXPECT_EQ(700, boldItalic->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
@@ -420,8 +422,8 @@
 
 TEST(TypefaceTest, createFromFamilies_Family) {
     std::vector<std::shared_ptr<minikin::FontFamily>> families = {
-            buildFamily(kRobotoRegular), buildFamily(kRobotoBold), buildFamily(kRobotoItalic),
-            buildFamily(kRobotoBoldItalic)};
+            buildFamily(kRegularFont), buildFamily(kBoldFont), buildFamily(kItalicFont),
+            buildFamily(kBoldItalicFont)};
     std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
             std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
     EXPECT_EQ(400, typeface->fStyle.weight());
@@ -430,7 +432,7 @@
 
 TEST(TypefaceTest, createFromFamilies_Family_withoutRegular) {
     std::vector<std::shared_ptr<minikin::FontFamily>> families = {
-            buildFamily(kRobotoBold), buildFamily(kRobotoItalic), buildFamily(kRobotoBoldItalic)};
+            buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)};
     std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
             std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
     EXPECT_EQ(700, typeface->fStyle.weight());
diff --git a/location/java/android/location/timezone/LocationTimeZoneEvent.java b/location/java/android/location/timezone/LocationTimeZoneEvent.java
index 55bc507..d3fd5c3 100644
--- a/location/java/android/location/timezone/LocationTimeZoneEvent.java
+++ b/location/java/android/location/timezone/LocationTimeZoneEvent.java
@@ -23,6 +23,8 @@
 import android.os.Parcelable;
 import android.os.UserHandle;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -37,7 +39,7 @@
 
     @IntDef({ EVENT_TYPE_UNKNOWN, EVENT_TYPE_PERMANENT_FAILURE, EVENT_TYPE_SUCCESS,
             EVENT_TYPE_UNCERTAIN })
-    @interface EventType {}
+    public @interface EventType {}
 
     /** Uninitialized value for {@link #mEventType} - must not be used for real events. */
     private static final int EVENT_TYPE_UNKNOWN = 0;
@@ -49,8 +51,8 @@
     public static final int EVENT_TYPE_PERMANENT_FAILURE = 1;
 
     /**
-     * Indicates a successful geolocation time zone detection event. {@link #mTimeZoneIds} will be
-     * non-null but can legitimately be empty, e.g. for disputed areas, oceans.
+     * Indicates a successful geolocation time zone detection event. {@link #getTimeZoneIds()} will
+     * be non-null but can legitimately be empty, e.g. for disputed areas, oceans.
      */
     public static final int EVENT_TYPE_SUCCESS = 2;
 
@@ -81,10 +83,7 @@
         mTimeZoneIds = immutableList(timeZoneIds);
 
         boolean emptyTimeZoneIdListExpected = eventType != EVENT_TYPE_SUCCESS;
-        if (emptyTimeZoneIdListExpected && !timeZoneIds.isEmpty()) {
-            throw new IllegalStateException(
-                    "timeZoneIds must only have values when eventType is success");
-        }
+        Preconditions.checkState(!emptyTimeZoneIdListExpected || timeZoneIds.isEmpty());
 
         mElapsedRealtimeNanos = elapsedRealtimeNanos;
     }
@@ -102,9 +101,7 @@
      *
      * <p>This value can be reliably compared to {@link
      * android.os.SystemClock#elapsedRealtimeNanos}, to calculate the age of a fix and to compare
-     * {@link LocationTimeZoneEvent} fixes. This is reliable because elapsed real-time is guaranteed
-     * monotonic for each system boot and continues to increment even when the system is in deep
-     * sleep.
+     * {@link LocationTimeZoneEvent} instances.
      *
      * @return elapsed real-time of fix, in nanoseconds since system boot.
      */
diff --git a/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java b/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java
index 2a37ef8..5c9d290 100644
--- a/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java
+++ b/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java
@@ -30,7 +30,9 @@
 public final class LocationTimeZoneProviderRequest implements Parcelable {
 
     public static final LocationTimeZoneProviderRequest EMPTY_REQUEST =
-            new LocationTimeZoneProviderRequest(false);
+            new LocationTimeZoneProviderRequest(
+                    false /* reportLocationTimeZone */,
+                    0 /* initializationTimeoutMillis */);
 
     public static final Creator<LocationTimeZoneProviderRequest> CREATOR =
             new Creator<LocationTimeZoneProviderRequest>() {
@@ -45,17 +47,36 @@
                 }
             };
 
-    /** Location time zone reporting is requested (true) */
     private final boolean mReportLocationTimeZone;
 
-    private LocationTimeZoneProviderRequest(boolean reportLocationTimeZone) {
+    private final long mInitializationTimeoutMillis;
+
+    private LocationTimeZoneProviderRequest(
+            boolean reportLocationTimeZone, long initializationTimeoutMillis) {
         mReportLocationTimeZone = reportLocationTimeZone;
+        mInitializationTimeoutMillis = initializationTimeoutMillis;
     }
 
+    /**
+     * Returns {@code true} if the provider should report events related to the device's current
+     * time zone, {@code false} otherwise.
+     */
     public boolean getReportLocationTimeZone() {
         return mReportLocationTimeZone;
     }
 
+    // TODO(b/152744911) - once there are a couple of implementations, decide whether this needs to
+    //  be passed to the LocationTimeZoneProvider and remove if it is not useful.
+    /**
+     * Returns the maximum time that the provider is allowed to initialize before it is expected to
+     * send an event of any sort. Only valid when {@link #getReportLocationTimeZone()} is {@code
+     * true}. Failure to send an event in this time (with some fuzz) may be interpreted as if the
+     * provider is uncertain of the time zone, and/or it could lead to the provider being disabled.
+     */
+    public long getInitializationTimeoutMillis() {
+        return mInitializationTimeoutMillis;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -63,14 +84,15 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeInt(mReportLocationTimeZone ? 1 : 0);
+        parcel.writeBoolean(mReportLocationTimeZone);
+        parcel.writeLong(mInitializationTimeoutMillis);
     }
 
     static LocationTimeZoneProviderRequest createFromParcel(Parcel in) {
-        ClassLoader classLoader = LocationTimeZoneProviderRequest.class.getClassLoader();
-        return new Builder()
-                .setReportLocationTimeZone(in.readInt() == 1)
-                .build();
+        boolean reportLocationTimeZone = in.readBoolean();
+        long initializationTimeoutMillis = in.readLong();
+        return new LocationTimeZoneProviderRequest(
+                reportLocationTimeZone, initializationTimeoutMillis);
     }
 
     @Override
@@ -82,31 +104,28 @@
             return false;
         }
         LocationTimeZoneProviderRequest that = (LocationTimeZoneProviderRequest) o;
-        return mReportLocationTimeZone == that.mReportLocationTimeZone;
+        return mReportLocationTimeZone == that.mReportLocationTimeZone
+            && mInitializationTimeoutMillis == that.mInitializationTimeoutMillis;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mReportLocationTimeZone);
+        return Objects.hash(mReportLocationTimeZone, mInitializationTimeoutMillis);
     }
 
     @Override
     public String toString() {
-        StringBuilder s = new StringBuilder();
-        s.append("TimeZoneProviderRequest[");
-        if (mReportLocationTimeZone) {
-            s.append("ON");
-        } else {
-            s.append("OFF");
-        }
-        s.append(']');
-        return s.toString();
+        return "LocationTimeZoneProviderRequest{"
+                + "mReportLocationTimeZone=" + mReportLocationTimeZone
+                + ", mInitializationTimeoutMillis=" + mInitializationTimeoutMillis
+                + "}";
     }
 
     /** @hide */
     public static final class Builder {
 
         private boolean mReportLocationTimeZone;
+        private long mInitializationTimeoutMillis;
 
         /**
          * Sets the property that enables / disables the provider. This is set to {@code false} by
@@ -117,10 +136,20 @@
             return this;
         }
 
+        /**
+         * Sets the initialization timeout. See {@link
+         * LocationTimeZoneProviderRequest#getInitializationTimeoutMillis()} for details.
+         */
+        public Builder setInitializationTimeoutMillis(long timeoutMillis) {
+            mInitializationTimeoutMillis = timeoutMillis;
+            return this;
+        }
+
         /** Builds the {@link LocationTimeZoneProviderRequest} instance. */
         @NonNull
         public LocationTimeZoneProviderRequest build() {
-            return new LocationTimeZoneProviderRequest(this.mReportLocationTimeZone);
+            return new LocationTimeZoneProviderRequest(
+                    mReportLocationTimeZone, mInitializationTimeoutMillis);
         }
     }
 }
diff --git a/location/lib/Android.bp b/location/lib/Android.bp
index cd45e8e..c0188c0 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -20,5 +20,8 @@
     libs: [
         "androidx.annotation_annotation",
     ],
-    api_packages: ["com.android.location.provider"],
+    api_packages: [
+        "com.android.location.provider",
+        "com.android.location.timezone.provider",
+    ],
 }
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java
new file mode 100644
index 0000000..3675574
--- /dev/null
+++ b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.location.timezone.provider;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.location.timezone.LocationTimeZoneEvent;
+import android.os.SystemClock;
+import android.os.UserHandle;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * An event from a {@link LocationTimeZoneProviderBase} sent while determining a device's time zone
+ * using its location.
+ *
+ * @hide
+ */
+public final class LocationTimeZoneEventUnbundled {
+
+    @IntDef({ EVENT_TYPE_PERMANENT_FAILURE, EVENT_TYPE_SUCCESS, EVENT_TYPE_UNCERTAIN })
+    @interface EventType {}
+
+    /**
+     * Indicates there was a permanent failure. This is not generally expected, and probably means a
+     * required backend service has been turned down, or the client is unreasonably old.
+     */
+    public static final int EVENT_TYPE_PERMANENT_FAILURE =
+            LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE;
+
+    /**
+     * Indicates a successful geolocation time zone detection event. {@link #getTimeZoneIds()} will
+     * be non-null but can legitimately be empty, e.g. for disputed areas, oceans.
+     */
+    public static final int EVENT_TYPE_SUCCESS = LocationTimeZoneEvent.EVENT_TYPE_SUCCESS;
+
+    /**
+     * Indicates the time zone is not known because of an expected runtime state or error, e.g. when
+     * the provider is unable to detect location, or there was a problem when resolving the location
+     * to a time zone.
+     */
+    public static final int EVENT_TYPE_UNCERTAIN = LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN;
+
+    @NonNull
+    private final LocationTimeZoneEvent mDelegate;
+
+    private LocationTimeZoneEventUnbundled(@NonNull LocationTimeZoneEvent delegate) {
+        mDelegate = Objects.requireNonNull(delegate);
+    }
+
+    /**
+     * Returns the event type.
+     */
+    @Nullable
+    public @EventType int getEventType() {
+        return mDelegate.getEventType();
+    }
+
+    /**
+     * Gets the time zone IDs of this event. Contains zero or more IDs for a successful lookup.
+     * The value is undefined for an unsuccessful lookup. See also {@link #getEventType()}.
+     */
+    @NonNull
+    public List<String> getTimeZoneIds() {
+        return mDelegate.getTimeZoneIds();
+    }
+
+    /**
+     * Returns the information from this as a {@link LocationTimeZoneEvent}.
+     * @hide
+     */
+    @NonNull
+    public LocationTimeZoneEvent getInternalLocationTimeZoneEvent() {
+        return mDelegate;
+    }
+
+    @Override
+    public String toString() {
+        return "LocationTimeZoneEventUnbundled{"
+                + "mDelegate=" + mDelegate
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        LocationTimeZoneEventUnbundled that = (LocationTimeZoneEventUnbundled) o;
+        return mDelegate.equals(that.mDelegate);
+    }
+
+    @Override
+    public int hashCode() {
+        return mDelegate.hashCode();
+    }
+
+    /**
+     * A builder of {@link LocationTimeZoneEventUnbundled} instances.
+     *
+     * @hide
+     */
+    public static final class Builder {
+
+        private @EventType int mEventType;
+        private @NonNull List<String> mTimeZoneIds = Collections.emptyList();
+
+        /**
+         * Set the time zone ID of this event.
+         */
+        @NonNull
+        public Builder setEventType(@EventType int eventType) {
+            checkValidEventType(eventType);
+            mEventType = eventType;
+            return this;
+        }
+
+        /**
+         * Sets the time zone IDs of this event.
+         */
+        @NonNull
+        public Builder setTimeZoneIds(@NonNull List<String> timeZoneIds) {
+            mTimeZoneIds = Objects.requireNonNull(timeZoneIds);
+            return this;
+        }
+
+        /**
+         * Builds a {@link LocationTimeZoneEventUnbundled} instance.
+         */
+        @NonNull
+        public LocationTimeZoneEventUnbundled build() {
+            final int internalEventType = this.mEventType;
+            LocationTimeZoneEvent event = new LocationTimeZoneEvent.Builder()
+                    .setUserHandle(UserHandle.of(ActivityManager.getCurrentUser()))
+                    .setEventType(internalEventType)
+                    .setTimeZoneIds(mTimeZoneIds)
+                    .setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos())
+                    .build();
+            return new LocationTimeZoneEventUnbundled(event);
+        }
+    }
+
+    private static int checkValidEventType(int eventType) {
+        if (eventType != EVENT_TYPE_SUCCESS
+                && eventType != EVENT_TYPE_UNCERTAIN
+                && eventType != EVENT_TYPE_PERMANENT_FAILURE) {
+            throw new IllegalStateException("eventType=" + eventType);
+        }
+        return eventType;
+    }
+}
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
index c533c20..9df7166 100644
--- a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
+++ b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
@@ -18,7 +18,6 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.location.timezone.LocationTimeZoneEvent;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -30,12 +29,34 @@
 import java.util.Objects;
 
 /**
- * Base class for location time zone providers implemented as unbundled services.
+ * A base class for location time zone providers implemented as unbundled services.
  *
- * TODO(b/152744911): Provide details of the expected service actions and threading.
+ * <p>Provider implementations are enabled / disabled via a call to {@link
+ * #onSetRequest(LocationTimeZoneProviderRequestUnbundled)}.
+ *
+ * <p>Once enabled, providers are expected to detect the time zone if possible, and report the
+ * result via {@link #reportLocationTimeZoneEvent(LocationTimeZoneEventUnbundled)}  with a type of
+ * either {@link LocationTimeZoneEventUnbundled#EVENT_TYPE_UNCERTAIN} or {@link
+ * LocationTimeZoneEventUnbundled#EVENT_TYPE_SUCCESS}. Providers may also report that they have
+ * permanently failed by sending an event of type {@link
+ * LocationTimeZoneEventUnbundled#EVENT_TYPE_PERMANENT_FAILURE}. See the javadocs for each event
+ * type for details.
+ *
+ * <p>Providers are expected to issue their first event within {@link
+ * LocationTimeZoneProviderRequest#getInitializationTimeoutMillis()}.
+ *
+ * <p>Once disabled or have failed, providers are required to stop producing events.
+ *
+ * <p>Threading:
+ *
+ * <p>Calls to {@link #reportLocationTimeZoneEvent(LocationTimeZoneEventUnbundled)} can be made on
+ * on any thread, but may be processed asynchronously by the system server. Similarly, calls to
+ * {@link #onSetRequest(LocationTimeZoneProviderRequestUnbundled)} may occur on any thread.
  *
  * <p>IMPORTANT: This class is effectively a public API for unbundled applications, and must remain
  * API stable.
+ *
+ * @hide
  */
 public abstract class LocationTimeZoneProviderBase {
 
@@ -64,11 +85,11 @@
     /**
      * Reports a new location time zone event from this provider.
      */
-    public void reportLocationTimeZoneEvent(LocationTimeZoneEvent locationTimeZoneEvent) {
+    protected void reportLocationTimeZoneEvent(LocationTimeZoneEventUnbundled event) {
         ILocationTimeZoneProviderManager manager = mManager;
         if (manager != null) {
             try {
-                manager.onLocationTimeZoneEvent(locationTimeZoneEvent);
+                manager.onLocationTimeZoneEvent(event.getInternalLocationTimeZoneEvent());
             } catch (RemoteException | RuntimeException e) {
                 Log.w(mTag, e);
             }
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
index e898bbf..ab50dc3 100644
--- a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
+++ b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
@@ -34,15 +34,30 @@
 
     private final LocationTimeZoneProviderRequest mRequest;
 
+    /** @hide */
     public LocationTimeZoneProviderRequestUnbundled(
             @NonNull LocationTimeZoneProviderRequest request) {
         mRequest = Objects.requireNonNull(request);
     }
 
+    /**
+     * Returns {@code true} if the provider should report events related to the device's current
+     * time zone, {@code false} otherwise.
+     */
     public boolean getReportLocationTimeZone() {
         return mRequest.getReportLocationTimeZone();
     }
 
+    /**
+     * Returns the maximum time that the provider is allowed to initialize before it is expected to
+     * send an event of any sort. Only valid when {@link #getReportLocationTimeZone()} is {@code
+     * true}. Failure to send an event in this time (with some fuzz) may be interpreted as if the
+     * provider is uncertain of the time zone, and/or it could lead to the provider being disabled.
+     */
+    public long getInitializationTimeoutMillis() {
+        return mRequest.getInitializationTimeoutMillis();
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
diff --git a/media/java/android/media/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java
index 21376bb..8cbe52f 100644
--- a/media/java/android/media/MediaTranscodeManager.java
+++ b/media/java/android/media/MediaTranscodeManager.java
@@ -462,9 +462,7 @@
                 mTranscodingClient = service.registerClient(
                         mTranscodingClientCallback,
                         mPackageName,
-                        mPackageName,
-                        IMediaTranscodingService.USE_CALLING_UID,
-                        IMediaTranscodingService.USE_CALLING_PID);
+                        mPackageName);
 
                 if (mTranscodingClient != null) {
                     mTranscodingClient.asBinder().linkToDeath(() -> onClientDied(), /* flags */ 0);
diff --git a/non-updatable-api/module-lib-current.txt b/non-updatable-api/module-lib-current.txt
index 85136df..6c0dd33 100644
--- a/non-updatable-api/module-lib-current.txt
+++ b/non-updatable-api/module-lib-current.txt
@@ -11,7 +11,7 @@
   }
 
   public class StatusBarManager {
-    method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
+    method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
   }
 
 }
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index e27ca09..746f8aa 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -10514,11 +10514,11 @@
     method public int getSuggestedRetryTime();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.DataCallResponse> CREATOR;
-    field public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 2; // 0x2
-    field public static final int HANDOVER_FAILURE_MODE_LEGACY = 1; // 0x1
-    field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 3; // 0x3
-    field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 4; // 0x4
-    field public static final int HANDOVER_FAILURE_MODE_UNKNOWN = 0; // 0x0
+    field public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 1; // 0x1
+    field public static final int HANDOVER_FAILURE_MODE_LEGACY = 0; // 0x0
+    field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 2; // 0x2
+    field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 3; // 0x3
+    field public static final int HANDOVER_FAILURE_MODE_UNKNOWN = -1; // 0xffffffff
     field public static final int LINK_STATUS_ACTIVE = 2; // 0x2
     field public static final int LINK_STATUS_DORMANT = 1; // 0x1
     field public static final int LINK_STATUS_INACTIVE = 0; // 0x0
diff --git a/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml b/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml
new file mode 100644
index 0000000..1b12eb4
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+  <solid android:color="@color/system_bar_background_pill_color"/>
+  <corners android:radius="30dp"/>
+</shape>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index b07dde5..8314ba5 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -30,22 +30,13 @@
         android:layout_height="wrap_content"
         android:layoutDirection="ltr">
 
-        <com.android.systemui.car.navigationbar.CarNavigationButton
+        <com.android.systemui.car.hvac.AdjustableTemperatureView
+            android:id="@+id/driver_hvac"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:layout_alignParentStart="true"
-            android:background="@null"
-            systemui:broadcast="true"
-            systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end">
-
-            <com.android.systemui.car.hvac.AdjustableTemperatureView
-                android:id="@+id/driver_hvac"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:gravity="center_vertical"
-                systemui:hvacAreaId="49"
-                systemui:hvacTempFormat="%.0f\u00B0" />
-        </com.android.systemui.car.navigationbar.CarNavigationButton>
+            android:gravity="center_vertical"
+            systemui:hvacAreaId="49"
+            systemui:hvacTempFormat="%.0f\u00B0" />
 
         <LinearLayout
             android:layout_width="wrap_content"
@@ -66,69 +57,55 @@
                 android:id="@+id/home"
                 style="@style/NavigationBarButton"
                 systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+                systemui:highlightWhenSelected="true"
                 systemui:icon="@drawable/car_ic_home"
-                systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
-                systemui:selectedIcon="@drawable/car_ic_home_selected"/>
+                systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"/>
 
             <com.android.systemui.car.navigationbar.CarNavigationButton
                 android:id="@+id/phone_nav"
                 style="@style/NavigationBarButton"
+                systemui:highlightWhenSelected="true"
                 systemui:icon="@drawable/car_ic_phone"
                 systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;package=com.android.car.dialer;launchFlags=0x10000000;end"
-                systemui:packages="com.android.car.dialer"
-                systemui:selectedIcon="@drawable/car_ic_phone_selected"/>
+                systemui:packages="com.android.car.dialer"/>
 
             <com.android.systemui.car.navigationbar.CarNavigationButton
                 android:id="@+id/grid_nav"
                 style="@style/NavigationBarButton"
                 systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
+                systemui:highlightWhenSelected="true"
                 systemui:icon="@drawable/car_ic_apps"
-                systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
-                systemui:selectedIcon="@drawable/car_ic_apps_selected"/>
+                systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"/>
 
             <com.android.systemui.car.navigationbar.CarNavigationButton
                 android:id="@+id/hvac"
                 style="@style/NavigationBarButton"
+                systemui:highlightWhenSelected="true"
                 systemui:icon="@drawable/car_ic_hvac"
                 systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
-                systemui:selectedIcon="@drawable/car_ic_hvac_selected"
                 systemui:broadcast="true"/>
 
             <com.android.systemui.car.navigationbar.CarNavigationButton
                 android:id="@+id/notifications"
                 style="@style/NavigationBarButton"
+                systemui:highlightWhenSelected="true"
                 systemui:icon="@drawable/car_ic_notification"
                 systemui:longIntent="intent:#Intent;component=com.android.car.bugreport/.BugReportActivity;end"/>
 
-            <com.android.systemui.car.navigationbar.AssitantButton
-                android:id="@+id/assist"
-                style="@style/NavigationBarButton"
-                systemui:icon="@drawable/ic_mic_white"
-                systemui:useDefaultAppIconForRole="true"/>
-
             <Space
                 android:layout_width="0dp"
                 android:layout_height="match_parent"
                 android:layout_weight="1"/>
         </LinearLayout>
 
-        <com.android.systemui.car.navigationbar.CarNavigationButton
+        <com.android.systemui.car.hvac.AdjustableTemperatureView
+            android:id="@+id/passenger_hvac"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
             android:layout_alignParentEnd="true"
-            android:background="@null"
-            systemui:broadcast="true"
-            systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end">
-
-            <com.android.systemui.car.hvac.AdjustableTemperatureView
-                android:id="@+id/passenger_hvac"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:layout_alignParentEnd="true"
-                android:gravity="center_vertical"
-                systemui:hvacAreaId="68"
-                systemui:hvacTempFormat="%.0f\u00B0" />
-        </com.android.systemui.car.navigationbar.CarNavigationButton>
+            android:gravity="center_vertical"
+            systemui:hvacAreaId="68"
+            systemui:hvacTempFormat="%.0f\u00B0" />
     </RelativeLayout>
 
     <LinearLayout
diff --git a/packages/CarSystemUI/res/layout/car_navigation_button.xml b/packages/CarSystemUI/res/layout/car_navigation_button.xml
index a8f1157..9f79023 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_button.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_button.xml
@@ -36,7 +36,7 @@
             android:background="@android:color/transparent"
             android:scaleType="fitCenter"
             android:tintMode="src_in"
-            android:tint="@color/car_nav_icon_fill_color"
+            android:tint="@color/car_nav_icon_fill_color_selected"
             android:clickable="false"
         />
 
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
index af8482a8..07c11c7 100644
--- a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
@@ -31,43 +31,30 @@
         android:layoutDirection="ltr">
 
         <FrameLayout
-            android:id="@+id/user_name_container"
+            android:id="@+id/system_icon_area"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
             android:layout_alignParentStart="true"
-            android:layout_toStartOf="@+id/clock_container"
+            android:layout_marginTop="@dimen/car_padding_2"
+            android:layout_centerVertical="true"
+            android:gravity="center_vertical"
         >
-
             <com.android.systemui.car.navigationbar.CarNavigationButton
-                android:id="@+id/user_name"
-                android:layout_width="match_parent"
+                android:layout_width="wrap_content"
                 android:layout_height="match_parent"
-                systemui:icon="@null"
-                systemui:intent="intent:#Intent;component=com.android.car.settings/.users.UserSwitcherActivity;launchFlags=0x24000000;end"
-            >
-                <LinearLayout
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:orientation="horizontal"
+                android:background="@drawable/system_bar_background_pill"
+                android:layout_weight="1"
+                android:layout_marginStart="@dimen/car_padding_2"
+                android:gravity="center_vertical"
+                systemui:intent="intent:#Intent;component=com.android.car.settings/.common.CarSettingActivities$QuickSettingActivity;launchFlags=0x24000000;end">
+
+                <include
+                    layout="@layout/system_icons"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
                     android:gravity="center_vertical"
-                >
-                    <ImageView
-                        android:id="@+id/user_avatar"
-                        android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
-                        android:src="@drawable/car_ic_user_icon"
-                        android:paddingLeft="@dimen/system_bar_user_icon_padding"
-                        android:paddingRight="@dimen/system_bar_user_icon_padding"
-                    />
-                    <TextView
-                        android:id="@+id/user_name_text"
-                        android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
-                        android:gravity="center_vertical"
-                        android:textAppearance="@style/TextAppearance.SystemBar.Username"
-                        android:maxLines="1"
-                    />
-                </LinearLayout>
+                />
             </com.android.systemui.car.navigationbar.CarNavigationButton>
         </FrameLayout>
 
@@ -96,25 +83,51 @@
             />
         </FrameLayout>
 
-        <LinearLayout
-            android:id="@+id/system_icon_area"
+        <FrameLayout
+            android:id="@+id/user_name_container"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
             android:layout_alignParentEnd="true"
             android:layout_centerVertical="true"
-            android:paddingEnd="@*android:dimen/car_padding_1"
-            android:gravity="center_vertical"
-            android:orientation="horizontal"
+            android:layout_marginTop="@dimen/car_padding_2"
         >
-
-            <include
-                layout="@layout/system_icons"
+            <com.android.systemui.car.navigationbar.CarNavigationButton
+                android:id="@+id/user_name"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
+                android:layout_height="match_parent"
+                android:layout_marginEnd="@dimen/car_padding_2"
+                android:background="@drawable/system_bar_background_pill"
                 android:gravity="center_vertical"
-            />
-        </LinearLayout>
+                systemui:intent="intent:#Intent;component=com.android.car.settings/.users.UserSwitcherActivity;launchFlags=0x24000000;end"
+            >
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:orientation="horizontal"
+                    android:layout_marginStart="@dimen/car_padding_2"
+                    android:layout_marginEnd="@dimen/car_padding_2"
+                    android:gravity="center_vertical"
+                >
+                    <ImageView
+                        android:id="@+id/user_avatar"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:src="@drawable/car_ic_user_icon"
+                        android:layout_marginEnd="@dimen/system_bar_user_icon_padding"
+                    />
+                    <TextView
+                        android:id="@+id/user_name_text"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:gravity="center_vertical"
+                        android:textAppearance="@style/TextAppearance.SystemBar.Username"
+                        android:maxLines="1"
+                        android:maxLength="10"
+                        android:layout_marginEnd="@dimen/system_bar_user_icon_padding"
+                    />
+                </LinearLayout>
+            </com.android.systemui.car.navigationbar.CarNavigationButton>
+        </FrameLayout>
     </RelativeLayout>
 
 </com.android.systemui.car.navigationbar.CarNavigationBarView>
diff --git a/packages/CarSystemUI/res/layout/system_icons.xml b/packages/CarSystemUI/res/layout/system_icons.xml
index d235792..5c06075 100644
--- a/packages/CarSystemUI/res/layout/system_icons.xml
+++ b/packages/CarSystemUI/res/layout/system_icons.xml
@@ -24,10 +24,11 @@
 
     <com.android.systemui.statusbar.phone.StatusIconContainer
         android:id="@+id/statusIcons"
-        android:layout_width="0dp"
+        android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:layout_weight="1"
-        android:paddingEnd="4dp"
+        android:padding="10dp"
+        android:scaleType="fitCenter"
         android:gravity="center_vertical"
         android:orientation="horizontal"
     />
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
index c390cc8..6fe5004 100644
--- a/packages/CarSystemUI/res/values/colors.xml
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -32,8 +32,9 @@
     <color name="system_bar_background_opaque">#ff172026</color>
 
     <!-- colors for status bar -->
-    <color name="system_bar_user_icon_color">#ffffff</color>
-    <color name="system_bar_text_color">#ffffff</color>
+    <color name="system_bar_background_pill_color">#282A2D</color>
+    <color name="system_bar_user_icon_color">#FFFFFF</color>
+    <color name="system_bar_text_color">#FFFFFF</color>
     <color name="status_bar_background_color">#33000000</color>
     <drawable name="system_bar_background">@color/status_bar_background_color</drawable>
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java
index e7e33a54..d2b931b 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java
@@ -96,11 +96,11 @@
     public void setSelected(boolean selected) {
         super.setSelected(selected);
         mSelected = selected;
+
         if (mHighlightWhenSelected) {
-            // Always apply selected alpha if the button does not toggle alpha based on selection
-            // state.
-            setAlpha(!mHighlightWhenSelected || mSelected ? mSelectedAlpha : mUnselectedAlpha);
+            setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha);
         }
+
         if (mShowMoreWhenSelected && mMoreIcon != null) {
             mMoreIcon.setVisibility(selected ? VISIBLE : GONE);
         }
@@ -299,10 +299,10 @@
         mIsDefaultAppIconForRoleEnabled = typedArray.getBoolean(
                 R.styleable.CarNavigationButton_useDefaultAppIconForRole, false);
         mIcon = findViewById(R.id.car_nav_button_icon_image);
-        // Always apply selected alpha if the button does not toggle alpha based on selection state.
-        mIcon.setAlpha(mHighlightWhenSelected ? mUnselectedAlpha : mSelectedAlpha);
+        // Always apply un-selected alpha regardless of if the button toggles alpha based on
+        // selection state.
+        setAlpha(mHighlightWhenSelected ? mUnselectedAlpha : mSelectedAlpha);
         mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
-        mMoreIcon.setAlpha(mSelectedAlpha);
         mUnseenIcon = findViewById(R.id.car_nav_button_unseen_icon);
         updateImage();
     }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 91510f6..9788b30 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -228,6 +228,7 @@
                     Settings.Global.DEVELOPMENT_FORCE_RTL,
                     Settings.Global.DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM,
                     Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR,
+                    Settings.Global.DEVELOPMENT_USE_BLAST_ADAPTER_SV,
                     Settings.Global.DEVICE_DEMO_MODE,
                     Settings.Global.BATTERY_SAVER_ADAPTIVE_CONSTANTS,
                     Settings.Global.BATTERY_SAVER_CONSTANTS,
diff --git a/packages/SystemUI/res/values-h740dp-port/dimens.xml b/packages/SystemUI/res/values-h740dp-port/dimens.xml
new file mode 100644
index 0000000..966066f
--- /dev/null
+++ b/packages/SystemUI/res/values-h740dp-port/dimens.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<resources>
+    <dimen name="qs_tile_height">106dp</dimen>
+    <dimen name="qs_tile_margin_vertical">24dp</dimen>
+
+    <!-- The height of the qs customize header. Should be
+         (qs_panel_padding_top (48dp) +  brightness_mirror_height (48dp) + qs_tile_margin_top (18dp)) -
+         (Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (12dp))
+    -->
+    <dimen name="qs_customize_header_min_height">46dp</dimen>
+    <dimen name="qs_tile_margin_top">18dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d5362ba..48da5d9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -484,20 +484,20 @@
     <!-- The size of the gesture span needed to activate the "pull" notification expansion -->
     <dimen name="pull_span_min">25dp</dimen>
 
-    <dimen name="qs_tile_height">106dp</dimen>
+    <dimen name="qs_tile_height">96dp</dimen>
     <!--notification_side_paddings + notification_content_margin_start - (qs_quick_tile_size - qs_tile_background_size) / 2 -->
     <dimen name="qs_tile_layout_margin_side">18dp</dimen>
     <dimen name="qs_tile_margin_horizontal">18dp</dimen>
     <dimen name="qs_tile_margin_horizontal_two_line">2dp</dimen>
-    <dimen name="qs_tile_margin_vertical">24dp</dimen>
+    <dimen name="qs_tile_margin_vertical">2dp</dimen>
     <dimen name="qs_tile_margin_top_bottom">12dp</dimen>
     <dimen name="qs_tile_margin_top_bottom_negative">-12dp</dimen>
     <!-- The height of the qs customize header. Should be
-         (qs_panel_padding_top (48dp) +  brightness_mirror_height (48dp) + qs_tile_margin_top (18dp)) -
+         (qs_panel_padding_top (48dp) +  brightness_mirror_height (48dp) + qs_tile_margin_top (0dp)) -
          (Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (12dp))
     -->
-    <dimen name="qs_customize_header_min_height">46dp</dimen>
-    <dimen name="qs_tile_margin_top">18dp</dimen>
+    <dimen name="qs_customize_header_min_height">28dp</dimen>
+    <dimen name="qs_tile_margin_top">0dp</dimen>
     <dimen name="qs_tile_icon_background_stroke_width">-1dp</dimen>
     <dimen name="qs_tile_background_size">44dp</dimen>
     <dimen name="qs_quick_tile_size">48dp</dimen>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 8ed7929..c94bcaa 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -44,11 +44,6 @@
     void startScreenPinning(int taskId) = 1;
 
     /**
-     * Notifies SystemUI that split screen has been invoked.
-     */
-    void onSplitScreenInvoked() = 5;
-
-    /**
      * Notifies SystemUI that Overview is shown.
      */
     void onOverviewShown(boolean fromHome) = 6;
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 832edf7..f24644b 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -37,7 +37,7 @@
 import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -126,6 +126,7 @@
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.SystemWindows;
 
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -167,6 +168,15 @@
      * Generic handler on the main thread.
      */
     private static final String MAIN_HANDLER_NAME = "main_handler";
+    /**
+     * Generic executor on the main thread.
+     */
+    private static final String MAIN_EXECUTOR_NAME = "main_executor";
+
+    /**
+     * Generic executor on a background thread.
+     */
+    private static final String BACKGROUND_EXECUTOR_NAME = "background_executor";
 
     /**
      * An email address to send memory leak reports to by default.
@@ -199,6 +209,17 @@
             new DependencyKey<>(MAIN_HANDLER_NAME);
 
     /**
+     * Generic executor on the main thread.
+     */
+    public static final DependencyKey<Executor> MAIN_EXECUTOR =
+            new DependencyKey<>(MAIN_EXECUTOR_NAME);
+    /**
+     * Generic executor on a background thread.
+     */
+    public static final DependencyKey<Executor> BACKGROUND_EXECUTOR =
+            new DependencyKey<>(BACKGROUND_EXECUTOR_NAME);
+
+    /**
      * An email address to send memory leak reports to by default.
      */
     public static final DependencyKey<String> LEAK_REPORT_EMAIL =
@@ -288,7 +309,7 @@
     @Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
     @Inject Lazy<SmartReplyController> mSmartReplyController;
     @Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler;
-    @Inject Lazy<BubbleController> mBubbleController;
+    @Inject Lazy<Bubbles> mBubbles;
     @Inject Lazy<NotificationEntryManager> mNotificationEntryManager;
     @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
     @Inject Lazy<AutoHideController> mAutoHideController;
@@ -301,6 +322,8 @@
     @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
     @Nullable
     @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
+    @Inject @Main Lazy<Executor> mMainExecutor;
+    @Inject @Background Lazy<Executor> mBackgroundExecutor;
     @Inject Lazy<ClockManager> mClockManager;
     @Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
     @Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper;
@@ -336,6 +359,8 @@
         mProviders.put(BG_LOOPER, mBgLooper::get);
         mProviders.put(MAIN_LOOPER, mMainLooper::get);
         mProviders.put(MAIN_HANDLER, mMainHandler::get);
+        mProviders.put(MAIN_EXECUTOR, mMainExecutor::get);
+        mProviders.put(BACKGROUND_EXECUTOR, mBackgroundExecutor::get);
         mProviders.put(ActivityStarter.class, mActivityStarter::get);
         mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get);
 
@@ -483,7 +508,7 @@
         mProviders.put(SmartReplyController.class, mSmartReplyController::get);
         mProviders.put(RemoteInputQuickSettingsDisabler.class,
                 mRemoteInputQuickSettingsDisabler::get);
-        mProviders.put(BubbleController.class, mBubbleController::get);
+        mProviders.put(Bubbles.class, mBubbles::get);
         mProviders.put(NotificationEntryManager.class, mNotificationEntryManager::get);
         mProviders.put(ForegroundServiceNotificationListener.class,
                 mForegroundServiceNotificationListener::get);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index fbb47e2..5b85208 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -294,6 +294,15 @@
     }
 
     /**
+     * Sets whether this bubble is considered visually interruptive. Normally pulled from the
+     * {@link NotificationEntry}, this method is purely for testing.
+     */
+    @VisibleForTesting
+    void setVisuallyInterruptiveForTest(boolean visuallyInterruptive) {
+        mIsVisuallyInterruptive = visuallyInterruptive;
+    }
+
+    /**
      * Starts a task to inflate & load any necessary information to display a bubble.
      *
      * @param callback the callback to notify one the bubble is ready to be displayed.
@@ -419,6 +428,7 @@
             } else if (mIntent != null && entry.getBubbleMetadata().getIntent() == null) {
                 // Was an intent bubble now it's a shortcut bubble... still unregister the listener
                 mIntent.unregisterCancelListener(mIntentCancelListener);
+                mIntentActive = false;
                 mIntent = null;
             }
             mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 90b1f55..dff405c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -129,7 +129,8 @@
  *
  * The controller manages addition, removal, and visible state of bubbles on screen.
  */
-public class BubbleController implements ConfigurationController.ConfigurationListener, Dumpable {
+public class BubbleController implements Bubbles, ConfigurationController.ConfigurationListener,
+        Dumpable {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
 
@@ -414,7 +415,8 @@
             if (bubble.getBubbleIntent() == null) {
                 return;
             }
-            if (bubble.isIntentActive()) {
+            if (bubble.isIntentActive()
+                    || mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
                 bubble.setPendingIntentCanceled();
                 return;
             }
@@ -519,6 +521,7 @@
     /**
      * See {@link NotifCallback}.
      */
+    @Override
     public void addNotifCallback(NotifCallback callback) {
         mCallbacks.add(callback);
     }
@@ -700,6 +703,7 @@
      * since we want the scrim's appearance and behavior to be identical to that of the notification
      * shade scrim.
      */
+    @Override
     public ScrimView getScrimForBubble() {
         return mBubbleScrim;
     }
@@ -708,6 +712,7 @@
      * Called when the status bar has become visible or invisible (either permanently or
      * temporarily).
      */
+    @Override
     public void onStatusBarVisibilityChanged(boolean visible) {
         if (mStackView != null) {
             // Hide the stack temporarily if the status bar has been made invisible, and the stack
@@ -725,14 +730,16 @@
         mInflateSynchronously = inflateSynchronously;
     }
 
-    void setOverflowListener(BubbleData.Listener listener) {
+    @Override
+    public void setOverflowListener(BubbleData.Listener listener) {
         mOverflowListener = listener;
     }
 
     /**
      * @return Bubbles for updating overflow.
      */
-    List<Bubble> getOverflowBubbles() {
+    @Override
+    public List<Bubble> getOverflowBubbles() {
         return mBubbleData.getOverflowBubbles();
     }
 
@@ -955,13 +962,10 @@
         }
     }
 
-    boolean inLandscape() {
-        return mOrientation == Configuration.ORIENTATION_LANDSCAPE;
-    }
-
     /**
      * Set a listener to be notified of bubble expand events.
      */
+    @Override
     public void setExpandListener(BubbleExpandListener listener) {
         mExpandListener = ((isExpanding, key) -> {
             if (listener != null) {
@@ -987,29 +991,17 @@
         return mBubbleData.hasBubbles();
     }
 
-    /**
-     * Whether the stack of bubbles is expanded or not.
-     */
+    @Override
     public boolean isStackExpanded() {
         return mBubbleData.isExpanded();
     }
 
-    /**
-     * Tell the stack of bubbles to collapse.
-     */
+    @Override
     public void collapseStack() {
         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.
-     */
+    @Override
     public boolean isBubbleNotificationSuppressedFromShade(NotificationEntry entry) {
         String key = entry.getKey();
         boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key)
@@ -1021,19 +1013,14 @@
         return (isSummary && isSuppressedSummary) || isSuppressedBubble;
     }
 
-    /**
-     * True if:
-     * (1) The current notification entry same as selected bubble notification entry and the
-     * stack is currently expanded.
-     *
-     * False otherwise.
-     */
+    @Override
     public boolean isBubbleExpanded(NotificationEntry entry) {
         return isStackExpanded() && mBubbleData != null && mBubbleData.getSelectedBubble() != null
                 && mBubbleData.getSelectedBubble().getKey().equals(entry.getKey()) ? true : false;
     }
 
-    void promoteBubbleFromOverflow(Bubble bubble) {
+    @Override
+    public void promoteBubbleFromOverflow(Bubble bubble) {
         mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK);
         bubble.setInflateSynchronously(mInflateSynchronously);
         bubble.setShouldAutoExpand(true);
@@ -1041,12 +1028,7 @@
         setIsBubble(bubble, true /* isBubble */);
     }
 
-    /**
-     * Request the stack expand if needed, then select the specified Bubble as current.
-     * If no bubble exists for this entry, one is created.
-     *
-     * @param entry the notification for the bubble to be selected
-     */
+    @Override
     public void expandStackAndSelectBubble(NotificationEntry entry) {
         if (mStatusBarStateListener.getCurrentState() == SHADE) {
             mNotifEntryToExpandOnShadeUnlock = null;
@@ -1074,12 +1056,7 @@
         }
     }
 
-    /**
-     * When a notification is marked Priority, expand the stack if needed,
-     * then (maybe create and) select the given bubble.
-     *
-     * @param entry the notification for the bubble to show
-     */
+    @Override
     public void onUserChangedImportance(NotificationEntry entry) {
         try {
             int flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
@@ -1094,10 +1071,7 @@
         }
     }
 
-    /**
-     * Directs a back gesture at the bubble stack. When opened, the current expanded bubble
-     * is forwarded a back key down/up pair.
-     */
+    @Override
     public void performBackPressIfNeeded() {
         if (mStackView != null) {
             mStackView.performBackPressIfNeeded();
@@ -1140,8 +1114,17 @@
         if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
             notif.setInterruption();
         }
-        Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
-        inflateAndAdd(bubble, suppressFlyout, showInShade);
+        if (!notif.getRanking().visuallyInterruptive()
+                && (notif.getBubbleMetadata() != null
+                    && !notif.getBubbleMetadata().getAutoExpandBubble())
+                && mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) {
+            // Update the bubble but don't promote it out of overflow
+            Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey());
+            b.setEntry(notif);
+        } else {
+            Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
+            inflateAndAdd(bubble, suppressFlyout, showInShade);
+        }
     }
 
     void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
@@ -1152,15 +1135,7 @@
                 mContext, mStackView, mBubbleIconFactory, false /* skipInflation */);
     }
 
-    /**
-     * Called when a user has indicated that an active notification should be shown as a bubble.
-     * <p>
-     * This method will collapse the shade, create the bubble without a flyout or dot, and suppress
-     * the notification from appearing in the shade.
-     *
-     * @param entry the notification to change bubble state for.
-     * @param shouldBubble whether the notification should show as a bubble or not.
-     */
+    @Override
     public void onUserChangedBubble(@NonNull final NotificationEntry entry, boolean shouldBubble) {
         NotificationChannel channel = entry.getChannel();
         final String appPkg = entry.getSbn().getPackageName();
@@ -1199,13 +1174,9 @@
         }
     }
 
-    /**
-     * Removes the bubble with the given key.
-     * <p>
-     * Must be called from the main thread.
-     */
     @MainThread
-    void removeBubble(String key, int reason) {
+    @Override
+    public void removeBubble(String key, int reason) {
         if (mBubbleData.hasAnyBubbleWithKey(key)) {
             mBubbleData.dismissBubbleWithKey(key, reason);
         }
@@ -1447,16 +1418,7 @@
         }
     };
 
-    /**
-     * We intercept notification entries (including group summaries) dismissed by the user when
-     * there is an active bubble associated with it. We do this so that developers can still
-     * cancel it (and hence the bubbles associated with it). However, these intercepted
-     * notifications should then be hidden from the shade since the user has cancelled them, so we
-     *  {@link Bubble#setSuppressNotification}.  For the case of suppressed summaries, we also add
-     *  {@link BubbleData#addSummaryToSuppress}.
-     *
-     * @return true if we want to intercept the dismissal of the entry, else false.
-     */
+    @Override
     public boolean handleDismissalInterception(NotificationEntry entry) {
         if (entry == null) {
             return false;
@@ -1579,10 +1541,7 @@
         mStackView.updateContentDescription();
     }
 
-    /**
-     * The display id of the expanded view, if the stack is expanded and not occluded by the
-     * status bar, otherwise returns {@link Display#INVALID_DISPLAY}.
-     */
+    @Override
     public int getExpandedDisplayId(Context context) {
         if (mStackView == null) {
             return INVALID_DISPLAY;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index bab18ec..2c3cb5f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -284,7 +284,8 @@
         } else {
             // Updates an existing bubble
             bubble.setSuppressFlyout(suppressFlyout);
-            doUpdate(bubble);
+            // If there is no flyout, we probably shouldn't show the bubble at the top
+            doUpdate(bubble, !suppressFlyout /* reorder */);
         }
 
         if (bubble.shouldAutoExpand()) {
@@ -438,12 +439,12 @@
         }
     }
 
-    private void doUpdate(Bubble bubble) {
+    private void doUpdate(Bubble bubble, boolean reorder) {
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "doUpdate: " + bubble);
         }
         mStateChange.updatedBubble = bubble;
-        if (!isExpanded()) {
+        if (!isExpanded() && reorder) {
             int prevPos = mBubbles.indexOf(bubble);
             mBubbles.remove(bubble);
             mBubbles.add(0, bubble);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index ec60cbd..83a816b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -127,7 +127,7 @@
 
     private boolean mIsOverflow;
 
-    private BubbleController mBubbleController = Dependency.get(BubbleController.class);
+    private Bubbles mBubbles = Dependency.get(Bubbles.class);
     private WindowManager mWindowManager;
     private ActivityManager mActivityManager;
 
@@ -168,7 +168,7 @@
                                     + "bubble=" + getBubbleKey());
                         }
                         if (mActivityView == null) {
-                            mBubbleController.removeBubble(getBubbleKey(),
+                            mBubbles.removeBubble(getBubbleKey(),
                                     BubbleController.DISMISS_INVALID_INTENT);
                             return;
                         }
@@ -194,7 +194,7 @@
                             // the bubble again so we'll just remove it.
                             Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
                                     + ", " + e.getMessage() + "; removing bubble");
-                            mBubbleController.removeBubble(getBubbleKey(),
+                            mBubbles.removeBubble(getBubbleKey(),
                                     BubbleController.DISMISS_INVALID_INTENT);
                         }
                     });
@@ -242,7 +242,7 @@
             }
             if (mBubble != null) {
                 // Must post because this is called from a binder thread.
-                post(() -> mBubbleController.removeBubble(mBubble.getKey(),
+                post(() -> mBubbles.removeBubble(mBubble.getKey(),
                         BubbleController.DISMISS_TASK_FINISHED));
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 160addc..5fdda97 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -60,7 +60,7 @@
     private TextView mEmptyStateTitle;
     private TextView mEmptyStateSubtitle;
     private ImageView mEmptyStateImage;
-    private BubbleController mBubbleController;
+    private Bubbles mBubbles;
     private BubbleOverflowAdapter mAdapter;
     private RecyclerView mRecyclerView;
     private List<Bubble> mOverflowBubbles = new ArrayList<>();
@@ -71,7 +71,8 @@
         }
         @Override
         public boolean canScrollVertically() {
-            if (mBubbleController.inLandscape()) {
+            if (getResources().getConfiguration().orientation
+                    == Configuration.ORIENTATION_LANDSCAPE) {
                 return super.canScrollVertically();
             }
             return false;
@@ -93,8 +94,8 @@
     }
 
     @Inject
-    public BubbleOverflowActivity(BubbleController controller) {
-        mBubbleController = controller;
+    public BubbleOverflowActivity(Bubbles bubbles) {
+        mBubbles = bubbles;
     }
 
     @Override
@@ -131,15 +132,15 @@
         final int viewHeight = recyclerViewHeight / rows;
 
         mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
-                mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight);
+                mBubbles::promoteBubbleFromOverflow, viewWidth, viewHeight);
         mRecyclerView.setAdapter(mAdapter);
 
         mOverflowBubbles.clear();
-        mOverflowBubbles.addAll(mBubbleController.getOverflowBubbles());
+        mOverflowBubbles.addAll(mBubbles.getOverflowBubbles());
         mAdapter.notifyDataSetChanged();
         updateEmptyStateVisibility();
 
-        mBubbleController.setOverflowListener(mDataListener);
+        mBubbles.setOverflowListener(mDataListener);
         updateTheme();
     }
 
@@ -209,8 +210,7 @@
 
             if (DEBUG_OVERFLOW) {
                 Log.d(TAG, BubbleDebugConfig.formatBubblesString(
-                        mBubbleController.getOverflowBubbles(),
-                        null));
+                        mBubbles.getOverflowBubbles(), null));
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java
new file mode 100644
index 0000000..34828b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.view.Display;
+
+import androidx.annotation.MainThread;
+
+import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.phone.ScrimController;
+
+import java.util.List;
+
+/**
+ * Interface to engage bubbles feature.
+ */
+public interface Bubbles {
+
+    /**
+     * @return {@code true} if there is a bubble associated with the provided key and if its
+     * notification is hidden from the shade or 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.
+     */
+    boolean isBubbleNotificationSuppressedFromShade(NotificationEntry entry);
+
+    /**
+     * @return {@code true} if the current notification entry same as selected bubble
+     * notification entry and the stack is currently expanded.
+     */
+    boolean isBubbleExpanded(NotificationEntry entry);
+
+    /** @return {@code true} if stack of bubbles is expanded or not. */
+    boolean isStackExpanded();
+
+    /**
+     * @return the {@link ScrimView} drawn behind the bubble stack. This is managed by
+     * {@link ScrimController} since we want the scrim's appearance and behavior to be identical to
+     * that of the notification shade scrim.
+     */
+    ScrimView getScrimForBubble();
+
+    /**
+     * @return the display id of the expanded view, if the stack is expanded and not occluded by the
+     * status bar, otherwise returns {@link Display#INVALID_DISPLAY}.
+     */
+    int getExpandedDisplayId(Context context);
+
+    /** @return Bubbles for updating overflow. */
+    List<Bubble> getOverflowBubbles();
+
+    /** Tell the stack of bubbles to collapse. */
+    void collapseStack();
+
+    /**
+     * Request the stack expand if needed, then select the specified Bubble as current.
+     * If no bubble exists for this entry, one is created.
+     *
+     * @param entry the notification for the bubble to be selected
+     */
+    void expandStackAndSelectBubble(NotificationEntry entry);
+
+
+    /**
+     * Directs a back gesture at the bubble stack. When opened, the current expanded bubble
+     * is forwarded a back key down/up pair.
+     */
+    void performBackPressIfNeeded();
+
+    /** Promote the provided bubbles when overflow view. */
+    void promoteBubbleFromOverflow(Bubble bubble);
+
+    /**
+     * We intercept notification entries (including group summaries) dismissed by the user when
+     * there is an active bubble associated with it. We do this so that developers can still
+     * cancel it (and hence the bubbles associated with it). However, these intercepted
+     * notifications should then be hidden from the shade since the user has cancelled them, so we
+     * {@link Bubble#setSuppressNotification}.  For the case of suppressed summaries, we also add
+     * {@link BubbleData#addSummaryToSuppress}.
+     *
+     * @return true if we want to intercept the dismissal of the entry, else false.
+     */
+    boolean handleDismissalInterception(NotificationEntry entry);
+
+    /**
+     * Removes the bubble with the given key.
+     * <p>
+     * Must be called from the main thread.
+     */
+    @MainThread
+    void removeBubble(String key, int reason);
+
+
+    /**
+     * When a notification is marked Priority, expand the stack if needed,
+     * then (maybe create and) select the given bubble.
+     *
+     * @param entry the notification for the bubble to show
+     */
+    void onUserChangedImportance(NotificationEntry entry);
+
+    /**
+     * Called when the status bar has become visible or invisible (either permanently or
+     * temporarily).
+     */
+    void onStatusBarVisibilityChanged(boolean visible);
+
+    /**
+     * Called when a user has indicated that an active notification should be shown as a bubble.
+     * <p>
+     * This method will collapse the shade, create the bubble without a flyout or dot, and suppress
+     * the notification from appearing in the shade.
+     *
+     * @param entry the notification to change bubble state for.
+     * @param shouldBubble whether the notification should show as a bubble or not.
+     */
+    void onUserChangedBubble(@NonNull NotificationEntry entry, boolean shouldBubble);
+
+
+    /** See {@link BubbleController.NotifCallback}. */
+    void addNotifCallback(BubbleController.NotifCallback callback);
+
+    /** Set a listener to be notified of bubble expand events. */
+    void setExpandListener(BubbleController.BubbleExpandListener listener);
+
+    /** Set a listener to be notified of when overflow view update. */
+    void setOverflowListener(BubbleData.Listener listener);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
index 5bf1053..08902f8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -25,6 +25,7 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.bubbles.BubbleData;
 import com.android.systemui.bubbles.BubbleDataRepository;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
@@ -53,7 +54,7 @@
      */
     @SysUISingleton
     @Provides
-    static BubbleController newBubbleController(
+    static Bubbles newBubbleController(
             Context context,
             NotificationShadeWindowController notificationShadeWindowController,
             StatusBarStateController statusBarStateController,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index c2d6cd4..1beb875 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -250,13 +250,13 @@
             val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                     ViewGroup.LayoutParams.WRAP_CONTENT)
             newPlayer.view?.player?.setLayoutParams(lp)
-            newPlayer.bind(data)
+            newPlayer.bind(data, key)
             newPlayer.setListening(currentlyExpanded)
             MediaPlayerData.addMediaPlayer(key, data, newPlayer)
             updatePlayerToState(newPlayer, noAnimation = true)
             reorderAllPlayers()
         } else {
-            existingPlayer.bind(data)
+            existingPlayer.bind(data, key)
             MediaPlayerData.addMediaPlayer(key, data, existingPlayer)
             if (visualStabilityManager.isReorderingAllowed) {
                 reorderAllPlayers()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index e55678dc..810cecc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -82,6 +82,7 @@
 
     private Context mContext;
     private PlayerViewHolder mViewHolder;
+    private String mKey;
     private MediaViewController mMediaViewController;
     private MediaSession.Token mToken;
     private MediaController mController;
@@ -206,10 +207,11 @@
     /**
      * Bind this view based on the data given
      */
-    public void bind(@NonNull MediaData data) {
+    public void bind(@NonNull MediaData data, String key) {
         if (mViewHolder == null) {
             return;
         }
+        mKey = key;
         MediaSession.Token token = data.getToken();
         mBackgroundColor = data.getBackgroundColor();
         if (mToken == null || !mToken.equals(token)) {
@@ -359,10 +361,10 @@
 
         // Dismiss
         mViewHolder.getDismiss().setOnClickListener(v -> {
-            if (data.getNotificationKey() != null) {
+            if (mKey != null) {
                 closeGuts();
                 mKeyguardDismissUtil.executeWhenUnlocked(() -> {
-                    mMediaDataManagerLazy.get().dismissMediaData(data.getNotificationKey(),
+                    mMediaDataManagerLazy.get().dismissMediaData(mKey,
                             MediaViewController.GUTS_ANIMATION_DURATION + 100);
                     return true;
                 }, /* requiresShadeOpen */ true);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index d6b8316..af851a7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -58,7 +58,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.shared.system.QuickStepContract;
 
@@ -426,9 +426,9 @@
         if (getDisplay() != null) {
             displayId = getDisplay().getDisplayId();
         }
-        // Bubble controller will give us a valid display id if it should get the back event
-        BubbleController bubbleController = Dependency.get(BubbleController.class);
-        int bubbleDisplayId = bubbleController.getExpandedDisplayId(mContext);
+        // Bubbles will give us a valid display id if it should get the back event
+        Bubbles Bubbles = Dependency.get(Bubbles.class);
+        int bubbleDisplayId = Bubbles.getExpandedDisplayId(mContext);
         if (mCode == KeyEvent.KEYCODE_BACK && bubbleDisplayId != INVALID_DISPLAY) {
             displayId = bubbleDisplayId;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 5694360..6d6d6cb 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -57,7 +57,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -725,9 +725,8 @@
                 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
                 InputDevice.SOURCE_KEYBOARD);
 
-        // Bubble controller will give us a valid display id if it should get the back event
-        BubbleController bubbleController = Dependency.get(BubbleController.class);
-        int bubbleDisplayId = bubbleController.getExpandedDisplayId(mContext);
+        // Bubbles will give us a valid display id if it should get the back event
+        final int bubbleDisplayId = Dependency.get(Bubbles.class).getExpandedDisplayId(mContext);
         if (bubbleDisplayId != INVALID_DISPLAY) {
             ev.setDisplayId(bubbleDisplayId);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 9cf2751..fb86535 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -548,16 +548,17 @@
 
     /**
      * Setup the ViewHost and attach the provided menu view to the ViewHost.
+     * @return The input token belonging to the PipMenuView.
      */
-    public void attachPipMenuViewHost(View menuView, WindowManager.LayoutParams lp) {
+    public IBinder attachPipMenuViewHost(View menuView, WindowManager.LayoutParams lp) {
         if (mPipMenuSurface != null) {
             Log.e(TAG, "PIP Menu View already created and attached.");
-            return;
+            return null;
         }
 
         if (mLeash == null) {
             Log.e(TAG, "PiP Leash is not yet ready.");
-            return;
+            return null;
         }
 
         if (Looper.getMainLooper() != Looper.myLooper()) {
@@ -573,6 +574,8 @@
         transaction.setRelativeLayer(mPipMenuSurface, mLeash, 1);
         transaction.apply();
         mPipViewHost.setView(menuView, lp);
+
+        return mPipViewHost.getSurfacePackage().getInputToken();
     }
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index 4c86ea3..6c23225 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -27,10 +27,12 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.Debug;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 
 import com.android.systemui.pip.PipTaskOrganizer;
 import com.android.systemui.pip.phone.PipMediaController.ActionListener;
@@ -92,6 +94,7 @@
     private int mMenuState;
 
     private PipMenuView mPipMenuView;
+    private IBinder mPipMenuInputToken;
 
     private ActionListener mMediaActionListener = new ActionListener() {
         @Override
@@ -120,6 +123,7 @@
         hideMenu();
         mPipTaskOrganizer.detachPipMenuViewHost();
         mPipMenuView = null;
+        mPipMenuInputToken = null;
     }
 
     public void onPinnedStackAnimationEnded() {
@@ -133,7 +137,13 @@
             mPipMenuView = new PipMenuView(mContext, this);
 
         }
-        mPipTaskOrganizer.attachPipMenuViewHost(mPipMenuView, getPipMenuLayoutParams(0, 0));
+
+        // If we haven't gotten the input toekn, that means we haven't had a success attempt
+        // yet at attaching the PipMenuView
+        if (mPipMenuInputToken == null) {
+            mPipMenuInputToken = mPipTaskOrganizer.attachPipMenuViewHost(mPipMenuView,
+                    getPipMenuLayoutParams(0, 0));
+        }
     }
 
     /**
@@ -352,6 +362,13 @@
                 // the menu actions to be updated again.
                 mMediaController.removeListener(mMediaActionListener);
             }
+
+            try {
+                WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+                        mPipMenuInputToken, menuState != MENU_STATE_NONE /* grantFocus */);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to update focus as menu appears/disappears", e);
+            }
         }
         mMenuState = menuState;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java
index 1c38ab3..48ddbff 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java
@@ -154,10 +154,6 @@
                 expandPip();
             }
         });
-        // TODO (b/161710689): Remove this once focusability for Windowless window is working
-        findViewById(R.id.expand_button).setFocusable(false);
-        mDismissButton.setFocusable(false);
-        mSettingsButton.setFocusable(false);
 
         mResizeHandle = findViewById(R.id.resize_handle);
         mResizeHandle.setAlpha(0);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
index f911b49..1e3d871 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
@@ -308,6 +308,10 @@
         return mTmpRegion.contains(x, y);
     }
 
+    public boolean isUsingPinchToZoom() {
+        return mEnablePinchResize;
+    }
+
     public boolean willStartResizeGesture(MotionEvent ev) {
         if (isInValidSysUiState()) {
             switch (ev.getActionMasked()) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 6e2c046..1f9125da 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -831,9 +831,7 @@
             // we store back to this snap fraction.  Otherwise, we'll reset the snap
             // fraction and snap to the closest edge.
             if (resize) {
-                Rect expandedBounds = new Rect(mExpandedBounds);
-                mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds,
-                        mMovementBounds, mExpandedMovementBounds, callback);
+                animateToExpandedState(callback);
             }
         } else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) {
             // Try and restore the PiP to the closest edge, using the saved snap fraction
@@ -859,13 +857,7 @@
                 }
 
                 if (mDeferResizeToNormalBoundsUntilRotation == -1) {
-                    Rect restoreBounds = new Rect(getUserResizeBounds());
-                    Rect restoredMovementBounds = new Rect();
-                    mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(restoreBounds,
-                            mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
-                    mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
-                            restoredMovementBounds, mMovementBounds, false /* immediate */);
-                    mSavedSnapFraction = -1f;
+                    animateToUnexpandedState(getUserResizeBounds());
                 }
             } else {
                 mSavedSnapFraction = -1f;
@@ -883,6 +875,21 @@
         }
     }
 
+    private void animateToExpandedState(Runnable callback) {
+        Rect expandedBounds = new Rect(mExpandedBounds);
+        mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds,
+                mMovementBounds, mExpandedMovementBounds, callback);
+    }
+
+    private void animateToUnexpandedState(Rect restoreBounds) {
+        Rect restoredMovementBounds = new Rect();
+        mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(restoreBounds,
+                mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
+        mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
+                restoredMovementBounds, mMovementBounds, false /* immediate */);
+        mSavedSnapFraction = -1f;
+    }
+
     /**
      * @return the motion helper.
      */
@@ -1025,10 +1032,24 @@
                             this::flingEndAction /* endAction */);
                 }
             } else if (mTouchState.isDoubleTap()) {
-                // Expand to fullscreen if this is a double tap
-                // the PiP should be frozen until the transition ends
-                setTouchEnabled(false);
-                mMotionHelper.expandLeavePip();
+                // If using pinch to zoom, double-tap functions as resizing between max/min size
+                if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
+                    final boolean toExpand =
+                            mMotionHelper.getBounds().width() < mExpandedBounds.width()
+                            && mMotionHelper.getBounds().height() < mExpandedBounds.height();
+                    mPipResizeGestureHandler.setUserResizeBounds(toExpand ? mExpandedBounds
+                            : mNormalBounds);
+                    if (toExpand) {
+                        animateToExpandedState(null);
+                    } else {
+                        animateToUnexpandedState(mNormalBounds);
+                    }
+                } else {
+                    // Expand to fullscreen if this is a double tap
+                    // the PiP should be frozen until the transition ends
+                    setTouchEnabled(false);
+                    mMotionHelper.expandLeavePip();
+                }
             } else if (mMenuState != MENU_STATE_FULL) {
                 if (!mTouchState.isWaitingForDoubleTap()) {
                     // User has stalled long enough for this not to be a drag or a double tap, just
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 56b939d..a6c3221 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -51,6 +51,7 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
+import com.android.systemui.statusbar.policy.HotspotController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.NetworkController;
 
@@ -72,6 +73,7 @@
     private final Callback mCallback = new Callback();
     private Dialog mDialog;
     private boolean mWifiConnected;
+    private boolean mHotspotConnected;
 
     @Inject
     public CastTile(
@@ -84,7 +86,8 @@
             QSLogger qsLogger,
             CastController castController,
             KeyguardStateController keyguardStateController,
-            NetworkController networkController
+            NetworkController networkController,
+            HotspotController hotspotController
     ) {
         super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
                 activityStarter, qsLogger);
@@ -95,6 +98,7 @@
         mController.observe(this, mCallback);
         mKeyguard.observe(this, mCallback);
         mNetworkController.observe(this, mSignalCallback);
+        hotspotController.observe(this, mHotspotCallback);
     }
 
     @Override
@@ -222,7 +226,7 @@
         }
         state.icon = ResourceIcon.get(state.value ? R.drawable.ic_cast_connected
                 : R.drawable.ic_cast);
-        if (mWifiConnected || state.value) {
+        if (canCastToWifi() || state.value) {
             state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
             if (!state.value) {
                 state.secondaryLabel = "";
@@ -258,6 +262,10 @@
                 : mContext.getString(R.string.quick_settings_cast_device_default_name);
     }
 
+    private boolean canCastToWifi() {
+        return mWifiConnected || mHotspotConnected;
+    }
+
     private final NetworkController.SignalCallback mSignalCallback =
             new NetworkController.SignalCallback() {
                 @Override
@@ -269,7 +277,25 @@
                     boolean enabledAndConnected = enabled && qsIcon.visible;
                     if (enabledAndConnected != mWifiConnected) {
                         mWifiConnected = enabledAndConnected;
-                        refreshState();
+                        // Hotspot is not connected, so changes here should update
+                        if (!mHotspotConnected) {
+                            refreshState();
+                        }
+                    }
+                }
+            };
+
+    private final HotspotController.Callback mHotspotCallback =
+            new HotspotController.Callback() {
+                @Override
+                public void onHotspotChanged(boolean enabled, int numDevices) {
+                    boolean enabledAndConnected = enabled && numDevices > 0;
+                    if (enabledAndConnected != mHotspotConnected) {
+                        mHotspotConnected = enabledAndConnected;
+                        // Wifi is not connected, so changes here should update
+                        if (!mWifiConnected) {
+                            refreshState();
+                        }
                     }
                 }
             };
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index aa43516..f11683d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -233,21 +233,6 @@
         }
 
         @Override
-        public void onSplitScreenInvoked() {
-            if (!verifyCaller("onSplitScreenInvoked")) {
-                return;
-            }
-            long token = Binder.clearCallingIdentity();
-            try {
-                mSplitScreenOptional.ifPresent(splitScreen -> {
-                    splitScreen.onDockedFirstAnimationFrame();
-                });
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
         public void onOverviewShown(boolean fromHome) {
             if (!verifyCaller("onOverviewShown")) {
                 return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 38c7e5c..53179ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -26,7 +26,7 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.dagger.StatusBarModule;
@@ -47,6 +47,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Optional;
 import java.util.Stack;
 
 /**
@@ -84,7 +85,7 @@
      * possible.
      */
     private final boolean mAlwaysExpandNonGroupedNotification;
-    private final BubbleController mBubbleController;
+    private final Optional<Bubbles> mBubblesOptional;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final KeyguardBypassController mBypassController;
     private final ForegroundServiceSectionController mFgsSectionController;
@@ -112,7 +113,7 @@
             StatusBarStateController statusBarStateController,
             NotificationEntryManager notificationEntryManager,
             KeyguardBypassController bypassController,
-            BubbleController bubbleController,
+            Optional<Bubbles> bubblesOptional,
             DynamicPrivacyController privacyController,
             ForegroundServiceSectionController fgsSectionController,
             DynamicChildBindController dynamicChildBindController,
@@ -130,7 +131,7 @@
         Resources res = context.getResources();
         mAlwaysExpandNonGroupedNotification =
                 res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
-        mBubbleController = bubbleController;
+        mBubblesOptional = bubblesOptional;
         mDynamicPrivacyController = privacyController;
         mDynamicChildBindController = dynamicChildBindController;
         mLowPriorityInflationHelper = lowPriorityInflationHelper;
@@ -157,8 +158,10 @@
         final int N = activeNotifications.size();
         for (int i = 0; i < N; i++) {
             NotificationEntry ent = activeNotifications.get(i);
+            final boolean isBubbleNotificationSuppressedFromShade = mBubblesOptional.isPresent()
+                    && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(ent);
             if (ent.isRowDismissed() || ent.isRowRemoved()
-                    || mBubbleController.isBubbleNotificationSuppressedFromShade(ent)
+                    || isBubbleNotificationSuppressedFromShade
                     || mFgsSectionController.hasEntry(ent)) {
                 // we don't want to update removed notifications because they could
                 // temporarily become children if they were isolated before.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index d15b847..969cd90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -21,7 +21,7 @@
 import android.os.Handler;
 
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.media.MediaDataManager;
@@ -59,6 +59,8 @@
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
+import java.util.Optional;
+
 import dagger.Binds;
 import dagger.Lazy;
 import dagger.Module;
@@ -162,7 +164,7 @@
             StatusBarStateController statusBarStateController,
             NotificationEntryManager notificationEntryManager,
             KeyguardBypassController bypassController,
-            BubbleController bubbleController,
+            Optional<Bubbles> bubblesOptional,
             DynamicPrivacyController privacyController,
             ForegroundServiceSectionController fgsSectionController,
             DynamicChildBindController dynamicChildBindController,
@@ -177,7 +179,7 @@
                 statusBarStateController,
                 notificationEntryManager,
                 bypassController,
-                bubbleController,
+                bubblesOptional,
                 privacyController,
                 fgsSectionController,
                 dynamicChildBindController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index d364689..7d8979c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -22,7 +22,7 @@
 import android.view.View;
 
 import com.android.systemui.DejankUtils;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -38,19 +38,19 @@
 public final class NotificationClicker implements View.OnClickListener {
     private static final String TAG = "NotificationClicker";
 
-    private final BubbleController mBubbleController;
     private final NotificationClickerLogger mLogger;
-    private final Optional<StatusBar> mStatusBar;
+    private final Optional<StatusBar> mStatusBarOptional;
+    private final Optional<Bubbles> mBubblesOptional;
     private final NotificationActivityStarter mNotificationActivityStarter;
 
     private NotificationClicker(
-            BubbleController bubbleController,
             NotificationClickerLogger logger,
-            Optional<StatusBar> statusBar,
+            Optional<StatusBar> statusBarOptional,
+            Optional<Bubbles> bubblesOptional,
             NotificationActivityStarter notificationActivityStarter) {
-        mBubbleController = bubbleController;
         mLogger = logger;
-        mStatusBar = statusBar;
+        mStatusBarOptional = statusBarOptional;
+        mBubblesOptional = bubblesOptional;
         mNotificationActivityStarter = notificationActivityStarter;
     }
 
@@ -61,7 +61,7 @@
             return;
         }
 
-        mStatusBar.ifPresent(statusBar -> statusBar.wakeUpIfDozing(
+        mStatusBarOptional.ifPresent(statusBar -> statusBar.wakeUpIfDozing(
                 SystemClock.uptimeMillis(), v, "NOTIFICATION_CLICK"));
 
         final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
@@ -92,8 +92,8 @@
         row.setJustClicked(true);
         DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
 
-        if (!row.getEntry().isBubble()) {
-            mBubbleController.collapseStack();
+        if (!row.getEntry().isBubble() && mBubblesOptional.isPresent()) {
+            mBubblesOptional.get().collapseStack();
         }
 
         mNotificationActivityStarter.onNotificationClicked(entry.getSbn(), row);
@@ -118,26 +118,23 @@
 
     /** Daggerized builder for NotificationClicker. */
     public static class Builder {
-        private final BubbleController mBubbleController;
         private final NotificationClickerLogger mLogger;
 
         @Inject
-        public Builder(
-                BubbleController bubbleController,
-                NotificationClickerLogger logger) {
-            mBubbleController = bubbleController;
+        public Builder(NotificationClickerLogger logger) {
             mLogger = logger;
         }
 
         /** Builds an instance. */
         public NotificationClicker build(
-                Optional<StatusBar> statusBar,
+                Optional<StatusBar> statusBarOptional,
+                Optional<Bubbles> bubblesOptional,
                 NotificationActivityStarter notificationActivityStarter
         ) {
             return new NotificationClicker(
-                    mBubbleController,
                     mLogger,
-                    statusBar,
+                    statusBarOptional,
+                    bubblesOptional,
                     notificationActivityStarter);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 4ddc1dc..0455b0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -26,6 +27,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
 
 import java.util.HashSet;
+import java.util.Optional;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -54,7 +56,7 @@
 public class BubbleCoordinator implements Coordinator {
     private static final String TAG = "BubbleCoordinator";
 
-    private final BubbleController mBubbleController;
+    private final Optional<Bubbles> mBubblesOptional;
     private final NotifCollection mNotifCollection;
     private final Set<String> mInterceptedDismissalEntries = new HashSet<>();
     private NotifPipeline mNotifPipeline;
@@ -62,9 +64,9 @@
 
     @Inject
     public BubbleCoordinator(
-            BubbleController bubbleController,
+            Optional<Bubbles> bubblesOptional,
             NotifCollection notifCollection) {
-        mBubbleController = bubbleController;
+        mBubblesOptional = bubblesOptional;
         mNotifCollection = notifCollection;
     }
 
@@ -73,13 +75,17 @@
         mNotifPipeline = pipeline;
         mNotifPipeline.addNotificationDismissInterceptor(mDismissInterceptor);
         mNotifPipeline.addFinalizeFilter(mNotifFilter);
-        mBubbleController.addNotifCallback(mNotifCallback);
+        if (mBubblesOptional.isPresent()) {
+            mBubblesOptional.get().addNotifCallback(mNotifCallback);
+        }
+
     }
 
     private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
         @Override
         public boolean shouldFilterOut(NotificationEntry entry, long now) {
-            return mBubbleController.isBubbleNotificationSuppressedFromShade(entry);
+            return mBubblesOptional.isPresent()
+                    && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(entry);
         }
     };
 
@@ -97,7 +103,8 @@
         @Override
         public boolean shouldInterceptDismissal(NotificationEntry entry) {
             // for experimental bubbles
-            if (mBubbleController.handleDismissalInterception(entry)) {
+            if (mBubblesOptional.isPresent()
+                    && mBubblesOptional.get().handleDismissalInterception(entry)) {
                 mInterceptedDismissalEntries.add(entry.getKey());
                 return true;
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index 21d54c8..490989d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -21,9 +21,8 @@
 import android.util.ArraySet;
 import android.util.Log;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -43,6 +42,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 
 import javax.inject.Inject;
 
@@ -64,25 +64,20 @@
             new ArraySet<>();
     private final ArraySet<OnGroupChangeListener> mGroupChangeListeners = new ArraySet<>();
     private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier;
+    private final Optional<Lazy<Bubbles>> mBubblesOptional;
     private int mBarState = -1;
     private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
     private HeadsUpManager mHeadsUpManager;
     private boolean mIsUpdatingUnchangedGroup;
-    @Nullable private BubbleController mBubbleController = null;
 
     @Inject
     public NotificationGroupManagerLegacy(
             StatusBarStateController statusBarStateController,
-            Lazy<PeopleNotificationIdentifier> peopleNotificationIdentifier) {
+            Lazy<PeopleNotificationIdentifier> peopleNotificationIdentifier,
+            Optional<Lazy<Bubbles>> bubblesOptional) {
         statusBarStateController.addCallback(this);
         mPeopleNotificationIdentifier = peopleNotificationIdentifier;
-    }
-
-    private BubbleController getBubbleController() {
-        if (mBubbleController == null) {
-            mBubbleController = Dependency.get(BubbleController.class);
-        }
-        return mBubbleController;
+        mBubblesOptional = bubblesOptional;
     }
 
     /**
@@ -247,7 +242,8 @@
         int childCount = 0;
         boolean hasBubbles = false;
         for (NotificationEntry entry : group.children.values()) {
-            if (!getBubbleController().isBubbleNotificationSuppressedFromShade(entry)) {
+            if (mBubblesOptional.isPresent() && !mBubblesOptional.get().get()
+                    .isBubbleNotificationSuppressedFromShade(entry)) {
                 childCount++;
             } else {
                 hasBubbles = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 498b8e8..1311e3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -17,11 +17,13 @@
 package com.android.systemui.statusbar.notification.collection.render
 
 import android.annotation.StringRes
+import android.content.Intent
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import com.android.systemui.R
-import com.android.systemui.statusbar.notification.dagger.HeaderClick
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.notification.dagger.HeaderClickAction
 import com.android.systemui.statusbar.notification.dagger.HeaderText
 import com.android.systemui.statusbar.notification.dagger.NodeLabel
 import com.android.systemui.statusbar.notification.dagger.SectionHeaderScope
@@ -39,11 +41,19 @@
     @NodeLabel override val nodeLabel: String,
     private val layoutInflater: LayoutInflater,
     @HeaderText @StringRes private val headerTextResId: Int,
-    @HeaderClick private val onHeaderClickListener: View.OnClickListener
+    private val activityStarter: ActivityStarter,
+    @HeaderClickAction private val clickIntentAction: String
 ) : NodeController, SectionHeaderController {
 
     private var _view: SectionHeaderView? = null
     private var clearAllClickListener: View.OnClickListener? = null
+    private val onHeaderClickListener = View.OnClickListener {
+        activityStarter.startActivity(
+                Intent(clickIntentAction),
+                true /* onlyProvisioned */,
+                true /* dismissShade */,
+                Intent.FLAG_ACTIVITY_SINGLE_TOP)
+    }
 
     override fun reinflateView(parent: ViewGroup) {
         var oldPos = -1
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
index 179d49c..2a9cfd0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
@@ -17,12 +17,9 @@
 package com.android.systemui.statusbar.notification.dagger
 
 import android.annotation.StringRes
-import android.content.Intent
 import android.provider.Settings
-import android.view.View
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderNodeControllerImpl
@@ -39,18 +36,6 @@
 object NotificationSectionHeadersModule {
 
     @Provides
-    @HeaderClick
-    @JvmStatic fun providesOnHeaderClickListener(
-        activityStarter: ActivityStarter
-    ) = View.OnClickListener {
-        activityStarter.startActivity(
-                Intent(Settings.ACTION_NOTIFICATION_SETTINGS),
-                true /* onlyProvisioned */,
-                true /* dismissShade */,
-                Intent.FLAG_ACTIVITY_SINGLE_TOP)
-    }
-
-    @Provides
     @IncomingHeader
     @SysUISingleton
     @JvmStatic fun providesIncomingHeaderSubcomponent(
@@ -58,6 +43,7 @@
     ) = builder.get()
             .nodeLabel("incoming header")
             .headerText(R.string.notification_section_header_incoming)
+            .clickIntentAction(Settings.ACTION_NOTIFICATION_SETTINGS)
             .build()
 
     @Provides
@@ -68,6 +54,7 @@
     ) = builder.get()
             .nodeLabel("alerting header")
             .headerText(R.string.notification_section_header_alerting)
+            .clickIntentAction(Settings.ACTION_NOTIFICATION_SETTINGS)
             .build()
 
     @Provides
@@ -78,6 +65,7 @@
     ) = builder.get()
             .nodeLabel("people header")
             .headerText(R.string.notification_section_header_conversations)
+            .clickIntentAction(Settings.ACTION_CONVERSATION_SETTINGS)
             .build()
 
     @Provides
@@ -88,6 +76,7 @@
     ) = builder.get()
             .nodeLabel("silent header")
             .headerText(R.string.notification_section_header_gentle)
+            .clickIntentAction(Settings.ACTION_NOTIFICATION_SETTINGS)
             .build()
 
     @Provides
@@ -151,6 +140,7 @@
         fun build(): SectionHeaderControllerSubcomponent
         @BindsInstance fun nodeLabel(@NodeLabel nodeLabel: String): Builder
         @BindsInstance fun headerText(@HeaderText @StringRes headerText: Int): Builder
+        @BindsInstance fun clickIntentAction(@HeaderClickAction clickIntentAction: String): Builder
     }
 }
 
@@ -188,7 +178,7 @@
 
 @Qualifier
 @Retention(AnnotationRetention.BINARY)
-annotation class HeaderClick
+annotation class HeaderClickAction
 
 @Scope
 @Retention(AnnotationRetention.BINARY)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 01333f0..4fff99b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -26,7 +26,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -74,6 +74,7 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.leak.LeakDetector;
 
+import java.util.Optional;
 import java.util.concurrent.Executor;
 
 import javax.inject.Provider;
@@ -132,7 +133,7 @@
             UserContextProvider contextTracker,
             Provider<PriorityOnboardingDialogController.Builder> builderProvider,
             AssistantFeedbackController assistantFeedbackController,
-            BubbleController bubbleController,
+            Optional<Bubbles> bubblesOptional,
             UiEventLogger uiEventLogger,
             OnUserInteractionCallback onUserInteractionCallback) {
         return new NotificationGutsManager(
@@ -149,7 +150,7 @@
                 contextTracker,
                 builderProvider,
                 assistantFeedbackController,
-                bubbleController,
+                bubblesOptional,
                 uiEventLogger,
                 onUserInteractionCallback);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
index 9da8b8a3..049b471 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.init
 
 import android.service.notification.StatusBarNotification
+import com.android.systemui.bubbles.Bubbles
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
@@ -25,6 +26,7 @@
 import com.android.systemui.statusbar.phone.StatusBar
 import java.io.FileDescriptor
 import java.io.PrintWriter
+import java.util.Optional
 
 /**
  * The master controller for all notifications-related work
@@ -35,6 +37,7 @@
 interface NotificationsController {
     fun initialize(
         statusBar: StatusBar,
+        bubblesOptional: Optional<Bubbles>,
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
         notificationActivityStarter: NotificationActivityStarter,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 9fb2928..45a5d10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.init
 
 import android.service.notification.StatusBarNotification
+import com.android.systemui.bubbles.Bubbles
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.FeatureFlags
@@ -75,6 +76,7 @@
 
     override fun initialize(
         statusBar: StatusBar,
+        bubblesOptional: Optional<Bubbles>,
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
         notificationActivityStarter: NotificationActivityStarter,
@@ -90,7 +92,8 @@
         listController.bind()
 
         notificationRowBinder.setNotificationClicker(
-                clickerBuilder.build(Optional.of(statusBar), notificationActivityStarter))
+                clickerBuilder.build(
+                        Optional.of(statusBar), bubblesOptional, notificationActivityStarter))
         notificationRowBinder.setUpWithPresenter(
                 presenter,
                 listContainer,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
index ded855d..7569c1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.init
 
 import android.service.notification.StatusBarNotification
+import com.android.systemui.bubbles.Bubbles
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.NotificationListener
 import com.android.systemui.statusbar.NotificationPresenter
@@ -26,6 +27,7 @@
 import com.android.systemui.statusbar.phone.StatusBar
 import java.io.FileDescriptor
 import java.io.PrintWriter
+import java.util.Optional
 import javax.inject.Inject
 
 /**
@@ -37,6 +39,7 @@
 
     override fun initialize(
         statusBar: StatusBar,
+        bubblesOptional: Optional<Bubbles>,
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
         notificationActivityStarter: NotificationActivityStarter,
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 811a72d..113c115 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
@@ -72,7 +72,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -1074,7 +1074,7 @@
         return new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                Dependency.get(BubbleController.class)
+                Dependency.get(Bubbles.class)
                         .onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */);
                 mHeadsUpManager.removeNotification(mEntry.getKey(), true /* releaseImmediately */);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index b19997d..07a4a18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -67,7 +67,7 @@
 import com.android.settingslib.notification.ConversationIconFactory;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.notification.NotificationChannelHelper;
@@ -75,6 +75,7 @@
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 import java.lang.annotation.Retention;
+import java.util.Optional;
 
 import javax.inject.Provider;
 
@@ -93,7 +94,7 @@
     private OnUserInteractionCallback mOnUserInteractionCallback;
     private Handler mMainHandler;
     private Handler mBgHandler;
-    private BubbleController mBubbleController;
+    private Optional<Bubbles> mBubblesOptional;
     private String mPackageName;
     private String mAppName;
     private int mAppUid;
@@ -222,7 +223,7 @@
             @Main Handler mainHandler,
             @Background Handler bgHandler,
             OnConversationSettingsClickListener onConversationSettingsClickListener,
-            BubbleController bubbleController) {
+            Optional<Bubbles> bubblesOptional) {
         mSelectedAction = -1;
         mINotificationManager = iNotificationManager;
         mOnUserInteractionCallback = onUserInteractionCallback;
@@ -241,7 +242,7 @@
         mIconFactory = conversationIconFactory;
         mUserContext = userContext;
         mBubbleMetadata = bubbleMetadata;
-        mBubbleController = bubbleController;
+        mBubblesOptional = bubblesOptional;
         mBuilderProvider = builderProvider;
         mMainHandler = mainHandler;
         mBgHandler = bgHandler;
@@ -640,9 +641,11 @@
                                 mINotificationManager.setBubblesAllowed(mAppPkg, mAppUid,
                                         BUBBLE_PREFERENCE_SELECTED);
                             }
-                            post(() -> {
-                                mBubbleController.onUserChangedImportance(mEntry);
-                            });
+                            if (mBubblesOptional.isPresent()) {
+                                post(() -> {
+                                    mBubblesOptional.get().onUserChangedImportance(mEntry);
+                                });
+                            }
                         }
                         mChannelToUpdate.setImportance(Math.max(
                                 mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 7d418f3..373f20e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -47,7 +47,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -70,6 +70,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Optional;
 
 import javax.inject.Provider;
 
@@ -116,7 +117,7 @@
     private final Lazy<StatusBar> mStatusBarLazy;
     private final Handler mMainHandler;
     private final Handler mBgHandler;
-    private final BubbleController mBubbleController;
+    private final Optional<Bubbles> mBubblesOptional;
     private Runnable mOpenRunnable;
     private final INotificationManager mNotificationManager;
     private final LauncherApps mLauncherApps;
@@ -141,7 +142,7 @@
             UserContextProvider contextTracker,
             Provider<PriorityOnboardingDialogController.Builder> builderProvider,
             AssistantFeedbackController assistantFeedbackController,
-            BubbleController bubbleController,
+            Optional<Bubbles> bubblesOptional,
             UiEventLogger uiEventLogger,
             OnUserInteractionCallback onUserInteractionCallback) {
         mContext = context;
@@ -157,7 +158,7 @@
         mBuilderProvider = builderProvider;
         mChannelEditorDialogController = channelEditorDialogController;
         mAssistantFeedbackController = assistantFeedbackController;
-        mBubbleController = bubbleController;
+        mBubblesOptional = bubblesOptional;
         mUiEventLogger = uiEventLogger;
         mOnUserInteractionCallback = onUserInteractionCallback;
     }
@@ -490,7 +491,7 @@
                 mMainHandler,
                 mBgHandler,
                 onConversationSettingsListener,
-                mBubbleController);
+                mBubblesOptional);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index b47c59a..0a366c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -85,8 +85,6 @@
 import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory;
 import com.android.systemui.tuner.TunerService;
 
-import java.util.concurrent.Executor;
-
 /**
  * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
  * text.
@@ -561,7 +559,7 @@
             }
         };
         if (!mKeyguardStateController.canDismissLockScreen()) {
-            Dependency.get(Executor.class).execute(runnable);
+            Dependency.get(Dependency.BACKGROUND_EXECUTOR).execute(runnable);
         } else {
             boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
                     && Dependency.get(TunerService.class).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index bda35fb..d1c8355 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -19,7 +19,7 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
@@ -40,6 +40,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.function.Function;
 
 import javax.inject.Inject;
@@ -65,7 +66,7 @@
     private final NotificationWakeUpCoordinator mWakeUpCoordinator;
     private final KeyguardBypassController mBypassController;
     private final DozeParameters mDozeParameters;
-    private final BubbleController mBubbleController;
+    private final Optional<Bubbles> mBubblesOptional;
     private final StatusBarWindowController mStatusBarWindowController;
 
     private int mIconSize;
@@ -114,7 +115,7 @@
             NotificationMediaManager notificationMediaManager,
             NotificationListener notificationListener,
             DozeParameters dozeParameters,
-            BubbleController bubbleController,
+            Optional<Bubbles> bubblesOptional,
             DemoModeController demoModeController,
             DarkIconDispatcher darkIconDispatcher,
             StatusBarWindowController statusBarWindowController) {
@@ -127,7 +128,7 @@
         mWakeUpCoordinator = wakeUpCoordinator;
         wakeUpCoordinator.addListener(this);
         mBypassController = keyguardBypassController;
-        mBubbleController = bubbleController;
+        mBubblesOptional = bubblesOptional;
         mDemoModeController = demoModeController;
         mDemoModeController.addCallback(this);
         mStatusBarWindowController = statusBarWindowController;
@@ -298,7 +299,7 @@
                         || !entry.isPulseSuppressed())) {
             return false;
         }
-        if (mBubbleController.isBubbleExpanded(entry)) {
+        if (mBubblesOptional.isPresent() && mBubblesOptional.get().isBubbleExpanded(entry)) {
             return false;
         }
         return true;
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 e95cf28..4af2787 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -34,6 +34,8 @@
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
@@ -139,6 +141,7 @@
 
     private ScrimView mScrimInFront;
     private ScrimView mScrimBehind;
+    @Nullable
     private ScrimView mScrimForBubble;
 
     private Runnable mScrimBehindChangeRunnable;
@@ -238,7 +241,7 @@
      * Attach the controller to the supplied views.
      */
     public void attachViews(
-            ScrimView scrimBehind, ScrimView scrimInFront, ScrimView scrimForBubble) {
+            ScrimView scrimBehind, ScrimView scrimInFront, @Nullable ScrimView scrimForBubble) {
         mScrimBehind = scrimBehind;
         mScrimInFront = scrimInFront;
         mScrimForBubble = scrimForBubble;
@@ -258,7 +261,9 @@
 
         mScrimBehind.setDefaultFocusHighlightEnabled(false);
         mScrimInFront.setDefaultFocusHighlightEnabled(false);
-        mScrimForBubble.setDefaultFocusHighlightEnabled(false);
+        if (mScrimForBubble != null) {
+            mScrimForBubble.setDefaultFocusHighlightEnabled(false);
+        }
         updateScrims();
         mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
     }
@@ -455,7 +460,11 @@
         }
     }
 
-    private void setOrAdaptCurrentAnimation(View scrim) {
+    private void setOrAdaptCurrentAnimation(@Nullable View scrim) {
+        if (scrim == null) {
+            return;
+        }
+
         float alpha = getCurrentScrimAlpha(scrim);
         if (isAnimating(scrim)) {
             // Adapt current animation.
@@ -606,11 +615,9 @@
             // 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;
@@ -632,7 +639,12 @@
         }
         setScrimAlpha(mScrimInFront, mInFrontAlpha);
         setScrimAlpha(mScrimBehind, mBehindAlpha);
-        setScrimAlpha(mScrimForBubble, mBubbleAlpha);
+
+        if (mScrimForBubble != null) {
+            boolean animateScrimForBubble = mScrimForBubble.getViewAlpha() != 0 && !mBlankScreen;
+            mScrimForBubble.setColors(mColors, animateScrimForBubble);
+            setScrimAlpha(mScrimForBubble, mBubbleAlpha);
+        }
         // The animation could have all already finished, let's call onFinished just in case
         onFinished();
         dispatchScrimsVisible();
@@ -828,12 +840,14 @@
             mBubbleTint = Color.TRANSPARENT;
             updateScrimColor(mScrimInFront, mInFrontAlpha, mInFrontTint);
             updateScrimColor(mScrimBehind, mBehindAlpha, mBehindTint);
-            updateScrimColor(mScrimForBubble, mBubbleAlpha, mBubbleTint);
+            if (mScrimForBubble != null) {
+                updateScrimColor(mScrimForBubble, mBubbleAlpha, mBubbleTint);
+            }
         }
     }
 
-    private boolean isAnimating(View scrim) {
-        return scrim.getTag(TAG_KEY_ANIM) != null;
+    private boolean isAnimating(@Nullable View scrim) {
+        return scrim != null && scrim.getTag(TAG_KEY_ANIM) != null;
     }
 
     @VisibleForTesting
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 2db36f4..fc91c16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -19,6 +19,8 @@
 import android.graphics.Color;
 import android.os.Trace;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -212,7 +214,9 @@
                 // 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 */);
+                if (mScrimForBubble != null) {
+                    updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
+                }
 
                 // Scrims should still be black at the end of the transition.
                 mFrontTint = Color.BLACK;
@@ -258,7 +262,7 @@
     float mDefaultScrimAlpha;
     ScrimView mScrimInFront;
     ScrimView mScrimBehind;
-    ScrimView mScrimForBubble;
+    @Nullable ScrimView mScrimForBubble;
 
     DozeParameters mDozeParameters;
     DockManager mDockManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index 1ce2219..af2f3e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -22,7 +22,7 @@
 import android.view.WindowManager;
 
 import com.android.systemui.assist.AssistManager;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
@@ -31,6 +31,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 
 import java.util.ArrayList;
+import java.util.Optional;
 
 import javax.inject.Inject;
 
@@ -50,7 +51,7 @@
     private final int mDisplayId;
     protected final Lazy<StatusBar> mStatusBarLazy;
     private final Lazy<AssistManager> mAssistManagerLazy;
-    private final Lazy<BubbleController> mBubbleControllerLazy;
+    private final Optional<Lazy<Bubbles>> mBubblesOptional;
 
     private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
 
@@ -63,7 +64,7 @@
             WindowManager windowManager,
             Lazy<StatusBar> statusBarLazy,
             Lazy<AssistManager> assistManagerLazy,
-            Lazy<BubbleController> bubbleControllerLazy
+            Optional<Lazy<Bubbles>> bubblesOptional
     ) {
         mCommandQueue = commandQueue;
         mStatusBarStateController = statusBarStateController;
@@ -73,7 +74,7 @@
         // TODO: Remove circular reference to StatusBar when possible.
         mStatusBarLazy = statusBarLazy;
         mAssistManagerLazy = assistManagerLazy;
-        mBubbleControllerLazy = bubbleControllerLazy;
+        mBubblesOptional = bubblesOptional;
     }
 
     @Override
@@ -133,8 +134,8 @@
 
             getStatusBar().getNotificationShadeWindowViewController().cancelExpandHelper();
             getStatusBarView().collapsePanel(true /* animate */, delayed, speedUpFactor);
-        } else {
-            mBubbleControllerLazy.get().collapseStack();
+        } else if (mBubblesOptional.isPresent()) {
+            mBubblesOptional.get().get().collapseStack();
         }
     }
 
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 8254b7f..994af09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -147,6 +147,7 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.charging.WirelessChargingAnimation;
 import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -649,7 +650,7 @@
     protected StatusBarNotificationPresenter mPresenter;
     private NotificationActivityStarter mNotificationActivityStarter;
     private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
-    private final BubbleController mBubbleController;
+    private final Optional<Bubbles> mBubblesOptional;
     private final BubbleController.BubbleExpandListener mBubbleExpandListener;
 
     private ActivityIntentHelper mActivityIntentHelper;
@@ -698,7 +699,7 @@
             WakefulnessLifecycle wakefulnessLifecycle,
             SysuiStatusBarStateController statusBarStateController,
             VibratorHelper vibratorHelper,
-            BubbleController bubbleController,
+            Optional<Bubbles> bubblesOptional,
             VisualStabilityManager visualStabilityManager,
             DeviceProvisionedController deviceProvisionedController,
             NavigationBarController navigationBarController,
@@ -778,7 +779,7 @@
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mStatusBarStateController = statusBarStateController;
         mVibratorHelper = vibratorHelper;
-        mBubbleController = bubbleController;
+        mBubblesOptional = bubblesOptional;
         mVisualStabilityManager = visualStabilityManager;
         mDeviceProvisionedController = deviceProvisionedController;
         mNavigationBarController = navigationBarController;
@@ -834,7 +835,10 @@
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
         mBypassHeadsUpNotifier.setUp();
-        mBubbleController.setExpandListener(mBubbleExpandListener);
+        if (mBubblesOptional.isPresent()) {
+            mBubblesOptional.get().setExpandListener(mBubbleExpandListener);
+        }
+
         mActivityIntentHelper = new ActivityIntentHelper(mContext);
 
         mColorExtractor.addOnColorsChangedListener(this);
@@ -1145,7 +1149,8 @@
 
         ScrimView scrimBehind = mNotificationShadeWindowView.findViewById(R.id.scrim_behind);
         ScrimView scrimInFront = mNotificationShadeWindowView.findViewById(R.id.scrim_in_front);
-        ScrimView scrimForBubble = mBubbleController.getScrimForBubble();
+        ScrimView scrimForBubble = mBubblesOptional.isPresent()
+                ? mBubblesOptional.get().getScrimForBubble() : null;
 
         mScrimController.setScrimVisibleListener(scrimsVisible -> {
             mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible);
@@ -1341,6 +1346,7 @@
 
         mNotificationsController.initialize(
                 this,
+                mBubblesOptional,
                 mPresenter,
                 mStackScrollerController.getNotificationListContainer(),
                 mNotificationActivityStarter,
@@ -2491,10 +2497,12 @@
 
     /** Temporarily hides Bubbles if the status bar is hidden. */
     private void updateBubblesVisibility() {
-        mBubbleController.onStatusBarVisibilityChanged(
-                mStatusBarMode != MODE_LIGHTS_OUT
-                        && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT
-                        && !mStatusBarWindowHidden);
+        if (mBubblesOptional.isPresent()) {
+            mBubblesOptional.get().onStatusBarVisibilityChanged(
+                    mStatusBarMode != MODE_LIGHTS_OUT
+                            && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT
+                            && !mStatusBarWindowHidden);
+        }
     }
 
     void checkBarMode(@TransitionMode int mode, @WindowVisibleState int windowState,
@@ -2817,8 +2825,8 @@
                 if (mRemoteInputManager.getController() != null) {
                     mRemoteInputManager.getController().closeRemoteInputs();
                 }
-                if (mBubbleController.isStackExpanded()) {
-                    mBubbleController.collapseStack();
+                if (mBubblesOptional.isPresent() && mBubblesOptional.get().isStackExpanded()) {
+                    mBubblesOptional.get().collapseStack();
                 }
                 if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) {
                     int flags = CommandQueue.FLAG_EXCLUDE_NONE;
@@ -2833,9 +2841,9 @@
                 if (mNotificationShadeWindowController != null) {
                     mNotificationShadeWindowController.setNotTouchable(false);
                 }
-                if (mBubbleController.isStackExpanded()) {
+                if (mBubblesOptional.isPresent() && mBubblesOptional.get().isStackExpanded()) {
                     // Post to main thread handler, since updating the UI.
-                    mMainThreadHandler.post(() -> mBubbleController.collapseStack());
+                    mMainThreadHandler.post(() -> mBubblesOptional.get().collapseStack());
                 }
                 finishBarAnimations();
                 resetUserExpandedStates();
@@ -3535,8 +3543,8 @@
         if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
             if (mNotificationPanelViewController.canPanelBeCollapsed()) {
                 mShadeController.animateCollapsePanels();
-            } else {
-                mBubbleController.performBackPressIfNeeded();
+            } else if (mBubblesOptional.isPresent()) {
+                mBubblesOptional.get().performBackPressIfNeeded();
             }
             return true;
         }
@@ -4054,7 +4062,7 @@
             mScrimController.transitionTo(ScrimState.AOD);
         } else if (mIsKeyguard && !unlocking) {
             mScrimController.transitionTo(ScrimState.KEYGUARD);
-        } else if (mBubbleController.isStackExpanded()) {
+        } else if (mBubblesOptional.isPresent() && mBubblesOptional.get().isStackExpanded()) {
             mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED, mUnlockScrimCallback);
         } else {
             mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 737cdeb..aa01642 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -48,7 +48,7 @@
 import com.android.systemui.ActivityIntentHelper;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.assist.AssistManager;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -77,6 +77,7 @@
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import java.util.Optional;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
@@ -103,7 +104,7 @@
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final KeyguardManager mKeyguardManager;
     private final IDreamManager mDreamManager;
-    private final BubbleController mBubbleController;
+    private final Optional<Bubbles> mBubblesOptional;
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final NotificationRemoteInputManager mRemoteInputManager;
     private final GroupMembershipManager mGroupMembershipManager;
@@ -141,7 +142,7 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             KeyguardManager keyguardManager,
             IDreamManager dreamManager,
-            BubbleController bubbleController,
+            Optional<Bubbles> bubblesOptional,
             Lazy<AssistManager> assistManagerLazy,
             NotificationRemoteInputManager remoteInputManager,
             GroupMembershipManager groupMembershipManager,
@@ -175,7 +176,7 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mKeyguardManager = keyguardManager;
         mDreamManager = dreamManager;
-        mBubbleController = bubbleController;
+        mBubblesOptional = bubblesOptional;
         mAssistManagerLazy = assistManagerLazy;
         mRemoteInputManager = remoteInputManager;
         mGroupMembershipManager = groupMembershipManager;
@@ -386,11 +387,14 @@
     }
 
     private void expandBubbleStackOnMainThread(NotificationEntry entry) {
+        if (!mBubblesOptional.isPresent()) {
+            return;
+        }
+
         if (Looper.getMainLooper().isCurrentThread()) {
-            mBubbleController.expandStackAndSelectBubble(entry);
+            mBubblesOptional.get().expandStackAndSelectBubble(entry);
         } else {
-            mMainThreadHandler.post(
-                    () -> mBubbleController.expandStackAndSelectBubble(entry));
+            mMainThreadHandler.post(() -> mBubblesOptional.get().expandStackAndSelectBubble(entry));
         }
     }
 
@@ -602,7 +606,7 @@
         private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
         private final KeyguardManager mKeyguardManager;
         private final IDreamManager mDreamManager;
-        private final BubbleController mBubbleController;
+        private final Optional<Bubbles> mBubblesOptional;
         private final Lazy<AssistManager> mAssistManagerLazy;
         private final NotificationRemoteInputManager mRemoteInputManager;
         private final GroupMembershipManager mGroupMembershipManager;
@@ -639,7 +643,7 @@
                 StatusBarKeyguardViewManager statusBarKeyguardViewManager,
                 KeyguardManager keyguardManager,
                 IDreamManager dreamManager,
-                BubbleController bubbleController,
+                Optional<Bubbles> bubblesOptional,
                 Lazy<AssistManager> assistManagerLazy,
                 NotificationRemoteInputManager remoteInputManager,
                 GroupMembershipManager groupMembershipManager,
@@ -669,7 +673,7 @@
             mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
             mKeyguardManager = keyguardManager;
             mDreamManager = dreamManager;
-            mBubbleController = bubbleController;
+            mBubblesOptional = bubblesOptional;
             mAssistManagerLazy = assistManagerLazy;
             mRemoteInputManager = remoteInputManager;
             mGroupMembershipManager = groupMembershipManager;
@@ -725,7 +729,7 @@
                     mStatusBarKeyguardViewManager,
                     mKeyguardManager,
                     mDreamManager,
-                    mBubbleController,
+                    mBubblesOptional,
                     mAssistManagerLazy,
                     mRemoteInputManager,
                     mGroupMembershipManager,
@@ -736,12 +740,10 @@
                     mLockPatternUtils,
                     mRemoteInputCallback,
                     mActivityIntentHelper,
-
                     mFeatureFlags,
                     mMetricsLogger,
                     mLogger,
                     mOnUserInteractionCallback,
-
                     mStatusBar,
                     mNotificationPresenter,
                     mNotificationPanelViewController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index b7f8314..3f29a4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -31,7 +31,7 @@
 import com.android.systemui.InitController;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -156,7 +156,7 @@
             WakefulnessLifecycle wakefulnessLifecycle,
             SysuiStatusBarStateController statusBarStateController,
             VibratorHelper vibratorHelper,
-            BubbleController bubbleController,
+            Optional<Bubbles> bubblesOptional,
             VisualStabilityManager visualStabilityManager,
             DeviceProvisionedController deviceProvisionedController,
             NavigationBarController navigationBarController,
@@ -235,7 +235,7 @@
                 wakefulnessLifecycle,
                 statusBarStateController,
                 vibratorHelper,
-                bubbleController,
+                bubblesOptional,
                 visualStabilityManager,
                 deviceProvisionedController,
                 navigationBarController,
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index ae96829..bf9df8e 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -24,6 +24,7 @@
 import android.view.IWindowManager;
 
 import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.pip.Pip;
@@ -148,5 +149,8 @@
     abstract SplitScreen optionalSplitScreen();
 
     @BindsOptionalOf
+    abstract Bubbles optionalBubbles();
+
+    @BindsOptionalOf
     abstract OneHanded optionalOneHanded();
 }
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 3b8f1bb..2f8d3f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -1011,6 +1011,29 @@
         verify(mNotificationGroupManager, times(1)).onEntryRemoved(groupSummary.getEntry());
     }
 
+
+    /**
+     * Verifies that when a non visually interruptive update occurs for a bubble in the overflow,
+     * the that bubble does not get promoted from the overflow.
+     */
+    @Test
+    public void test_notVisuallyInterruptive_updateOverflowBubble_notAdded() {
+        // Setup
+        mBubbleController.updateBubble(mRow.getEntry());
+        mBubbleController.updateBubble(mRow2.getEntry());
+        assertTrue(mBubbleController.hasBubbles());
+
+        // Overflow it
+        mBubbleData.dismissBubbleWithKey(mRow.getEntry().getKey(),
+                BubbleController.DISMISS_USER_GESTURE);
+        assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getEntry().getKey())).isFalse();
+        assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey())).isTrue();
+
+        // Test
+        mBubbleController.updateBubble(mRow.getEntry());
+        assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getEntry().getKey())).isFalse();
+    }
+
     /**
      * 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
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 315caee..4bbc41e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -513,6 +513,26 @@
     }
 
     /**
+     * Verifies that when a non visually interruptive update occurs, that the selection does not
+     * change.
+     */
+    @Test
+    public void test_notVisuallyInterruptive_updateBubble_selectionDoesntChange() {
+        // Setup
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryB1, 2000);
+        sendUpdatedEntryAtTime(mEntryB2, 3000);
+        sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, B2, B1, A1]
+        mBubbleData.setListener(mListener);
+
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA2);
+
+        // Test
+        sendUpdatedEntryAtTime(mEntryB1, 5000, false /* isVisuallyInterruptive */);
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA2);
+    }
+
+    /**
      * Verifies that a request to expand the stack has no effect if there are no bubbles.
      */
     @Test
@@ -883,9 +903,15 @@
     }
 
     private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
+        sendUpdatedEntryAtTime(entry, postTime, true /* visuallyInterruptive */);
+    }
+
+    private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime,
+            boolean visuallyInterruptive) {
         setPostTime(entry, postTime);
         // BubbleController calls this:
         Bubble b = mBubbleData.getOrCreateBubble(entry, null /* persistedBubble */);
+        b.setVisuallyInterruptiveForTest(visuallyInterruptive);
         // And then this
         mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/,
                 true /* showInShade */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 8a30b00..81139f19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -43,7 +43,6 @@
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
@@ -53,7 +52,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers
 import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.Mock
 import org.mockito.Mockito.anyBoolean
@@ -203,7 +201,7 @@
     fun bindWhenUnattached() {
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, null, null, device, true, null)
-        player.bind(state)
+        player.bind(state, PACKAGE)
         assertThat(player.isPlaying()).isFalse()
     }
 
@@ -212,7 +210,7 @@
         player.attach(holder)
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
-        player.bind(state)
+        player.bind(state, PACKAGE)
         assertThat(appName.getText()).isEqualTo(APP)
         assertThat(titleText.getText()).isEqualTo(TITLE)
         assertThat(artistText.getText()).isEqualTo(ARTIST)
@@ -223,7 +221,7 @@
         player.attach(holder)
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
-        player.bind(state)
+        player.bind(state, PACKAGE)
         val list = ArgumentCaptor.forClass(ColorStateList::class.java)
         verify(view).setBackgroundTintList(list.capture())
         assertThat(list.value).isEqualTo(ColorStateList.valueOf(BG_COLOR))
@@ -234,7 +232,7 @@
         player.attach(holder)
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
-        player.bind(state)
+        player.bind(state, PACKAGE)
         assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
         assertThat(seamless.isEnabled()).isTrue()
     }
@@ -246,7 +244,7 @@
         player.attach(holder)
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
-        player.bind(state)
+        player.bind(state, PACKAGE)
         verify(expandedSet).setVisibility(seamless.id, View.GONE)
         verify(expandedSet).setVisibility(seamlessFallback.id, View.VISIBLE)
         verify(collapsedSet).setVisibility(seamless.id, View.GONE)
@@ -258,7 +256,7 @@
         player.attach(holder)
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
-        player.bind(state)
+        player.bind(state, PACKAGE)
         assertThat(seamless.isEnabled()).isTrue()
         assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
                 com.android.internal.R.string.ext_media_seamless_action))
@@ -270,7 +268,7 @@
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null,
                 resumption = true)
-        player.bind(state)
+        player.bind(state, PACKAGE)
         assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
         assertThat(seamless.isEnabled()).isFalse()
     }
@@ -322,31 +320,18 @@
 
     @Test
     fun dismissButtonClick() {
+        val mediaKey = "key for dismissal"
         player.attach(holder)
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
                 notificationKey = KEY)
-        player.bind(state)
+        player.bind(state, mediaKey)
 
         dismiss.callOnClick()
         val captor = ArgumentCaptor.forClass(ActivityStarter.OnDismissAction::class.java)
         verify(keyguardDismissUtil).executeWhenUnlocked(captor.capture(), anyBoolean())
 
         captor.value.onDismiss()
-        verify(mediaDataManager).dismissMediaData(eq(KEY), anyLong())
-    }
-
-    @Test
-    fun dismissButtonClick_nullNotificationKey() {
-        player.attach(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
-        player.bind(state)
-
-        verify(keyguardDismissUtil, never())
-                .executeWhenUnlocked(
-                        any(ActivityStarter.OnDismissAction::class.java),
-                        ArgumentMatchers.anyBoolean()
-                )
+        verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
index 3f10c8d..9b6dd05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
@@ -53,8 +53,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.navigationbar.buttons.KeyButtonView;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.recents.OverviewProxyService;
 
 import org.junit.Before;
@@ -71,7 +70,7 @@
 
     private KeyButtonView mKeyButtonView;
     private MetricsLogger mMetricsLogger;
-    private BubbleController mBubbleController;
+    private Bubbles mBubbles;
     private UiEventLogger mUiEventLogger;
     private InputManager mInputManager = mock(InputManager.class);
     @Captor
@@ -81,7 +80,7 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
         mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
-        mBubbleController = mDependency.injectMockDependency(BubbleController.class);
+        mBubbles = mDependency.injectMockDependency(Bubbles.class);
         mDependency.injectMockDependency(OverviewProxyService.class);
         mUiEventLogger = mDependency.injectMockDependency(UiEventLogger.class);
 
@@ -155,7 +154,7 @@
 
     @Test
     public void testBubbleEvents_bubbleExpanded() {
-        when(mBubbleController.getExpandedDisplayId(mContext)).thenReturn(3);
+        when(mBubbles.getExpandedDisplayId(mContext)).thenReturn(3);
 
         int action = KeyEvent.ACTION_DOWN;
         int flags = 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index 5d14898..faf43a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
+import com.android.systemui.statusbar.policy.HotspotController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.NetworkController;
 
@@ -73,12 +74,16 @@
     @Mock
     private QSTileHost mHost;
     @Mock
-    NetworkController.SignalCallback mCallback;
+    NetworkController.SignalCallback mSignalCallback;
     @Mock
     private MetricsLogger mMetricsLogger;
     @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
+    private HotspotController mHotspotController;
+    @Mock
+    private HotspotController.Callback mHotspotCallback;
+    @Mock
     private QSLogger mQSLogger;
 
     private TestableLooper mTestableLooper;
@@ -101,7 +106,8 @@
                 mQSLogger,
                 mController,
                 mKeyguard,
-                mNetworkController
+                mNetworkController,
+                mHotspotController
         );
 
         // We are not setting the mocks to listening, so we trigger a first refresh state to
@@ -113,14 +119,22 @@
                 ArgumentCaptor.forClass(NetworkController.SignalCallback.class);
         verify(mNetworkController).observe(any(LifecycleOwner.class),
                 signalCallbackArgumentCaptor.capture());
-        mCallback = signalCallbackArgumentCaptor.getValue();
+        mSignalCallback = signalCallbackArgumentCaptor.getValue();
+
+        ArgumentCaptor<HotspotController.Callback> hotspotCallbackArgumentCaptor =
+                ArgumentCaptor.forClass(HotspotController.Callback.class);
+        verify(mHotspotController).observe(any(LifecycleOwner.class),
+                hotspotCallbackArgumentCaptor.capture());
+        mHotspotCallback = hotspotCallbackArgumentCaptor.getValue();
     }
 
+    // -------------------------------------------------
+    // All these tests for enabled/disabled wifi have hotspot not enabled
     @Test
     public void testStateUnavailable_wifiDisabled() {
         NetworkController.IconState qsIcon =
                 new NetworkController.IconState(false, 0, "");
-        mCallback.setWifiIndicators(false, mock(NetworkController.IconState.class),
+        mSignalCallback.setWifiIndicators(false, mock(NetworkController.IconState.class),
                 qsIcon, false,false, "",
                 false, "");
         mTestableLooper.processAllMessages();
@@ -132,7 +146,7 @@
     public void testStateUnavailable_wifiNotConnected() {
         NetworkController.IconState qsIcon =
                 new NetworkController.IconState(false, 0, "");
-        mCallback.setWifiIndicators(true, mock(NetworkController.IconState.class),
+        mSignalCallback.setWifiIndicators(true, mock(NetworkController.IconState.class),
                 qsIcon, false,false, "",
                 false, "");
         mTestableLooper.processAllMessages();
@@ -143,7 +157,7 @@
     private void enableWifiAndProcessMessages() {
         NetworkController.IconState qsIcon =
                 new NetworkController.IconState(true, 0, "");
-        mCallback.setWifiIndicators(true, mock(NetworkController.IconState.class),
+        mSignalCallback.setWifiIndicators(true, mock(NetworkController.IconState.class),
                 qsIcon, false,false, "",
                 false, "");
         mTestableLooper.processAllMessages();
@@ -166,6 +180,46 @@
         enableWifiAndProcessMessages();
         assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
     }
+    // -------------------------------------------------
+
+    // -------------------------------------------------
+    // All these tests for enabled/disabled hotspot have wifi not enabled
+    @Test
+    public void testStateUnavailable_hotspotDisabled() {
+        mHotspotCallback.onHotspotChanged(false, 0);
+        mTestableLooper.processAllMessages();
+
+        assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
+    }
+
+    @Test
+    public void testStateUnavailable_hotspotEnabledNotConnected() {
+        mHotspotCallback.onHotspotChanged(true, 0);
+        mTestableLooper.processAllMessages();
+
+        assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
+    }
+
+    @Test
+    public void testStateActive_hotspotEnabledAndConnectedAndCasting() {
+        CastController.CastDevice device = new CastController.CastDevice();
+        device.state = CastController.CastDevice.STATE_CONNECTED;
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(device);
+        when(mController.getCastDevices()).thenReturn(devices);
+
+        mHotspotCallback.onHotspotChanged(true, 1);
+        mTestableLooper.processAllMessages();
+        assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
+    }
+
+    @Test
+    public void testStateInactive_hotspotEnabledAndConnectedAndNotCasting() {
+        mHotspotCallback.onHotspotChanged(true, 1);
+        mTestableLooper.processAllMessages();
+        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+    }
+    // -------------------------------------------------
 
     @Test
     public void testHandleClick_castDevicePresent() {
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 d041ee0..10eca00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -36,7 +36,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 import com.android.systemui.statusbar.notification.DynamicChildBindController;
@@ -65,6 +65,7 @@
 import org.mockito.Spy;
 
 import java.util.List;
+import java.util.Optional;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -106,7 +107,7 @@
                 mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
                 mock(StatusBarStateControllerImpl.class), mEntryManager,
                 mock(KeyguardBypassController.class),
-                mock(BubbleController.class),
+                Optional.of(mock(Bubbles.class)),
                 mock(DynamicPrivacyController.class),
                 mock(ForegroundServiceSectionController.class),
                 mock(DynamicChildBindController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index dfe006d..d835123 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -42,6 +42,7 @@
 
 import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -61,6 +62,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Optional;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -112,7 +115,8 @@
         mDependency.injectTestDependency(NotificationGroupManagerLegacy.class,
                 new NotificationGroupManagerLegacy(
                         mock(StatusBarStateController.class),
-                        () -> mock(PeopleNotificationIdentifier.class)));
+                        () -> mock(PeopleNotificationIdentifier.class),
+                        Optional.of(() -> mock(Bubbles.class))));
         mDependency.injectMockDependency(ShadeController.class);
         mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
         mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
index edb8776..b20f95c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
@@ -46,7 +46,6 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -78,7 +77,6 @@
     public void setUp() {
         allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
-        mDependency.injectMockDependency(BubbleController.class);
         when(mGutsManager.openGuts(
                 any(View.class),
                 anyInt(),
@@ -86,7 +84,6 @@
                 any(NotificationMenuRowPlugin.MenuItem.class)))
                 .thenReturn(true);
         when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem);
-        mDependency.injectMockDependency(BubbleController.class);
 
         mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 3c5aa1a..9465a3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -76,7 +76,7 @@
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.bubbles.BubblesTestActivity;
 import com.android.systemui.statusbar.SbnBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -97,6 +97,7 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.CountDownLatch;
 
 import javax.inject.Provider;
@@ -136,7 +137,7 @@
     @Mock
     private OnUserInteractionCallback mOnUserInteractionCallback;
     @Mock
-    private BubbleController mBubbleController;
+    private Bubbles mBubbles;
     @Mock
     private LauncherApps mLauncherApps;
     @Mock
@@ -161,7 +162,6 @@
 
         mTestHandler = new Handler(mTestableLooper.getLooper());
         mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
-        mDependency.injectTestDependency(BubbleController.class, mBubbleController);
         mDependency.injectTestDependency(ShadeController.class, mShadeController);
         // Inflate the layout
         final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
@@ -255,7 +255,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
         final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
         assertEquals(mIconDrawable, view.getDrawable());
     }
@@ -279,7 +279,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
         final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
         assertTrue(textView.getText().toString().contains("App Name"));
         assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -330,7 +330,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
         final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
         assertTrue(textView.getText().toString().contains(group.getName()));
         assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -355,7 +355,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
         final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
         assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
         assertEquals(GONE, textView.getVisibility());
@@ -379,7 +379,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
         final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
         assertEquals(GONE, nameView.getVisibility());
     }
@@ -414,7 +414,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
         final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
         assertEquals(VISIBLE, nameView.getVisibility());
         assertTrue(nameView.getText().toString().contains("Proxied"));
@@ -442,7 +442,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
 
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         settingsButton.performClick();
@@ -468,7 +468,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertTrue(settingsButton.getVisibility() != View.VISIBLE);
     }
@@ -495,7 +495,7 @@
                 mBuilderProvider,
                 false,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertTrue(settingsButton.getVisibility() != View.VISIBLE);
     }
@@ -520,7 +520,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
         View view = mNotificationInfo.findViewById(R.id.silence);
         assertThat(view.isSelected()).isTrue();
     }
@@ -548,7 +548,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
         View view = mNotificationInfo.findViewById(R.id.default_behavior);
         assertThat(view.isSelected()).isTrue();
         assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo(
@@ -579,7 +579,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
         View view = mNotificationInfo.findViewById(R.id.default_behavior);
         assertThat(view.isSelected()).isTrue();
         assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo(
@@ -609,7 +609,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
 
         View fave = mNotificationInfo.findViewById(R.id.priority);
         fave.performClick();
@@ -653,7 +653,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
 
         mNotificationInfo.findViewById(R.id.default_behavior).performClick();
         mTestableLooper.processAllMessages();
@@ -696,7 +696,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
 
         View silence = mNotificationInfo.findViewById(R.id.silence);
 
@@ -740,7 +740,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
 
         View fave = mNotificationInfo.findViewById(R.id.priority);
         fave.performClick();
@@ -777,7 +777,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
 
         View fave = mNotificationInfo.findViewById(R.id.priority);
         fave.performClick();
@@ -813,7 +813,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
 
         View fave = mNotificationInfo.findViewById(R.id.priority);
         fave.performClick();
@@ -851,7 +851,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
 
         mNotificationInfo.findViewById(R.id.default_behavior).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -887,7 +887,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
 
         mNotificationInfo.findViewById(R.id.default_behavior).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -923,7 +923,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
 
         mNotificationInfo.findViewById(R.id.default_behavior).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -958,7 +958,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
 
         View silence = mNotificationInfo.findViewById(R.id.silence);
         silence.performClick();
@@ -992,7 +992,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
 
         verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage(
                 anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
@@ -1017,7 +1017,7 @@
                 mBuilderProvider,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
 
         verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
                 anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
@@ -1052,7 +1052,7 @@
                 () -> b,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
 
         // WHEN user clicks "priority"
         mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
@@ -1092,7 +1092,7 @@
                 () -> b,
                 true,
                 mTestHandler,
-                mTestHandler, null, mBubbleController);
+                mTestHandler, null, Optional.of(mBubbles));
 
         // WHEN user clicks "priority"
         mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index e1668ca..bbc1df2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -67,7 +67,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -93,6 +93,8 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.Optional;
+
 import javax.inject.Provider;
 
 /**
@@ -129,7 +131,7 @@
     @Mock private ChannelEditorDialogController mChannelEditorDialogController;
     @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
     @Mock private UserContextProvider mContextTracker;
-    @Mock private BubbleController mBubbleController;
+    @Mock private Bubbles mBubbles;
     @Mock(answer = Answers.RETURNS_SELF)
     private PriorityOnboardingDialogController.Builder mBuilder;
     private Provider<PriorityOnboardingDialogController.Builder> mProvider = () -> mBuilder;
@@ -145,7 +147,6 @@
         mDependency.injectTestDependency(
                 OnUserInteractionCallback.class,
                 mOnUserInteractionCallback);
-        mDependency.injectTestDependency(BubbleController.class, mBubbleController);
         mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
         mHandler = Handler.createAsync(mTestableLooper.getLooper());
         mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
@@ -155,7 +156,7 @@
                 () -> mStatusBar, mHandler, mHandler, mAccessibilityManager, mHighPriorityProvider,
                 mINotificationManager, mLauncherApps, mShortcutManager,
                 mChannelEditorDialogController, mContextTracker, mProvider,
-                mAssistantFeedbackController, mBubbleController,
+                mAssistantFeedbackController, Optional.of(mBubbles),
                 new UiEventLoggerFake(), mOnUserInteractionCallback);
         mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
                 mCheckSaveListener, mOnSettingsClickListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index b952c05..2ce8b34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -45,7 +45,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.TestableDependency;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.bubbles.BubblesTestActivity;
 import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.plugins.FalsingManager;
@@ -73,6 +73,7 @@
 
 import org.mockito.ArgumentCaptor;
 
+import java.util.Optional;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -114,12 +115,12 @@
         mContext = context;
         mTestLooper = testLooper;
         dependency.injectMockDependency(NotificationMediaManager.class);
-        dependency.injectMockDependency(BubbleController.class);
         dependency.injectMockDependency(NotificationShadeWindowController.class);
         mStatusBarStateController = mock(StatusBarStateController.class);
         mGroupMembershipManager = new NotificationGroupManagerLegacy(
                 mStatusBarStateController,
-                () -> mock(PeopleNotificationIdentifier.class));
+                () -> mock(PeopleNotificationIdentifier.class),
+                Optional.of(() -> mock(Bubbles.class)));
         mGroupExpansionManager = mGroupMembershipManager;
         mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarStateController,
                 mock(KeyguardBypassController.class), mock(NotificationGroupManagerLegacy.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 57020eb..83b6d2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -29,7 +29,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
@@ -95,7 +94,6 @@
         when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
                 .thenReturn(TEST_AUTO_DISMISS_TIME);
         when(mVSManager.isReorderingAllowed()).thenReturn(true);
-        mDependency.injectMockDependency(BubbleController.class);
         mDependency.injectMockDependency(NotificationShadeWindowController.class);
         mDependency.injectMockDependency(ConfigurationController.class);
         mHeadsUpManager = new TestableHeadsUpManagerPhone(mContext, mGroupManager, mVSManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index 2ece8be..7d84f86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -37,7 +37,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -61,6 +61,7 @@
 import org.mockito.junit.MockitoRule;
 
 import java.util.HashMap;
+import java.util.Optional;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -83,7 +84,6 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mDependency.injectMockDependency(BubbleController.class);
         mHeadsUpManager = new HeadsUpManager(mContext) {};
 
         when(mNotificationEntryManager.getPendingNotificationsIterator())
@@ -91,7 +91,8 @@
 
         mGroupManager = new NotificationGroupManagerLegacy(
                 mock(StatusBarStateController.class),
-                () -> mock(PeopleNotificationIdentifier.class));
+                () -> mock(PeopleNotificationIdentifier.class),
+                Optional.of(() -> mock(Bubbles.class)));
         mDependency.injectTestDependency(NotificationGroupManagerLegacy.class, mGroupManager);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
index 0aa0091..29e445a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
@@ -30,7 +30,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
@@ -45,6 +45,8 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.Optional;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -60,14 +62,15 @@
 
     @Before
     public void setup() {
-        mDependency.injectMockDependency(BubbleController.class);
+        mDependency.injectMockDependency(Bubbles.class);
         initializeGroupManager();
     }
 
     private void initializeGroupManager() {
         mGroupManager = new NotificationGroupManagerLegacy(
                 mock(StatusBarStateController.class),
-                () -> mock(PeopleNotificationIdentifier.class));
+                () -> mock(PeopleNotificationIdentifier.class),
+                Optional.of(() -> mock(Bubbles.class)));
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
index 5222fff..ede5fce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
@@ -26,7 +26,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -41,6 +41,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Optional;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -66,7 +68,7 @@
     StatusBarWindowController mStatusBarWindowController;
     private NotificationIconAreaController mController;
     @Mock
-    private BubbleController mBubbleController;
+    private Bubbles mBubbles;
     @Mock private DemoModeController mDemoModeController;
     @Mock
     private NotificationIconContainer mAodIcons;
@@ -83,7 +85,7 @@
                 mNotificationMediaManager,
                 mListener,
                 mDozeParameters,
-                mBubbleController,
+                Optional.of(mBubbles),
                 mDemoModeController,
                 mDarkIconDispatcher,
                 mStatusBarWindowController);
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 792637d..f7489b1 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
@@ -53,7 +53,7 @@
 import com.android.systemui.ActivityIntentHelper;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
@@ -86,6 +86,7 @@
 import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
+import java.util.Optional;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -115,7 +116,7 @@
     @Mock
     private Handler mHandler;
     @Mock
-    private BubbleController mBubbleController;
+    private Bubbles mBubbles;
     @Mock
     private ShadeControllerImpl mShadeController;
     @Mock
@@ -192,7 +193,7 @@
                         mStatusBarKeyguardViewManager,
                         mock(KeyguardManager.class),
                         mock(IDreamManager.class),
-                        mBubbleController,
+                        Optional.of(mBubbles),
                         () -> mAssistManager,
                         mRemoteInputManager,
                         mock(NotificationGroupManagerLegacy.class),
@@ -279,7 +280,7 @@
         mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow);
 
         // Then
-        verify(mBubbleController).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
+        verify(mBubbles).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
 
         // This is called regardless, and simply short circuits when there is nothing to do.
         verify(mShadeController, atLeastOnce()).collapsePanel();
@@ -311,7 +312,7 @@
         mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow);
 
         // Then
-        verify(mBubbleController).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
+        verify(mBubbles).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
 
         verify(mShadeController, atLeastOnce()).collapsePanel();
 
@@ -341,7 +342,7 @@
         mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow);
 
         // Then
-        verify(mBubbleController).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
+        verify(mBubbles).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
 
         verify(mShadeController, atLeastOnce()).collapsePanel();
 
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 7d8a626..23b12d4 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
@@ -80,7 +80,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.demomode.DemoModeController;
@@ -219,7 +219,7 @@
     @Mock private UserSwitcherController mUserSwitcherController;
     @Mock private NetworkController mNetworkController;
     @Mock private VibratorHelper mVibratorHelper;
-    @Mock private BubbleController mBubbleController;
+    @Mock private Bubbles mBubbles;
     @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
     @Mock private NotificationIconAreaController mNotificationIconAreaController;
     @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
@@ -332,7 +332,7 @@
         mShadeController = new ShadeControllerImpl(mCommandQueue,
                 mStatusBarStateController, mNotificationShadeWindowController,
                 mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class),
-                () -> mStatusBar, () -> mAssistManager, () -> mBubbleController);
+                () -> mStatusBar, () -> mAssistManager, Optional.of(() -> mBubbles));
 
         mStatusBar = new StatusBar(
                 mContext,
@@ -374,7 +374,7 @@
                 wakefulnessLifecycle,
                 mStatusBarStateController,
                 mVibratorHelper,
-                mBubbleController,
+                Optional.of(mBubbles),
                 mVisualStabilityManager,
                 mDeviceProvisionedController,
                 mNavigationBarController,
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 8099f8f..e67b9d8 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -2,8 +2,7 @@
 per-file ConnectivityService.java,NetworkManagementService.java,NsdService.java = codewiz@google.com, ek@google.com, jchalard@google.com, junyulai@google.com, lorenzo@google.com, reminv@google.com, satk@google.com
 
 # Vibrator / Threads
-per-file VibratorService.java, DisplayThread.java = michaelwr@google.com
-per-file VibratorService.java, DisplayThread.java = ogunwale@google.com
+per-file VibratorManagerService.java, VibratorService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com
 
 # Zram writeback
 per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com, srnvs@google.com
diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/VibratorManagerService.java
new file mode 100644
index 0000000..2f35da7
--- /dev/null
+++ b/services/core/java/com/android/server/VibratorManagerService.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.IVibratorManagerService;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/** System implementation of {@link IVibratorManagerService}. */
+public class VibratorManagerService extends IVibratorManagerService.Stub {
+    private static final String TAG = "VibratorManagerService";
+    private static final boolean DEBUG = false;
+
+    private final Context mContext;
+    private final NativeWrapper mNativeWrapper;
+    private final int[] mVibratorIds;
+
+    static native long nativeInit();
+
+    static native long nativeGetFinalizer();
+
+    static native int[] nativeGetVibratorIds(long nativeServicePtr);
+
+    VibratorManagerService(Context context) {
+        this(context, new Injector());
+    }
+
+    @VisibleForTesting
+    VibratorManagerService(Context context, Injector injector) {
+        mContext = context;
+        mNativeWrapper = injector.getNativeWrapper();
+
+        mNativeWrapper.init();
+
+        int[] vibratorIds = mNativeWrapper.getVibratorIds();
+        mVibratorIds = vibratorIds == null ? new int[0] : vibratorIds;
+    }
+
+    @Override // Binder call
+    public int[] getVibratorIds() {
+        return Arrays.copyOf(mVibratorIds, mVibratorIds.length);
+    }
+
+    @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ShellCallback cb, ResultReceiver resultReceiver) {
+        new VibratorManagerShellCommand(this).exec(this, in, out, err, args, cb, resultReceiver);
+    }
+
+    /** Point of injection for test dependencies */
+    @VisibleForTesting
+    static class Injector {
+
+        NativeWrapper getNativeWrapper() {
+            return new NativeWrapper();
+        }
+    }
+
+    /** Wrapper around the static-native methods of {@link VibratorManagerService} for tests. */
+    @VisibleForTesting
+    public static class NativeWrapper {
+
+        private long mNativeServicePtr = 0;
+
+        /** Returns native pointer to newly created controller and connects with HAL service. */
+        public void init() {
+            mNativeServicePtr = VibratorManagerService.nativeInit();
+            long finalizerPtr = VibratorManagerService.nativeGetFinalizer();
+
+            if (finalizerPtr != 0) {
+                NativeAllocationRegistry registry =
+                        NativeAllocationRegistry.createMalloced(
+                                VibratorManagerService.class.getClassLoader(), finalizerPtr);
+                registry.registerNativeAllocation(this, mNativeServicePtr);
+            }
+        }
+
+        /** Returns vibrator ids. */
+        public int[] getVibratorIds() {
+            return VibratorManagerService.nativeGetVibratorIds(mNativeServicePtr);
+        }
+    }
+
+    /** Provides limited functionality from {@link VibratorManagerService} as shell commands. */
+    private final class VibratorManagerShellCommand extends ShellCommand {
+
+        private final IBinder mToken;
+
+        private VibratorManagerShellCommand(IBinder token) {
+            mToken = token;
+        }
+
+        @Override
+        public int onCommand(String cmd) {
+            if ("list".equals(cmd)) {
+                return runListVibrators();
+            }
+            return handleDefaultCommands(cmd);
+        }
+
+        private int runListVibrators() {
+            try (PrintWriter pw = getOutPrintWriter();) {
+                if (mVibratorIds.length == 0) {
+                    pw.println("No vibrator found");
+                } else {
+                    for (int id : mVibratorIds) {
+                        pw.println(id);
+                    }
+                }
+                pw.println("");
+                return 0;
+            }
+        }
+
+        @Override
+        public void onHelp() {
+            try (PrintWriter pw = getOutPrintWriter();) {
+                pw.println("Vibrator Manager commands:");
+                pw.println("  help");
+                pw.println("    Prints this help text.");
+                pw.println("");
+                pw.println("  list");
+                pw.println("    Prints the id of device vibrators. This do not include any ");
+                pw.println("    connected input device.");
+                pw.println("");
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 687af10..cb6f616 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -385,15 +385,7 @@
         mNativeWrapper = injector.getNativeWrapper();
         mH = injector.createHandler(Looper.myLooper());
 
-        long nativeServicePtr = mNativeWrapper.vibratorInit(this::onVibrationComplete);
-        long finalizerPtr = mNativeWrapper.vibratorGetFinalizer();
-
-        if (finalizerPtr != 0) {
-            NativeAllocationRegistry registry =
-                    NativeAllocationRegistry.createMalloced(
-                            VibratorService.class.getClassLoader(), finalizerPtr);
-            registry.registerNativeAllocation(this, nativeServicePtr);
-        }
+        mNativeWrapper.vibratorInit(this::onVibrationComplete);
 
         // Reset the hardware to a default state, in case this is a runtime
         // restart instead of a fresh boot.
@@ -1746,18 +1738,17 @@
             return VibratorService.vibratorExists(mNativeServicePtr);
         }
 
-        /**
-         * Returns native pointer to newly created controller and initializes connection to vibrator
-         * HAL service.
-         */
-        public long vibratorInit(OnCompleteListener listener) {
+        /** Initializes connection to vibrator HAL service. */
+        public void vibratorInit(OnCompleteListener listener) {
             mNativeServicePtr = VibratorService.vibratorInit(listener);
-            return mNativeServicePtr;
-        }
+            long finalizerPtr = VibratorService.vibratorGetFinalizer();
 
-        /** Returns pointer to native finalizer function to be called by GC. */
-        public long vibratorGetFinalizer() {
-            return VibratorService.vibratorGetFinalizer();
+            if (finalizerPtr != 0) {
+                NativeAllocationRegistry registry =
+                        NativeAllocationRegistry.createMalloced(
+                                VibratorService.class.getClassLoader(), finalizerPtr);
+                registry.registerNativeAllocation(this, mNativeServicePtr);
+            }
         }
 
         /** Turns vibrator on for given time. */
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c0f6011..91a3fb0 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -478,15 +478,15 @@
     }
 
     ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
-            int callingPid, int callingUid, boolean fgRequired, boolean hideFgNotification,
-            String callingPackage, @Nullable String callingFeatureId, final int userId)
+            int callingPid, int callingUid, boolean fgRequired, String callingPackage,
+            @Nullable String callingFeatureId, final int userId)
             throws TransactionTooLargeException {
         return startServiceLocked(caller, service, resolvedType, callingPid, callingUid, fgRequired,
-                hideFgNotification, callingPackage, callingFeatureId, userId, false, null);
+                callingPackage, callingFeatureId, userId, false, null);
     }
 
     ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
-            int callingPid, int callingUid, boolean fgRequired, boolean hideFgNotification,
+            int callingPid, int callingUid, boolean fgRequired,
             String callingPackage, @Nullable String callingFeatureId, final int userId,
             boolean allowBackgroundActivityStarts, @Nullable IBinder backgroundActivityStartsToken)
             throws TransactionTooLargeException {
@@ -653,7 +653,6 @@
         r.startRequested = true;
         r.delayedStop = false;
         r.fgRequired = fgRequired;
-        r.hideFgNotification = hideFgNotification;
         r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                 service, neededGrants, callingUid));
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ef75264..4f056df 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1177,10 +1177,6 @@
 
     final Injector mInjector;
 
-    /** The package verifier app. */
-    private String mPackageVerifier;
-    private int mPackageVerifierUid = UserHandle.USER_NULL;
-
     static final class ProcessChangeItem {
         static final int CHANGE_ACTIVITIES = 1<<0;
         static final int CHANGE_FOREGROUND_SERVICES = 1<<1;
@@ -1809,18 +1805,6 @@
             if (phase == PHASE_SYSTEM_SERVICES_READY) {
                 mService.mBatteryStatsService.systemServicesReady();
                 mService.mServices.systemServicesReady();
-                mService.mPackageVerifier = ArrayUtils.firstOrNull(
-                        LocalServices.getService(PackageManagerInternal.class).getKnownPackageNames(
-                                PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM));
-                if (mService.mPackageVerifier != null) {
-                    try {
-                        mService.mPackageVerifierUid =
-                                getContext().getPackageManager().getPackageUid(
-                                        mService.mPackageVerifier, UserHandle.USER_SYSTEM);
-                    } catch (NameNotFoundException e) {
-                        Slog.wtf(TAG, "Package manager couldn't get package verifier uid", e);
-                    }
-                }
             } else if (phase == PHASE_ACTIVITY_MANAGER_READY) {
                 mService.startBroadcastObservers();
             } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
@@ -8269,7 +8253,7 @@
 
     private void dumpEverything(FileDescriptor fd, PrintWriter pw, String[] args, int opti,
             boolean dumpAll, String dumpPackage, boolean dumpClient, boolean dumpNormalPriority,
-            int dumpAppId) {
+            int dumpAppId, boolean dumpProxies) {
 
         ActiveServices.ServiceDumper sdumper;
 
@@ -8328,7 +8312,7 @@
             }
             sdumper.dumpWithClient();
         }
-        if (dumpPackage == null) {
+        if (dumpPackage == null && dumpProxies) {
             // Intentionally dropping the lock for this, because dumpBinderProxies() will make many
             // outgoing binder calls to retrieve interface descriptors; while that is system code,
             // there is nothing preventing an app from overriding this implementation by talking to
@@ -8737,13 +8721,14 @@
                 // dumpEverything() will take the lock when needed, and momentarily drop
                 // it for dumping client state.
                 dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpClient,
-                        dumpNormalPriority, dumpAppId);
+                        dumpNormalPriority, dumpAppId, true /* dumpProxies */);
             } else {
                 // Take the lock here, so we get a consistent state for the entire dump;
-                // dumpEverything() will take the lock as well, but that is fine.
+                // dumpEverything() will take the lock as well, which is fine for everything
+                // except dumping proxies, which can take a long time; exclude them.
                 synchronized(this) {
                     dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpClient,
-                            dumpNormalPriority, dumpAppId);
+                            dumpNormalPriority, dumpAppId, false /* dumpProxies */);
                 }
             }
         }
@@ -12359,8 +12344,8 @@
 
     @Override
     public ComponentName startService(IApplicationThread caller, Intent service,
-            String resolvedType, boolean requireForeground, boolean hideForegroundNotification,
-            String callingPackage, String callingFeatureId, int userId)
+            String resolvedType, boolean requireForeground, String callingPackage,
+            String callingFeatureId, int userId)
             throws TransactionTooLargeException {
         enforceNotIsolatedCaller("startService");
         // Refuse possible leaked file descriptors
@@ -12372,27 +12357,17 @@
             throw new IllegalArgumentException("callingPackage cannot be null");
         }
 
-        final int callingUid = Binder.getCallingUid();
-        if (requireForeground && hideForegroundNotification) {
-            if (!UserHandle.isSameApp(callingUid, mPackageVerifierUid)
-                    || !callingPackage.equals(mPackageVerifier)) {
-                throw new IllegalArgumentException(
-                        "Only the package verifier can hide its foreground service notification");
-            }
-            Slog.i(TAG, "Foreground service notification hiding requested by " + callingPackage);
-        }
-
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
                 "*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground);
         synchronized(this) {
             final int callingPid = Binder.getCallingPid();
+            final int callingUid = Binder.getCallingUid();
             final long origId = Binder.clearCallingIdentity();
             ComponentName res;
             try {
                 res = mServices.startServiceLocked(caller, service,
                         resolvedType, callingPid, callingUid,
-                        requireForeground, hideForegroundNotification,
-                        callingPackage, callingFeatureId, userId);
+                        requireForeground, callingPackage, callingFeatureId, userId);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
@@ -16305,7 +16280,7 @@
                 ComponentName res;
                 try {
                     res = mServices.startServiceLocked(null, service,
-                            resolvedType, -1, uid, fgRequired, false, callingPackage,
+                            resolvedType, -1, uid, fgRequired, callingPackage,
                             callingFeatureId, userId, allowBackgroundActivityStarts,
                             backgroundActivityStartsToken);
                 } finally {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 1119728..d346430 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -655,7 +655,7 @@
         pw.println("Starting service: " + intent);
         pw.flush();
         ComponentName cn = mInterface.startService(null, intent, intent.getType(),
-                asForeground, false, SHELL_PACKAGE_NAME, null, mUserId);
+                asForeground, SHELL_PACKAGE_NAME, null, mUserId);
         if (cn == null) {
             err.println("Error: Not found; no service started.");
             return -1;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index a7d8ca4..4cdf661 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -105,7 +105,6 @@
     boolean whitelistManager; // any bindings to this service have BIND_ALLOW_WHITELIST_MANAGEMENT?
     boolean delayed;        // are we waiting to start this service in the background?
     boolean fgRequired;     // is the service required to go foreground after starting?
-    boolean hideFgNotification; // Hide the fg service notification
     boolean fgWaiting;      // is a timeout for going foreground already scheduled?
     boolean isForeground;   // is service currently in foreground mode?
     int foregroundId;       // Notification ID of last foreground req.
@@ -886,9 +885,6 @@
     }
 
     public void postNotification() {
-        if (hideFgNotification) {
-            return;
-        }
         final int appUid = appInfo.uid;
         final int appPid = app.pid;
         if (foregroundId != 0 && foregroundNoti != null) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
index 6e6ec74..c17bc91 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
@@ -61,7 +61,6 @@
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.RemovalConsumer;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUpdateActiveUserClient;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -404,7 +403,7 @@
     }
 
     /**
-     * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the
+     * Schedules the {@link FaceUpdateActiveUserClient} without posting the work onto the
      * handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
      * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
      * this operation on the same lambda/runnable as those operations so that the ordering is
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 2f0e564..2903b9970 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -57,6 +57,8 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21;
+import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21UdfpsMock;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
similarity index 93%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 507b5dd..c87bfec 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -64,6 +64,8 @@
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -81,7 +83,7 @@
  * Supports a single instance of the {@link android.hardware.biometrics.fingerprint.V2_1} or
  * its extended minor versions.
  */
-class Fingerprint21 implements IHwBinder.DeathRecipient {
+public class Fingerprint21 implements IHwBinder.DeathRecipient {
 
     private static final String TAG = "Fingerprint21";
     private static final int ENROLL_TIMEOUT_SEC = 60;
@@ -349,7 +351,7 @@
                 sensorType, resetLockoutRequiresHardwareAuthToken);
     }
 
-    static Fingerprint21 newInstance(@NonNull Context context, int sensorId, int strength,
+    public static Fingerprint21 newInstance(@NonNull Context context, int sensorId, int strength,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
         final Handler handler = new Handler(Looper.getMainLooper());
@@ -433,7 +435,7 @@
     @Nullable IUdfpsOverlayController getUdfpsOverlayController() {
         return mUdfpsOverlayController;
     }
-    @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) {
+    @LockoutTracker.LockoutMode public int getLockoutModeForUser(int userId) {
         return mLockoutTracker.getLockoutModeForUser(userId);
     }
 
@@ -479,7 +481,7 @@
         });
     }
 
-    void scheduleResetLockout(int userId) {
+    public void scheduleResetLockout(int userId) {
         // Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler
         // thread.
         mHandler.post(() -> {
@@ -487,7 +489,7 @@
         });
     }
 
-    void scheduleGenerateChallenge(@NonNull IBinder token,
+    public void scheduleGenerateChallenge(@NonNull IBinder token,
             @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
             final FingerprintGenerateChallengeClient client =
@@ -498,7 +500,7 @@
         });
     }
 
-    void scheduleRevokeChallenge(@NonNull IBinder token, @NonNull String opPackageName) {
+    public void scheduleRevokeChallenge(@NonNull IBinder token, @NonNull String opPackageName) {
         mHandler.post(() -> {
             final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
                     mContext, mLazyDaemon, token, opPackageName, mSensorProperties.sensorId);
@@ -506,7 +508,7 @@
         });
     }
 
-    void scheduleEnroll(@NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId,
+    public void scheduleEnroll(@NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId,
             @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
             @Nullable Surface surface) {
         mHandler.post(() -> {
@@ -529,13 +531,13 @@
         });
     }
 
-    void cancelEnrollment(@NonNull IBinder token) {
+    public void cancelEnrollment(@NonNull IBinder token) {
         mHandler.post(() -> {
             mScheduler.cancelEnrollment(token);
         });
     }
 
-    void scheduleFingerDetect(@NonNull IBinder token, int userId,
+    public void scheduleFingerDetect(@NonNull IBinder token, int userId,
             @NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
             @Nullable Surface surface, int statsClient) {
         mHandler.post(() -> {
@@ -550,9 +552,10 @@
         });
     }
 
-    void scheduleAuthenticate(@NonNull IBinder token, long operationId, int userId, int cookie,
-            @NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
-            boolean restricted, int statsClient, boolean isKeyguard) {
+    public void scheduleAuthenticate(@NonNull IBinder token, long operationId, int userId,
+            int cookie, @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull String opPackageName, boolean restricted, int statsClient,
+            boolean isKeyguard) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
@@ -566,20 +569,21 @@
         });
     }
 
-    void startPreparedClient(int cookie) {
+    public void startPreparedClient(int cookie) {
         mHandler.post(() -> {
             mScheduler.startPreparedClient(cookie);
         });
     }
 
-    void cancelAuthentication(@NonNull IBinder token) {
+    public void cancelAuthentication(@NonNull IBinder token) {
         mHandler.post(() -> {
             mScheduler.cancelAuthentication(token);
         });
     }
 
-    void scheduleRemove(@NonNull IBinder token, @NonNull IFingerprintServiceReceiver receiver,
-            int fingerId, int userId, @NonNull String opPackageName) {
+    public void scheduleRemove(@NonNull IBinder token,
+            @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
+            @NonNull String opPackageName) {
         mHandler.post(() -> {
            scheduleUpdateActiveUserWithoutHandler(userId);
 
@@ -604,30 +608,30 @@
         });
     }
 
-    boolean isHardwareDetected() {
+    public boolean isHardwareDetected() {
         final IBiometricsFingerprint daemon = getDaemon();
         return daemon != null;
     }
 
-    @NonNull FingerprintSensorProperties getFingerprintSensorProperties() {
+    @NonNull public FingerprintSensorProperties getFingerprintSensorProperties() {
         return mSensorProperties;
     }
 
-    void rename(int fingerId, int userId, String name) {
+    public void rename(int fingerId, int userId, String name) {
         mHandler.post(() -> {
             FingerprintUtils.getInstance().renameBiometricForUser(mContext, userId, fingerId, name);
         });
     }
 
-    List<Fingerprint> getEnrolledFingerprints(int userId) {
+    public List<Fingerprint> getEnrolledFingerprints(int userId) {
         return FingerprintUtils.getInstance().getBiometricsForUser(mContext, userId);
     }
 
-    long getAuthenticatorId(int userId) {
+    public long getAuthenticatorId(int userId) {
         return mAuthenticatorIds.get(userId);
     }
 
-    void onFingerDown(int x, int y, float minor, float major) {
+    public void onFingerDown(int x, int y, float minor, float major) {
         final ClientMonitor<?> client = mScheduler.getCurrentClient();
         if (!(client instanceof Udfps)) {
             Slog.w(TAG, "onFingerDown received during client: " + client);
@@ -637,7 +641,7 @@
         udfps.onFingerDown(x, y, minor, major);
     }
 
-    void onFingerUp() {
+    public void onFingerUp() {
         final ClientMonitor<?> client = mScheduler.getCurrentClient();
         if (!(client instanceof Udfps)) {
             Slog.w(TAG, "onFingerDown received during client: " + client);
@@ -647,11 +651,11 @@
         udfps.onFingerUp();
     }
 
-    void setUdfpsOverlayController(IUdfpsOverlayController controller) {
+    public void setUdfpsOverlayController(IUdfpsOverlayController controller) {
         mUdfpsOverlayController = controller;
     }
 
-    void dumpProto(FileDescriptor fd) {
+    public void dumpProto(FileDescriptor fd) {
         PerformanceTracker tracker =
                 PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
 
@@ -691,7 +695,7 @@
         tracker.clear();
     }
 
-    void dumpInternal(@NonNull PrintWriter pw) {
+    public void dumpInternal(@NonNull PrintWriter pw) {
         PerformanceTracker performanceTracker =
                 PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
similarity index 98%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 044dbe9..6d8f241 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -43,6 +43,7 @@
 import com.android.server.biometrics.sensors.ClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
 import java.util.ArrayList;
 import java.util.Random;
@@ -267,7 +268,7 @@
         }
     }
 
-    static Fingerprint21UdfpsMock newInstance(@NonNull Context context, int sensorId,
+    public static Fingerprint21UdfpsMock newInstance(@NonNull Context context, int sensorId,
             @BiometricManager.Authenticators.Types int strength,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
@@ -450,12 +451,12 @@
 
     @Override
     @NonNull
-    FingerprintSensorProperties getFingerprintSensorProperties() {
+    public FingerprintSensorProperties getFingerprintSensorProperties() {
         return mSensorProperties;
     }
 
     @Override
-    void onFingerDown(int x, int y, float minor, float major) {
+    public void onFingerDown(int x, int y, float minor, float major) {
         mHandler.post(() -> {
             Slog.d(TAG, "onFingerDown");
             final AuthenticationConsumer lastAuthenticatedConsumer =
@@ -502,7 +503,7 @@
     }
 
     @Override
-    void onFingerUp() {
+    public void onFingerUp() {
         mHandler.post(() -> {
             Slog.d(TAG, "onFingerUp");
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
similarity index 98%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 99d348a..8087e15 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
similarity index 98%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintDetectClient.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 8652ee4..5865617 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
similarity index 98%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index d5db6e4..1b9fae9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
similarity index 96%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintGenerateChallengeClient.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
index 8fb8c99..abaaac5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
 
 import android.annotation.NonNull;
 import android.content.Context;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
similarity index 97%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalCleanupClient.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
index 571d2b8..e061112 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
 
 import android.annotation.NonNull;
 import android.content.Context;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
similarity index 97%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalEnumerateClient.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
index 834bf42..5fd1d1e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
 
 import android.annotation.NonNull;
 import android.content.Context;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
similarity index 97%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRemovalClient.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
index 9f54563..4bbb7ef 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
 
 import android.annotation.NonNull;
 import android.content.Context;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
similarity index 96%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRevokeChallengeClient.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
index 882660e..8f58cae 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
 
 import android.annotation.NonNull;
 import android.content.Context;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
similarity index 98%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index c1c3593..00e2413 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
 
 import android.annotation.NonNull;
 import android.content.Context;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
similarity index 98%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/LockoutFrameworkImpl.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
index e7b2ae7..4fc1545 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/LockoutFrameworkImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
 
 import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Udfps.java
similarity index 93%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Udfps.java
index e0806ff..74cae02 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Udfps.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
 
 /**
  * Interface for under-display fingerprint sensors.
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/UdfpsHelper.java
similarity index 97%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/UdfpsHelper.java
index 5e521d2..c71ecbf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/UdfpsHelper.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
 
 import android.annotation.Nullable;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
index 3e61920..0304cdc 100644
--- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java
+++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
@@ -23,7 +23,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.net.ConnectivityManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -70,8 +69,6 @@
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SIM_STATE_CHANGED);
-        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
-        filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
         mContext.registerReceiver(this, filter, null /* broadcastPermission */, mListenerHandler);
     }
 
@@ -81,10 +78,7 @@
         if (action.equals(Intent.ACTION_SIM_STATE_CHANGED)) {
             updateSimState(intent);
             notePhoneDataConnectionState();
-        } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
-                action.equals(ConnectivityManager.INET_CONDITION_ACTION)) {
-            notePhoneDataConnectionState();
-       }
+        }
     }
 
     private void notePhoneDataConnectionState() {
diff --git a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
index 92dabe3..e28d73e 100644
--- a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
@@ -29,6 +29,7 @@
 
 import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
 
+import java.time.Duration;
 import java.util.Objects;
 
 /**
@@ -142,14 +143,13 @@
     }
 
     @Override
-    void onEnable() {
+    void onEnable(@NonNull Duration initializationTimeout) {
         // Set a request on the proxy - it will be sent immediately if the service is bound,
         // or will be sent as soon as the service becomes bound.
-        // TODO(b/152744911): Decide whether to send a timeout so the provider knows how long
-        //  it has to generate the first event before it could be bypassed.
         LocationTimeZoneProviderRequest request =
                 new LocationTimeZoneProviderRequest.Builder()
                         .setReportLocationTimeZone(true)
+                        .setInitializationTimeoutMillis(initializationTimeout.toMillis())
                         .build();
         mProxy.setRequest(request);
     }
diff --git a/services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java
index 2e2481c..b1e3306 100644
--- a/services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java
+++ b/services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java
@@ -22,6 +22,7 @@
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.TimeZoneDetectorInternal;
 
+import java.time.Duration;
 import java.util.Objects;
 
 /**
@@ -30,6 +31,10 @@
  */
 class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment {
 
+    private static final Duration PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
+    private static final Duration PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
+    private static final Duration PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5);
+
     @NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal;
     @NonNull private final LocationTimeZoneProviderController mController;
 
@@ -45,7 +50,26 @@
     }
 
     @Override
+    @NonNull
     ConfigurationInternal getCurrentUserConfigurationInternal() {
         return mTimeZoneDetectorInternal.getCurrentUserConfigurationInternal();
     }
+
+    @Override
+    @NonNull
+    Duration getProviderInitializationTimeout() {
+        return PROVIDER_INITIALIZATION_TIMEOUT;
+    }
+
+    @Override
+    @NonNull
+    Duration getProviderInitializationTimeoutFuzz() {
+        return PROVIDER_INITIALIZATION_TIMEOUT_FUZZ;
+    }
+
+    @Override
+    @NonNull
+    Duration getUncertaintyDelay() {
+        return PROVIDER_UNCERTAINTY_DELAY;
+    }
 }
diff --git a/services/core/java/com/android/server/location/timezone/ControllerImpl.java b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
index e31cfc4..bedaeda 100644
--- a/services/core/java/com/android/server/location/timezone/ControllerImpl.java
+++ b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
@@ -33,7 +33,6 @@
 import android.util.IndentingPrintWriter;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue;
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
@@ -50,9 +49,6 @@
  */
 class ControllerImpl extends LocationTimeZoneProviderController {
 
-    @VisibleForTesting
-    static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(5);
-
     @NonNull private final LocationTimeZoneProvider mProvider;
     @NonNull private final SingleRunnableQueue mDelayedSuggestionQueue;
 
@@ -140,7 +136,8 @@
             switch (providerState.stateEnum) {
                 case PROVIDER_STATE_DISABLED: {
                     debugLog("Enabling " + mProvider);
-                    mProvider.enable(configuration);
+                    mProvider.enable(
+                            configuration, mEnvironment.getProviderInitializationTimeout());
                     break;
                 }
                 case PROVIDER_STATE_ENABLED: {
@@ -183,13 +180,16 @@
         if (isProviderEnabled) {
             if (!providerWasEnabled) {
                 // When a provider has first been enabled, we allow it some time for it to
-                // initialize.
+                // initialize before sending its first event.
+                Duration initializationTimeout = mEnvironment.getProviderInitializationTimeout()
+                        .plus(mEnvironment.getProviderInitializationTimeoutFuzz());
                 // This sets up an empty suggestion to trigger if no explicit "certain" or
-                // "uncertain" suggestion preempts it within UNCERTAINTY_DELAY. If, for some reason,
-                // the provider does provide any events then this scheduled suggestion will ensure
-                // the controller makes at least an uncertain suggestion.
-                suggestDelayed(createEmptySuggestion(
-                        "No event received in delay=" + UNCERTAINTY_DELAY), UNCERTAINTY_DELAY);
+                // "uncertain" suggestion preempts it within initializationTimeout. If, for some
+                // reason, the provider does not produce any events then this scheduled suggestion
+                // will ensure the controller makes at least an "uncertain" suggestion.
+                suggestDelayed(createEmptySuggestion("No event received from provider in"
+                                + " initializationTimeout=" + initializationTimeout),
+                        initializationTimeout);
             }
         } else {
             // Clear any queued suggestions.
@@ -199,7 +199,8 @@
             // made, then a new "uncertain" suggestion must be made to indicate the provider no
             // longer has an opinion and will not be sending updates.
             if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) {
-                suggestImmediate(createEmptySuggestion(""));
+                suggestImmediate(createEmptySuggestion(
+                        "Provider disabled, clearing previous suggestion"));
             }
         }
     }
@@ -309,21 +310,23 @@
      *
      * <p>Providers are expected to report their uncertainty as soon as they become uncertain, as
      * this enables the most flexibility for the controller to enable other providers when there are
-     * multiple ones. The controller is therefore responsible for deciding when to make a
+     * multiple ones available. The controller is therefore responsible for deciding when to make a
      * "uncertain" suggestion.
      *
      * <p>This method schedules an "uncertain" suggestion (if one isn't already scheduled) to be
      * made later if nothing else preempts it. It can be preempted if the provider becomes certain
      * (or does anything else that calls {@link #suggestImmediate(GeolocationTimeZoneSuggestion)})
-     * within UNCERTAINTY_DELAY. Preemption causes the scheduled "uncertain" event to be cancelled.
-     * If the provider repeatedly sends uncertainty events within UNCERTAINTY_DELAY, those events
-     * are effectively ignored (i.e. the timer is not reset each time).
+     * within {@link Environment#getUncertaintyDelay()}. Preemption causes the scheduled
+     * "uncertain" event to be cancelled. If the provider repeatedly sends uncertainty events within
+     * the uncertainty delay period, those events are effectively ignored (i.e. the timer is not
+     * reset each time).
      */
     private void scheduleUncertainSuggestionIfNeeded(@Nullable LocationTimeZoneEvent event) {
         if (mPendingSuggestion == null || mPendingSuggestion.getZoneIds() != null) {
             GeolocationTimeZoneSuggestion suggestion = createEmptySuggestion(
                     "provider=" + mProvider + " became uncertain, event=" + event);
-            suggestDelayed(suggestion, UNCERTAINTY_DELAY);
+            // Only send the empty suggestion after the uncertainty delay.
+            suggestDelayed(suggestion, mEnvironment.getUncertaintyDelay());
         }
     }
 
@@ -334,6 +337,11 @@
 
             ipw.increaseIndent(); // level 1
             ipw.println("mCurrentUserConfiguration=" + mCurrentUserConfiguration);
+            ipw.println("providerInitializationTimeout="
+                    + mEnvironment.getProviderInitializationTimeout());
+            ipw.println("providerInitializationTimeoutFuzz="
+                    + mEnvironment.getProviderInitializationTimeoutFuzz());
+            ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay());
             ipw.println("mPendingSuggestion=" + mPendingSuggestion);
             ipw.println("mLastSuggestion=" + mLastSuggestion);
 
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
index 3743779..abfa580 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
@@ -37,6 +37,7 @@
 import com.android.server.timezonedetector.Dumpable;
 import com.android.server.timezonedetector.ReferenceWithHistory;
 
+import java.time.Duration;
 import java.util.Objects;
 
 /**
@@ -368,7 +369,8 @@
      * #getCurrentState()} is at {@link ProviderState#PROVIDER_STATE_DISABLED}. This method must be
      * called using the handler thread from the {@link ThreadingDomain}.
      */
-    final void enable(@NonNull ConfigurationInternal currentUserConfiguration) {
+    final void enable(@NonNull ConfigurationInternal currentUserConfiguration,
+            @NonNull Duration initializationTimeout) {
         mThreadingDomain.assertCurrentThread();
 
         synchronized (mSharedLock) {
@@ -378,14 +380,14 @@
             ProviderState newState = currentState.newState(
                     PROVIDER_STATE_ENABLED, null, currentUserConfiguration, "enable() called");
             setCurrentState(newState, false);
-            onEnable();
+            onEnable(initializationTimeout);
         }
     }
 
     /**
      * Implemented by subclasses to do work during {@link #enable}.
      */
-    abstract void onEnable();
+    abstract void onEnable(@NonNull Duration initializationTimeout);
 
     /**
      * Disables the provider. It is an error* to call this method except when the {@link
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
index 2f75c43..88f0f00 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
@@ -24,6 +24,7 @@
 import com.android.server.timezonedetector.Dumpable;
 import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
 
+import java.time.Duration;
 import java.util.Objects;
 
 /**
@@ -100,6 +101,24 @@
 
         /** Returns the {@link ConfigurationInternal} for the current user of the device. */
         abstract ConfigurationInternal getCurrentUserConfigurationInternal();
+
+        /**
+         * Returns the value passed to LocationTimeZoneProviders informing them of how long they
+         * have to return their first time zone suggestion.
+         */
+        abstract Duration getProviderInitializationTimeout();
+
+        /**
+         * Returns the extra time granted on top of {@link #getProviderInitializationTimeout()} to
+         * allow for slop like communication delays.
+         */
+        abstract Duration getProviderInitializationTimeoutFuzz();
+
+        /**
+         * Returns the delay allowed after receiving uncertainty from a provider before it should be
+         * passed on.
+         */
+        abstract Duration getUncertaintyDelay();
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java
index 79e2b97..53afaf0 100644
--- a/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java
@@ -23,6 +23,8 @@
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
+import java.time.Duration;
+
 /**
  * A {@link LocationTimeZoneProvider} that provides minimal responses needed for the {@link
  * LocationTimeZoneProviderController} to operate correctly when there is no "real" provider
@@ -55,7 +57,7 @@
     }
 
     @Override
-    void onEnable() {
+    void onEnable(@NonNull Duration initializationTimeout) {
         // Report a failure (asynchronously using the mThreadingDomain thread to avoid recursion).
         mThreadingDomain.post(()-> {
             // Enter the perm-failed state.
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 2acc60d..0450e5b 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -96,7 +96,9 @@
 import static com.android.internal.util.XmlUtils.readIntAttribute;
 import static com.android.internal.util.XmlUtils.readLongAttribute;
 import static com.android.internal.util.XmlUtils.readStringAttribute;
+import static com.android.internal.util.XmlUtils.readThisIntArrayXml;
 import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeIntArrayXml;
 import static com.android.internal.util.XmlUtils.writeIntAttribute;
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
@@ -229,6 +231,7 @@
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.StatLogger;
+import com.android.internal.util.XmlUtils;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
@@ -239,6 +242,7 @@
 import libcore.io.IoUtils;
 
 import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.File;
@@ -313,7 +317,8 @@
     private static final int VERSION_ADDED_NETWORK_ID = 9;
     private static final int VERSION_SWITCH_UID = 10;
     private static final int VERSION_ADDED_CYCLE = 11;
-    private static final int VERSION_LATEST = VERSION_ADDED_CYCLE;
+    private static final int VERSION_ADDED_NETWORK_TYPES = 12;
+    private static final int VERSION_LATEST = VERSION_ADDED_NETWORK_TYPES;
 
     @VisibleForTesting
     public static final int TYPE_WARNING = SystemMessage.NOTE_NET_WARNING;
@@ -332,6 +337,7 @@
     private static final String TAG_WHITELIST = "whitelist";
     private static final String TAG_RESTRICT_BACKGROUND = "restrict-background";
     private static final String TAG_REVOKED_RESTRICT_BACKGROUND = "revoked-restrict-background";
+    private static final String TAG_XML_UTILS_INT_ARRAY = "int-array";
 
     private static final String ATTR_VERSION = "version";
     private static final String ATTR_RESTRICT_BACKGROUND = "restrictBackground";
@@ -360,6 +366,8 @@
     private static final String ATTR_USAGE_BYTES = "usageBytes";
     private static final String ATTR_USAGE_TIME = "usageTime";
     private static final String ATTR_OWNER_PACKAGE = "ownerPackage";
+    private static final String ATTR_NETWORK_TYPES = "networkTypes";
+    private static final String ATTR_XML_UTILS_NAME = "name";
 
     private static final String ACTION_ALLOW_BACKGROUND =
             "com.android.server.net.action.ALLOW_BACKGROUND";
@@ -2317,13 +2325,25 @@
                         }
 
                         final int subId = readIntAttribute(in, ATTR_SUB_ID);
+                        final String ownerPackage = readStringAttribute(in, ATTR_OWNER_PACKAGE);
+
+                        if (version >= VERSION_ADDED_NETWORK_TYPES) {
+                            final int depth = in.getDepth();
+                            while (XmlUtils.nextElementWithin(in, depth)) {
+                                if (TAG_XML_UTILS_INT_ARRAY.equals(in.getName())
+                                        && ATTR_NETWORK_TYPES.equals(
+                                                readStringAttribute(in, ATTR_XML_UTILS_NAME))) {
+                                    final int[] networkTypes =
+                                            readThisIntArrayXml(in, TAG_XML_UTILS_INT_ARRAY, null);
+                                    builder.setNetworkTypes(networkTypes);
+                                }
+                            }
+                        }
+
                         final SubscriptionPlan plan = builder.build();
                         mSubscriptionPlans.put(subId, ArrayUtils.appendElement(
                                 SubscriptionPlan.class, mSubscriptionPlans.get(subId), plan));
-
-                        final String ownerPackage = readStringAttribute(in, ATTR_OWNER_PACKAGE);
                         mSubscriptionPlansOwner.put(subId, ownerPackage);
-
                     } else if (TAG_UID_POLICY.equals(tag)) {
                         final int uid = readIntAttribute(in, ATTR_UID);
                         final int policy = readIntAttribute(in, ATTR_POLICY);
@@ -2519,6 +2539,9 @@
                     writeIntAttribute(out, ATTR_LIMIT_BEHAVIOR, plan.getDataLimitBehavior());
                     writeLongAttribute(out, ATTR_USAGE_BYTES, plan.getDataUsageBytes());
                     writeLongAttribute(out, ATTR_USAGE_TIME, plan.getDataUsageTime());
+                    try {
+                        writeIntArrayXml(plan.getNetworkTypes(), ATTR_NETWORK_TYPES, out);
+                    } catch (XmlPullParserException ignored) { }
                     out.endTag(null, TAG_SUBSCRIPTION_PLAN);
                 }
             }
@@ -3316,7 +3339,8 @@
             // let in core system components (like the Settings app).
             final String ownerPackage = mSubscriptionPlansOwner.get(subId);
             if (Objects.equals(ownerPackage, callingPackage)
-                    || (UserHandle.getCallingAppId() == android.os.Process.SYSTEM_UID)) {
+                    || (UserHandle.getCallingAppId() == android.os.Process.SYSTEM_UID)
+                    || (UserHandle.getCallingAppId() == android.os.Process.PHONE_UID)) {
                 return mSubscriptionPlans.get(subId);
             } else {
                 Log.w(TAG, "Not returning plans because caller " + callingPackage
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1fd7a73..79882da 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -8019,7 +8019,7 @@
             int callingUid, int callingPid, String pkg, boolean nullPkgIndicatesUserSwitch,
             String channelId, FlagChecker flagChecker, boolean includeCurrentProfiles, int userId,
             boolean sendDelete, int reason, String listenerName, boolean wasPosted) {
-        ArrayList<NotificationRecord> canceledNotifications = null;
+        Set<String> childNotifications = null;
         for (int i = notificationList.size() - 1; i >= 0; --i) {
             NotificationRecord r = notificationList.get(i);
             if (includeCurrentProfiles) {
@@ -8042,20 +8042,30 @@
             if (channelId != null && !channelId.equals(r.getChannel().getId())) {
                 continue;
             }
-            if (canceledNotifications == null) {
-                canceledNotifications = new ArrayList<>();
+            if (r.getSbn().isGroup() && r.getNotification().isGroupChild()) {
+                if (childNotifications == null) {
+                    childNotifications = new HashSet<>();
+                }
+                childNotifications.add(r.getKey());
+                continue;
             }
             notificationList.remove(i);
             mNotificationsByKey.remove(r.getKey());
             r.recordDismissalSentiment(NotificationStats.DISMISS_SENTIMENT_NEUTRAL);
-            canceledNotifications.add(r);
             cancelNotificationLocked(r, sendDelete, reason, wasPosted, listenerName);
         }
-        if (canceledNotifications != null) {
-            final int M = canceledNotifications.size();
-            for (int i = 0; i < M; i++) {
-                cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
-                        listenerName, false /* sendDelete */, flagChecker, reason);
+        if (childNotifications != null) {
+            final int M = notificationList.size();
+            for (int i = M - 1; i >= 0; i--) {
+                NotificationRecord r = notificationList.get(i);
+                if (childNotifications.contains(r.getKey())) {
+                    // dismiss conditions were checked in the first loop and so don't need to be
+                    // checked again
+                    notificationList.remove(i);
+                    mNotificationsByKey.remove(r.getKey());
+                    r.recordDismissalSentiment(NotificationStats.DISMISS_SENTIMENT_NEUTRAL);
+                    cancelNotificationLocked(r, sendDelete, reason, wasPosted, listenerName);
+                }
             }
             updateLightsLocked();
         }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 5bbe490..4c1b700 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -75,6 +75,7 @@
 import android.util.Pair;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
@@ -143,6 +144,9 @@
 
         private final MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
 
+        @GuardedBy("mListeners")
+        private boolean mIsWatchingPackageBroadcasts = false;
+
         private final ShortcutChangeHandler mShortcutChangeHandler;
 
         private final Handler mCallbackHandler;
@@ -281,7 +285,10 @@
          * Register a receiver to watch for package broadcasts
          */
         private void startWatchingPackageBroadcasts() {
-            mPackageMonitor.register(mContext, UserHandle.ALL, true, mCallbackHandler);
+            if (!mIsWatchingPackageBroadcasts) {
+                mPackageMonitor.register(mContext, UserHandle.ALL, true, mCallbackHandler);
+                mIsWatchingPackageBroadcasts = true;
+            }
         }
 
         /**
@@ -291,7 +298,10 @@
             if (DEBUG) {
                 Log.d(TAG, "Stopped watching for packages");
             }
-            mPackageMonitor.unregister();
+            if (mIsWatchingPackageBroadcasts) {
+                mPackageMonitor.unregister();
+                mIsWatchingPackageBroadcasts = false;
+            }
         }
 
         void checkCallbackCount() {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4c58b06..6b29129 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -4709,7 +4709,7 @@
                 final boolean running = am.isUserRunning(user.id, 0);
                 final boolean current = user.id == currentUser;
                 if (verbose) {
-                    pw.printf("%d: id=%d, name=%s, flags=%s%s%s%s%s\n", i, user.id, user.name,
+                    pw.printf("%d: id=%d, name=%s, flags=%s%s%s%s%s%s\n", i, user.id, user.name,
                             UserInfo.flagsToString(user.flags),
                             running ? " (running)" : "",
                             user.partial ? " (partial)" : "",
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 6fffde1..e54da9e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -29,6 +29,8 @@
 import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
 import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT;
 import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
@@ -215,7 +217,8 @@
     /** Use the transit animation in style resource (see {@link #selectAnimation}). */
     static final int ANIMATION_STYLEABLE = 0;
 
-    private static final int[] SHOW_TYPES_FOR_SWIPE = {ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR};
+    private static final int[] SHOW_TYPES_FOR_SWIPE = {ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR,
+            ITYPE_CLIMATE_BAR, ITYPE_EXTRA_NAVIGATION_BAR};
     private static final int[] SHOW_TYPES_FOR_PANIC = {ITYPE_NAVIGATION_BAR};
 
     private final WindowManagerService mService;
@@ -301,6 +304,16 @@
     private WindowState mNavigationBarAlt = null;
     @WindowManagerPolicy.AltBarPosition
     private int mNavigationBarAltPosition = ALT_BAR_UNKNOWN;
+    // Alternative climate bar for when flexible insets mapping is used to place a climate bar on
+    // the screen.
+    private WindowState mClimateBarAlt = null;
+    @WindowManagerPolicy.AltBarPosition
+    private int mClimateBarAltPosition = ALT_BAR_UNKNOWN;
+    // Alternative extra nav bar for when flexible insets mapping is used to place an extra nav bar
+    // on the screen.
+    private WindowState mExtraNavBarAlt = null;
+    @WindowManagerPolicy.AltBarPosition
+    private int mExtraNavBarAltPosition = ALT_BAR_UNKNOWN;
 
     /** See {@link #getNavigationBarFrameHeight} */
     private int[] mNavigationBarFrameHeightForRotationDefault = new int[4];
@@ -669,6 +682,12 @@
         if (mNavigationBarAlt != null && mNavigationBarAltPosition == pos) {
             requestTransientBars(mNavigationBarAlt);
         }
+        if (mClimateBarAlt != null && mClimateBarAltPosition == pos) {
+            requestTransientBars(mClimateBarAlt);
+        }
+        if (mExtraNavBarAlt != null && mExtraNavBarAltPosition == pos) {
+            requestTransientBars(mExtraNavBarAlt);
+        }
     }
 
     void systemReady() {
@@ -936,6 +955,12 @@
         if (mNavigationBarAlt == win) {
             mNavigationBarAltPosition = getAltBarPosition(attrs);
         }
+        if (mClimateBarAlt == win) {
+            mClimateBarAltPosition = getAltBarPosition(attrs);
+        }
+        if (mExtraNavBarAlt == win) {
+            mExtraNavBarAltPosition = getAltBarPosition(attrs);
+        }
     }
 
     /**
@@ -1033,6 +1058,16 @@
                             return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
                         }
                         break;
+                    case ITYPE_CLIMATE_BAR:
+                        if (mClimateBarAlt != null && mClimateBarAlt.isAlive()) {
+                            return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+                        }
+                        break;
+                    case ITYPE_EXTRA_NAVIGATION_BAR:
+                        if (mExtraNavBarAlt != null && mExtraNavBarAlt.isAlive()) {
+                            return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+                        }
+                        break;
                 }
             }
         }
@@ -1146,6 +1181,14 @@
                                 mNavigationBarAlt = win;
                                 mNavigationBarAltPosition = getAltBarPosition(attrs);
                                 break;
+                            case ITYPE_CLIMATE_BAR:
+                                mClimateBarAlt = win;
+                                mClimateBarAltPosition = getAltBarPosition(attrs);
+                                break;
+                            case ITYPE_EXTRA_NAVIGATION_BAR:
+                                mExtraNavBarAlt = win;
+                                mExtraNavBarAltPosition = getAltBarPosition(attrs);
+                                break;
                         }
                         mDisplayContent.setInsetProvider(insetsType, win, null);
                     }
@@ -1194,6 +1237,8 @@
             switch (insetsType) {
                 case ITYPE_NAVIGATION_BAR:
                 case ITYPE_STATUS_BAR:
+                case ITYPE_CLIMATE_BAR:
+                case ITYPE_EXTRA_NAVIGATION_BAR:
                 case ITYPE_CAPTION_BAR:
                     if (++count > 1) {
                         throw new IllegalArgumentException(
@@ -1223,6 +1268,12 @@
             if (mDisplayContent.isDefaultDisplay) {
                 mService.mPolicy.setKeyguardCandidateLw(null);
             }
+        } else if (mClimateBarAlt == win) {
+            mClimateBarAlt = null;
+            mDisplayContent.setInsetProvider(ITYPE_CLIMATE_BAR, null, null);
+        } else if (mExtraNavBarAlt == win) {
+            mExtraNavBarAlt = null;
+            mDisplayContent.setInsetProvider(ITYPE_EXTRA_NAVIGATION_BAR, null, null);
         }
         if (mLastFocusedWindow == win) {
             mLastFocusedWindow = null;
@@ -1311,7 +1362,8 @@
                     return R.anim.dock_left_enter;
                 }
             }
-        } else if (win == mStatusBarAlt || win == mNavigationBarAlt) {
+        } else if (win == mStatusBarAlt || win == mNavigationBarAlt || win == mClimateBarAlt
+                || win == mExtraNavBarAlt) {
             if (win.getAttrs().windowAnimations != 0) {
                 return ANIMATION_STYLEABLE;
             }
@@ -2810,10 +2862,19 @@
         }
 
         final InsetsState requestedState = controlTarget.getRequestedInsetsState();
-        final @InsetsType int restorePositionTypes = (requestedState.getSourceOrDefaultVisibility(
-                ITYPE_NAVIGATION_BAR) ? Type.navigationBars() : 0) | (
-                requestedState.getSourceOrDefaultVisibility(ITYPE_STATUS_BAR) ? Type.statusBars()
-                        : 0);
+        final @InsetsType int restorePositionTypes =
+                (requestedState.getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR)
+                        ? Type.navigationBars() : 0)
+                | (requestedState.getSourceOrDefaultVisibility(ITYPE_STATUS_BAR)
+                        ? Type.statusBars() : 0)
+                | (mExtraNavBarAlt != null
+                        && requestedState.getSourceOrDefaultVisibility(
+                                ITYPE_EXTRA_NAVIGATION_BAR)
+                        ? Type.navigationBars() : 0)
+                | (mClimateBarAlt != null
+                        && requestedState.getSourceOrDefaultVisibility(
+                                ITYPE_CLIMATE_BAR)
+                        ? Type.statusBars() : 0);
 
         if (swipeTarget == mNavigationBar
                 && (restorePositionTypes & Type.navigationBars()) != 0) {
@@ -3326,6 +3387,16 @@
             pw.print(prefix); pw.print("mNavigationBarAltPosition=");
             pw.println(mNavigationBarAltPosition);
         }
+        if (mClimateBarAlt != null) {
+            pw.print(prefix); pw.print("mClimateBarAlt="); pw.println(mClimateBarAlt);
+            pw.print(prefix); pw.print("mClimateBarAltPosition=");
+            pw.println(mClimateBarAltPosition);
+        }
+        if (mExtraNavBarAlt != null) {
+            pw.print(prefix); pw.print("mExtraNavBarAlt="); pw.println(mExtraNavBarAlt);
+            pw.print(prefix); pw.print("mExtraNavBarAltPosition=");
+            pw.println(mExtraNavBarAltPosition);
+        }
         if (mFocusedWindow != null) {
             pw.print(prefix); pw.print("mFocusedWindow="); pw.println(mFocusedWindow);
         }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index a7f32c0..5bed186 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -93,6 +95,8 @@
             case ITYPE_STATUS_BAR:
             case ITYPE_NAVIGATION_BAR:
             case ITYPE_IME:
+            case ITYPE_CLIMATE_BAR:
+            case ITYPE_EXTRA_NAVIGATION_BAR:
                 mControllable = true;
                 break;
             default:
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index c0bdbff..b59452f 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_INVALID;
@@ -42,6 +43,7 @@
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams.WindowType;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -115,7 +117,7 @@
     }
 
     InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
-        final @InternalInsetsType int type = getInsetsTypeForWindowType(attrs.type);
+        final @InternalInsetsType int type = getInsetsTypeForLayoutParams(attrs);
         final WindowToken token = mDisplayContent.getWindowToken(attrs.token);
         final @WindowingMode int windowingMode = token != null
                 ? token.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
@@ -135,7 +137,9 @@
         return false;
     }
 
-    private static @InternalInsetsType int getInsetsTypeForWindowType(int type) {
+    private static @InternalInsetsType
+            int getInsetsTypeForLayoutParams(WindowManager.LayoutParams attrs) {
+        @WindowType int type = attrs.type;
         switch (type) {
             case TYPE_STATUS_BAR:
                 return ITYPE_STATUS_BAR;
@@ -143,9 +147,22 @@
                 return ITYPE_NAVIGATION_BAR;
             case TYPE_INPUT_METHOD:
                 return ITYPE_IME;
-            default:
-                return ITYPE_INVALID;
         }
+
+        // If not one of the types above, check whether an internal inset mapping is specified.
+        if (attrs.providesInsetsTypes != null) {
+            for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) {
+                switch (insetsType) {
+                    case ITYPE_STATUS_BAR:
+                    case ITYPE_NAVIGATION_BAR:
+                    case ITYPE_CLIMATE_BAR:
+                    case ITYPE_EXTRA_NAVIGATION_BAR:
+                        return insetsType;
+                }
+            }
+        }
+
+        return ITYPE_INVALID;
     }
 
     /** @see #getInsetsForDispatch */
@@ -158,14 +175,15 @@
             state.removeSource(type);
 
             // Navigation bar doesn't get influenced by anything else
-            if (type == ITYPE_NAVIGATION_BAR) {
+            if (type == ITYPE_NAVIGATION_BAR || type == ITYPE_EXTRA_NAVIGATION_BAR) {
                 state.removeSource(ITYPE_IME);
                 state.removeSource(ITYPE_STATUS_BAR);
+                state.removeSource(ITYPE_CLIMATE_BAR);
                 state.removeSource(ITYPE_CAPTION_BAR);
             }
 
             // Status bar doesn't get influenced by caption bar
-            if (type == ITYPE_STATUS_BAR) {
+            if (type == ITYPE_STATUS_BAR || type == ITYPE_CLIMATE_BAR) {
                 state.removeSource(ITYPE_CAPTION_BAR);
             }
 
@@ -336,8 +354,12 @@
             @Nullable InsetsControlTarget fakeNavControlling) {
         onControlChanged(ITYPE_STATUS_BAR, statusControlling);
         onControlChanged(ITYPE_NAVIGATION_BAR, navControlling);
+        onControlChanged(ITYPE_CLIMATE_BAR, statusControlling);
+        onControlChanged(ITYPE_EXTRA_NAVIGATION_BAR, navControlling);
         onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeStatusControlling);
         onControlFakeTargetChanged(ITYPE_NAVIGATION_BAR, fakeNavControlling);
+        onControlFakeTargetChanged(ITYPE_CLIMATE_BAR, fakeStatusControlling);
+        onControlFakeTargetChanged(ITYPE_EXTRA_NAVIGATION_BAR, fakeNavControlling);
         notifyPendingInsetsControlChanged();
     }
 
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 1b649fd..44462c3 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -54,6 +54,7 @@
         "com_android_server_UsbDescriptorParser.cpp",
         "com_android_server_UsbMidiDevice.cpp",
         "com_android_server_UsbHostManager.cpp",
+        "com_android_server_VibratorManagerService.cpp",
         "com_android_server_VibratorService.cpp",
         "com_android_server_PersistentDataBlockService.cpp",
         "com_android_server_am_CachedAppOptimizer.cpp",
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 8646a53..4c017f5 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -2,6 +2,7 @@
 per-file com_android_server_lights_LightsService.cpp = michaelwr@google.com, santoscordon@google.com
 
 # Haptics
+per-file com_android_server_VibratorManagerService.cpp = michaelwr@google.com
 per-file com_android_server_VibratorService.cpp = michaelwr@google.com
 
 # Input
diff --git a/services/core/jni/com_android_server_VibratorManagerService.cpp b/services/core/jni/com_android_server_VibratorManagerService.cpp
new file mode 100644
index 0000000..dae9cef
--- /dev/null
+++ b/services/core/jni/com_android_server_VibratorManagerService.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VibratorManagerService"
+
+#include <nativehelper/JNIHelp.h>
+#include "android_runtime/AndroidRuntime.h"
+#include "core_jni_helpers.h"
+#include "jni.h"
+
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+#include <vibratorservice/VibratorManagerHalWrapper.h>
+
+namespace android {
+
+class NativeVibratorManagerService {
+public:
+    NativeVibratorManagerService() : mHal(std::make_unique<vibrator::LegacyManagerHalWrapper>()) {}
+    ~NativeVibratorManagerService() = default;
+
+    vibrator::ManagerHalWrapper* hal() const { return mHal.get(); }
+
+private:
+    const std::unique_ptr<vibrator::ManagerHalWrapper> mHal;
+};
+
+static void destroyNativeService(void* ptr) {
+    NativeVibratorManagerService* service = reinterpret_cast<NativeVibratorManagerService*>(ptr);
+    if (service) {
+        delete service;
+    }
+}
+
+static jlong nativeInit(JNIEnv* /* env */, jclass /* clazz */) {
+    std::unique_ptr<NativeVibratorManagerService> service =
+            std::make_unique<NativeVibratorManagerService>();
+    return reinterpret_cast<jlong>(service.release());
+}
+
+static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeService));
+}
+
+static jintArray nativeGetVibratorIds(JNIEnv* env, jclass /* clazz */, jlong servicePtr) {
+    NativeVibratorManagerService* service =
+            reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("nativeGetVibratorIds failed because native service was not initialized");
+        return nullptr;
+    }
+    auto result = service->hal()->getVibratorIds();
+    if (!result.isOk()) {
+        return nullptr;
+    }
+    std::vector<int32_t> vibratorIds = result.value();
+    jintArray ids = env->NewIntArray(vibratorIds.size());
+    env->SetIntArrayRegion(ids, 0, vibratorIds.size(), reinterpret_cast<jint*>(vibratorIds.data()));
+    return ids;
+}
+
+static const JNINativeMethod method_table[] = {
+        {"nativeInit", "()J", (void*)nativeInit},
+        {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer},
+        {"nativeGetVibratorIds", "(J)[I", (void*)nativeGetVibratorIds},
+};
+
+int register_android_server_VibratorManagerService(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "com/android/server/VibratorManagerService", method_table,
+                                    NELEM(method_table));
+}
+
+}; // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index e7f6db9..48d5244 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -38,6 +38,7 @@
 int register_android_server_UsbMidiDevice(JNIEnv* env);
 int register_android_server_UsbHostManager(JNIEnv* env);
 int register_android_server_vr_VrManagerService(JNIEnv* env);
+int register_android_server_VibratorManagerService(JNIEnv* env);
 int register_android_server_VibratorService(JavaVM* vm, JNIEnv* env);
 int register_android_server_location_GnssLocationProvider(JNIEnv* env);
 int register_android_server_connectivity_Vpn(JNIEnv* env);
@@ -92,6 +93,7 @@
     register_android_server_UsbAlsaJackDetector(env);
     register_android_server_UsbHostManager(env);
     register_android_server_vr_VrManagerService(env);
+    register_android_server_VibratorManagerService(env);
     register_android_server_VibratorService(vm, env);
     register_android_server_SystemServer(env);
     register_android_server_location_GnssLocationProvider(env);
diff --git a/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneProviderRequestTest.java b/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneProviderRequestTest.java
index 1fa1b8f..75696da 100644
--- a/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneProviderRequestTest.java
+++ b/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneProviderRequestTest.java
@@ -20,16 +20,20 @@
 
 import org.junit.Test;
 
+import java.time.Duration;
+
 public class LocationTimeZoneProviderRequestTest {
 
     @Test
     public void testParcelable() {
         LocationTimeZoneProviderRequest.Builder builder =
                 new LocationTimeZoneProviderRequest.Builder()
-                        .setReportLocationTimeZone(true);
+                        .setReportLocationTimeZone(false);
         assertRoundTripParcelable(builder.build());
 
-        builder.setReportLocationTimeZone(false);
+        builder.setReportLocationTimeZone(true)
+                .setInitializationTimeoutMillis(Duration.ofMinutes(5).toMillis());
+
         assertRoundTripParcelable(builder.build());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
new file mode 100644
index 0000000..8a22a2f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Tests for {@link VibratorManagerService}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:VibratorManagerServiceTest
+ */
+@Presubmit
+public class VibratorManagerServiceTest {
+
+    @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+    @Mock private VibratorManagerService.NativeWrapper mNativeWrapperMock;
+
+    @Before
+    public void setUp() throws Exception {
+    }
+
+    private VibratorManagerService createService() {
+        return new VibratorManagerService(InstrumentationRegistry.getContext(),
+                new VibratorManagerService.Injector() {
+                    @Override
+                    VibratorManagerService.NativeWrapper getNativeWrapper() {
+                        return mNativeWrapperMock;
+                    }
+                });
+    }
+
+    @Test
+    public void createService_initializesNativeService() {
+        createService();
+        verify(mNativeWrapperMock).init();
+    }
+
+    @Test
+    public void getVibratorIds_withNullResultFromNative_returnsEmptyArray() {
+        when(mNativeWrapperMock.getVibratorIds()).thenReturn(null);
+        assertArrayEquals(new int[0], createService().getVibratorIds());
+    }
+
+    @Test
+    public void getVibratorIds_withNonEmptyResultFromNative_returnsSameArray() {
+        when(mNativeWrapperMock.getVibratorIds()).thenReturn(new int[]{ 1, 2 });
+        assertArrayEquals(new int[]{ 1, 2 }, createService().getVibratorIds());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
index dbaad66..631b8d3 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
@@ -18,7 +18,6 @@
 import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_SUCCESS;
 import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN;
 
-import static com.android.server.location.timezone.ControllerImpl.UNCERTAINTY_DELAY;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
 import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_DISABLED;
@@ -47,6 +46,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -92,7 +92,8 @@
         mTestLocationTimeZoneProvider.assertInitialized();
 
         mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertNextQueueItemIsDelayed(UNCERTAINTY_DELAY);
+        Duration expectedTimeout = expectedProviderInitializationTimeout();
+        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
         mTestCallback.assertNoSuggestionMade();
     }
 
@@ -121,7 +122,8 @@
 
         mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestCallback.assertNoSuggestionMade();
-        mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+        Duration expectedTimeout = expectedProviderInitializationTimeout();
+        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
 
         // Simulate time passing with no event being received.
         mTestThreadingDomain.executeNext();
@@ -140,7 +142,8 @@
         controllerImpl.initialize(testEnvironment, mTestCallback);
 
         mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+        Duration expectedTimeout = expectedProviderInitializationTimeout();
+        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
         mTestCallback.assertNoSuggestionMade();
 
         // Simulate a location event being received by the provider. This should cause a suggestion
@@ -163,7 +166,8 @@
         controllerImpl.initialize(testEnvironment, mTestCallback);
 
         mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+        Duration expectedTimeout = expectedProviderInitializationTimeout();
+        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
         mTestCallback.assertNoSuggestionMade();
 
         // Simulate a location event being received by the provider. This should cause a suggestion
@@ -203,7 +207,8 @@
         controllerImpl.initialize(testEnvironment, mTestCallback);
 
         mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+        Duration expectedTimeout = expectedProviderInitializationTimeout();
+        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
         mTestCallback.assertNoSuggestionMade();
 
         // Simulate a location event being received by the provider. This should cause a suggestion
@@ -216,15 +221,16 @@
         mTestCallback.assertSuggestionMadeAndCommit(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
 
-        // Uncertainty should cause
+        // Uncertainty should cause a suggestion to (only) be queued.
         mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
                 USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
 
         mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+        mTestThreadingDomain.assertSingleDelayedQueueItem(testEnvironment.getUncertaintyDelay());
         mTestCallback.assertNoSuggestionMade();
 
-        // And a third event should cause yet another suggestion.
+        // And a third event should cause yet another suggestion and for the queued item to be
+        // removed.
         mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
                 USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
 
@@ -250,7 +256,8 @@
         testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
 
         mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertNextQueueItemIsDelayed(UNCERTAINTY_DELAY);
+        Duration expectedTimeout = expectedProviderInitializationTimeout();
+        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
         mTestCallback.assertNoSuggestionMade();
 
         // Now signal a config change so that geo detection is disabled.
@@ -270,7 +277,8 @@
         controllerImpl.initialize(testEnvironment, mTestCallback);
 
         mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+        Duration expectedTimeout = expectedProviderInitializationTimeout();
+        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
         mTestCallback.assertNoSuggestionMade();
 
         // Simulate a location event being received by the provider. This should cause a suggestion
@@ -304,7 +312,8 @@
 
         // There should be a runnable scheduled to suggest uncertainty if no event is received.
         mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+        Duration expectedTimeout = expectedProviderInitializationTimeout();
+        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
         mTestCallback.assertNoSuggestionMade();
 
         // Have the provider suggest a time zone.
@@ -328,7 +337,8 @@
         int[] expectedStateTransitions = { PROVIDER_STATE_DISABLED, PROVIDER_STATE_ENABLED };
         mTestLocationTimeZoneProvider.assertStateChangesAndCommit(expectedStateTransitions);
         mTestLocationTimeZoneProvider.assertConfig(USER2_CONFIG_GEO_DETECTION_ENABLED);
-        mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+        expectedTimeout = expectedProviderInitializationTimeout();
+        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
         mTestCallback.assertNoSuggestionMade();
 
         // Simulate no event being received, and time passing.
@@ -351,8 +361,18 @@
         return builder.build();
     }
 
+
+    private Duration expectedProviderInitializationTimeout() {
+        return TestEnvironment.PROVIDER_INITIALIZATION_TIMEOUT
+                .plus(TestEnvironment.PROVIDER_INITIALIZATION_TIMEOUT_FUZZ);
+    }
+
     private static class TestEnvironment extends LocationTimeZoneProviderController.Environment {
 
+        static final Duration PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
+        static final Duration PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
+        private static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(3);
+
         private final LocationTimeZoneProviderController mController;
         private ConfigurationInternal mConfigurationInternal;
 
@@ -369,6 +389,21 @@
             return mConfigurationInternal;
         }
 
+        @Override
+        Duration getProviderInitializationTimeout() {
+            return PROVIDER_INITIALIZATION_TIMEOUT;
+        }
+
+        @Override
+        Duration getProviderInitializationTimeoutFuzz() {
+            return PROVIDER_INITIALIZATION_TIMEOUT_FUZZ;
+        }
+
+        @Override
+        Duration getUncertaintyDelay() {
+            return UNCERTAINTY_DELAY;
+        }
+
         void simulateConfigChange(ConfigurationInternal newConfig) {
             ConfigurationInternal oldConfig = mConfigurationInternal;
             mConfigurationInternal = Objects.requireNonNull(newConfig);
@@ -432,7 +467,7 @@
         }
 
         @Override
-        void onEnable() {
+        void onEnable(Duration initializationTimeout) {
             // Nothing needed for tests.
         }
 
@@ -464,7 +499,7 @@
 
         /**
          * Asserts the provider's config matches the expected, and the current state is set
-         * accordinly. Commits the latest changes to the state.
+         * accordingly. Commits the latest changes to the state.
          */
         void assertIsEnabled(@NonNull ConfigurationInternal expectedConfig) {
             assertConfig(expectedConfig);
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
index 7c882fc..5542db0 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
@@ -34,6 +34,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.time.Duration;
+
 /**
  * Tests for {@link NullLocationTimeZoneProvider} and, indirectly, the class it extends
  * {@link LocationTimeZoneProvider}.
@@ -73,7 +75,8 @@
         provider.initialize(providerState -> mTestController.onProviderStateChange(providerState));
 
         ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
-        provider.enable(config);
+        Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
+        provider.enable(config, arbitraryInitializationTimeout);
 
         // The StubbedProvider should enters enabled state, but immediately schedule a runnable to
         // switch to perm failure.
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java b/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
index 70ff22d..def919e 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
@@ -105,8 +105,8 @@
         assertTrue(getNextQueueItemDelayMillis() == 0);
     }
 
-    void assertNextQueueItemIsDelayed(Duration expectedDelay) {
-        assertTrue(getNextQueueItemDelayMillis() == expectedDelay.toMillis());
+    private void assertNextQueueItemIsDelayed(Duration expectedDelay) {
+        assertEquals(getNextQueueItemDelayMillis(), expectedDelay.toMillis());
     }
 
     void assertQueueEmpty() {
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 1a4b119..ed6a20b 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -74,6 +74,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -185,6 +186,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
@@ -563,6 +565,37 @@
                 .getUiAutomation().dropShellPermissionIdentity();
     }
 
+    private void simulatePackageSuspendBroadcast(boolean suspend, String pkg,
+            int uid) {
+        // mimics receive broadcast that package is (un)suspended
+        // but does not actually (un)suspend the package
+        final Bundle extras = new Bundle();
+        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
+                new String[]{pkg});
+        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, new int[]{uid});
+
+        final String action = suspend ? Intent.ACTION_PACKAGES_SUSPENDED
+                : Intent.ACTION_PACKAGES_UNSUSPENDED;
+        final Intent intent = new Intent(action);
+        intent.putExtras(extras);
+
+        mPackageIntentReceiver.onReceive(getContext(), intent);
+    }
+
+    private void simulatePackageDistractionBroadcast(int flag, String[] pkgs, int[] uids) {
+        // mimics receive broadcast that package is (un)distracting
+        // but does not actually register that info with packagemanager
+        final Bundle extras = new Bundle();
+        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgs);
+        extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, flag);
+        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uids);
+
+        final Intent intent = new Intent(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED);
+        intent.putExtras(extras);
+
+        mPackageIntentReceiver.onReceive(getContext(), intent);
+    }
+
     private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() {
         ArrayMap<Boolean, ArrayList<ComponentName>> changed = new ArrayMap<>();
         changed.put(true, new ArrayList<>());
@@ -7066,34 +7099,39 @@
         assertTrue(mService.isVisibleToListener(sbn, info));
     }
 
-    private void simulatePackageSuspendBroadcast(boolean suspend, String pkg,
-            int uid) {
-        // mimics receive broadcast that package is (un)suspended
-        // but does not actually (un)suspend the package
-        final Bundle extras = new Bundle();
-        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
-                new String[]{pkg});
-        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, new int[]{uid});
+    @Test
+    public void testUserInitiatedCancelAll_groupCancellationOrder_groupPostedFirst() {
+        final NotificationRecord parent = spy(generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true));
+        final NotificationRecord child = spy(generateNotificationRecord(
+                mTestNotificationChannel, 2, "group", false));
+        mService.addNotification(parent);
+        mService.addNotification(child);
 
-        final String action = suspend ? Intent.ACTION_PACKAGES_SUSPENDED
-                : Intent.ACTION_PACKAGES_UNSUSPENDED;
-        final Intent intent = new Intent(action);
-        intent.putExtras(extras);
+        InOrder inOrder = inOrder(parent, child);
 
-        mPackageIntentReceiver.onReceive(getContext(), intent);
+        mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
+                parent.getUserId());
+        waitForIdle();
+        inOrder.verify(parent).recordDismissalSentiment(anyInt());
+        inOrder.verify(child).recordDismissalSentiment(anyInt());
     }
 
-    private void simulatePackageDistractionBroadcast(int flag, String[] pkgs, int[] uids) {
-        // mimics receive broadcast that package is (un)distracting
-        // but does not actually register that info with packagemanager
-        final Bundle extras = new Bundle();
-        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgs);
-        extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, flag);
-        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uids);
+    @Test
+    public void testUserInitiatedCancelAll_groupCancellationOrder_groupPostedSecond() {
+        final NotificationRecord parent = spy(generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true));
+        final NotificationRecord child = spy(generateNotificationRecord(
+                mTestNotificationChannel, 2, "group", false));
+        mService.addNotification(child);
+        mService.addNotification(parent);
 
-        final Intent intent = new Intent(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED);
-        intent.putExtras(extras);
+        InOrder inOrder = inOrder(parent, child);
 
-        mPackageIntentReceiver.onReceive(getContext(), intent);
+        mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
+                parent.getUserId());
+        waitForIdle();
+        inOrder.verify(parent).recordDismissalSentiment(anyInt());
+        inOrder.verify(child).recordDismissalSentiment(anyInt());
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 9511181..2920c1d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -20,6 +20,8 @@
 import static android.view.Gravity.LEFT;
 import static android.view.Gravity.RIGHT;
 import static android.view.Gravity.TOP;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.InsetsState.ITYPE_TOP_GESTURES;
@@ -218,10 +220,15 @@
 
     @Test
     public void addingWindow_throwsException_WithMultipleInsetTypes() {
-        WindowState win = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
-        win.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR};
+        WindowState win1 = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
+        win1.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR};
 
-        expectThrows(IllegalArgumentException.class, () -> addWindow(win));
+        expectThrows(IllegalArgumentException.class, () -> addWindow(win1));
+
+        WindowState win2 = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
+        win2.mAttrs.providesInsetsTypes = new int[]{ITYPE_CLIMATE_BAR, ITYPE_EXTRA_NAVIGATION_BAR};
+
+        expectThrows(IllegalArgumentException.class, () -> addWindow(win2));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 4a49743..e2cd8a9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -20,6 +20,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -284,12 +286,17 @@
     public void testBarControllingWinChanged() {
         final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+        final WindowState climateBar = createWindow(null, TYPE_APPLICATION, "climateBar");
+        final WindowState extraNavBar = createWindow(null, TYPE_APPLICATION, "extraNavBar");
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
         getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
+        getController().getSourceProvider(ITYPE_CLIMATE_BAR).setWindow(climateBar, null, null);
+        getController().getSourceProvider(ITYPE_EXTRA_NAVIGATION_BAR).setWindow(extraNavBar, null,
+                null);
         getController().onBarControlTargetChanged(app, null, app, null);
         InsetsSourceControl[] controls = getController().getControlsForDispatch(app);
-        assertEquals(2, controls.length);
+        assertEquals(4, controls.length);
     }
 
     @Test
diff --git a/telephony/api/system-current.txt b/telephony/api/system-current.txt
index 0a7e158..72a739c 100644
--- a/telephony/api/system-current.txt
+++ b/telephony/api/system-current.txt
@@ -933,11 +933,11 @@
     method public int getSuggestedRetryTime();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.DataCallResponse> CREATOR;
-    field public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 2; // 0x2
-    field public static final int HANDOVER_FAILURE_MODE_LEGACY = 1; // 0x1
-    field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 3; // 0x3
-    field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 4; // 0x4
-    field public static final int HANDOVER_FAILURE_MODE_UNKNOWN = 0; // 0x0
+    field public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 1; // 0x1
+    field public static final int HANDOVER_FAILURE_MODE_LEGACY = 0; // 0x0
+    field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 2; // 0x2
+    field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 3; // 0x3
+    field public static final int HANDOVER_FAILURE_MODE_UNKNOWN = -1; // 0xffffffff
     field public static final int LINK_STATUS_ACTIVE = 2; // 0x2
     field public static final int LINK_STATUS_DORMANT = 1; // 0x1
     field public static final int LINK_STATUS_INACTIVE = 0; // 0x0
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0ec2793..a82d988 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1888,8 +1888,8 @@
      *
      * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
      * restrictions, and apps are recommended to use resettable identifiers (see <a
-     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
-     * the following requirements is met:
+     * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+     * method can be invoked if one of the following requirements is met:
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
@@ -1941,8 +1941,8 @@
      *
      * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
      * restrictions, and apps are recommended to use resettable identifiers (see <a
-     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
-     * the following requirements is met:
+     * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+     * method can be invoked if one of the following requirements is met:
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
@@ -2010,8 +2010,8 @@
      *
      * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
      * restrictions, and apps are recommended to use resettable identifiers (see <a
-     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
-     * the following requirements is met:
+     * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+     * method can be invoked if one of the following requirements is met:
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
@@ -2088,8 +2088,8 @@
      *
      * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
      * restrictions, and apps are recommended to use resettable identifiers (see <a
-     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
-     * the following requirements is met:
+     * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+     * method can be invoked if one of the following requirements is met:
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
@@ -2126,8 +2126,8 @@
      *
      * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
      * restrictions, and apps are recommended to use resettable identifiers (see <a
-     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
-     * the following requirements is met:
+     * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+     * method can be invoked if one of the following requirements is met:
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
@@ -2210,8 +2210,8 @@
      *
      * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
      * restrictions, and apps are recommended to use resettable identifiers (see <a
-     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
-     * the following requirements is met:
+     * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+     * method can be invoked if one of the following requirements is met:
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
@@ -2247,8 +2247,8 @@
      *
      * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
      * restrictions, and apps are recommended to use resettable identifiers (see <a
-     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
-     * the following requirements is met:
+     * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+     * method can be invoked if one of the following requirements is met:
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
@@ -3723,8 +3723,8 @@
      *
      * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
      * restrictions, and apps are recommended to use resettable identifiers (see <a
-     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
-     * the following requirements is met:
+     * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+     * method can be invoked if one of the following requirements is met:
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
@@ -3761,8 +3761,8 @@
      *
      * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
      * restrictions, and apps are recommended to use resettable identifiers (see <a
-     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
-     * the following requirements is met:
+     * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+     * method can be invoked if one of the following requirements is met:
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
@@ -4015,8 +4015,8 @@
      *
      * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
      * restrictions, and apps are recommended to use resettable identifiers (see <a
-     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
-     * the following requirements is met:
+     * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+     * method can be invoked if one of the following requirements is met:
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
@@ -4054,8 +4054,8 @@
      *
      * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
      * restrictions, and apps are recommended to use resettable identifiers (see <a
-     * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
-     * the following requirements is met:
+     * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+     * method can be invoked if one of the following requirements is met:
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 5ead8de..39859b1 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -80,33 +80,33 @@
     /**
      * Data handover failure mode is unknown.
      */
-    public static final int HANDOVER_FAILURE_MODE_UNKNOWN = 0;
+    public static final int HANDOVER_FAILURE_MODE_UNKNOWN = -1;
 
     /**
      * Perform fallback to the source data transport on data handover failure using
      * the legacy logic, which is fallback if the fail cause is
      * {@link DataFailCause#HANDOFF_PREFERENCE_CHANGED}.
      */
-    public static final int HANDOVER_FAILURE_MODE_LEGACY = 1;
+    public static final int HANDOVER_FAILURE_MODE_LEGACY = 0;
 
     /**
      * Perform fallback to the source data transport on data handover failure.
      */
-    public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 2;
+    public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 1;
 
     /**
      * Do not perform fallback to the source data transport on data handover failure.
-     * Frameworks should keep retrying handover by sending
+     * Framework will retry setting up a new data connection by sending
      * {@link DataService#REQUEST_REASON_HANDOVER} request to the underlying {@link DataService}.
      */
-    public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 3;
+    public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 2;
 
     /**
      * Do not perform fallback to the source transport on data handover failure.
-     * Frameworks should retry setup a new data connection by sending
+     * Framework will retry setting up a new data connection by sending
      * {@link DataService#REQUEST_REASON_NORMAL} request to the underlying {@link DataService}.
      */
-    public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 4;
+    public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 3;
 
     private final @DataFailureCause int mCause;
     private final int mSuggestedRetryTime;
@@ -332,6 +332,7 @@
            .append(" mtu=").append(getMtu())
            .append(" mtuV4=").append(getMtuV4())
            .append(" mtuV6=").append(getMtuV6())
+           .append(" handoverFailureMode=").append(getHandoverFailureMode())
            .append("}");
         return sb.toString();
     }
@@ -361,14 +362,15 @@
                 && mPcscfAddresses.containsAll(other.mPcscfAddresses)
                 && mMtu == other.mMtu
                 && mMtuV4 == other.mMtuV4
-                && mMtuV6 == other.mMtuV6;
+                && mMtuV6 == other.mMtuV6
+                && mHandoverFailureMode == other.mHandoverFailureMode;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType,
                 mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses,
-                mMtu, mMtuV4, mMtuV6);
+                mMtu, mMtuV4, mMtuV6, mHandoverFailureMode);
     }
 
     @Override