Merge "AudioService: optimize service creation"
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/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index 833ae63..e0c11cf 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -20,7 +20,17 @@
             <action android:name="com.android.perftests.core.PERFTEST" />
           </intent-filter>
         </activity>
-        <service android:name="android.os.SomeService" android:exported="false" android:process=":some_service" />
+
+        <service
+            android:name="android.os.SomeService"
+            android:exported="false"
+            android:process=":some_service" />
+
+        <provider
+            android:name="android.os.SomeProvider"
+            android:authorities="android.os.SomeProvider"
+            android:exported="false"
+            android:process=":some_provider" />
 
         <service
             android:name="android.view.autofill.MyAutofillService"
diff --git a/apct-tests/perftests/core/src/android/database/CrossProcessCursorPerfTest.java b/apct-tests/perftests/core/src/android/database/CrossProcessCursorPerfTest.java
new file mode 100644
index 0000000..77654df
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/database/CrossProcessCursorPerfTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.database;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.ContentProviderClient;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.net.Uri;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class CrossProcessCursorPerfTest {
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    /**
+     * Measure transporting a small {@link Cursor}, roughly 1KB in size.
+     */
+    @Test
+    public void timeSmall() throws Exception {
+        time(1);
+    }
+
+    /**
+     * Measure transporting a small {@link Cursor}, roughly 54KB in size.
+     */
+    @Test
+    public void timeMedium() throws Exception {
+        time(100);
+    }
+
+    /**
+     * Measure transporting a small {@link Cursor}, roughly 5.4MB in size.
+     */
+    @Test
+    public void timeLarge() throws Exception {
+        time(10_000);
+    }
+
+    private static final Uri TEST_URI = Uri.parse("content://android.os.SomeProvider/");
+
+    private void time(int count) throws Exception {
+        try (ContentProviderClient client = InstrumentationRegistry.getTargetContext()
+                .getContentResolver().acquireContentProviderClient(TEST_URI)) {
+            // Configure remote side once with data size to return
+            final ContentValues values = new ContentValues();
+            values.put(Intent.EXTRA_INDEX, count);
+            client.update(TEST_URI, values, null);
+
+            // Repeatedly query that data until we reach convergence
+            final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+            while (state.keepRunning()) {
+                try (Cursor c = client.query(TEST_URI, null, null, null)) {
+                    // Actually walk the returned values to ensure we pull all
+                    // data from the remote side
+                    while (c.moveToNext()) {
+                        assertEquals(c.getPosition(), c.getInt(0));
+                    }
+                }
+            }
+        }
+    }
+}
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/apct-tests/perftests/core/src/android/os/SomeProvider.java b/apct-tests/perftests/core/src/android/os/SomeProvider.java
new file mode 100644
index 0000000..f5e247e
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/SomeProvider.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.os;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+
+import java.util.Arrays;
+
+public class SomeProvider extends ContentProvider {
+    private Cursor mCursor;
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return mCursor;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        final char[] valueRaw = new char[512];
+        Arrays.fill(valueRaw, '!');
+        final String value = new String(valueRaw);
+
+        final int count = values.getAsInteger(Intent.EXTRA_INDEX);
+        final MatrixCursor cursor = new MatrixCursor(new String[] { "_id", "value" });
+        for (int i = 0; i < count; i++) {
+            MatrixCursor.RowBuilder row = cursor.newRow();
+            row.add(0, i);
+            row.add(1, value);
+        }
+        mCursor = cursor;
+        return 1;
+    }
+}
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/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 167b5a8..ef4f099 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -81,8 +81,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.lang.reflect.Method;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
@@ -5021,8 +5019,7 @@
          * @hide
          */
         public static double round(double value) {
-            final BigDecimal decimalScale = new BigDecimal(value);
-            return decimalScale.setScale(0, RoundingMode.HALF_UP).doubleValue();
+            return Math.floor(value + 0.5);
         }
 
         @Override
diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java
index 59dc999..55fff8b 100644
--- a/core/java/android/app/NotificationHistory.java
+++ b/core/java/android/app/NotificationHistory.java
@@ -22,12 +22,10 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
-import android.util.Slog;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
@@ -387,12 +385,13 @@
     /**
      * Removes all notifications from a conversation and regenerates the string pool
      */
-    public boolean removeConversationFromWrite(String packageName, String conversationId) {
+    public boolean removeConversationsFromWrite(String packageName, Set<String> conversationIds) {
         boolean removed = false;
         for (int i = mNotificationsToWrite.size() - 1; i >= 0; i--) {
             HistoricalNotification hn = mNotificationsToWrite.get(i);
             if (packageName.equals(hn.getPackage())
-                    && conversationId.equals(hn.getConversationId())) {
+                    && hn.getConversationId() != null
+                    && conversationIds.contains(hn.getConversationId())) {
                 removed = true;
                 mNotificationsToWrite.remove(i);
             }
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/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/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index a3c95a91..8adb7e5 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -610,7 +610,8 @@
         @Override
         public void startInputAsyncOnWindowFocusGain(View focusedView,
                 @SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus) {
-            final int startInputFlags = getStartInputFlags(focusedView, 0);
+            int startInputFlags = getStartInputFlags(focusedView, 0);
+            startInputFlags |= StartInputFlags.WINDOW_GAINED_FOCUS;
 
             final ImeFocusController controller = getFocusController();
             if (controller == null) {
diff --git a/core/java/android/widget/TEST_MAPPING b/core/java/android/widget/TEST_MAPPING
index b5beac9..49c4093 100644
--- a/core/java/android/widget/TEST_MAPPING
+++ b/core/java/android/widget/TEST_MAPPING
@@ -22,7 +22,7 @@
       "name": "CtsAutoFillServiceTestCases",
       "options": [
         {
-          "include-filter": "android.autofillservice.cts.LoginActivityTest"
+          "include-filter": "android.autofillservice.cts.dropdown.LoginActivityTest"
         },
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
@@ -36,7 +36,7 @@
       "name": "CtsAutoFillServiceTestCases",
       "options": [
         {
-          "include-filter": "android.autofillservice.cts.CheckoutActivityTest"
+          "include-filter": "android.autofillservice.cts.dropdown.CheckoutActivityTest"
         },
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index 37f6823..3bcba75 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -224,6 +224,8 @@
                 return "HIDE_DOCKED_STACK_ATTACHED";
             case SoftInputShowHideReason.HIDE_RECENTS_ANIMATION:
                 return "HIDE_RECENTS_ANIMATION";
+            case SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR:
+                return "HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR";
             default:
                 return "Unknown=" + reason;
         }
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index 4b968b4..f46626b 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -47,7 +47,8 @@
         SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME,
         SoftInputShowHideReason.HIDE_DOCKED_STACK_ATTACHED,
         SoftInputShowHideReason.HIDE_RECENTS_ANIMATION,
-        SoftInputShowHideReason.HIDE_BUBBLES})
+        SoftInputShowHideReason.HIDE_BUBBLES,
+        SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR})
 public @interface SoftInputShowHideReason {
     /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */
     int SHOW_SOFT_INPUT = 0;
@@ -147,4 +148,17 @@
      * switching, or collapsing Bubbles.
      */
     int HIDE_BUBBLES = 19;
+
+    /**
+     * Hide soft input when focusing the same window (e.g. screen turned-off and turn-on) which no
+     * valid focused editor.
+     *
+     * Note: From Android R, the window focus change callback is processed by InputDispatcher,
+     * some focus behavior changes (e.g. There are an activity with a dialog window, after
+     * screen turned-off and turned-on, before Android R the window focus sequence would be
+     * the activity first and then the dialog focused, however, in R the focus sequence would be
+     * only the dialog focused as it's the latest window with input focus) makes we need to hide
+     * soft-input when the same window focused again to align with the same behavior prior to R.
+     */
+    int HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR = 20;
 }
diff --git a/core/java/com/android/internal/inputmethod/StartInputFlags.java b/core/java/com/android/internal/inputmethod/StartInputFlags.java
index 5a8d2c2..ac83987 100644
--- a/core/java/com/android/internal/inputmethod/StartInputFlags.java
+++ b/core/java/com/android/internal/inputmethod/StartInputFlags.java
@@ -47,4 +47,10 @@
      * documented hence we probably need to revisit this though.
      */
     int INITIAL_CONNECTION = 4;
+
+    /**
+     * The start input happens when the window gained focus to call
+     * {@code android.view.inputmethod.InputMethodManager#startInputAsyncOnWindowFocusGain}.
+     */
+    int WINDOW_GAINED_FOCUS = 8;
 }
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 19dc2ed..e60f7fc 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -51,6 +51,11 @@
     public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 0;
     public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = 0;
     public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = 0;
+    public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS = 0;
+    public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON = 0;
+    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = 0;
+    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_PIP = 0;
+    public static final int CUJ_LAUNCHER_QUICK_SWITCH = 0;
 
     private static final int NO_STATSD_LOGGING = -1;
 
@@ -78,7 +83,12 @@
             CUJ_NOTIFICATION_SHADE_ROW_EXPAND,
             CUJ_NOTIFICATION_SHADE_ROW_SWIPE,
             CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
-            CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE
+            CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE,
+            CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS,
+            CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON,
+            CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
+            CUJ_LAUNCHER_APP_CLOSE_TO_PIP,
+            CUJ_LAUNCHER_QUICK_SWITCH,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {}
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index be68c4a..2435406 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -84,23 +84,31 @@
 }
 
 static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) {
+    status_t status;
     String8 name;
+    CursorWindow* window;
+
     const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
     name.setTo(nameStr);
     env->ReleaseStringUTFChars(nameObj, nameStr);
 
-    CursorWindow* window;
-    status_t status = CursorWindow::create(name, cursorWindowSize, &window);
+    if (cursorWindowSize < 0) {
+        status = INVALID_OPERATION;
+        goto fail;
+    }
+    status = CursorWindow::create(name, cursorWindowSize, &window);
     if (status || !window) {
-        jniThrowExceptionFmt(env,
-                "android/database/CursorWindowAllocationException",
-                "Could not allocate CursorWindow '%s' of size %d due to error %d.",
-                name.string(), cursorWindowSize, status);
-        return 0;
+        goto fail;
     }
 
     LOG_WINDOW("nativeInitializeEmpty: window = %p", window);
     return reinterpret_cast<jlong>(window);
+
+fail:
+    jniThrowExceptionFmt(env, "android/database/CursorWindowAllocationException",
+                         "Could not allocate CursorWindow '%s' of size %d due to error %d.",
+                         name.string(), cursorWindowSize, status);
+    return 0;
 }
 
 static jlong nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
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/OWNERS b/core/proto/OWNERS
index 4892faa..11f6a91 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -14,6 +14,7 @@
 # Frameworks
 ogunwale@google.com
 jjaggi@google.com
+roosa@google.com
 per-file usagestatsservice.proto, usagestatsservice_v2.proto = mwachens@google.com
 
 # Launcher
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/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
index c951091..7fa1613 100644
--- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java
+++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
@@ -21,7 +21,6 @@
 import android.app.NotificationHistory.HistoricalNotification;
 import android.graphics.drawable.Icon;
 import android.os.Parcel;
-import android.util.Slog;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -31,6 +30,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
 public class NotificationHistoryTest {
@@ -297,7 +297,7 @@
         for (int i = 1; i <= 10; i++) {
             HistoricalNotification n = getHistoricalNotification("pkg", i);
 
-            if (i != 2) {
+            if (i != 2 && i != 4) {
                 postRemoveExpectedStrings.add(n.getPackage());
                 postRemoveExpectedStrings.add(n.getChannelName());
                 postRemoveExpectedStrings.add(n.getChannelId());
@@ -318,10 +318,10 @@
         // 1 package name and 20 unique channel names and ids and 5 conversation ids
         assertThat(history.getPooledStringsToWrite().length).isEqualTo(26);
 
-        history.removeConversationFromWrite("pkg", "convo2");
+        history.removeConversationsFromWrite("pkg", Set.of("convo2", "convo4"));
 
-        // 1 package names and 9 * 2 unique channel names and ids and 4 conversation ids
-        assertThat(history.getPooledStringsToWrite().length).isEqualTo(23);
+        // 1 package names and 8 * 2 unique channel names and ids and 3 conversation ids
+        assertThat(history.getPooledStringsToWrite().length).isEqualTo(20);
         assertThat(history.getNotificationsToWrite())
                 .containsExactlyElementsIn(postRemoveExpectedEntries);
     }
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/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
index 9047b712..b275331 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
@@ -22,18 +22,18 @@
 import android.view.SurfaceControl;
 
 import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
     private static final String TAG = "FullscreenTaskOrg";
 
-    private final TransactionPool mTransactionPool;
+    private final SyncTransactionQueue mSyncQueue;
 
     private final ArraySet<Integer> mTasks = new ArraySet<>();
 
-    FullscreenTaskListener(TransactionPool transactionPool) {
-        mTransactionPool = transactionPool;
+    FullscreenTaskListener(SyncTransactionQueue syncQueue) {
+        mSyncQueue = syncQueue;
     }
 
     @Override
@@ -42,18 +42,18 @@
             if (mTasks.contains(taskInfo.taskId)) {
                 throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId);
             }
-            mTasks.add(taskInfo.taskId);
-            final SurfaceControl.Transaction t = mTransactionPool.acquire();
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d",
                     taskInfo.taskId);
-            // Reset several properties back to fullscreen (PiP, for example, leaves all these
-            // properties in a bad state).
-            t.setPosition(leash, 0, 0);
-            t.setWindowCrop(leash, null);
-            t.setAlpha(leash, 1f);
-            t.setMatrix(leash, 1, 0, 0, 1);
-            t.show(leash);
-            t.apply();
+            mTasks.add(taskInfo.taskId);
+            mSyncQueue.runInSync(t -> {
+                // Reset several properties back to fullscreen (PiP, for example, leaves all these
+                // properties in a bad state).
+                t.setPosition(leash, 0, 0);
+                t.setWindowCrop(leash, null);
+                t.setAlpha(leash, 1f);
+                t.setMatrix(leash, 1, 0, 0, 1);
+                t.show(leash);
+            });
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 2d82fb1..d650a95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -28,7 +28,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.util.ArrayList;
@@ -59,16 +59,16 @@
     // require us to report to both old and new listeners)
     private final SparseArray<Pair<RunningTaskInfo, SurfaceControl>> mTasks = new SparseArray<>();
 
-    public ShellTaskOrganizer(TransactionPool transactionPool) {
+    public ShellTaskOrganizer(SyncTransactionQueue syncQueue) {
         super();
-        addListener(new FullscreenTaskListener(transactionPool), WINDOWING_MODE_FULLSCREEN);
+        addListener(new FullscreenTaskListener(syncQueue), WINDOWING_MODE_FULLSCREEN);
     }
 
     @VisibleForTesting
     ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController,
-                       TransactionPool transactionPool) {
+                       SyncTransactionQueue syncQueue) {
         super(taskOrganizerController);
-        addListener(new FullscreenTaskListener(transactionPool), WINDOWING_MODE_FULLSCREEN);
+        addListener(new FullscreenTaskListener(syncQueue), WINDOWING_MODE_FULLSCREEN);
     }
 
     /**
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..ce49dd9 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
@@ -40,6 +40,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TransactionPool;
 
@@ -99,7 +100,7 @@
     public SplitScreenController(Context context,
             DisplayController displayController, SystemWindows systemWindows,
             DisplayImeController imeController, Handler handler, TransactionPool transactionPool,
-            ShellTaskOrganizer shellTaskOrganizer) {
+            ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue) {
         mContext = context;
         mDisplayController = displayController;
         mSystemWindows = systemWindows;
@@ -107,8 +108,7 @@
         mHandler = handler;
         mForcedResizableController = new ForcedResizableInfoActivityController(context, this);
         mTransactionPool = transactionPool;
-        mWindowManagerProxy = new WindowManagerProxy(mTransactionPool, mHandler,
-                shellTaskOrganizer);
+        mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer);
         mTaskOrganizer = shellTaskOrganizer;
         mSplits = new SplitScreenTaskOrganizer(this, shellTaskOrganizer);
         mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler,
@@ -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/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java
index 25827cd..47e7c99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java
@@ -28,7 +28,6 @@
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.graphics.Rect;
-import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.Display;
@@ -41,7 +40,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TransactionPool;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -85,9 +83,8 @@
 
     private final TaskOrganizer mTaskOrganizer;
 
-    WindowManagerProxy(TransactionPool transactionPool, Handler handler,
-            TaskOrganizer taskOrganizer) {
-        mSyncTransactionQueue = new SyncTransactionQueue(transactionPool, handler);
+    WindowManagerProxy(SyncTransactionQueue syncQueue, TaskOrganizer taskOrganizer) {
+        mSyncTransactionQueue = syncQueue;
         mTaskOrganizer = taskOrganizer;
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 7b499d4..823e0b7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -33,7 +33,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.SyncTransactionQueue;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -54,7 +54,7 @@
     private ITaskOrganizerController mTaskOrganizerController;
 
     ShellTaskOrganizer mOrganizer;
-    private final TransactionPool mTransactionPool = mock(TransactionPool.class);
+    private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
 
     private class TrackingTaskListener implements ShellTaskOrganizer.TaskListener {
         final ArrayList<RunningTaskInfo> appeared = new ArrayList<>();
@@ -85,7 +85,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mOrganizer = new ShellTaskOrganizer(mTaskOrganizerController, mTransactionPool);
+        mOrganizer = new ShellTaskOrganizer(mTaskOrganizerController, mSyncTransactionQueue);
     }
 
     @Test
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index 6f05cbd..71c8e1f 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -30,23 +30,62 @@
 
 namespace android {
 
-CursorWindow::CursorWindow(const String8& name, int ashmemFd,
-        void* data, size_t size, bool readOnly) :
-        mName(name), mAshmemFd(ashmemFd), mData(data), mSize(size), mReadOnly(readOnly) {
+/**
+ * By default windows are lightweight inline allocations of this size;
+ * they're only inflated to ashmem regions when more space is needed.
+ */
+static constexpr const size_t kInlineSize = 16384;
+
+CursorWindow::CursorWindow(const String8& name, int ashmemFd, void* data, size_t size,
+                           size_t inflatedSize, bool readOnly) :
+        mName(name), mAshmemFd(ashmemFd), mData(data), mSize(size),
+        mInflatedSize(inflatedSize), mReadOnly(readOnly) {
     mHeader = static_cast<Header*>(mData);
 }
 
 CursorWindow::~CursorWindow() {
-    ::munmap(mData, mSize);
-    ::close(mAshmemFd);
+    if (mAshmemFd != -1) {
+        ::munmap(mData, mSize);
+        ::close(mAshmemFd);
+    } else {
+        free(mData);
+    }
 }
 
-status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** outCursorWindow) {
+status_t CursorWindow::create(const String8& name, size_t inflatedSize,
+                              CursorWindow** outCursorWindow) {
+    *outCursorWindow = nullptr;
+
+    size_t size = std::min(kInlineSize, inflatedSize);
+    void* data = calloc(size, 1);
+    if (!data) return NO_MEMORY;
+
+    CursorWindow* window = new CursorWindow(name, -1, data, size,
+                                            inflatedSize, false /*readOnly*/);
+    status_t result = window->clear();
+    if (!result) {
+        LOG_WINDOW("Created new CursorWindow: freeOffset=%d, "
+                "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
+                window->mHeader->freeOffset,
+                window->mHeader->numRows,
+                window->mHeader->numColumns,
+                window->mSize, window->mData);
+        *outCursorWindow = window;
+        return OK;
+    }
+    delete window;
+    return result;
+}
+
+status_t CursorWindow::inflate() {
+    // Shortcut when we can't expand any further
+    if (mSize == mInflatedSize) return INVALID_OPERATION;
+
     String8 ashmemName("CursorWindow: ");
-    ashmemName.append(name);
+    ashmemName.append(mName);
 
     status_t result;
-    int ashmemFd = ashmem_create_region(ashmemName.string(), size);
+    int ashmemFd = ashmem_create_region(ashmemName.string(), mInflatedSize);
     if (ashmemFd < 0) {
         result = -errno;
         ALOGE("CursorWindow: ashmem_create_region() failed: errno=%d.", errno);
@@ -55,7 +94,8 @@
         if (result < 0) {
             ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d",errno);
         } else {
-            void* data = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0);
+            void* data = ::mmap(NULL, mInflatedSize, PROT_READ | PROT_WRITE,
+                                MAP_SHARED, ashmemFd, 0);
             if (data == MAP_FAILED) {
                 result = -errno;
                 ALOGE("CursorWindow: mmap() failed: errno=%d.", errno);
@@ -64,33 +104,49 @@
                 if (result < 0) {
                     ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d.", errno);
                 } else {
-                    CursorWindow* window = new CursorWindow(name, ashmemFd,
-                            data, size, false /*readOnly*/);
-                    result = window->clear();
-                    if (!result) {
-                        LOG_WINDOW("Created new CursorWindow: freeOffset=%d, "
-                                "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
-                                window->mHeader->freeOffset,
-                                window->mHeader->numRows,
-                                window->mHeader->numColumns,
-                                window->mSize, window->mData);
-                        *outCursorWindow = window;
-                        return OK;
-                    }
-                    delete window;
+                    // Move inline contents into new ashmem region
+                    memcpy(data, mData, mSize);
+                    free(mData);
+                    mAshmemFd = ashmemFd;
+                    mData = data;
+                    mHeader = static_cast<Header*>(mData);
+                    mSize = mInflatedSize;
+                    LOG_WINDOW("Inflated CursorWindow: freeOffset=%d, "
+                            "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
+                            mHeader->freeOffset,
+                            mHeader->numRows,
+                            mHeader->numColumns,
+                            mSize, mData);
+                    return OK;
                 }
             }
-            ::munmap(data, size);
+            ::munmap(data, mInflatedSize);
         }
         ::close(ashmemFd);
     }
-    *outCursorWindow = NULL;
     return result;
 }
 
 status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursorWindow) {
-    String8 name = parcel->readString8();
+    *outCursorWindow = nullptr;
 
+    String8 name;
+    status_t result = parcel->readString8(&name);
+    if (result) return result;
+
+    bool isAshmem;
+    result = parcel->readBool(&isAshmem);
+    if (result) return result;
+
+    if (isAshmem) {
+        return createFromParcelAshmem(parcel, name, outCursorWindow);
+    } else {
+        return createFromParcelInline(parcel, name, outCursorWindow);
+    }
+}
+
+status_t CursorWindow::createFromParcelAshmem(Parcel* parcel, String8& name,
+                                              CursorWindow** outCursorWindow) {
     status_t result;
     int actualSize;
     int ashmemFd = parcel->readFileDescriptor();
@@ -122,8 +178,8 @@
                             actualSize, (int) size, errno);
                 } else {
                     CursorWindow* window = new CursorWindow(name, dupAshmemFd,
-                            data, size, true /*readOnly*/);
-                    LOG_WINDOW("Created CursorWindow from parcel: freeOffset=%d, "
+                            data, size, size, true /*readOnly*/);
+                    LOG_WINDOW("Created CursorWindow from ashmem parcel: freeOffset=%d, "
                             "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
                             window->mHeader->freeOffset,
                             window->mHeader->numRows,
@@ -140,12 +196,62 @@
     return result;
 }
 
+status_t CursorWindow::createFromParcelInline(Parcel* parcel, String8& name,
+                                              CursorWindow** outCursorWindow) {
+    uint32_t sentSize;
+    status_t result = parcel->readUint32(&sentSize);
+    if (result) return result;
+    if (sentSize > kInlineSize) return NO_MEMORY;
+
+    void* data = calloc(sentSize, 1);
+    if (!data) return NO_MEMORY;
+
+    result = parcel->read(data, sentSize);
+    if (result) return result;
+
+    CursorWindow* window = new CursorWindow(name, -1, data, sentSize,
+                                            sentSize, true /*readOnly*/);
+    LOG_WINDOW("Created CursorWindow from inline parcel: freeOffset=%d, "
+            "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
+            window->mHeader->freeOffset,
+            window->mHeader->numRows,
+            window->mHeader->numColumns,
+            window->mSize, window->mData);
+    *outCursorWindow = window;
+    return OK;
+}
+
 status_t CursorWindow::writeToParcel(Parcel* parcel) {
-    status_t status = parcel->writeString8(mName);
-    if (!status) {
-        status = parcel->writeDupFileDescriptor(mAshmemFd);
+        LOG_WINDOW("Writing CursorWindow: freeOffset=%d, "
+                "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
+                mHeader->freeOffset,
+                mHeader->numRows,
+                mHeader->numColumns,
+                mSize, mData);
+
+    status_t result = parcel->writeString8(mName);
+    if (result) return result;
+
+    if (mAshmemFd != -1) {
+        result = parcel->writeBool(true);
+        if (result) return result;
+        return writeToParcelAshmem(parcel);
+    } else {
+        result = parcel->writeBool(false);
+        if (result) return result;
+        return writeToParcelInline(parcel);
     }
-    return status;
+}
+
+status_t CursorWindow::writeToParcelAshmem(Parcel* parcel) {
+    return parcel->writeDupFileDescriptor(mAshmemFd);
+}
+
+status_t CursorWindow::writeToParcelInline(Parcel* parcel) {
+    status_t result = parcel->writeUint32(mHeader->freeOffset);
+    if (result) return result;
+
+    return parcel->write(mData, mHeader->freeOffset);
 }
 
 status_t CursorWindow::clear() {
@@ -187,6 +293,7 @@
     if (rowSlot == NULL) {
         return NO_MEMORY;
     }
+    uint32_t rowSlotOffset = offsetFromPtr(rowSlot);
 
     // Allocate the slots for the field directory
     size_t fieldDirSize = mHeader->numColumns * sizeof(FieldSlot);
@@ -201,7 +308,8 @@
     memset(fieldDir, 0, fieldDirSize);
 
     LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %zu bytes at offset %u\n",
-            mHeader->numRows - 1, offsetFromPtr(rowSlot), fieldDirSize, fieldDirOffset);
+            mHeader->numRows - 1, rowSlotOffset, fieldDirSize, fieldDirOffset);
+    rowSlot = static_cast<RowSlot*>(offsetToPtr(rowSlotOffset));
     rowSlot->offset = fieldDirOffset;
     return OK;
 }
@@ -229,10 +337,14 @@
     uint32_t offset = mHeader->freeOffset + padding;
     uint32_t nextFreeOffset = offset + size;
     if (nextFreeOffset > mSize) {
-        ALOGW("Window is full: requested allocation %zu bytes, "
-                "free space %zu bytes, window size %zu bytes",
-                size, freeSpace(), mSize);
-        return 0;
+        // Try inflating to ashmem before finally giving up
+        inflate();
+        if (nextFreeOffset > mSize) {
+            ALOGW("Window is full: requested allocation %zu bytes, "
+                    "free space %zu bytes, window size %zu bytes",
+                    size, freeSpace(), mSize);
+            return 0;
+        }
     }
 
     mHeader->freeOffset = nextFreeOffset;
@@ -260,7 +372,10 @@
     }
     if (chunkPos == ROW_SLOT_CHUNK_NUM_ROWS) {
         if (!chunk->nextChunkOffset) {
-            chunk->nextChunkOffset = alloc(sizeof(RowSlotChunk), true /*aligned*/);
+            uint32_t chunkOffset = offsetFromPtr(chunk);
+            uint32_t newChunk = alloc(sizeof(RowSlotChunk), true /*aligned*/);
+            chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunkOffset));
+            chunk->nextChunkOffset = newChunk;
             if (!chunk->nextChunkOffset) {
                 return NULL;
             }
@@ -308,6 +423,7 @@
     if (!fieldSlot) {
         return BAD_VALUE;
     }
+    uint32_t fieldSlotOffset = offsetFromPtr(fieldSlot);
 
     uint32_t offset = alloc(size);
     if (!offset) {
@@ -316,6 +432,7 @@
 
     memcpy(offsetToPtr(offset), value, size);
 
+    fieldSlot = static_cast<FieldSlot*>(offsetToPtr(fieldSlotOffset));
     fieldSlot->type = type;
     fieldSlot->data.buffer.offset = offset;
     fieldSlot->data.buffer.size = size;
diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h
index ad64b24..73c76f0 100644
--- a/libs/androidfw/include/androidfw/CursorWindow.h
+++ b/libs/androidfw/include/androidfw/CursorWindow.h
@@ -50,8 +50,8 @@
  * Strings are stored in UTF-8.
  */
 class CursorWindow {
-    CursorWindow(const String8& name, int ashmemFd,
-            void* data, size_t size, bool readOnly);
+    CursorWindow(const String8& name, int ashmemFd, void* data, size_t size,
+                 size_t inflatedSize, bool readOnly);
 
 public:
     /* Field types. */
@@ -165,6 +165,7 @@
     int mAshmemFd;
     void* mData;
     size_t mSize;
+    size_t mInflatedSize;
     bool mReadOnly;
     Header* mHeader;
 
@@ -185,6 +186,18 @@
         return static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(mData);
     }
 
+    static status_t createFromParcelAshmem(Parcel*, String8&, CursorWindow**);
+    static status_t createFromParcelInline(Parcel*, String8&, CursorWindow**);
+
+    status_t writeToParcelAshmem(Parcel*);
+    status_t writeToParcelInline(Parcel*);
+
+    /**
+     * By default windows are lightweight inline allocations; this method
+     * inflates the window into a larger ashmem region.
+     */
+    status_t inflate();
+
     /**
      * Allocate a portion of the window. Returns the offset
      * of the allocation, or 0 if there isn't enough space.
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/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/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/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
index e99245f..23195af 100644
--- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
@@ -33,9 +33,13 @@
 import android.view.ViewGroup;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.dagger.KeyguardBouncerScope;
+import com.android.systemui.dagger.qualifiers.Main;
 
 import java.util.NoSuchElementException;
 
+import javax.inject.Inject;
+
 /**
  * Encapsulates all logic for secondary lockscreen state management.
  */
@@ -142,9 +146,9 @@
         }
     };
 
-    public AdminSecondaryLockScreenController(Context context, ViewGroup parent,
+    private AdminSecondaryLockScreenController(Context context, KeyguardSecurityContainer parent,
             KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback,
-            Handler handler) {
+            @Main Handler handler) {
         mContext = context;
         mHandler = handler;
         mParent = parent;
@@ -234,4 +238,26 @@
             getHolder().removeCallback(mSurfaceHolderCallback);
         }
     }
+
+    @KeyguardBouncerScope
+    public static class Factory {
+        private final Context mContext;
+        private final KeyguardSecurityContainer mParent;
+        private final KeyguardUpdateMonitor mUpdateMonitor;
+        private final Handler mHandler;
+
+        @Inject
+        public Factory(Context context, KeyguardSecurityContainer parent,
+                KeyguardUpdateMonitor updateMonitor, @Main Handler handler) {
+            mContext = context;
+            mParent = parent;
+            mUpdateMonitor = updateMonitor;
+            mHandler = handler;
+        }
+
+        public AdminSecondaryLockScreenController create(KeyguardSecurityCallback callback) {
+            return new AdminSecondaryLockScreenController(mContext, mParent, mUpdateMonitor,
+                    callback, mHandler);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index 88f4176..cc6df45 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -16,46 +16,26 @@
 
 package com.android.keyguard;
 
-import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
-import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
-
 import android.content.Context;
-import android.content.res.ColorStateList;
-import android.os.AsyncTask;
-import android.os.CountDownTimer;
-import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.View;
-import android.widget.LinearLayout;
 
-import com.android.internal.util.LatencyTracker;
-import com.android.internal.widget.LockPatternChecker;
-import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockscreenCredential;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
 /**
  * Base class for PIN and password unlock screens.
  */
-public abstract class KeyguardAbsKeyInputView extends LinearLayout
-        implements KeyguardSecurityView, EmergencyButton.EmergencyButtonCallback {
-    protected KeyguardSecurityCallback mCallback;
-    protected LockPatternUtils mLockPatternUtils;
-    protected AsyncTask<?, ?, ?> mPendingLockCheck;
-    protected SecurityMessageDisplay mSecurityMessageDisplay;
+public abstract class KeyguardAbsKeyInputView extends KeyguardInputView {
     protected View mEcaView;
     protected boolean mEnableHaptics;
-    private boolean mDismissing;
-    protected boolean mResumed;
-    private CountDownTimer mCountdownTimer = null;
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     // To avoid accidental lockout due to events while the device in in the pocket, ignore
     // any passwords with length less than or equal to this length.
     protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
+    private KeyDownListener mKeyDownListener;
 
     public KeyguardAbsKeyInputView(Context context) {
         this(context, null);
@@ -63,38 +43,10 @@
 
     public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
     }
 
-    @Override
-    public void setKeyguardCallback(KeyguardSecurityCallback callback) {
-        mCallback = callback;
-    }
-
-    @Override
-    public void setLockPatternUtils(LockPatternUtils utils) {
-        mLockPatternUtils = utils;
-        mEnableHaptics = mLockPatternUtils.isTactileFeedbackEnabled();
-    }
-
-    @Override
-    public void reset() {
-        // start fresh
-        mDismissing = false;
-        resetPasswordText(false /* animate */, false /* announce */);
-        // if the user is currently locked out, enforce it.
-        long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
-                KeyguardUpdateMonitor.getCurrentUser());
-        if (shouldLockout(deadline)) {
-            handleAttemptLockout(deadline);
-        } else {
-            resetState();
-        }
-    }
-
-    // Allow subclasses to override this behavior
-    protected boolean shouldLockout(long deadline) {
-        return deadline != 0;
+    void setEnableHaptics(boolean enableHaptics) {
+        mEnableHaptics = enableHaptics;
     }
 
     protected abstract int getPasswordTextViewId();
@@ -102,24 +54,7 @@
 
     @Override
     protected void onFinishInflate() {
-        mLockPatternUtils = new LockPatternUtils(mContext);
         mEcaView = findViewById(R.id.keyguard_selector_fade_container);
-
-        EmergencyButton button = findViewById(R.id.emergency_call_button);
-        if (button != null) {
-            button.setCallback(this);
-        }
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mSecurityMessageDisplay = KeyguardMessageArea.findSecurityMessageDisplay(this);
-    }
-
-    @Override
-    public void onEmergencyButtonClickedWhenInCall() {
-        mCallback.reset();
     }
 
     /*
@@ -131,195 +66,14 @@
         return R.string.kg_wrong_password;
     }
 
-    protected void verifyPasswordAndUnlock() {
-        if (mDismissing) return; // already verified but haven't been dismissed; don't do it again.
-
-        final LockscreenCredential password = getEnteredCredential();
-        setPasswordEntryInputEnabled(false);
-        if (mPendingLockCheck != null) {
-            mPendingLockCheck.cancel(false);
-        }
-
-        final int userId = KeyguardUpdateMonitor.getCurrentUser();
-        if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
-            // to avoid accidental lockout, only count attempts that are long enough to be a
-            // real password. This may require some tweaking.
-            setPasswordEntryInputEnabled(true);
-            onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */);
-            password.zeroize();
-            return;
-        }
-
-        if (LatencyTracker.isEnabled(mContext)) {
-            LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
-            LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
-        }
-
-        mKeyguardUpdateMonitor.setCredentialAttempted();
-        mPendingLockCheck = LockPatternChecker.checkCredential(
-                mLockPatternUtils,
-                password,
-                userId,
-                new LockPatternChecker.OnCheckCallback() {
-
-                    @Override
-                    public void onEarlyMatched() {
-                        if (LatencyTracker.isEnabled(mContext)) {
-                            LatencyTracker.getInstance(mContext).onActionEnd(
-                                    ACTION_CHECK_CREDENTIAL);
-                        }
-                        onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */,
-                                true /* isValidPassword */);
-                        password.zeroize();
-                    }
-
-                    @Override
-                    public void onChecked(boolean matched, int timeoutMs) {
-                        if (LatencyTracker.isEnabled(mContext)) {
-                            LatencyTracker.getInstance(mContext).onActionEnd(
-                                    ACTION_CHECK_CREDENTIAL_UNLOCKED);
-                        }
-                        setPasswordEntryInputEnabled(true);
-                        mPendingLockCheck = null;
-                        if (!matched) {
-                            onPasswordChecked(userId, false /* matched */, timeoutMs,
-                                    true /* isValidPassword */);
-                        }
-                        password.zeroize();
-                    }
-
-                    @Override
-                    public void onCancelled() {
-                        // We already got dismissed with the early matched callback, so we cancelled
-                        // the check. However, we still need to note down the latency.
-                        if (LatencyTracker.isEnabled(mContext)) {
-                            LatencyTracker.getInstance(mContext).onActionEnd(
-                                    ACTION_CHECK_CREDENTIAL_UNLOCKED);
-                        }
-                        password.zeroize();
-                    }
-                });
-    }
-
-    private void onPasswordChecked(int userId, boolean matched, int timeoutMs,
-            boolean isValidPassword) {
-        boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
-        if (matched) {
-            mCallback.reportUnlockAttempt(userId, true, 0);
-            if (dismissKeyguard) {
-                mDismissing = true;
-                mCallback.dismiss(true, userId);
-            }
-        } else {
-            if (isValidPassword) {
-                mCallback.reportUnlockAttempt(userId, false, timeoutMs);
-                if (timeoutMs > 0) {
-                    long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
-                            userId, timeoutMs);
-                    handleAttemptLockout(deadline);
-                }
-            }
-            if (timeoutMs == 0) {
-                mSecurityMessageDisplay.setMessage(getWrongPasswordStringId());
-            }
-        }
-        resetPasswordText(true /* animate */, !matched /* announce deletion if no match */);
-    }
-
     protected abstract void resetPasswordText(boolean animate, boolean announce);
     protected abstract LockscreenCredential getEnteredCredential();
     protected abstract void setPasswordEntryEnabled(boolean enabled);
     protected abstract void setPasswordEntryInputEnabled(boolean enabled);
 
-    // Prevent user from using the PIN/Password entry until scheduled deadline.
-    protected void handleAttemptLockout(long elapsedRealtimeDeadline) {
-        setPasswordEntryEnabled(false);
-        long elapsedRealtime = SystemClock.elapsedRealtime();
-        long secondsInFuture = (long) Math.ceil(
-                (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
-        mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
-
-            @Override
-            public void onTick(long millisUntilFinished) {
-                int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
-                mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString(
-                        R.plurals.kg_too_many_failed_attempts_countdown,
-                        secondsRemaining, secondsRemaining));
-            }
-
-            @Override
-            public void onFinish() {
-                mSecurityMessageDisplay.setMessage("");
-                resetState();
-            }
-        }.start();
-    }
-
-    protected void onUserInput() {
-        if (mCallback != null) {
-            mCallback.userActivity();
-            mCallback.onUserInput();
-        }
-        mSecurityMessageDisplay.setMessage("");
-    }
-
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
-        // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN.
-        // We don't want to consider it valid user input because the UI
-        // will already respond to the event.
-        if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
-            onUserInput();
-        }
-        return false;
-    }
-
-    @Override
-    public boolean needsInput() {
-        return false;
-    }
-
-    @Override
-    public void onPause() {
-        mResumed = false;
-
-        if (mCountdownTimer != null) {
-            mCountdownTimer.cancel();
-            mCountdownTimer = null;
-        }
-        if (mPendingLockCheck != null) {
-            mPendingLockCheck.cancel(false);
-            mPendingLockCheck = null;
-        }
-        reset();
-    }
-
-    @Override
-    public void onResume(int reason) {
-        mResumed = true;
-    }
-
-    @Override
-    public KeyguardSecurityCallback getCallback() {
-        return mCallback;
-    }
-
-    @Override
-    public void showPromptReason(int reason) {
-        if (reason != PROMPT_REASON_NONE) {
-            int promtReasonStringRes = getPromptReasonStringRes(reason);
-            if (promtReasonStringRes != 0) {
-                mSecurityMessageDisplay.setMessage(promtReasonStringRes);
-            }
-        }
-    }
-
-    @Override
-    public void showMessage(CharSequence message, ColorStateList colorState) {
-        if (colorState != null) {
-            mSecurityMessageDisplay.setNextMessageColor(colorState);
-        }
-        mSecurityMessageDisplay.setMessage(message);
+        return mKeyDownListener != null && mKeyDownListener.onKeyDown(keyCode, event);
     }
 
     protected abstract int getPromptReasonStringRes(int reason);
@@ -333,9 +87,12 @@
         }
     }
 
-    @Override
-    public boolean startDisappearAnimation(Runnable finishRunnable) {
-        return false;
+    public void setKeyDownListener(KeyDownListener keyDownListener) {
+        mKeyDownListener = keyDownListener;
+    }
+
+    public interface KeyDownListener {
+        boolean onKeyDown(int keyCode, KeyEvent keyEvent);
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
new file mode 100644
index 0000000..53f8474
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -0,0 +1,275 @@
+/*
+ * 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.keyguard;
+
+import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
+import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
+import static com.android.keyguard.KeyguardAbsKeyInputView.MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT;
+
+import android.content.res.ColorStateList;
+import android.os.AsyncTask;
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.view.KeyEvent;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternChecker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.keyguard.EmergencyButton.EmergencyButtonCallback;
+import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+
+public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKeyInputView>
+        extends KeyguardInputViewController<T> {
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final LockPatternUtils mLockPatternUtils;
+    private final LatencyTracker mLatencyTracker;
+    private CountDownTimer mCountdownTimer;
+    protected KeyguardMessageAreaController mMessageAreaController;
+    private boolean mDismissing;
+    protected AsyncTask<?, ?, ?> mPendingLockCheck;
+    protected boolean mResumed;
+
+    private final KeyDownListener mKeyDownListener = (keyCode, keyEvent) -> {
+        // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN.
+        // We don't want to consider it valid user input because the UI
+        // will already respond to the event.
+        if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
+            onUserInput();
+        }
+        return false;
+    };
+
+    private final EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() {
+        @Override
+        public void onEmergencyButtonClickedWhenInCall() {
+            getKeyguardSecurityCallback().reset();
+        }
+    };
+
+    protected KeyguardAbsKeyInputViewController(T view,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SecurityMode securityMode,
+            LockPatternUtils lockPatternUtils,
+            KeyguardSecurityCallback keyguardSecurityCallback,
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+            LatencyTracker latencyTracker) {
+        super(view, securityMode, keyguardSecurityCallback);
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mLockPatternUtils = lockPatternUtils;
+        mLatencyTracker = latencyTracker;
+        KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
+        mMessageAreaController = messageAreaControllerFactory.create(kma);
+    }
+
+    abstract void resetState();
+
+    @Override
+    public void init() {
+        super.init();
+        mMessageAreaController.init();
+    }
+
+    @Override
+    protected void onViewAttached() {
+        mView.setKeyDownListener(mKeyDownListener);
+        mView.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled());
+        EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
+        if (button != null) {
+            button.setCallback(mEmergencyButtonCallback);
+        }
+    }
+
+    @Override
+    public void reset() {
+        // start fresh
+        mDismissing = false;
+        mView.resetPasswordText(false /* animate */, false /* announce */);
+        // if the user is currently locked out, enforce it.
+        long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+                KeyguardUpdateMonitor.getCurrentUser());
+        if (shouldLockout(deadline)) {
+            handleAttemptLockout(deadline);
+        } else {
+            resetState();
+        }
+    }
+
+    @Override
+    public boolean needsInput() {
+        return false;
+    }
+
+    @Override
+    public void showMessage(CharSequence message, ColorStateList colorState) {
+        if (colorState != null) {
+            mMessageAreaController.setNextMessageColor(colorState);
+        }
+        mMessageAreaController.setMessage(message);
+    }
+
+    // Allow subclasses to override this behavior
+    protected boolean shouldLockout(long deadline) {
+        return deadline != 0;
+    }
+
+    // Prevent user from using the PIN/Password entry until scheduled deadline.
+    protected void handleAttemptLockout(long elapsedRealtimeDeadline) {
+        mView.setPasswordEntryEnabled(false);
+        long elapsedRealtime = SystemClock.elapsedRealtime();
+        long secondsInFuture = (long) Math.ceil(
+                (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
+        mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
+
+            @Override
+            public void onTick(long millisUntilFinished) {
+                int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
+                mMessageAreaController.setMessage(mView.getResources().getQuantityString(
+                        R.plurals.kg_too_many_failed_attempts_countdown,
+                        secondsRemaining, secondsRemaining));
+            }
+
+            @Override
+            public void onFinish() {
+                mMessageAreaController.setMessage("");
+                resetState();
+            }
+        }.start();
+    }
+
+    void onPasswordChecked(int userId, boolean matched, int timeoutMs, boolean isValidPassword) {
+        boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
+        if (matched) {
+            getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
+            if (dismissKeyguard) {
+                mDismissing = true;
+                getKeyguardSecurityCallback().dismiss(true, userId);
+            }
+        } else {
+            if (isValidPassword) {
+                getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs);
+                if (timeoutMs > 0) {
+                    long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
+                            userId, timeoutMs);
+                    handleAttemptLockout(deadline);
+                }
+            }
+            if (timeoutMs == 0) {
+                mMessageAreaController.setMessage(mView.getWrongPasswordStringId());
+            }
+        }
+        mView.resetPasswordText(true /* animate */, !matched /* announce deletion if no match */);
+    }
+
+    protected void verifyPasswordAndUnlock() {
+        if (mDismissing) return; // already verified but haven't been dismissed; don't do it again.
+
+        final LockscreenCredential password = mView.getEnteredCredential();
+        mView.setPasswordEntryInputEnabled(false);
+        if (mPendingLockCheck != null) {
+            mPendingLockCheck.cancel(false);
+        }
+
+        final int userId = KeyguardUpdateMonitor.getCurrentUser();
+        if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
+            // to avoid accidental lockout, only count attempts that are long enough to be a
+            // real password. This may require some tweaking.
+            mView.setPasswordEntryInputEnabled(true);
+            onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */);
+            password.zeroize();
+            return;
+        }
+
+        mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL);
+        mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+
+        mKeyguardUpdateMonitor.setCredentialAttempted();
+        mPendingLockCheck = LockPatternChecker.checkCredential(
+                mLockPatternUtils,
+                password,
+                userId,
+                new LockPatternChecker.OnCheckCallback() {
+
+                    @Override
+                    public void onEarlyMatched() {
+                        mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL);
+
+                        onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */,
+                                true /* isValidPassword */);
+                        password.zeroize();
+                    }
+
+                    @Override
+                    public void onChecked(boolean matched, int timeoutMs) {
+                        mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+                        mView.setPasswordEntryInputEnabled(true);
+                        mPendingLockCheck = null;
+                        if (!matched) {
+                            onPasswordChecked(userId, false /* matched */, timeoutMs,
+                                    true /* isValidPassword */);
+                        }
+                        password.zeroize();
+                    }
+
+                    @Override
+                    public void onCancelled() {
+                        // We already got dismissed with the early matched callback, so we cancelled
+                        // the check. However, we still need to note down the latency.
+                        mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+                        password.zeroize();
+                    }
+                });
+    }
+
+    @Override
+    public void showPromptReason(int reason) {
+        if (reason != PROMPT_REASON_NONE) {
+            int promtReasonStringRes = mView.getPromptReasonStringRes(reason);
+            if (promtReasonStringRes != 0) {
+                mMessageAreaController.setMessage(promtReasonStringRes);
+            }
+        }
+    }
+
+    protected void onUserInput() {
+        getKeyguardSecurityCallback().userActivity();
+        getKeyguardSecurityCallback().onUserInput();
+        mMessageAreaController.setMessage("");
+    }
+
+    @Override
+    public void onResume(int reason) {
+        mResumed = true;
+    }
+
+    @Override
+    public void onPause() {
+        mResumed = false;
+
+        if (mCountdownTimer != null) {
+            mCountdownTimer.cancel();
+            mCountdownTimer = null;
+        }
+        if (mPendingLockCheck != null) {
+            mPendingLockCheck.cancel(false);
+            mPendingLockCheck = null;
+        }
+        reset();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index be21d20..36d5543 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -39,7 +39,6 @@
 import com.android.systemui.R;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
-import com.android.systemui.util.InjectionInflationController;
 
 import javax.inject.Inject;
 
@@ -49,7 +48,6 @@
 
     private final MediaRouter mMediaRouter;
     private final DisplayManager mDisplayService;
-    private final InjectionInflationController mInjectableInflater;
     private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     private final Context mContext;
 
@@ -92,10 +90,8 @@
 
     @Inject
     public KeyguardDisplayManager(Context context,
-            InjectionInflationController injectableInflater,
             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory) {
         mContext = context;
-        mInjectableInflater = injectableInflater;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
         mMediaRouter = mContext.getSystemService(MediaRouter.class);
         mDisplayService = mContext.getSystemService(DisplayManager.class);
@@ -131,8 +127,7 @@
         Presentation presentation = mPresentations.get(displayId);
         if (presentation == null) {
             final Presentation newPresentation = new KeyguardPresentation(mContext, display,
-                    mKeyguardStatusViewComponentFactory,
-                    mInjectableInflater.injectable(LayoutInflater.from(mContext)));
+                    mKeyguardStatusViewComponentFactory, LayoutInflater.from(mContext));
             newPresentation.setOnDismissListener(dialog -> {
                 if (newPresentation.equals(mPresentations.get(displayId))) {
                     mPresentations.remove(displayId);
@@ -250,7 +245,7 @@
         private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
         private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
         private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
-        private final LayoutInflater mInjectableLayoutInflater;
+        private final LayoutInflater mLayoutInflater;
         private KeyguardClockSwitchController mKeyguardClockSwitchController;
         private View mClock;
         private int mUsableWidth;
@@ -270,10 +265,10 @@
 
         KeyguardPresentation(Context context, Display display,
                 KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
-                LayoutInflater injectionLayoutInflater) {
+                LayoutInflater layoutInflater) {
             super(context, display, R.style.Theme_SystemUI_KeyguardPresentation);
             mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
-            mInjectableLayoutInflater = injectionLayoutInflater;
+            mLayoutInflater = layoutInflater;
             getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
             setCancelable(false);
         }
@@ -299,7 +294,7 @@
             mMarginLeft = (100 - VIDEO_SAFE_REGION) * p.x / 200;
             mMarginTop = (100 - VIDEO_SAFE_REGION) * p.y / 200;
 
-            setContentView(mInjectableLayoutInflater.inflate(R.layout.keyguard_presentation, null));
+            setContentView(mLayoutInflater.inflate(R.layout.keyguard_presentation, null));
 
             // Logic to make the lock screen fullscreen
             getWindow().getDecorView().setSystemUiVisibility(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index 7aabb17..351369c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -163,33 +163,34 @@
     @Inject
     public KeyguardHostViewController(KeyguardHostView view,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            KeyguardSecurityContainerController keyguardSecurityContainerController,
             AudioManager audioManager,
             TelephonyManager telephonyManager,
-            ViewMediatorCallback viewMediatorCallback) {
+            ViewMediatorCallback viewMediatorCallback,
+            KeyguardSecurityContainerController.Factory
+                    keyguardSecurityContainerControllerFactory) {
         super(view);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mKeyguardSecurityContainerController = keyguardSecurityContainerController;
         mAudioManager = audioManager;
         mTelephonyManager = telephonyManager;
         mViewMediatorCallback = viewMediatorCallback;
+        mKeyguardSecurityContainerController = keyguardSecurityContainerControllerFactory.create(
+                mSecurityCallback);
     }
 
     /** Initialize the Controller. */
     public void init() {
         super.init();
-        mView.setViewMediatorCallback(mViewMediatorCallback);
-        // Update ViewMediator with the current input method requirements
-        mViewMediatorCallback.setNeedsInput(mKeyguardSecurityContainerController.needsInput());
         mKeyguardSecurityContainerController.init();
-        mKeyguardSecurityContainerController.setSecurityCallback(mSecurityCallback);
-        mKeyguardSecurityContainerController.showPrimarySecurityScreen(false);
     }
 
     @Override
     protected void onViewAttached() {
+        mView.setViewMediatorCallback(mViewMediatorCallback);
+        // Update ViewMediator with the current input method requirements
+        mViewMediatorCallback.setNeedsInput(mKeyguardSecurityContainerController.needsInput());
         mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
         mView.setOnKeyListener(mOnKeyListener);
+        mKeyguardSecurityContainerController.showPrimarySecurityScreen(false);
     }
 
     @Override
@@ -350,7 +351,7 @@
     }
 
     public boolean handleBackKey() {
-        if (mKeyguardSecurityContainerController.getCurrentSecuritySelection()
+        if (mKeyguardSecurityContainerController.getCurrentSecurityMode()
                 != SecurityMode.None) {
             mKeyguardSecurityContainerController.dismiss(
                     false, KeyguardUpdateMonitor.getCurrentUser());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
new file mode 100644
index 0000000..d42a53c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
@@ -0,0 +1,55 @@
+/*
+ * 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.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A Base class for all Keyguard password/pattern/pin related inputs.
+ */
+public abstract class KeyguardInputView extends LinearLayout {
+
+    public KeyguardInputView(Context context) {
+        super(context);
+    }
+
+    public KeyguardInputView(Context context,
+            @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public KeyguardInputView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    abstract CharSequence getTitle();
+
+    boolean disallowInterceptTouch(MotionEvent event) {
+        return false;
+    }
+
+    void startAppearAnimation() {}
+
+    boolean startDisappearAnimation(Runnable finishRunnable) {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
new file mode 100644
index 0000000..fbda818
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.telephony.TelephonyManager;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.ViewController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import javax.inject.Inject;
+
+
+/** Controller for a {@link KeyguardSecurityView}. */
+public abstract class KeyguardInputViewController<T extends KeyguardInputView>
+        extends ViewController<T> implements KeyguardSecurityView {
+
+    private final SecurityMode mSecurityMode;
+    private final KeyguardSecurityCallback mKeyguardSecurityCallback;
+    private boolean mPaused;
+
+
+    // The following is used to ignore callbacks from SecurityViews that are no longer current
+    // (e.g. face unlock). This avoids unwanted asynchronous events from messing with the
+    // state for the current security method.
+    private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {
+        @Override
+        public void userActivity() { }
+        @Override
+        public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { }
+        @Override
+        public boolean isVerifyUnlockOnly() {
+            return false;
+        }
+        @Override
+        public void dismiss(boolean securityVerified, int targetUserId) { }
+        @Override
+        public void dismiss(boolean authenticated, int targetId,
+                boolean bypassSecondaryLockScreen) { }
+        @Override
+        public void onUserInput() { }
+        @Override
+        public void reset() {}
+    };
+
+    protected KeyguardInputViewController(T view, SecurityMode securityMode,
+            KeyguardSecurityCallback keyguardSecurityCallback) {
+        super(view);
+        mSecurityMode = securityMode;
+        mKeyguardSecurityCallback = keyguardSecurityCallback;
+    }
+
+    @Override
+    protected void onViewAttached() {
+    }
+
+    @Override
+    protected void onViewDetached() {
+    }
+
+    SecurityMode getSecurityMode() {
+        return mSecurityMode;
+    }
+
+    protected KeyguardSecurityCallback getKeyguardSecurityCallback() {
+        if (mPaused) {
+            return mNullCallback;
+        }
+
+        return mKeyguardSecurityCallback;
+    }
+
+    @Override
+    public void reset() {
+    }
+
+    @Override
+    public void onPause() {
+        mPaused = true;
+    }
+
+    @Override
+    public void onResume(int reason) {
+        mPaused = false;
+    }
+
+    @Override
+    public void showPromptReason(int reason) {
+    }
+
+    @Override
+    public void showMessage(CharSequence message, ColorStateList colorState) {
+    }
+
+    public void startAppearAnimation() {
+        mView.startAppearAnimation();
+    }
+
+    public boolean startDisappearAnimation(Runnable finishRunnable) {
+        return mView.startDisappearAnimation(finishRunnable);
+    }
+
+    @Override
+    public CharSequence getTitle() {
+        return mView.getTitle();
+    }
+
+    /** Finds the index of this view in the suppplied parent view. */
+    public int getIndexIn(KeyguardSecurityViewFlipper view) {
+        return view.indexOfChild(mView);
+    }
+
+    /** Factory for a {@link KeyguardInputViewController}. */
+    public static class Factory {
+        private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+        private final LockPatternUtils mLockPatternUtils;
+        private final LatencyTracker mLatencyTracker;
+        private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
+        private final InputMethodManager mInputMethodManager;
+        private final DelayableExecutor mMainExecutor;
+        private final Resources mResources;
+        private LiftToActivateListener mLiftToActivateListener;
+        private TelephonyManager mTelephonyManager;
+
+        @Inject
+        public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
+                LockPatternUtils lockPatternUtils,
+                LatencyTracker latencyTracker,
+                KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+                InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor,
+                @Main Resources resources, LiftToActivateListener liftToActivateListener,
+                TelephonyManager telephonyManager) {
+            mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+            mLockPatternUtils = lockPatternUtils;
+            mLatencyTracker = latencyTracker;
+            mMessageAreaControllerFactory = messageAreaControllerFactory;
+            mInputMethodManager = inputMethodManager;
+            mMainExecutor = mainExecutor;
+            mResources = resources;
+            mLiftToActivateListener = liftToActivateListener;
+            mTelephonyManager = telephonyManager;
+        }
+
+        /** Create a new {@link KeyguardInputViewController}. */
+        public KeyguardInputViewController create(KeyguardInputView keyguardInputView,
+                SecurityMode securityMode, KeyguardSecurityCallback keyguardSecurityCallback) {
+            if (keyguardInputView instanceof KeyguardPatternView) {
+                return new KeyguardPatternViewController((KeyguardPatternView) keyguardInputView,
+                        mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
+                        keyguardSecurityCallback, mLatencyTracker, mMessageAreaControllerFactory);
+            } else if (keyguardInputView instanceof KeyguardPasswordView) {
+                return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
+                        mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
+                        keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
+                        mInputMethodManager, mMainExecutor, mResources);
+            } else if (keyguardInputView instanceof KeyguardPINView) {
+                return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
+                        mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
+                        keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
+                        mLiftToActivateListener);
+            } else if (keyguardInputView instanceof KeyguardSimPinView) {
+                return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
+                        mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
+                        keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
+                        mLiftToActivateListener, mTelephonyManager);
+            } else if (keyguardInputView instanceof KeyguardSimPukView) {
+                return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
+                        mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
+                        keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
+                        mLiftToActivateListener, mTelephonyManager);
+            }
+
+            throw new RuntimeException("Unable to find controller for " + keyguardInputView);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index a8b1451..1a0a437 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -16,8 +16,6 @@
 
 package com.android.keyguard;
 
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
@@ -31,20 +29,14 @@
 import android.view.View;
 import android.widget.TextView;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.lang.ref.WeakReference;
 
-import javax.inject.Inject;
-import javax.inject.Named;
-
 /***
  * Manages a number of views inside of the given layout. See below for a list of widgets.
  */
-public class KeyguardMessageArea extends TextView implements SecurityMessageDisplay,
-        ConfigurationController.ConfigurationListener {
+public class KeyguardMessageArea extends TextView implements SecurityMessageDisplay {
     /** Handler token posted with accessibility announcement runnables. */
     private static final Object ANNOUNCE_TOKEN = new Object();
 
@@ -56,71 +48,26 @@
     private static final int DEFAULT_COLOR = -1;
 
     private final Handler mHandler;
-    private final ConfigurationController mConfigurationController;
 
     private ColorStateList mDefaultColorState;
     private CharSequence mMessage;
     private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR);
     private boolean mBouncerVisible;
 
-    private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
-        public void onFinishedGoingToSleep(int why) {
-            setSelected(false);
-        }
-
-        public void onStartedWakingUp() {
-            setSelected(true);
-        }
-
-        @Override
-        public void onKeyguardBouncerChanged(boolean bouncer) {
-            mBouncerVisible = bouncer;
-            update();
-        }
-    };
-
-    public KeyguardMessageArea(Context context) {
-        super(context, null);
-        throw new IllegalStateException("This constructor should never be invoked");
-    }
-
-    @Inject
-    public KeyguardMessageArea(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
-            ConfigurationController configurationController) {
-        this(context, attrs, Dependency.get(KeyguardUpdateMonitor.class), configurationController);
-    }
-
-    public KeyguardMessageArea(Context context, AttributeSet attrs, KeyguardUpdateMonitor monitor,
-            ConfigurationController configurationController) {
+    public KeyguardMessageArea(Context context, AttributeSet attrs) {
         super(context, attrs);
         setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
 
-        monitor.registerCallback(mInfoCallback);
         mHandler = new Handler(Looper.myLooper());
-        mConfigurationController = configurationController;
         onThemeChanged();
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mConfigurationController.addCallback(this);
-        onThemeChanged();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mConfigurationController.removeCallback(this);
-    }
-
-    @Override
     public void setNextMessageColor(ColorStateList colorState) {
         mNextMessageColorState = colorState;
     }
 
-    @Override
-    public void onThemeChanged() {
+    void onThemeChanged() {
         TypedArray array = mContext.obtainStyledAttributes(new int[] {
                 R.attr.wallpaperTextColor
         });
@@ -130,8 +77,7 @@
         update();
     }
 
-    @Override
-    public void onDensityOrFontScaleChanged() {
+    void onDensityOrFontScaleChanged() {
         TypedArray array = mContext.obtainStyledAttributes(R.style.Keyguard_TextView, new int[] {
                 android.R.attr.textSize
         });
@@ -177,12 +123,6 @@
         return messageArea;
     }
 
-    @Override
-    protected void onFinishInflate() {
-        boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
-        setSelected(shouldMarquee); // This is required to ensure marquee works
-    }
-
     private void securityMessageChanged(CharSequence message) {
         mMessage = message;
         update();
@@ -196,7 +136,7 @@
         update();
     }
 
-    private void update() {
+    void update() {
         CharSequence status = mMessage;
         setVisibility(TextUtils.isEmpty(status) || !mBouncerVisible ? INVISIBLE : VISIBLE);
         setText(status);
@@ -208,6 +148,9 @@
         setTextColor(colorState);
     }
 
+    public void setBouncerVisible(boolean bouncerVisible) {
+        mBouncerVisible = bouncerVisible;
+    }
 
     /**
      * Runnable used to delay accessibility announcements.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index f056bdb..1618e8e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -16,7 +16,10 @@
 
 package com.android.keyguard;
 
+import android.content.res.ColorStateList;
+
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
@@ -26,6 +29,35 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final ConfigurationController mConfigurationController;
 
+
+    private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
+        public void onFinishedGoingToSleep(int why) {
+            mView.setSelected(false);
+        }
+
+        public void onStartedWakingUp() {
+            mView.setSelected(true);
+        }
+
+        @Override
+        public void onKeyguardBouncerChanged(boolean bouncer) {
+            mView.setBouncerVisible(bouncer);
+            mView.update();
+        }
+    };
+
+    private ConfigurationListener mConfigurationListener = new ConfigurationListener() {
+        @Override
+        public void onThemeChanged() {
+            mView.onThemeChanged();
+        }
+
+        @Override
+        public void onDensityOrFontScaleChanged() {
+            mView.onDensityOrFontScaleChanged();
+        }
+    };
+
     private KeyguardMessageAreaController(KeyguardMessageArea view,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             ConfigurationController configurationController) {
@@ -37,17 +69,31 @@
 
     @Override
     protected void onViewAttached() {
-        //mConfigurationController.addCallback();
-        //mKeyguardUpdateMonitor.registerCallback();
+        mConfigurationController.addCallback(mConfigurationListener);
+        mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
+        mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive());
+        mView.onThemeChanged();
     }
 
     @Override
     protected void onViewDetached() {
-        //mConfigurationController.removeCallback();
-        //mKeyguardUpdateMonitor.removeCallback();
+        mConfigurationController.removeCallback(mConfigurationListener);
+        mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
     }
 
-    /** Factory for createing {@link com.android.keyguard.KeyguardMessageAreaController}. */
+    public void setMessage(CharSequence s) {
+        mView.setMessage(s);
+    }
+
+    public void setMessage(int resId) {
+        mView.setMessage(resId);
+    }
+
+    public void setNextMessageColor(ColorStateList colorState) {
+        mView.setNextMessageColor(colorState);
+    }
+
+    /** Factory for creating {@link com.android.keyguard.KeyguardMessageAreaController}. */
     public static class Factory {
         private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
         private final ConfigurationController mConfigurationController;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 12ea1d5..580d704 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -24,7 +24,6 @@
 
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
 /**
@@ -40,10 +39,8 @@
     private ViewGroup mRow1;
     private ViewGroup mRow2;
     private ViewGroup mRow3;
-    private View mDivider;
     private int mDisappearYTranslation;
     private View[][] mViews;
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     public KeyguardPINView(Context context) {
         this(context, null);
@@ -63,15 +60,10 @@
                         mContext, android.R.interpolator.fast_out_linear_in));
         mDisappearYTranslation = getResources().getDimensionPixelSize(
                 R.dimen.disappear_y_translation);
-        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
     }
 
     @Override
     protected void resetState() {
-        super.resetState();
-        if (mSecurityMessageDisplay != null) {
-            mSecurityMessageDisplay.setMessage("");
-        }
     }
 
     @Override
@@ -88,7 +80,6 @@
         mRow1 = findViewById(R.id.row1);
         mRow2 = findViewById(R.id.row2);
         mRow3 = findViewById(R.id.row3);
-        mDivider = findViewById(R.id.divider);
         mViews = new View[][]{
                 new View[]{
                         mRow0, null, null
@@ -112,18 +103,6 @@
                 new View[]{
                         null, mEcaView, null
                 }};
-
-        View cancelBtn = findViewById(R.id.cancel_button);
-        if (cancelBtn != null) {
-            cancelBtn.setOnClickListener(view -> {
-                mCallback.reset();
-                mCallback.onCancelClicked();
-            });
-        }
-    }
-
-    @Override
-    public void showUsabilityHint() {
     }
 
     @Override
@@ -147,24 +126,21 @@
                 });
     }
 
-    @Override
-    public boolean startDisappearAnimation(final Runnable finishRunnable) {
+    public boolean startDisappearAnimation(boolean needsSlowUnlockTransition,
+            final Runnable finishRunnable) {
+
         enableClipping(false);
         setTranslationY(0);
         AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 280 /* duration */,
                 mDisappearYTranslation, mDisappearAnimationUtils.getInterpolator());
-        DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor
-                .needsSlowUnlockTransition()
+        DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
                         ? mDisappearAnimationUtilsLocked
                         : mDisappearAnimationUtils;
         disappearAnimationUtils.startAnimation2d(mViews,
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        enableClipping(true);
-                        if (finishRunnable != null) {
-                            finishRunnable.run();
-                        }
+                () -> {
+                    enableClipping(true);
+                    if (finishRunnable != null) {
+                        finishRunnable.run();
                     }
                 });
         return true;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 97317cf..aaa5efe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -16,50 +16,37 @@
 
 package com.android.keyguard;
 
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+
 import android.content.Context;
 import android.graphics.Rect;
-import android.os.UserHandle;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.text.method.TextKeyListener;
 import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.View;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
 import android.widget.TextView;
-import android.widget.TextView.OnEditorActionListener;
 
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.TextViewInputDisabler;
 import com.android.systemui.R;
-
-import java.util.List;
 /**
  * Displays an alphanumeric (latin-1) key entry for the user to enter
  * an unlock password
  */
-public class KeyguardPasswordView extends KeyguardAbsKeyInputView
-        implements KeyguardSecurityView, OnEditorActionListener, TextWatcher {
+public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
 
-    private final boolean mShowImeAtScreenOn;
     private final int mDisappearYTranslation;
 
     // A delay constant to be used in a workaround for the situation where InputMethodManagerService
     // is not switched to the new user yet.
     // TODO: Remove this by ensuring such a race condition never happens.
-    private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500;  // 500ms
 
-    InputMethodManager mImm;
     private TextView mPasswordEntry;
     private TextViewInputDisabler mPasswordEntryDisabler;
-    private View mSwitchImeButton;
 
     private Interpolator mLinearOutSlowInInterpolator;
     private Interpolator mFastOutLinearInInterpolator;
@@ -70,8 +57,6 @@
 
     public KeyguardPasswordView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mShowImeAtScreenOn = context.getResources().
-                getBoolean(R.bool.kg_show_ime_at_screen_on);
         mDisappearYTranslation = getResources().getDimensionPixelSize(
                 R.dimen.disappear_y_translation);
         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
@@ -82,20 +67,6 @@
 
     @Override
     protected void resetState() {
-        mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
-        if (mSecurityMessageDisplay != null) {
-            mSecurityMessageDisplay.setMessage("");
-        }
-        final boolean wasDisabled = mPasswordEntry.isEnabled();
-        setPasswordEntryEnabled(true);
-        setPasswordEntryInputEnabled(true);
-        // Don't call showSoftInput when PasswordEntry is invisible or in pausing stage.
-        if (!mResumed || !mPasswordEntry.isVisibleToUser()) {
-            return;
-        }
-        if (wasDisabled) {
-            mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
-        }
     }
 
     @Override
@@ -104,29 +75,6 @@
     }
 
     @Override
-    public boolean needsInput() {
-        return true;
-    }
-
-    @Override
-    public void onResume(final int reason) {
-        super.onResume(reason);
-
-        // Wait a bit to focus the field so the focusable flag on the window is already set then.
-        post(new Runnable() {
-            @Override
-            public void run() {
-                if (isShown() && mPasswordEntry.isEnabled()) {
-                    mPasswordEntry.requestFocus();
-                    if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
-                        mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
-                    }
-                }
-            }
-        });
-    }
-
-    @Override
     protected int getPromptReasonStringRes(int reason) {
         switch (reason) {
             case PROMPT_REASON_RESTART:
@@ -146,97 +94,13 @@
         }
     }
 
-    @Override
-    public void onPause() {
-        super.onPause();
-        mImm.hideSoftInputFromWindow(getWindowToken(), 0);
-    }
-
-    @Override
-    public void onStartingToHide() {
-        mImm.hideSoftInputFromWindow(getWindowToken(), 0);
-    }
-
-    private void updateSwitchImeButton() {
-        // If there's more than one IME, enable the IME switcher button
-        final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE;
-        final boolean shouldBeVisible = hasMultipleEnabledIMEsOrSubtypes(mImm, false);
-        if (wasVisible != shouldBeVisible) {
-            mSwitchImeButton.setVisibility(shouldBeVisible ? View.VISIBLE : View.GONE);
-        }
-
-        // TODO: Check if we still need this hack.
-        // If no icon is visible, reset the start margin on the password field so the text is
-        // still centered.
-        if (mSwitchImeButton.getVisibility() != View.VISIBLE) {
-            android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams();
-            if (params instanceof MarginLayoutParams) {
-                final MarginLayoutParams mlp = (MarginLayoutParams) params;
-                mlp.setMarginStart(0);
-                mPasswordEntry.setLayoutParams(params);
-            }
-        }
-    }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mImm = (InputMethodManager) getContext().getSystemService(
-                Context.INPUT_METHOD_SERVICE);
-
         mPasswordEntry = findViewById(getPasswordTextViewId());
-        mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
         mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry);
-        mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
-        mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
-                | InputType.TYPE_TEXT_VARIATION_PASSWORD);
-        mPasswordEntry.setOnEditorActionListener(this);
-        mPasswordEntry.addTextChangedListener(this);
-
-        // Poke the wakelock any time the text is selected or modified
-        mPasswordEntry.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mCallback.userActivity();
-            }
-        });
-
-        // Set selected property on so the view can send accessibility events.
-        mPasswordEntry.setSelected(true);
-
-        mSwitchImeButton = findViewById(R.id.switch_ime_button);
-        mSwitchImeButton.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mCallback.userActivity(); // Leave the screen on a bit longer
-                // Do not show auxiliary subtypes in password lock screen.
-                mImm.showInputMethodPickerFromSystem(false /* showAuxiliarySubtypes */,
-                        getContext().getDisplayId());
-            }
-        });
-
-        View cancelBtn = findViewById(R.id.cancel_button);
-        if (cancelBtn != null) {
-            cancelBtn.setOnClickListener(view -> {
-                mCallback.reset();
-                mCallback.onCancelClicked();
-            });
-        }
-
-        // If there's more than one IME, enable the IME switcher button
-        updateSwitchImeButton();
-
-        // When we the current user is switching, InputMethodManagerService sometimes has not
-        // switched internal state yet here. As a quick workaround, we check the keyboard state
-        // again.
-        // TODO: Remove this workaround by ensuring such a race condition never happens.
-        postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                updateSwitchImeButton();
-            }
-        }, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
     }
 
     @Override
@@ -265,59 +129,6 @@
         mPasswordEntryDisabler.setInputEnabled(enabled);
     }
 
-    /**
-     * Method adapted from com.android.inputmethod.latin.Utils
-     *
-     * @param imm The input method manager
-     * @param shouldIncludeAuxiliarySubtypes
-     * @return true if we have multiple IMEs to choose from
-     */
-    private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
-            final boolean shouldIncludeAuxiliarySubtypes) {
-        final List<InputMethodInfo> enabledImis =
-                imm.getEnabledInputMethodListAsUser(KeyguardUpdateMonitor.getCurrentUser());
-
-        // Number of the filtered IMEs
-        int filteredImisCount = 0;
-
-        for (InputMethodInfo imi : enabledImis) {
-            // We can return true immediately after we find two or more filtered IMEs.
-            if (filteredImisCount > 1) return true;
-            final List<InputMethodSubtype> subtypes =
-                    imm.getEnabledInputMethodSubtypeList(imi, true);
-            // IMEs that have no subtypes should be counted.
-            if (subtypes.isEmpty()) {
-                ++filteredImisCount;
-                continue;
-            }
-
-            int auxCount = 0;
-            for (InputMethodSubtype subtype : subtypes) {
-                if (subtype.isAuxiliary()) {
-                    ++auxCount;
-                }
-            }
-            final int nonAuxCount = subtypes.size() - auxCount;
-
-            // IMEs that have one or more non-auxiliary subtypes should be counted.
-            // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
-            // subtypes should be counted as well.
-            if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
-                ++filteredImisCount;
-                continue;
-            }
-        }
-
-        return filteredImisCount > 1
-        // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
-        // input method subtype (The current IME should be LatinIME.)
-                || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
-    }
-
-    @Override
-    public void showUsabilityHint() {
-    }
-
     @Override
     public int getWrongPasswordStringId() {
         return R.string.kg_wrong_password;
@@ -346,45 +157,8 @@
     }
 
     @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-        if (mCallback != null) {
-            mCallback.userActivity();
-        }
-    }
-
-    @Override
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
-    }
-
-    @Override
-    public void afterTextChanged(Editable s) {
-        // Poor man's user edit detection, assuming empty text is programmatic and everything else
-        // is from the user.
-        if (!TextUtils.isEmpty(s)) {
-            onUserInput();
-        }
-    }
-
-    @Override
-    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
-        // Check if this was the result of hitting the enter key
-        final boolean isSoftImeEvent = event == null
-                && (actionId == EditorInfo.IME_NULL
-                || actionId == EditorInfo.IME_ACTION_DONE
-                || actionId == EditorInfo.IME_ACTION_NEXT);
-        final boolean isKeyboardEnterKey = event != null
-                && KeyEvent.isConfirmKey(event.getKeyCode())
-                && event.getAction() == KeyEvent.ACTION_DOWN;
-        if (isSoftImeEvent || isKeyboardEnterKey) {
-            verifyPasswordAndUnlock();
-            return true;
-        }
-        return false;
-    }
-
-    @Override
     public CharSequence getTitle() {
-        return getContext().getString(
+        return getResources().getString(
                 com.android.internal.R.string.keyguard_accessibility_password_unlock);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
new file mode 100644
index 0000000..d34ea8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -0,0 +1,275 @@
+/*
+ * 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.keyguard;
+
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.method.TextKeyListener;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import java.util.List;
+
+public class KeyguardPasswordViewController
+        extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
+
+    private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500;  // 500ms
+
+    private final KeyguardSecurityCallback mKeyguardSecurityCallback;
+    private final InputMethodManager mInputMethodManager;
+    private final DelayableExecutor mMainExecutor;
+    private final boolean mShowImeAtScreenOn;
+    private TextView mPasswordEntry;
+    private View mSwitchImeButton;
+
+    private final OnEditorActionListener mOnEditorActionListener = (v, actionId, event) -> {
+        // Check if this was the result of hitting the enter key
+        final boolean isSoftImeEvent = event == null
+                && (actionId == EditorInfo.IME_NULL
+                || actionId == EditorInfo.IME_ACTION_DONE
+                || actionId == EditorInfo.IME_ACTION_NEXT);
+        final boolean isKeyboardEnterKey = event != null
+                && KeyEvent.isConfirmKey(event.getKeyCode())
+                && event.getAction() == KeyEvent.ACTION_DOWN;
+        if (isSoftImeEvent || isKeyboardEnterKey) {
+            verifyPasswordAndUnlock();
+            return true;
+        }
+        return false;
+    };
+
+    private final TextWatcher mTextWatcher = new TextWatcher() {
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            mKeyguardSecurityCallback.userActivity();
+        }
+
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+        }
+
+        @Override
+        public void afterTextChanged(Editable s) {
+            if (!TextUtils.isEmpty(s)) {
+                onUserInput();
+            }
+        }
+    };
+
+    protected KeyguardPasswordViewController(KeyguardPasswordView view,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SecurityMode securityMode,
+            LockPatternUtils lockPatternUtils,
+            KeyguardSecurityCallback keyguardSecurityCallback,
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+            LatencyTracker latencyTracker,
+            InputMethodManager inputMethodManager,
+            @Main DelayableExecutor mainExecutor,
+            @Main Resources resources) {
+        super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
+                messageAreaControllerFactory, latencyTracker);
+        mKeyguardSecurityCallback = keyguardSecurityCallback;
+        mInputMethodManager = inputMethodManager;
+        mMainExecutor = mainExecutor;
+        mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
+        mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
+        mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
+    }
+
+    @Override
+    protected void onViewAttached() {
+        super.onViewAttached();
+        mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
+        mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
+        mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
+                | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+        // Set selected property on so the view can send accessibility events.
+        mPasswordEntry.setSelected(true);
+        mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener);
+        mPasswordEntry.addTextChangedListener(mTextWatcher);
+        // Poke the wakelock any time the text is selected or modified
+        mPasswordEntry.setOnClickListener(v -> mKeyguardSecurityCallback.userActivity());
+
+        mSwitchImeButton.setOnClickListener(v -> {
+            mKeyguardSecurityCallback.userActivity(); // Leave the screen on a bit longer
+            // Do not show auxiliary subtypes in password lock screen.
+            mInputMethodManager.showInputMethodPickerFromSystem(false,
+                    mView.getContext().getDisplayId());
+        });
+
+        View cancelBtn = mView.findViewById(R.id.cancel_button);
+        if (cancelBtn != null) {
+            cancelBtn.setOnClickListener(view -> {
+                mKeyguardSecurityCallback.reset();
+                mKeyguardSecurityCallback.onCancelClicked();
+            });
+        }
+
+        // If there's more than one IME, enable the IME switcher button
+        updateSwitchImeButton();
+
+        // When we the current user is switching, InputMethodManagerService sometimes has not
+        // switched internal state yet here. As a quick workaround, we check the keyboard state
+        // again.
+        // TODO: Remove this workaround by ensuring such a race condition never happens.
+        mMainExecutor.executeDelayed(
+                this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
+    }
+
+    @Override
+    protected void onViewDetached() {
+        super.onViewDetached();
+        mPasswordEntry.setOnEditorActionListener(null);
+    }
+
+    @Override
+    public boolean needsInput() {
+        return true;
+    }
+
+    @Override
+    void resetState() {
+        mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
+        mMessageAreaController.setMessage("");
+        final boolean wasDisabled = mPasswordEntry.isEnabled();
+        mView.setPasswordEntryEnabled(true);
+        mView.setPasswordEntryInputEnabled(true);
+        // Don't call showSoftInput when PasswordEntry is invisible or in pausing stage.
+        if (!mResumed || !mPasswordEntry.isVisibleToUser()) {
+            return;
+        }
+        if (wasDisabled) {
+            mInputMethodManager.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
+        }
+    }
+
+    @Override
+    public void onResume(int reason) {
+        super.onResume(reason);
+        // Wait a bit to focus the field so the focusable flag on the window is already set then.
+        mMainExecutor.execute(() -> {
+            if (mView.isShown() && mPasswordEntry.isEnabled()) {
+                mPasswordEntry.requestFocus();
+                if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
+                    mInputMethodManager.showSoftInput(
+                            mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0);
+    }
+
+    @Override
+    public void onStartingToHide() {
+        mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0);
+    }
+
+    private void updateSwitchImeButton() {
+        // If there's more than one IME, enable the IME switcher button
+        final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE;
+        final boolean shouldBeVisible = hasMultipleEnabledIMEsOrSubtypes(
+                mInputMethodManager, false);
+        if (wasVisible != shouldBeVisible) {
+            mSwitchImeButton.setVisibility(shouldBeVisible ? View.VISIBLE : View.GONE);
+        }
+
+        // TODO: Check if we still need this hack.
+        // If no icon is visible, reset the start margin on the password field so the text is
+        // still centered.
+        if (mSwitchImeButton.getVisibility() != View.VISIBLE) {
+            android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams();
+            if (params instanceof MarginLayoutParams) {
+                final MarginLayoutParams mlp = (MarginLayoutParams) params;
+                mlp.setMarginStart(0);
+                mPasswordEntry.setLayoutParams(params);
+            }
+        }
+    }
+
+    /**
+     * Method adapted from com.android.inputmethod.latin.Utils
+     *
+     * @param imm The input method manager
+     * @param shouldIncludeAuxiliarySubtypes
+     * @return true if we have multiple IMEs to choose from
+     */
+    private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
+            final boolean shouldIncludeAuxiliarySubtypes) {
+        final List<InputMethodInfo> enabledImis =
+                imm.getEnabledInputMethodListAsUser(KeyguardUpdateMonitor.getCurrentUser());
+
+        // Number of the filtered IMEs
+        int filteredImisCount = 0;
+
+        for (InputMethodInfo imi : enabledImis) {
+            // We can return true immediately after we find two or more filtered IMEs.
+            if (filteredImisCount > 1) return true;
+            final List<InputMethodSubtype> subtypes =
+                    imm.getEnabledInputMethodSubtypeList(imi, true);
+            // IMEs that have no subtypes should be counted.
+            if (subtypes.isEmpty()) {
+                ++filteredImisCount;
+                continue;
+            }
+
+            int auxCount = 0;
+            for (InputMethodSubtype subtype : subtypes) {
+                if (subtype.isAuxiliary()) {
+                    ++auxCount;
+                }
+            }
+            final int nonAuxCount = subtypes.size() - auxCount;
+
+            // IMEs that have one or more non-auxiliary subtypes should be counted.
+            // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
+            // subtypes should be counted as well.
+            if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
+                ++filteredImisCount;
+                continue;
+            }
+        }
+
+        return filteredImisCount > 1
+                // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's
+                //enabled input method subtype (The current IME should be LatinIME.)
+                || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index c4a9fcb..bdcf467 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -15,62 +15,39 @@
  */
 package com.android.keyguard;
 
-import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
-import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
-
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.graphics.Rect;
-import android.os.AsyncTask;
-import android.os.CountDownTimer;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
-import android.widget.LinearLayout;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.LatencyTracker;
-import com.android.internal.widget.LockPatternChecker;
-import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternView;
-import com.android.internal.widget.LockscreenCredential;
 import com.android.settingslib.animation.AppearAnimationCreator;
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
-import java.util.List;
-
-public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView,
-        AppearAnimationCreator<LockPatternView.CellState>,
-        EmergencyButton.EmergencyButtonCallback {
+public class KeyguardPatternView extends KeyguardInputView
+        implements AppearAnimationCreator<LockPatternView.CellState> {
 
     private static final String TAG = "SecurityPatternView";
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
 
-    // how long before we clear the wrong pattern
-    private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
 
     // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK
     private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000;
 
-    // how many cells the user has to cross before we poke the wakelock
-    private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
-
     // How much we scale up the duration of the disappear animation when the current user is locked
     public static final float DISAPPEAR_MULTIPLIER_LOCKED = 1.5f;
 
     // Extra padding, in pixels, that should eat touch events.
     private static final int PATTERNS_TOUCH_AREA_EXTENSION = 40;
 
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final AppearAnimationUtils mAppearAnimationUtils;
     private final DisappearAnimationUtils mDisappearAnimationUtils;
     private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
@@ -78,11 +55,7 @@
     private final Rect mTempRect = new Rect();
     private final Rect mLockPatternScreenBounds = new Rect();
 
-    private CountDownTimer mCountdownTimer = null;
-    private LockPatternUtils mLockPatternUtils;
-    private AsyncTask<?, ?, ?> mPendingLockCheck;
     private LockPatternView mLockPatternView;
-    private KeyguardSecurityCallback mCallback;
 
     /**
      * Keeps track of the last time we poked the wake lock during dispatching of the touch event.
@@ -92,26 +65,9 @@
      */
     private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;
 
-    /**
-     * Useful for clearing out the wrong pattern after a delay
-     */
-    private Runnable mCancelPatternRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mLockPatternView.clearPattern();
-        }
-    };
-    @VisibleForTesting
     KeyguardMessageArea mSecurityMessageDisplay;
     private View mEcaView;
     private ViewGroup mContainer;
-    private int mDisappearYTranslation;
-
-    enum FooterMode {
-        Normal,
-        ForgotLockPattern,
-        VerifyUnlocked
-    }
 
     public KeyguardPatternView(Context context) {
         this(context, null);
@@ -119,7 +75,6 @@
 
     public KeyguardPatternView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mAppearAnimationUtils = new AppearAnimationUtils(context,
                 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */,
                 2.0f /* delayScale */, AnimationUtils.loadInterpolator(
@@ -132,50 +87,16 @@
                 (long) (125 * DISAPPEAR_MULTIPLIER_LOCKED), 1.2f /* translationScale */,
                 0.6f /* delayScale */, AnimationUtils.loadInterpolator(
                 mContext, android.R.interpolator.fast_out_linear_in));
-        mDisappearYTranslation = getResources().getDimensionPixelSize(
-                R.dimen.disappear_y_translation);
-    }
-
-    @Override
-    public void setKeyguardCallback(KeyguardSecurityCallback callback) {
-        mCallback = callback;
-    }
-
-    @Override
-    public void setLockPatternUtils(LockPatternUtils utils) {
-        mLockPatternUtils = utils;
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mLockPatternUtils = mLockPatternUtils == null
-                ? new LockPatternUtils(mContext) : mLockPatternUtils;
 
         mLockPatternView = findViewById(R.id.lockPatternView);
-        mLockPatternView.setSaveEnabled(false);
-        mLockPatternView.setOnPatternListener(new UnlockPatternListener());
-        mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
-                KeyguardUpdateMonitor.getCurrentUser()));
-
-        // vibrate mode will be the same for the life of this screen
-        mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
 
         mEcaView = findViewById(R.id.keyguard_selector_fade_container);
         mContainer = findViewById(R.id.container);
-
-        EmergencyButton button = findViewById(R.id.emergency_call_button);
-        if (button != null) {
-            button.setCallback(this);
-        }
-
-        View cancelBtn = findViewById(R.id.cancel_button);
-        if (cancelBtn != null) {
-            cancelBtn.setOnClickListener(view -> {
-                mCallback.reset();
-                mCallback.onCancelClicked();
-            });
-        }
     }
 
     @Override
@@ -185,11 +106,6 @@
     }
 
     @Override
-    public void onEmergencyButtonClickedWhenInCall() {
-        mCallback.reset();
-    }
-
-    @Override
     public boolean onTouchEvent(MotionEvent ev) {
         boolean result = super.onTouchEvent(ev);
         // as long as the user is entering a pattern (i.e sending a touch event that was handled
@@ -217,248 +133,11 @@
     }
 
     @Override
-    public void reset() {
-        // reset lock pattern
-        mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
-                KeyguardUpdateMonitor.getCurrentUser()));
-        mLockPatternView.enableInput();
-        mLockPatternView.setEnabled(true);
-        mLockPatternView.clearPattern();
-
-        if (mSecurityMessageDisplay == null) {
-            return;
-        }
-
-        // if the user is currently locked out, enforce it.
-        long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
-                KeyguardUpdateMonitor.getCurrentUser());
-        if (deadline != 0) {
-            handleAttemptLockout(deadline);
-        } else {
-            displayDefaultSecurityMessage();
-        }
-    }
-
-    private void displayDefaultSecurityMessage() {
-        if (mSecurityMessageDisplay != null) {
-            mSecurityMessageDisplay.setMessage("");
-        }
-    }
-
-    @Override
-    public void showUsabilityHint() {
-    }
-
-    @Override
-    public boolean disallowInterceptTouch(MotionEvent event) {
+    boolean disallowInterceptTouch(MotionEvent event) {
         return !mLockPatternView.isEmpty()
                 || mLockPatternScreenBounds.contains((int) event.getRawX(), (int) event.getRawY());
     }
 
-    /** TODO: hook this up */
-    public void cleanUp() {
-        if (DEBUG) Log.v(TAG, "Cleanup() called on " + this);
-        mLockPatternUtils = null;
-        mLockPatternView.setOnPatternListener(null);
-    }
-
-    private class UnlockPatternListener implements LockPatternView.OnPatternListener {
-
-        @Override
-        public void onPatternStart() {
-            mLockPatternView.removeCallbacks(mCancelPatternRunnable);
-            mSecurityMessageDisplay.setMessage("");
-        }
-
-        @Override
-        public void onPatternCleared() {
-        }
-
-        @Override
-        public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
-            mCallback.userActivity();
-            mCallback.onUserInput();
-        }
-
-        @Override
-        public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
-            mKeyguardUpdateMonitor.setCredentialAttempted();
-            mLockPatternView.disableInput();
-            if (mPendingLockCheck != null) {
-                mPendingLockCheck.cancel(false);
-            }
-
-            final int userId = KeyguardUpdateMonitor.getCurrentUser();
-            if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
-                mLockPatternView.enableInput();
-                onPatternChecked(userId, false, 0, false /* not valid - too short */);
-                return;
-            }
-
-            if (LatencyTracker.isEnabled(mContext)) {
-                LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
-                LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
-            }
-            mPendingLockCheck = LockPatternChecker.checkCredential(
-                    mLockPatternUtils,
-                    LockscreenCredential.createPattern(pattern),
-                    userId,
-                    new LockPatternChecker.OnCheckCallback() {
-
-                        @Override
-                        public void onEarlyMatched() {
-                            if (LatencyTracker.isEnabled(mContext)) {
-                                LatencyTracker.getInstance(mContext).onActionEnd(
-                                        ACTION_CHECK_CREDENTIAL);
-                            }
-                            onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
-                                    true /* isValidPattern */);
-                        }
-
-                        @Override
-                        public void onChecked(boolean matched, int timeoutMs) {
-                            if (LatencyTracker.isEnabled(mContext)) {
-                                LatencyTracker.getInstance(mContext).onActionEnd(
-                                        ACTION_CHECK_CREDENTIAL_UNLOCKED);
-                            }
-                            mLockPatternView.enableInput();
-                            mPendingLockCheck = null;
-                            if (!matched) {
-                                onPatternChecked(userId, false /* matched */, timeoutMs,
-                                        true /* isValidPattern */);
-                            }
-                        }
-
-                        @Override
-                        public void onCancelled() {
-                            // We already got dismissed with the early matched callback, so we
-                            // cancelled the check. However, we still need to note down the latency.
-                            if (LatencyTracker.isEnabled(mContext)) {
-                                LatencyTracker.getInstance(mContext).onActionEnd(
-                                        ACTION_CHECK_CREDENTIAL_UNLOCKED);
-                            }
-                        }
-                    });
-            if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
-                mCallback.userActivity();
-                mCallback.onUserInput();
-            }
-        }
-
-        private void onPatternChecked(int userId, boolean matched, int timeoutMs,
-                boolean isValidPattern) {
-            boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
-            if (matched) {
-                mCallback.reportUnlockAttempt(userId, true, 0);
-                if (dismissKeyguard) {
-                    mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
-                    mCallback.dismiss(true, userId);
-                }
-            } else {
-                mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
-                if (isValidPattern) {
-                    mCallback.reportUnlockAttempt(userId, false, timeoutMs);
-                    if (timeoutMs > 0) {
-                        long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
-                                userId, timeoutMs);
-                        handleAttemptLockout(deadline);
-                    }
-                }
-                if (timeoutMs == 0) {
-                    mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern);
-                    mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
-                }
-            }
-        }
-    }
-
-    private void handleAttemptLockout(long elapsedRealtimeDeadline) {
-        mLockPatternView.clearPattern();
-        mLockPatternView.setEnabled(false);
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-        final long secondsInFuture = (long) Math.ceil(
-                (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
-        mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
-
-            @Override
-            public void onTick(long millisUntilFinished) {
-                final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
-                mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString(
-                                R.plurals.kg_too_many_failed_attempts_countdown,
-                                secondsRemaining, secondsRemaining));
-            }
-
-            @Override
-            public void onFinish() {
-                mLockPatternView.setEnabled(true);
-                displayDefaultSecurityMessage();
-            }
-
-        }.start();
-    }
-
-    @Override
-    public boolean needsInput() {
-        return false;
-    }
-
-    @Override
-    public void onPause() {
-        if (mCountdownTimer != null) {
-            mCountdownTimer.cancel();
-            mCountdownTimer = null;
-        }
-        if (mPendingLockCheck != null) {
-            mPendingLockCheck.cancel(false);
-            mPendingLockCheck = null;
-        }
-        displayDefaultSecurityMessage();
-    }
-
-    @Override
-    public void onResume(int reason) {
-    }
-
-    @Override
-    public KeyguardSecurityCallback getCallback() {
-        return mCallback;
-    }
-
-    @Override
-    public void showPromptReason(int reason) {
-        switch (reason) {
-            case PROMPT_REASON_RESTART:
-                mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_restart_pattern);
-                break;
-            case PROMPT_REASON_TIMEOUT:
-                mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
-                break;
-            case PROMPT_REASON_DEVICE_ADMIN:
-                mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_device_admin);
-                break;
-            case PROMPT_REASON_USER_REQUEST:
-                mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_user_request);
-                break;
-            case PROMPT_REASON_PREPARE_FOR_UPDATE:
-                mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
-                break;
-            case PROMPT_REASON_NONE:
-                break;
-            default:
-                mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
-                break;
-        }
-    }
-
-    @Override
-    public void showMessage(CharSequence message, ColorStateList colorState) {
-        if (colorState != null) {
-            mSecurityMessageDisplay.setNextMessageColor(colorState);
-        }
-        mSecurityMessageDisplay.setMessage(message);
-    }
-
-    @Override
     public void startAppearAnimation() {
         enableClipping(false);
         setAlpha(1f);
@@ -467,12 +146,7 @@
                 0, mAppearAnimationUtils.getInterpolator());
         mAppearAnimationUtils.startAnimation2d(
                 mLockPatternView.getCellStates(),
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        enableClipping(true);
-                    }
-                },
+                () -> enableClipping(true),
                 this);
         if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
             mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
@@ -484,11 +158,9 @@
         }
     }
 
-    @Override
-    public boolean startDisappearAnimation(final Runnable finishRunnable) {
-        float durationMultiplier = mKeyguardUpdateMonitor.needsSlowUnlockTransition()
-                ? DISAPPEAR_MULTIPLIER_LOCKED
-                : 1f;
+    public boolean startDisappearAnimation(boolean needsSlowUnlockTransition,
+            final Runnable finishRunnable) {
+        float durationMultiplier = needsSlowUnlockTransition ? DISAPPEAR_MULTIPLIER_LOCKED : 1f;
         mLockPatternView.clearPattern();
         enableClipping(false);
         setTranslationY(0);
@@ -497,10 +169,8 @@
                 -mDisappearAnimationUtils.getStartTranslation(),
                 mDisappearAnimationUtils.getInterpolator());
 
-        DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor
-                .needsSlowUnlockTransition()
-                        ? mDisappearAnimationUtilsLocked
-                        : mDisappearAnimationUtils;
+        DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
+                        ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils;
         disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
                 () -> {
                     enableClipping(true);
@@ -549,7 +219,7 @@
 
     @Override
     public CharSequence getTitle() {
-        return getContext().getString(
+        return getResources().getString(
                 com.android.internal.R.string.keyguard_accessibility_pattern_unlock);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
new file mode 100644
index 0000000..3db9db7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -0,0 +1,349 @@
+/*
+ * 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.keyguard;
+
+import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
+import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
+
+import android.content.res.ColorStateList;
+import android.os.AsyncTask;
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.view.View;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternChecker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockPatternView.Cell;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.keyguard.EmergencyButton.EmergencyButtonCallback;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+
+import java.util.List;
+
+public class KeyguardPatternViewController
+        extends KeyguardInputViewController<KeyguardPatternView> {
+
+    // how many cells the user has to cross before we poke the wakelock
+    private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
+
+    // how long before we clear the wrong pattern
+    private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
+
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final LockPatternUtils mLockPatternUtils;
+    private final LatencyTracker mLatencyTracker;
+    private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
+
+    private KeyguardMessageAreaController mMessageAreaController;
+    private LockPatternView mLockPatternView;
+    private CountDownTimer mCountdownTimer;
+    private AsyncTask<?, ?, ?> mPendingLockCheck;
+
+    private EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() {
+        @Override
+        public void onEmergencyButtonClickedWhenInCall() {
+            getKeyguardSecurityCallback().reset();
+        }
+    };
+
+    /**
+     * Useful for clearing out the wrong pattern after a delay
+     */
+    private Runnable mCancelPatternRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mLockPatternView.clearPattern();
+        }
+    };
+
+    private class UnlockPatternListener implements LockPatternView.OnPatternListener {
+
+        @Override
+        public void onPatternStart() {
+            mLockPatternView.removeCallbacks(mCancelPatternRunnable);
+            mMessageAreaController.setMessage("");
+        }
+
+        @Override
+        public void onPatternCleared() {
+        }
+
+        @Override
+        public void onPatternCellAdded(List<Cell> pattern) {
+            getKeyguardSecurityCallback().userActivity();
+            getKeyguardSecurityCallback().onUserInput();
+        }
+
+        @Override
+        public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
+            mKeyguardUpdateMonitor.setCredentialAttempted();
+            mLockPatternView.disableInput();
+            if (mPendingLockCheck != null) {
+                mPendingLockCheck.cancel(false);
+            }
+
+            final int userId = KeyguardUpdateMonitor.getCurrentUser();
+            if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
+                mLockPatternView.enableInput();
+                onPatternChecked(userId, false, 0, false /* not valid - too short */);
+                return;
+            }
+
+            mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL);
+            mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+            mPendingLockCheck = LockPatternChecker.checkCredential(
+                    mLockPatternUtils,
+                    LockscreenCredential.createPattern(pattern),
+                    userId,
+                    new LockPatternChecker.OnCheckCallback() {
+
+                        @Override
+                        public void onEarlyMatched() {
+                            mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL);
+                            onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
+                                    true /* isValidPattern */);
+                        }
+
+                        @Override
+                        public void onChecked(boolean matched, int timeoutMs) {
+                            mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+                            mLockPatternView.enableInput();
+                            mPendingLockCheck = null;
+                            if (!matched) {
+                                onPatternChecked(userId, false /* matched */, timeoutMs,
+                                        true /* isValidPattern */);
+                            }
+                        }
+
+                        @Override
+                        public void onCancelled() {
+                            // We already got dismissed with the early matched callback, so we
+                            // cancelled the check. However, we still need to note down the latency.
+                            mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+                        }
+                    });
+            if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
+                getKeyguardSecurityCallback().userActivity();
+                getKeyguardSecurityCallback().onUserInput();
+            }
+        }
+
+        private void onPatternChecked(int userId, boolean matched, int timeoutMs,
+                boolean isValidPattern) {
+            boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
+            if (matched) {
+                getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
+                if (dismissKeyguard) {
+                    mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
+                    getKeyguardSecurityCallback().dismiss(true, userId);
+                }
+            } else {
+                mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
+                if (isValidPattern) {
+                    getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs);
+                    if (timeoutMs > 0) {
+                        long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
+                                userId, timeoutMs);
+                        handleAttemptLockout(deadline);
+                    }
+                }
+                if (timeoutMs == 0) {
+                    mMessageAreaController.setMessage(R.string.kg_wrong_pattern);
+                    mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
+                }
+            }
+        }
+    }
+
+    protected KeyguardPatternViewController(KeyguardPatternView view,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SecurityMode securityMode,
+            LockPatternUtils lockPatternUtils,
+            KeyguardSecurityCallback keyguardSecurityCallback,
+            LatencyTracker latencyTracker,
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
+        super(view, securityMode, keyguardSecurityCallback);
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mLockPatternUtils = lockPatternUtils;
+        mLatencyTracker = latencyTracker;
+        mMessageAreaControllerFactory = messageAreaControllerFactory;
+        KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
+        mMessageAreaController = mMessageAreaControllerFactory.create(kma);
+        mLockPatternView = mView.findViewById(R.id.lockPatternView);
+    }
+
+    @Override
+    public void init() {
+        super.init();
+        mMessageAreaController.init();
+    }
+
+    @Override
+    protected void onViewAttached() {
+        mLockPatternView.setOnPatternListener(new UnlockPatternListener());
+        mLockPatternView.setSaveEnabled(false);
+        mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
+                KeyguardUpdateMonitor.getCurrentUser()));
+        // vibrate mode will be the same for the life of this screen
+        mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
+
+        EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
+        if (button != null) {
+            button.setCallback(mEmergencyButtonCallback);
+        }
+
+        View cancelBtn = mView.findViewById(R.id.cancel_button);
+        if (cancelBtn != null) {
+            cancelBtn.setOnClickListener(view -> {
+                getKeyguardSecurityCallback().reset();
+                getKeyguardSecurityCallback().onCancelClicked();
+            });
+        }
+    }
+
+    @Override
+    protected void onViewDetached() {
+        super.onViewDetached();
+        mLockPatternView.setOnPatternListener(null);
+        EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
+        if (button != null) {
+            button.setCallback(null);
+        }
+        View cancelBtn = mView.findViewById(R.id.cancel_button);
+        if (cancelBtn != null) {
+            cancelBtn.setOnClickListener(null);
+        }
+    }
+
+    @Override
+    public void reset() {
+        // reset lock pattern
+        mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
+                KeyguardUpdateMonitor.getCurrentUser()));
+        mLockPatternView.enableInput();
+        mLockPatternView.setEnabled(true);
+        mLockPatternView.clearPattern();
+
+        // if the user is currently locked out, enforce it.
+        long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+                KeyguardUpdateMonitor.getCurrentUser());
+        if (deadline != 0) {
+            handleAttemptLockout(deadline);
+        } else {
+            displayDefaultSecurityMessage();
+        }
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        if (mCountdownTimer != null) {
+            mCountdownTimer.cancel();
+            mCountdownTimer = null;
+        }
+
+        if (mPendingLockCheck != null) {
+            mPendingLockCheck.cancel(false);
+            mPendingLockCheck = null;
+        }
+        displayDefaultSecurityMessage();
+    }
+
+    @Override
+    public boolean needsInput() {
+        return false;
+    }
+
+    @Override
+    public void showPromptReason(int reason) {
+        /// TODO: move all this logic into the MessageAreaController?
+        switch (reason) {
+            case PROMPT_REASON_RESTART:
+                mMessageAreaController.setMessage(R.string.kg_prompt_reason_restart_pattern);
+                break;
+            case PROMPT_REASON_TIMEOUT:
+                mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+                break;
+            case PROMPT_REASON_DEVICE_ADMIN:
+                mMessageAreaController.setMessage(R.string.kg_prompt_reason_device_admin);
+                break;
+            case PROMPT_REASON_USER_REQUEST:
+                mMessageAreaController.setMessage(R.string.kg_prompt_reason_user_request);
+                break;
+            case PROMPT_REASON_PREPARE_FOR_UPDATE:
+                mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+                break;
+            case PROMPT_REASON_NONE:
+                break;
+            default:
+                mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+                break;
+        }
+    }
+
+    @Override
+    public void showMessage(CharSequence message, ColorStateList colorState) {
+        if (colorState != null) {
+            mMessageAreaController.setNextMessageColor(colorState);
+        }
+        mMessageAreaController.setMessage(message);
+    }
+
+    @Override
+    public void startAppearAnimation() {
+        super.startAppearAnimation();
+    }
+
+    @Override
+    public boolean startDisappearAnimation(Runnable finishRunnable) {
+        return mView.startDisappearAnimation(
+                mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
+    }
+
+    private void displayDefaultSecurityMessage() {
+        mMessageAreaController.setMessage("");
+    }
+
+    private void handleAttemptLockout(long elapsedRealtimeDeadline) {
+        mLockPatternView.clearPattern();
+        mLockPatternView.setEnabled(false);
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long secondsInFuture = (long) Math.ceil(
+                (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
+        mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
+
+            @Override
+            public void onTick(long millisUntilFinished) {
+                final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
+                mMessageAreaController.setMessage(mView.getResources().getQuantityString(
+                        R.plurals.kg_too_many_failed_attempts_countdown,
+                        secondsRemaining, secondsRemaining));
+            }
+
+            @Override
+            public void onFinish() {
+                mLockPatternView.setEnabled(true);
+                displayDefaultSecurityMessage();
+            }
+
+        }.start();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index c7f27cf..7fa43116 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -16,11 +16,17 @@
 
 package com.android.keyguard;
 
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
-import android.view.MotionEvent;
 import android.view.View;
 
 import com.android.internal.widget.LockscreenCredential;
@@ -29,22 +35,12 @@
 /**
  * A Pin based Keyguard input view
  */
-public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
-        implements View.OnKeyListener, View.OnTouchListener {
+public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView {
 
     protected PasswordTextView mPasswordEntry;
     private View mOkButton;
     private View mDeleteButton;
-    private View mButton0;
-    private View mButton1;
-    private View mButton2;
-    private View mButton3;
-    private View mButton4;
-    private View mButton5;
-    private View mButton6;
-    private View mButton7;
-    private View mButton8;
-    private View mButton9;
+    private View[] mButtons = new View[10];
 
     public KeyguardPinBasedInputView(Context context) {
         this(context, null);
@@ -62,7 +58,6 @@
 
     @Override
     protected void resetState() {
-        setPasswordEntryEnabled(true);
     }
 
     @Override
@@ -86,10 +81,10 @@
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         if (KeyEvent.isConfirmKey(keyCode)) {
-            performClick(mOkButton);
+            mOkButton.performClick();
             return true;
         } else if (keyCode == KeyEvent.KEYCODE_DEL) {
-            performClick(mDeleteButton);
+            mDeleteButton.performClick();
             return true;
         }
         if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
@@ -125,42 +120,9 @@
         }
     }
 
-    private void performClick(View view) {
-        view.performClick();
-    }
-
     private void performNumberClick(int number) {
-        switch (number) {
-            case 0:
-                performClick(mButton0);
-                break;
-            case 1:
-                performClick(mButton1);
-                break;
-            case 2:
-                performClick(mButton2);
-                break;
-            case 3:
-                performClick(mButton3);
-                break;
-            case 4:
-                performClick(mButton4);
-                break;
-            case 5:
-                performClick(mButton5);
-                break;
-            case 6:
-                performClick(mButton6);
-                break;
-            case 7:
-                performClick(mButton7);
-                break;
-            case 8:
-                performClick(mButton8);
-                break;
-            case 9:
-                performClick(mButton9);
-                break;
+        if (number >= 0 && number <= 9) {
+            mButtons[number].performClick();
         }
     }
 
@@ -177,94 +139,31 @@
     @Override
     protected void onFinishInflate() {
         mPasswordEntry = findViewById(getPasswordTextViewId());
-        mPasswordEntry.setOnKeyListener(this);
 
         // Set selected property on so the view can send accessibility events.
         mPasswordEntry.setSelected(true);
 
-        mPasswordEntry.setUserActivityListener(new PasswordTextView.UserActivityListener() {
-            @Override
-            public void onUserActivity() {
-                onUserInput();
-            }
-        });
-
         mOkButton = findViewById(R.id.key_enter);
-        if (mOkButton != null) {
-            mOkButton.setOnTouchListener(this);
-            mOkButton.setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    if (mPasswordEntry.isEnabled()) {
-                        verifyPasswordAndUnlock();
-                    }
-                }
-            });
-            mOkButton.setOnHoverListener(new LiftToActivateListener(getContext()));
-        }
 
         mDeleteButton = findViewById(R.id.delete_button);
         mDeleteButton.setVisibility(View.VISIBLE);
-        mDeleteButton.setOnTouchListener(this);
-        mDeleteButton.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                // check for time-based lockouts
-                if (mPasswordEntry.isEnabled()) {
-                    mPasswordEntry.deleteLastChar();
-                }
-            }
-        });
-        mDeleteButton.setOnLongClickListener(new View.OnLongClickListener() {
-            @Override
-            public boolean onLongClick(View v) {
-                // check for time-based lockouts
-                if (mPasswordEntry.isEnabled()) {
-                    resetPasswordText(true /* animate */, true /* announce */);
-                }
-                doHapticKeyClick();
-                return true;
-            }
-        });
 
-        mButton0 = findViewById(R.id.key0);
-        mButton1 = findViewById(R.id.key1);
-        mButton2 = findViewById(R.id.key2);
-        mButton3 = findViewById(R.id.key3);
-        mButton4 = findViewById(R.id.key4);
-        mButton5 = findViewById(R.id.key5);
-        mButton6 = findViewById(R.id.key6);
-        mButton7 = findViewById(R.id.key7);
-        mButton8 = findViewById(R.id.key8);
-        mButton9 = findViewById(R.id.key9);
+        mButtons[0] = findViewById(R.id.key0);
+        mButtons[1] = findViewById(R.id.key1);
+        mButtons[2] = findViewById(R.id.key2);
+        mButtons[3] = findViewById(R.id.key3);
+        mButtons[4] = findViewById(R.id.key4);
+        mButtons[5] = findViewById(R.id.key5);
+        mButtons[6] = findViewById(R.id.key6);
+        mButtons[7] = findViewById(R.id.key7);
+        mButtons[8] = findViewById(R.id.key8);
+        mButtons[9] = findViewById(R.id.key9);
 
         mPasswordEntry.requestFocus();
         super.onFinishInflate();
     }
 
     @Override
-    public void onResume(int reason) {
-        super.onResume(reason);
-        mPasswordEntry.requestFocus();
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            doHapticKeyClick();
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onKey(View v, int keyCode, KeyEvent event) {
-        if (event.getAction() == KeyEvent.ACTION_DOWN) {
-            return onKeyDown(keyCode, event);
-        }
-        return false;
-    }
-
-    @Override
     public CharSequence getTitle() {
         return getContext().getString(
                 com.android.internal.R.string.keyguard_accessibility_pin_unlock);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
new file mode 100644
index 0000000..4d0ebff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -0,0 +1,113 @@
+/*
+ * 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.keyguard;
+
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnKeyListener;
+import android.view.View.OnTouchListener;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+
+public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
+        extends KeyguardAbsKeyInputViewController<T> {
+
+    private final LiftToActivateListener mLiftToActivateListener;
+    protected PasswordTextView mPasswordEntry;
+
+    private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> {
+        if (event.getAction() == KeyEvent.ACTION_DOWN) {
+            return mView.onKeyDown(keyCode, event);
+        }
+        return false;
+    };
+
+    private final OnTouchListener mOnTouchListener = (v, event) -> {
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            mView.doHapticKeyClick();
+        }
+        return false;
+    };
+
+    protected KeyguardPinBasedInputViewController(T view,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SecurityMode securityMode,
+            LockPatternUtils lockPatternUtils,
+            KeyguardSecurityCallback keyguardSecurityCallback,
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+            LatencyTracker latencyTracker,
+            LiftToActivateListener liftToActivateListener) {
+        super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
+                messageAreaControllerFactory, latencyTracker);
+        mLiftToActivateListener = liftToActivateListener;
+        mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
+    }
+
+    @Override
+    protected void onViewAttached() {
+        super.onViewAttached();
+
+        mPasswordEntry.setOnKeyListener(mOnKeyListener);
+        mPasswordEntry.setUserActivityListener(this::onUserInput);
+
+        View deleteButton = mView.findViewById(R.id.delete_button);
+        deleteButton.setOnTouchListener(mOnTouchListener);
+        deleteButton.setOnClickListener(v -> {
+            // check for time-based lockouts
+            if (mPasswordEntry.isEnabled()) {
+                mPasswordEntry.deleteLastChar();
+            }
+        });
+        deleteButton.setOnLongClickListener(v -> {
+            // check for time-based lockouts
+            if (mPasswordEntry.isEnabled()) {
+                mView.resetPasswordText(true /* animate */, true /* announce */);
+            }
+            mView.doHapticKeyClick();
+            return true;
+        });
+
+        View okButton = mView.findViewById(R.id.key_enter);
+        if (okButton != null) {
+            okButton.setOnTouchListener(mOnTouchListener);
+            okButton.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (mPasswordEntry.isEnabled()) {
+                        verifyPasswordAndUnlock();
+                    }
+                }
+            });
+            okButton.setOnHoverListener(mLiftToActivateListener);
+        }
+    }
+
+    @Override
+    public void onResume(int reason) {
+        super.onResume(reason);
+        mPasswordEntry.requestFocus();
+    }
+
+    @Override
+    void resetState() {
+        mView.setPasswordEntryEnabled(true);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
new file mode 100644
index 0000000..6769436
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -0,0 +1,66 @@
+/*
+ * 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.keyguard;
+
+import android.view.View;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+
+public class KeyguardPinViewController
+        extends KeyguardPinBasedInputViewController<KeyguardPINView> {
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+    protected KeyguardPinViewController(KeyguardPINView view,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SecurityMode securityMode, LockPatternUtils lockPatternUtils,
+            KeyguardSecurityCallback keyguardSecurityCallback,
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+            LatencyTracker latencyTracker,
+            LiftToActivateListener liftToActivateListener) {
+        super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
+                messageAreaControllerFactory, latencyTracker, liftToActivateListener);
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+    }
+
+    @Override
+    protected void onViewAttached() {
+        super.onViewAttached();
+
+        View cancelBtn = mView.findViewById(R.id.cancel_button);
+        if (cancelBtn != null) {
+            cancelBtn.setOnClickListener(view -> {
+                getKeyguardSecurityCallback().reset();
+                getKeyguardSecurityCallback().onCancelClicked();
+            });
+        }
+    }
+
+    @Override
+    void resetState() {
+        super.resetState();
+        mMessageAreaController.setMessage("");
+    }
+
+    @Override
+    public boolean startDisappearAnimation(Runnable finishRunnable) {
+        return mView.startDisappearAnimation(
+                mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 81d37a8..b62ea6b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -19,8 +19,6 @@
 import static android.view.WindowInsets.Type.systemBars;
 import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
 
-import static com.android.systemui.DejankUtils.whitelistIpcs;
-
 import static java.lang.Integer.max;
 
 import android.animation.Animator;
@@ -28,25 +26,14 @@
 import android.animation.ValueAnimator;
 import android.app.Activity;
 import android.app.AlertDialog;
-import android.app.admin.DevicePolicyManager;
 import android.content.Context;
-import android.content.Intent;
-import android.content.res.ColorStateList;
 import android.graphics.Insets;
 import android.graphics.Rect;
-import android.metrics.LogMaker;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.MathUtils;
-import android.util.Slog;
 import android.util.TypedValue;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
-import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimation;
@@ -61,42 +48,30 @@
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringAnimation;
 
-import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.settingslib.utils.ThreadUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
-import com.android.systemui.shared.system.SysUiStatsLog;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.InjectionInflationController;
 
 import java.util.List;
 
-public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSecurityView {
-    private static final boolean DEBUG = KeyguardConstants.DEBUG;
-    private static final String TAG = "KeyguardSecurityView";
-
-    private static final int USER_TYPE_PRIMARY = 1;
-    private static final int USER_TYPE_WORK_PROFILE = 2;
-    private static final int USER_TYPE_SECONDARY_USER = 3;
+public class KeyguardSecurityContainer extends FrameLayout {
+    static final int USER_TYPE_PRIMARY = 1;
+    static final int USER_TYPE_WORK_PROFILE = 2;
+    static final int USER_TYPE_SECONDARY_USER = 3;
 
     // Bouncer is dismissed due to no security.
-    private static final int BOUNCER_DISMISS_NONE_SECURITY = 0;
+    static final int BOUNCER_DISMISS_NONE_SECURITY = 0;
     // Bouncer is dismissed due to pin, password or pattern entered.
-    private static final int BOUNCER_DISMISS_PASSWORD = 1;
+    static final int BOUNCER_DISMISS_PASSWORD = 1;
     // Bouncer is dismissed due to biometric (face, fingerprint or iris) authenticated.
-    private static final int BOUNCER_DISMISS_BIOMETRIC = 2;
+    static final int BOUNCER_DISMISS_BIOMETRIC = 2;
     // Bouncer is dismissed due to extended access granted.
-    private static final int BOUNCER_DISMISS_EXTENDED_ACCESS = 3;
+    static final int BOUNCER_DISMISS_EXTENDED_ACCESS = 3;
     // Bouncer is dismissed due to sim card unlock code entered.
-    private static final int BOUNCER_DISMISS_SIM = 4;
+    static final int BOUNCER_DISMISS_SIM = 4;
 
     // Make the view move slower than the finger, as if the spring were applying force.
     private static final float TOUCH_Y_MULTIPLIER = 0.25f;
@@ -105,36 +80,23 @@
     // How much to scale the default slop by, to avoid accidental drags.
     private static final float SLOP_SCALE = 4f;
 
-    private static final UiEventLogger sUiEventLogger = new UiEventLoggerImpl();
-
     private static final long IME_DISAPPEAR_DURATION_MS = 125;
 
-    private KeyguardSecurityModel mSecurityModel;
-    private LockPatternUtils mLockPatternUtils;
-
     @VisibleForTesting
     KeyguardSecurityViewFlipper mSecurityViewFlipper;
-    private boolean mIsVerifyUnlockOnly;
-    private SecurityMode mCurrentSecuritySelection = SecurityMode.Invalid;
-    private KeyguardSecurityView mCurrentSecurityView;
-    private SecurityCallback mSecurityCallback;
     private AlertDialog mAlertDialog;
-    private InjectionInflationController mInjectionInflationController;
     private boolean mSwipeUpToRetry;
-    private AdminSecondaryLockScreenController mSecondaryLockScreenController;
 
     private final ViewConfiguration mViewConfiguration;
     private final SpringAnimation mSpringAnimation;
     private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
-    private final KeyguardUpdateMonitor mUpdateMonitor;
-    private final KeyguardStateController mKeyguardStateController;
 
-    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
     private float mLastTouchY = -1;
     private int mActivePointerId = -1;
     private boolean mIsDragging;
     private float mStartTouchY = -1;
     private boolean mDisappearAnimRunning;
+    private SwipeListener mSwipeListener;
 
     private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
             new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
@@ -186,19 +148,22 @@
 
     // Used to notify the container when something interesting happens.
     public interface SecurityCallback {
-        public boolean dismiss(boolean authenticated, int targetUserId,
-                boolean bypassSecondaryLockScreen);
-        public void userActivity();
-        public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput);
+        boolean dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen);
+        void userActivity();
+        void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput);
 
         /**
          * @param strongAuth wheher the user has authenticated with strong authentication like
          *                   pattern, password or PIN but not by trust agents or fingerprint
          * @param targetUserId a user that needs to be the foreground user at the finish completion.
          */
-        public void finish(boolean strongAuth, int targetUserId);
-        public void reset();
-        public void onCancelClicked();
+        void finish(boolean strongAuth, int targetUserId);
+        void reset();
+        void onCancelClicked();
+    }
+
+    public interface SwipeListener {
+        void onSwipeUp();
     }
 
     @VisibleForTesting
@@ -249,52 +214,24 @@
 
     public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-        mSecurityModel = Dependency.get(KeyguardSecurityModel.class);
-        mLockPatternUtils = new LockPatternUtils(context);
-        mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y);
-        mInjectionInflationController =  new InjectionInflationController(
-            SystemUIFactory.getInstance().getSysUIComponent().createViewInstanceCreatorFactory());
         mViewConfiguration = ViewConfiguration.get(context);
-        mKeyguardStateController = Dependency.get(KeyguardStateController.class);
-        mSecondaryLockScreenController = new AdminSecondaryLockScreenController(context, this,
-                mUpdateMonitor, mCallback, new Handler(Looper.myLooper()));
     }
 
-    public void setSecurityCallback(SecurityCallback callback) {
-        mSecurityCallback = callback;
-    }
-
-    @Override
-    public void onResume(int reason) {
-        if (mCurrentSecuritySelection != SecurityMode.None) {
-            getSecurityView(mCurrentSecuritySelection).onResume(reason);
-        }
+    void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
         mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
-        updateBiometricRetry();
+        updateBiometricRetry(securityMode, faceAuthEnabled);
     }
 
-    @Override
     public void onPause() {
         if (mAlertDialog != null) {
             mAlertDialog.dismiss();
             mAlertDialog = null;
         }
-        mSecondaryLockScreenController.hide();
-        if (mCurrentSecuritySelection != SecurityMode.None) {
-            getSecurityView(mCurrentSecuritySelection).onPause();
-        }
         mSecurityViewFlipper.setWindowInsetsAnimationCallback(null);
     }
 
     @Override
-    public void onStartingToHide() {
-        if (mCurrentSecuritySelection != SecurityMode.None) {
-            getSecurityView(mCurrentSecuritySelection).onStartingToHide();
-        }
-    }
-
-    @Override
     public boolean shouldDelayChildPressedState() {
         return true;
     }
@@ -316,13 +253,12 @@
                     return false;
                 }
                 // Avoid dragging the pattern view
-                if (mCurrentSecurityView.disallowInterceptTouch(event)) {
+                if (mSecurityViewFlipper.getSecurityView().disallowInterceptTouch(event)) {
                     return false;
                 }
                 int index = event.findPointerIndex(mActivePointerId);
                 float touchSlop = mViewConfiguration.getScaledTouchSlop() * SLOP_SCALE;
-                if (mCurrentSecurityView != null && index != -1
-                        && mStartTouchY - event.getY(index) > touchSlop) {
+                if (index != -1 && mStartTouchY - event.getY(index) > touchSlop) {
                     mIsDragging = true;
                     return true;
                 }
@@ -370,31 +306,28 @@
         }
         if (action == MotionEvent.ACTION_UP) {
             if (-getTranslationY() > TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                    MIN_DRAG_SIZE, getResources().getDisplayMetrics())
-                    && !mUpdateMonitor.isFaceDetectionRunning()) {
-                mUpdateMonitor.requestFaceAuth();
-                mCallback.userActivity();
-                showMessage(null, null);
+                    MIN_DRAG_SIZE, getResources().getDisplayMetrics())) {
+                if (mSwipeListener != null) {
+                    mSwipeListener.onSwipeUp();
+                }
             }
         }
         return true;
     }
 
+    void setSwipeListener(SwipeListener swipeListener) {
+        mSwipeListener = swipeListener;
+    }
+
     private void startSpringAnimation(float startVelocity) {
         mSpringAnimation
             .setStartVelocity(startVelocity)
             .animateToFinalPosition(0);
     }
 
-    public void startAppearAnimation() {
-        if (mCurrentSecuritySelection != SecurityMode.None) {
-            getSecurityView(mCurrentSecuritySelection).startAppearAnimation();
-        }
-    }
-
-    public boolean startDisappearAnimation(Runnable onFinishRunnable) {
+    public void startDisappearAnimation(SecurityMode securitySelection) {
         mDisappearAnimRunning = true;
-        if (mCurrentSecuritySelection == SecurityMode.Password) {
+        if (securitySelection == SecurityMode.Password) {
             mSecurityViewFlipper.getWindowInsetsController().controlWindowInsetsAnimation(ime(),
                     IME_DISAPPEAR_DURATION_MS,
                     Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() {
@@ -439,19 +372,13 @@
                         }
                     });
         }
-        if (mCurrentSecuritySelection != SecurityMode.None) {
-            return getSecurityView(mCurrentSecuritySelection).startDisappearAnimation(
-                    onFinishRunnable);
-        }
-        return false;
     }
 
     /**
      * Enables/disables swipe up to retry on the bouncer.
      */
-    private void updateBiometricRetry() {
-        SecurityMode securityMode = getSecurityMode();
-        mSwipeUpToRetry = mKeyguardStateController.isFaceAuthEnabled()
+    private void updateBiometricRetry(SecurityMode securityMode, boolean faceAuthEnabled) {
+        mSwipeUpToRetry = faceAuthEnabled
                 && securityMode != SecurityMode.SimPin
                 && securityMode != SecurityMode.SimPuk
                 && securityMode != SecurityMode.None;
@@ -461,53 +388,11 @@
         return mSecurityViewFlipper.getTitle();
     }
 
-    @VisibleForTesting
-    protected KeyguardSecurityView getSecurityView(SecurityMode securityMode) {
-        final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
-        KeyguardSecurityView view = null;
-        final int children = mSecurityViewFlipper.getChildCount();
-        for (int child = 0; child < children; child++) {
-            if (mSecurityViewFlipper.getChildAt(child).getId() == securityViewIdForMode) {
-                view = ((KeyguardSecurityView)mSecurityViewFlipper.getChildAt(child));
-                break;
-            }
-        }
-        int layoutId = getLayoutIdFor(securityMode);
-        if (view == null && layoutId != 0) {
-            final LayoutInflater inflater = LayoutInflater.from(mContext);
-            if (DEBUG) Log.v(TAG, "inflating id = " + layoutId);
-            View v = mInjectionInflationController.injectable(inflater)
-                    .inflate(layoutId, mSecurityViewFlipper, false);
-            mSecurityViewFlipper.addView(v);
-            updateSecurityView(v);
-            view = (KeyguardSecurityView)v;
-            view.reset();
-        }
-
-        return view;
-    }
-
-    private void updateSecurityView(View view) {
-        if (view instanceof KeyguardSecurityView) {
-            KeyguardSecurityView ksv = (KeyguardSecurityView) view;
-            ksv.setKeyguardCallback(mCallback);
-            ksv.setLockPatternUtils(mLockPatternUtils);
-        } else {
-            Log.w(TAG, "View " + view + " is not a KeyguardSecurityView");
-        }
-    }
 
     @Override
     public void onFinishInflate() {
         super.onFinishInflate();
         mSecurityViewFlipper = findViewById(R.id.view_flipper);
-        mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils);
-    }
-
-    public void setLockPatternUtils(LockPatternUtils utils) {
-        mLockPatternUtils = utils;
-        mSecurityModel.setLockPatternUtils(utils);
-        mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils);
     }
 
     @Override
@@ -539,11 +424,12 @@
         mAlertDialog.show();
     }
 
-    private void showTimeoutDialog(int userId, int timeoutMs) {
-        int timeoutInSeconds = (int) timeoutMs / 1000;
+    void showTimeoutDialog(int userId, int timeoutMs, LockPatternUtils lockPatternUtils,
+            SecurityMode securityMode) {
+        int timeoutInSeconds = timeoutMs / 1000;
         int messageId = 0;
 
-        switch (mSecurityModel.getSecurityMode(userId)) {
+        switch (securityMode) {
             case Pattern:
                 messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message;
                 break;
@@ -563,13 +449,13 @@
 
         if (messageId != 0) {
             final String message = mContext.getString(messageId,
-                    mLockPatternUtils.getCurrentFailedPasswordAttempts(userId),
+                    lockPatternUtils.getCurrentFailedPasswordAttempts(userId),
                     timeoutInSeconds);
             showDialog(null, message);
         }
     }
 
-    private void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
+    void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
         String message = null;
         switch (userType) {
             case USER_TYPE_PRIMARY:
@@ -588,7 +474,7 @@
         showDialog(null, message);
     }
 
-    private void showWipeDialog(int attempts, int userType) {
+    void showWipeDialog(int attempts, int userType) {
         String message = null;
         switch (userType) {
             case USER_TYPE_PRIMARY:
@@ -607,358 +493,8 @@
         showDialog(null, message);
     }
 
-    private void reportFailedUnlockAttempt(int userId, int timeoutMs) {
-        // +1 for this time
-        final int failedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(userId) + 1;
-
-        if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts);
-
-        final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
-        final int failedAttemptsBeforeWipe =
-                dpm.getMaximumFailedPasswordsForWipe(null, userId);
-
-        final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ?
-                (failedAttemptsBeforeWipe - failedAttempts)
-                : Integer.MAX_VALUE; // because DPM returns 0 if no restriction
-        if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
-            // The user has installed a DevicePolicyManager that requests a user/profile to be wiped
-            // N attempts. Once we get below the grace period, we post this dialog every time as a
-            // clear warning until the deletion fires.
-            // Check which profile has the strictest policy for failed password attempts
-            final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId);
-            int userType = USER_TYPE_PRIMARY;
-            if (expiringUser == userId) {
-                // TODO: http://b/23522538
-                if (expiringUser != UserHandle.USER_SYSTEM) {
-                    userType = USER_TYPE_SECONDARY_USER;
-                }
-            } else if (expiringUser != UserHandle.USER_NULL) {
-                userType = USER_TYPE_WORK_PROFILE;
-            } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY
-            if (remainingBeforeWipe > 0) {
-                showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType);
-            } else {
-                // Too many attempts. The device will be wiped shortly.
-                Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!");
-                showWipeDialog(failedAttempts, userType);
-            }
-        }
-        mLockPatternUtils.reportFailedPasswordAttempt(userId);
-        if (timeoutMs > 0) {
-            mLockPatternUtils.reportPasswordLockout(timeoutMs, userId);
-            showTimeoutDialog(userId, timeoutMs);
-        }
-    }
-
-    /**
-     * Shows the primary security screen for the user. This will be either the multi-selector
-     * or the user's security method.
-     * @param turningOff true if the device is being turned off
-     */
-    void showPrimarySecurityScreen(boolean turningOff) {
-        SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode(
-                KeyguardUpdateMonitor.getCurrentUser()));
-        if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")");
-        showSecurityScreen(securityMode);
-    }
-
-    /**
-     * Shows the next security screen if there is one.
-     * @param authenticated true if the user entered the correct authentication
-     * @param targetUserId a user that needs to be the foreground user at the finish (if called)
-     *     completion.
-     * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary
-     *     secondary lock screen requirement, if any.
-     * @return true if keyguard is done
-     */
-    boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId,
-            boolean bypassSecondaryLockScreen) {
-        if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")");
-        boolean finish = false;
-        boolean strongAuth = false;
-        int eventSubtype = -1;
-        BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN;
-        if (mUpdateMonitor.getUserHasTrust(targetUserId)) {
-            finish = true;
-            eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS;
-            uiEvent = BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS;
-        } else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) {
-            finish = true;
-            eventSubtype = BOUNCER_DISMISS_BIOMETRIC;
-            uiEvent = BouncerUiEvent.BOUNCER_DISMISS_BIOMETRIC;
-        } else if (SecurityMode.None == mCurrentSecuritySelection) {
-            SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
-            if (SecurityMode.None == securityMode) {
-                finish = true; // no security required
-                eventSubtype = BOUNCER_DISMISS_NONE_SECURITY;
-                uiEvent = BouncerUiEvent.BOUNCER_DISMISS_NONE_SECURITY;
-            } else {
-                showSecurityScreen(securityMode); // switch to the alternate security view
-            }
-        } else if (authenticated) {
-            switch (mCurrentSecuritySelection) {
-                case Pattern:
-                case Password:
-                case PIN:
-                    strongAuth = true;
-                    finish = true;
-                    eventSubtype = BOUNCER_DISMISS_PASSWORD;
-                    uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD;
-                    break;
-
-                case SimPin:
-                case SimPuk:
-                    // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
-                    SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
-                    if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled(
-                            KeyguardUpdateMonitor.getCurrentUser())) {
-                        finish = true;
-                        eventSubtype = BOUNCER_DISMISS_SIM;
-                        uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
-                    } else {
-                        showSecurityScreen(securityMode);
-                    }
-                    break;
-
-                default:
-                    Log.v(TAG, "Bad security screen " + mCurrentSecuritySelection + ", fail safe");
-                    showPrimarySecurityScreen(false);
-                    break;
-            }
-        }
-        // Check for device admin specified additional security measures.
-        if (finish && !bypassSecondaryLockScreen) {
-            Intent secondaryLockscreenIntent =
-                    mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId);
-            if (secondaryLockscreenIntent != null) {
-                mSecondaryLockScreenController.show(secondaryLockscreenIntent);
-                return false;
-            }
-        }
-        if (eventSubtype != -1) {
-            mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
-                    .setType(MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype));
-        }
-        if (uiEvent != BouncerUiEvent.UNKNOWN) {
-            sUiEventLogger.log(uiEvent);
-        }
-        if (finish) {
-            mSecurityCallback.finish(strongAuth, targetUserId);
-        }
-        return finish;
-    }
-
-    /**
-     * Switches to the given security view unless it's already being shown, in which case
-     * this is a no-op.
-     *
-     * @param securityMode
-     */
-    private void showSecurityScreen(SecurityMode securityMode) {
-        if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")");
-
-        if (securityMode == mCurrentSecuritySelection) return;
-
-        KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection);
-        KeyguardSecurityView newView = getSecurityView(securityMode);
-
-        // Emulate Activity life cycle
-        if (oldView != null) {
-            oldView.onPause();
-            oldView.setKeyguardCallback(mNullCallback); // ignore requests from old view
-        }
-        if (securityMode != SecurityMode.None) {
-            newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
-            newView.setKeyguardCallback(mCallback);
-        }
-
-        // Find and show this child.
-        final int childCount = mSecurityViewFlipper.getChildCount();
-
-        final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
-        for (int i = 0; i < childCount; i++) {
-            if (mSecurityViewFlipper.getChildAt(i).getId() == securityViewIdForMode) {
-                mSecurityViewFlipper.setDisplayedChild(i);
-                break;
-            }
-        }
-
-        mCurrentSecuritySelection = securityMode;
-        mCurrentSecurityView = newView;
-        mSecurityCallback.onSecurityModeChanged(securityMode,
-                securityMode != SecurityMode.None && newView.needsInput());
-    }
-
-    private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() {
-        public void userActivity() {
-            if (mSecurityCallback != null) {
-                mSecurityCallback.userActivity();
-            }
-        }
-
-        @Override
-        public void onUserInput() {
-            mUpdateMonitor.cancelFaceAuth();
-        }
-
-        @Override
-        public void dismiss(boolean authenticated, int targetId) {
-            dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false);
-        }
-
-        @Override
-        public void dismiss(boolean authenticated, int targetId,
-                boolean bypassSecondaryLockScreen) {
-            mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen);
-        }
-
-        public boolean isVerifyUnlockOnly() {
-            return mIsVerifyUnlockOnly;
-        }
-
-        public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
-            if (success) {
-                SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
-                        SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS);
-                mLockPatternUtils.reportSuccessfulPasswordAttempt(userId);
-                // Force a garbage collection in an attempt to erase any lockscreen password left in
-                // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard
-                // dismiss animation janky.
-                ThreadUtils.postOnBackgroundThread(() -> {
-                    try {
-                        Thread.sleep(5000);
-                    } catch (InterruptedException ignored) { }
-                    Runtime.getRuntime().gc();
-                });
-            } else {
-                SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
-                        SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE);
-                KeyguardSecurityContainer.this.reportFailedUnlockAttempt(userId, timeoutMs);
-            }
-            mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
-                    .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE));
-            sUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS
-                    : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE);
-        }
-
-        public void reset() {
-            mSecurityCallback.reset();
-        }
-
-        public void onCancelClicked() {
-            mSecurityCallback.onCancelClicked();
-        }
-    };
-
-    // The following is used to ignore callbacks from SecurityViews that are no longer current
-    // (e.g. face unlock). This avoids unwanted asynchronous events from messing with the
-    // state for the current security method.
-    private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {
-        @Override
-        public void userActivity() { }
-        @Override
-        public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { }
-        @Override
-        public boolean isVerifyUnlockOnly() { return false; }
-        @Override
-        public void dismiss(boolean securityVerified, int targetUserId) { }
-        @Override
-        public void dismiss(boolean authenticated, int targetId,
-                boolean bypassSecondaryLockScreen) { }
-        @Override
-        public void onUserInput() { }
-        @Override
-        public void reset() {}
-    };
-
-    private int getSecurityViewIdForMode(SecurityMode securityMode) {
-        switch (securityMode) {
-            case Pattern: return R.id.keyguard_pattern_view;
-            case PIN: return R.id.keyguard_pin_view;
-            case Password: return R.id.keyguard_password_view;
-            case SimPin: return R.id.keyguard_sim_pin_view;
-            case SimPuk: return R.id.keyguard_sim_puk_view;
-        }
-        return 0;
-    }
-
-    @VisibleForTesting
-    public int getLayoutIdFor(SecurityMode securityMode) {
-        switch (securityMode) {
-            case Pattern: return R.layout.keyguard_pattern_view;
-            case PIN: return R.layout.keyguard_pin_view;
-            case Password: return R.layout.keyguard_password_view;
-            case SimPin: return R.layout.keyguard_sim_pin_view;
-            case SimPuk: return R.layout.keyguard_sim_puk_view;
-            default:
-                return 0;
-        }
-    }
-
-    public SecurityMode getSecurityMode() {
-        return mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser());
-    }
-
-    public SecurityMode getCurrentSecurityMode() {
-        return mCurrentSecuritySelection;
-    }
-
-    public KeyguardSecurityView getCurrentSecurityView() {
-        return mCurrentSecurityView;
-    }
-
-    public void verifyUnlock() {
-        mIsVerifyUnlockOnly = true;
-        showSecurityScreen(getSecurityMode());
-    }
-
-    public SecurityMode getCurrentSecuritySelection() {
-        return mCurrentSecuritySelection;
-    }
-
-    public void dismiss(boolean authenticated, int targetUserId) {
-        mCallback.dismiss(authenticated, targetUserId);
-    }
-
-    public boolean needsInput() {
-        return mSecurityViewFlipper.needsInput();
-    }
-
-    @Override
-    public void setKeyguardCallback(KeyguardSecurityCallback callback) {
-        mSecurityViewFlipper.setKeyguardCallback(callback);
-    }
-
-    @Override
     public void reset() {
-        mSecurityViewFlipper.reset();
         mDisappearAnimRunning = false;
     }
-
-    @Override
-    public KeyguardSecurityCallback getCallback() {
-        return mSecurityViewFlipper.getCallback();
-    }
-
-    @Override
-    public void showPromptReason(int reason) {
-        if (mCurrentSecuritySelection != SecurityMode.None) {
-            if (reason != PROMPT_REASON_NONE) {
-                Log.i(TAG, "Strong auth required, reason: " + reason);
-            }
-            getSecurityView(mCurrentSecuritySelection).showPromptReason(reason);
-        }
-    }
-
-    public void showMessage(CharSequence message, ColorStateList colorState) {
-        if (mCurrentSecuritySelection != SecurityMode.None) {
-            getSecurityView(mCurrentSecuritySelection).showMessage(message, colorState);
-        }
-    }
-
-    @Override
-    public void showUsabilityHint() {
-        mSecurityViewFlipper.showUsabilityHint();
-    }
 }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 17f25bd08ef..1c23605 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -16,33 +16,167 @@
 
 package com.android.keyguard;
 
-import android.content.res.ColorStateList;
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_PASSWORD;
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM;
+import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_PRIMARY;
+import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER;
+import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE;
+import static com.android.systemui.DejankUtils.whitelistIpcs;
 
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.metrics.LogMaker;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityContainer.BouncerUiEvent;
 import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
+import com.android.keyguard.KeyguardSecurityContainer.SwipeListener;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.dagger.KeyguardBouncerScope;
+import com.android.settingslib.utils.ThreadUtils;
+import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
 
 /** Controller for {@link KeyguardSecurityContainer} */
-public class KeyguardSecurityContainerController extends ViewController<KeyguardSecurityContainer> {
+@KeyguardBouncerScope
+public class KeyguardSecurityContainerController extends ViewController<KeyguardSecurityContainer>
+        implements KeyguardSecurityView {
 
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+    private static final String TAG = "KeyguardSecurityView";
+
+    private final AdminSecondaryLockScreenController mAdminSecondaryLockScreenController;
     private final LockPatternUtils mLockPatternUtils;
-    private final KeyguardSecurityViewController.Factory mKeyguardSecurityViewControllerFactory;
+    private final KeyguardUpdateMonitor mUpdateMonitor;
+    private final KeyguardSecurityModel mSecurityModel;
+    private final MetricsLogger mMetricsLogger;
+    private final UiEventLogger mUiEventLogger;
+    private final KeyguardStateController mKeyguardStateController;
+    private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
+    private final SecurityCallback mSecurityCallback;
 
-    @Inject
-    KeyguardSecurityContainerController(KeyguardSecurityContainer view,
+    private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
+
+    private KeyguardSecurityCallback mKeyguardSecurityCallback = new KeyguardSecurityCallback() {
+        public void userActivity() {
+            if (mSecurityCallback != null) {
+                mSecurityCallback.userActivity();
+            }
+        }
+
+        @Override
+        public void onUserInput() {
+            mUpdateMonitor.cancelFaceAuth();
+        }
+
+        @Override
+        public void dismiss(boolean authenticated, int targetId) {
+            dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false);
+        }
+
+        @Override
+        public void dismiss(boolean authenticated, int targetId,
+                boolean bypassSecondaryLockScreen) {
+            mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen);
+        }
+
+        public boolean isVerifyUnlockOnly() {
+            return false;
+        }
+
+        public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
+            if (success) {
+                SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
+                        SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS);
+                mLockPatternUtils.reportSuccessfulPasswordAttempt(userId);
+                // Force a garbage collection in an attempt to erase any lockscreen password left in
+                // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard
+                // dismiss animation janky.
+                ThreadUtils.postOnBackgroundThread(() -> {
+                    try {
+                        Thread.sleep(5000);
+                    } catch (InterruptedException ignored) { }
+                    Runtime.getRuntime().gc();
+                });
+            } else {
+                SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
+                        SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE);
+                reportFailedUnlockAttempt(userId, timeoutMs);
+            }
+            mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
+                    .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE));
+            mUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS
+                    : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE);
+        }
+
+        public void reset() {
+            mSecurityCallback.reset();
+        }
+
+        public void onCancelClicked() {
+            mSecurityCallback.onCancelClicked();
+        }
+    };
+
+
+    private SwipeListener mSwipeListener = new SwipeListener() {
+        @Override
+        public void onSwipeUp() {
+            if (!mUpdateMonitor.isFaceDetectionRunning()) {
+                mUpdateMonitor.requestFaceAuth();
+                mKeyguardSecurityCallback.userActivity();
+                showMessage(null, null);
+            }
+        }
+    };
+
+    private KeyguardSecurityContainerController(KeyguardSecurityContainer view,
+            AdminSecondaryLockScreenController.Factory adminSecondaryLockScreenControllerFactory,
             LockPatternUtils lockPatternUtils,
-            KeyguardSecurityViewController.Factory keyguardSecurityViewControllerFactory) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            KeyguardSecurityModel keyguardSecurityModel,
+            MetricsLogger metricsLogger,
+            UiEventLogger uiEventLogger,
+            KeyguardStateController keyguardStateController,
+            SecurityCallback securityCallback,
+            KeyguardSecurityViewFlipperController securityViewFlipperController) {
         super(view);
         mLockPatternUtils = lockPatternUtils;
-        view.setLockPatternUtils(mLockPatternUtils);
-        mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
+        mUpdateMonitor = keyguardUpdateMonitor;
+        mSecurityModel = keyguardSecurityModel;
+        mMetricsLogger = metricsLogger;
+        mUiEventLogger = uiEventLogger;
+        mKeyguardStateController = keyguardStateController;
+        mSecurityCallback = securityCallback;
+        mSecurityViewFlipperController = securityViewFlipperController;
+        mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create(
+                mKeyguardSecurityCallback);
+    }
+
+    @Override
+    public void init() {
+        super.init();
+        mSecurityViewFlipperController.init();
     }
 
     @Override
     protected void onViewAttached() {
+        mView.setSwipeListener(mSwipeListener);
     }
 
     @Override
@@ -51,68 +185,311 @@
 
     /** */
     public void onPause() {
+        mAdminSecondaryLockScreenController.hide();
+        if (mCurrentSecurityMode != SecurityMode.None) {
+            getCurrentSecurityController().onPause();
+        }
         mView.onPause();
     }
 
+
+    /**
+     * Shows the primary security screen for the user. This will be either the multi-selector
+     * or the user's security method.
+     * @param turningOff true if the device is being turned off
+     */
     public void showPrimarySecurityScreen(boolean turningOff) {
-        mView.showPrimarySecurityScreen(turningOff);
+        SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode(
+                KeyguardUpdateMonitor.getCurrentUser()));
+        if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")");
+        showSecurityScreen(securityMode);
     }
 
+    @Override
     public void showPromptReason(int reason) {
-        mView.showPromptReason(reason);
+        if (mCurrentSecurityMode != SecurityMode.None) {
+            if (reason != PROMPT_REASON_NONE) {
+                Log.i(TAG, "Strong auth required, reason: " + reason);
+            }
+            getCurrentSecurityController().showPromptReason(reason);
+        }
     }
 
     public void showMessage(CharSequence message, ColorStateList colorState) {
-        mView.showMessage(message, colorState);
+        if (mCurrentSecurityMode != SecurityMode.None) {
+            getCurrentSecurityController().showMessage(message, colorState);
+        }
     }
 
-    public SecurityMode getCurrentSecuritySelection() {
-        return mView.getCurrentSecuritySelection();
+    public SecurityMode getCurrentSecurityMode() {
+        return mCurrentSecurityMode;
     }
 
     public void dismiss(boolean authenticated, int targetUserId) {
-        mView.dismiss(authenticated, targetUserId);
+        mKeyguardSecurityCallback.dismiss(authenticated, targetUserId);
     }
 
     public void reset() {
         mView.reset();
+        mSecurityViewFlipperController.reset();
     }
 
     public CharSequence getTitle() {
         return mView.getTitle();
     }
 
-    public void onResume(int screenOn) {
-        mView.onResume(screenOn);
+    @Override
+    public void onResume(int reason) {
+        if (mCurrentSecurityMode != SecurityMode.None) {
+            getCurrentSecurityController().onResume(reason);
+        }
+        mView.onResume(
+                mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()),
+                mKeyguardStateController.isFaceAuthEnabled());
     }
 
     public void startAppearAnimation() {
-        mView.startAppearAnimation();
+        if (mCurrentSecurityMode != SecurityMode.None) {
+            getCurrentSecurityController().startAppearAnimation();
+        }
     }
 
     public boolean startDisappearAnimation(Runnable onFinishRunnable) {
-        return mView.startDisappearAnimation(onFinishRunnable);
+        mView.startDisappearAnimation(getCurrentSecurityMode());
+
+        if (mCurrentSecurityMode != SecurityMode.None) {
+            return getCurrentSecurityController().startDisappearAnimation(onFinishRunnable);
+        }
+
+        return false;
     }
 
     public void onStartingToHide() {
-        mView.onStartingToHide();
+        if (mCurrentSecurityMode != SecurityMode.None) {
+            getCurrentSecurityController().onStartingToHide();
+        }
     }
 
-    public void setSecurityCallback(SecurityCallback securityCallback) {
-        mView.setSecurityCallback(securityCallback);
-    }
-
+    /**
+     * Shows the next security screen if there is one.
+     * @param authenticated true if the user entered the correct authentication
+     * @param targetUserId a user that needs to be the foreground user at the finish (if called)
+     *     completion.
+     * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary
+     *     secondary lock screen requirement, if any.
+     * @return true if keyguard is done
+     */
     public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId,
             boolean bypassSecondaryLockScreen) {
-        return mView.showNextSecurityScreenOrFinish(
-                authenticated, targetUserId, bypassSecondaryLockScreen);
+
+        if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")");
+        boolean finish = false;
+        boolean strongAuth = false;
+        int eventSubtype = -1;
+        BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN;
+        if (mUpdateMonitor.getUserHasTrust(targetUserId)) {
+            finish = true;
+            eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS;
+            uiEvent = BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS;
+        } else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) {
+            finish = true;
+            eventSubtype = BOUNCER_DISMISS_BIOMETRIC;
+            uiEvent = BouncerUiEvent.BOUNCER_DISMISS_BIOMETRIC;
+        } else if (SecurityMode.None == getCurrentSecurityMode()) {
+            SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
+            if (SecurityMode.None == securityMode) {
+                finish = true; // no security required
+                eventSubtype = BOUNCER_DISMISS_NONE_SECURITY;
+                uiEvent = BouncerUiEvent.BOUNCER_DISMISS_NONE_SECURITY;
+            } else {
+                showSecurityScreen(securityMode); // switch to the alternate security view
+            }
+        } else if (authenticated) {
+            switch (getCurrentSecurityMode()) {
+                case Pattern:
+                case Password:
+                case PIN:
+                    strongAuth = true;
+                    finish = true;
+                    eventSubtype = BOUNCER_DISMISS_PASSWORD;
+                    uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD;
+                    break;
+
+                case SimPin:
+                case SimPuk:
+                    // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
+                    SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
+                    if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled(
+                            KeyguardUpdateMonitor.getCurrentUser())) {
+                        finish = true;
+                        eventSubtype = BOUNCER_DISMISS_SIM;
+                        uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
+                    } else {
+                        showSecurityScreen(securityMode);
+                    }
+                    break;
+
+                default:
+                    Log.v(TAG, "Bad security screen " + getCurrentSecurityMode()
+                            + ", fail safe");
+                    showPrimarySecurityScreen(false);
+                    break;
+            }
+        }
+        // Check for device admin specified additional security measures.
+        if (finish && !bypassSecondaryLockScreen) {
+            Intent secondaryLockscreenIntent =
+                    mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId);
+            if (secondaryLockscreenIntent != null) {
+                mAdminSecondaryLockScreenController.show(secondaryLockscreenIntent);
+                return false;
+            }
+        }
+        if (eventSubtype != -1) {
+            mMetricsLogger.write(new LogMaker(MetricsProto.MetricsEvent.BOUNCER)
+                    .setType(MetricsProto.MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype));
+        }
+        if (uiEvent != BouncerUiEvent.UNKNOWN) {
+            mUiEventLogger.log(uiEvent);
+        }
+        if (finish) {
+            mSecurityCallback.finish(strongAuth, targetUserId);
+        }
+        return finish;
     }
 
     public boolean needsInput() {
-        return mView.needsInput();
+        return getCurrentSecurityController().needsInput();
     }
 
-    public SecurityMode getCurrentSecurityMode() {
-        return mView.getCurrentSecurityMode();
+    /**
+     * Switches to the given security view unless it's already being shown, in which case
+     * this is a no-op.
+     *
+     * @param securityMode
+     */
+    @VisibleForTesting
+    void showSecurityScreen(SecurityMode securityMode) {
+        if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")");
+
+        if (securityMode == SecurityMode.Invalid || securityMode == mCurrentSecurityMode) {
+            return;
+        }
+
+        KeyguardInputViewController<KeyguardInputView> oldView = getCurrentSecurityController();
+
+        // Emulate Activity life cycle
+        if (oldView != null) {
+            oldView.onPause();
+        }
+
+        KeyguardInputViewController<KeyguardInputView> newView = changeSecurityMode(securityMode);
+        if (newView != null) {
+            newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
+            mSecurityViewFlipperController.show(newView);
+        }
+
+        mSecurityCallback.onSecurityModeChanged(
+                securityMode, newView != null && newView.needsInput());
+    }
+
+    public void reportFailedUnlockAttempt(int userId, int timeoutMs) {
+        // +1 for this time
+        final int failedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(userId) + 1;
+
+        if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts);
+
+        final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
+        final int failedAttemptsBeforeWipe =
+                dpm.getMaximumFailedPasswordsForWipe(null, userId);
+
+        final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0
+                ? (failedAttemptsBeforeWipe - failedAttempts)
+                : Integer.MAX_VALUE; // because DPM returns 0 if no restriction
+        if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
+            // The user has installed a DevicePolicyManager that requests a user/profile to be wiped
+            // N attempts. Once we get below the grace period, we post this dialog every time as a
+            // clear warning until the deletion fires.
+            // Check which profile has the strictest policy for failed password attempts
+            final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId);
+            int userType = USER_TYPE_PRIMARY;
+            if (expiringUser == userId) {
+                // TODO: http://b/23522538
+                if (expiringUser != UserHandle.USER_SYSTEM) {
+                    userType = USER_TYPE_SECONDARY_USER;
+                }
+            } else if (expiringUser != UserHandle.USER_NULL) {
+                userType = USER_TYPE_WORK_PROFILE;
+            } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY
+            if (remainingBeforeWipe > 0) {
+                mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType);
+            } else {
+                // Too many attempts. The device will be wiped shortly.
+                Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!");
+                mView.showWipeDialog(failedAttempts, userType);
+            }
+        }
+        mLockPatternUtils.reportFailedPasswordAttempt(userId);
+        if (timeoutMs > 0) {
+            mLockPatternUtils.reportPasswordLockout(timeoutMs, userId);
+            mView.showTimeoutDialog(userId, timeoutMs, mLockPatternUtils,
+                    mSecurityModel.getSecurityMode(userId));
+        }
+    }
+
+    private KeyguardInputViewController<KeyguardInputView> getCurrentSecurityController() {
+        return mSecurityViewFlipperController
+                .getSecurityView(mCurrentSecurityMode, mKeyguardSecurityCallback);
+    }
+
+    private KeyguardInputViewController<KeyguardInputView> changeSecurityMode(
+            SecurityMode securityMode) {
+        mCurrentSecurityMode = securityMode;
+        return getCurrentSecurityController();
+    }
+
+    static class Factory {
+
+        private final KeyguardSecurityContainer mView;
+        private final AdminSecondaryLockScreenController.Factory
+                mAdminSecondaryLockScreenControllerFactory;
+        private final LockPatternUtils mLockPatternUtils;
+        private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+        private final KeyguardSecurityModel mKeyguardSecurityModel;
+        private final MetricsLogger mMetricsLogger;
+        private final UiEventLogger mUiEventLogger;
+        private final KeyguardStateController mKeyguardStateController;
+        private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
+
+        @Inject
+        Factory(KeyguardSecurityContainer view,
+                AdminSecondaryLockScreenController.Factory
+                        adminSecondaryLockScreenControllerFactory,
+                LockPatternUtils lockPatternUtils,
+                KeyguardUpdateMonitor keyguardUpdateMonitor,
+                KeyguardSecurityModel keyguardSecurityModel,
+                MetricsLogger metricsLogger,
+                UiEventLogger uiEventLogger,
+                KeyguardStateController keyguardStateController,
+                KeyguardSecurityViewFlipperController securityViewFlipperController) {
+            mView = view;
+            mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
+            mLockPatternUtils = lockPatternUtils;
+            mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+            mKeyguardSecurityModel = keyguardSecurityModel;
+            mMetricsLogger = metricsLogger;
+            mUiEventLogger = uiEventLogger;
+            mKeyguardStateController = keyguardStateController;
+            mSecurityViewFlipperController = securityViewFlipperController;
+        }
+
+        public KeyguardSecurityContainerController create(
+                SecurityCallback securityCallback) {
+            return new KeyguardSecurityContainerController(mView,
+                    mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
+                    mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
+                    mKeyguardStateController, securityCallback, mSecurityViewFlipperController);
+        }
+
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index ac2160e..c77c867 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -18,13 +18,14 @@
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
 import android.app.admin.DevicePolicyManager;
-import android.content.Context;
+import android.content.res.Resources;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 
 import javax.inject.Inject;
 
@@ -33,7 +34,7 @@
 
     /**
      * The different types of security available.
-     * @see KeyguardSecurityContainer#showSecurityScreen
+     * @see KeyguardSecurityContainerController#showSecurityScreen
      */
     public enum SecurityMode {
         Invalid, // NULL state
@@ -45,21 +46,15 @@
         SimPuk // Unlock by entering a sim puk
     }
 
-    private final Context mContext;
     private final boolean mIsPukScreenAvailable;
 
-    private LockPatternUtils mLockPatternUtils;
+    private final LockPatternUtils mLockPatternUtils;
 
     @Inject
-    KeyguardSecurityModel(Context context) {
-        mContext = context;
-        mLockPatternUtils = new LockPatternUtils(context);
-        mIsPukScreenAvailable = mContext.getResources().getBoolean(
+    KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils) {
+        mIsPukScreenAvailable = resources.getBoolean(
                 com.android.internal.R.bool.config_enable_puk_unlock_screen);
-    }
-
-    void setLockPatternUtils(LockPatternUtils utils) {
-        mLockPatternUtils = utils;
+        mLockPatternUtils = lockPatternUtils;
     }
 
     public SecurityMode getSecurityMode(int userId) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 43cef3a..ac00e94 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -18,11 +18,9 @@
 import android.content.res.ColorStateList;
 import android.view.MotionEvent;
 
-import com.android.internal.widget.LockPatternUtils;
-
 public interface KeyguardSecurityView {
-    static public final int SCREEN_ON = 1;
-    static public final int VIEW_REVEALED = 2;
+    int SCREEN_ON = 1;
+    int VIEW_REVEALED = 2;
 
     int PROMPT_REASON_NONE = 0;
 
@@ -63,18 +61,6 @@
     int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
 
     /**
-     * Interface back to keyguard to tell it when security
-     * @param callback
-     */
-    void setKeyguardCallback(KeyguardSecurityCallback callback);
-
-    /**
-     * Set {@link LockPatternUtils} object. Useful for providing a mock interface.
-     * @param utils
-     */
-    void setLockPatternUtils(LockPatternUtils utils);
-
-    /**
      * Reset the view and prepare to take input. This should do things like clearing the
      * password or pattern and clear error messages.
      */
@@ -101,12 +87,6 @@
     boolean needsInput();
 
     /**
-     * Get {@link KeyguardSecurityCallback} for the given object
-     * @return KeyguardSecurityCallback
-     */
-    KeyguardSecurityCallback getCallback();
-
-    /**
      * Show a string explaining why the security view needs to be solved.
      *
      * @param reason a flag indicating which string should be shown, see {@link #PROMPT_REASON_NONE}
@@ -123,12 +103,6 @@
     void showMessage(CharSequence message, ColorStateList colorState);
 
     /**
-     * Instruct the view to show usability hints, if any.
-     *
-     */
-    void showUsabilityHint();
-
-    /**
      * Starts the animation which should run when the security view appears.
      */
     void startAppearAnimation();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewController.java
deleted file mode 100644
index ef9ba19..0000000
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewController.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard;
-
-import android.view.View;
-
-import com.android.systemui.util.ViewController;
-
-import javax.inject.Inject;
-
-
-/** Controller for a {@link KeyguardSecurityView}. */
-public class KeyguardSecurityViewController extends ViewController<View> {
-
-    private final KeyguardSecurityView mView;
-
-    private KeyguardSecurityViewController(KeyguardSecurityView view) {
-        super((View) view);
-        // KeyguardSecurityView isn't actually a View, so we need to track it ourselves.
-        mView = view;
-    }
-
-    @Override
-    protected void onViewAttached() {
-
-    }
-
-    @Override
-    protected void onViewDetached() {
-
-    }
-
-    /** Factory for a {@link KeyguardSecurityViewController}. */
-    public static class Factory {
-        @Inject
-        public Factory() {
-        }
-
-        /** Create a new {@link KeyguardSecurityViewController}. */
-        public KeyguardSecurityViewController create(KeyguardSecurityView view) {
-            return new KeyguardSecurityViewController(view);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index 24da3ad..b8439af 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.util.AttributeSet;
@@ -31,7 +30,6 @@
 import android.widget.FrameLayout;
 import android.widget.ViewFlipper;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.R;
 
 /**
@@ -39,7 +37,7 @@
  * we can emulate {@link android.view.WindowManager.LayoutParams#FLAG_SLIPPERY} within a view
  * hierarchy.
  */
-public class KeyguardSecurityViewFlipper extends ViewFlipper implements KeyguardSecurityView {
+public class KeyguardSecurityViewFlipper extends ViewFlipper {
     private static final String TAG = "KeyguardSecurityViewFlipper";
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
 
@@ -69,111 +67,16 @@
         return result;
     }
 
-    KeyguardSecurityView getSecurityView() {
+    KeyguardInputView getSecurityView() {
         View child = getChildAt(getDisplayedChild());
-        if (child instanceof KeyguardSecurityView) {
-            return (KeyguardSecurityView) child;
+        if (child instanceof KeyguardInputView) {
+            return (KeyguardInputView) child;
         }
         return null;
     }
 
-    @Override
-    public void setKeyguardCallback(KeyguardSecurityCallback callback) {
-        KeyguardSecurityView ksv = getSecurityView();
-        if (ksv != null) {
-            ksv.setKeyguardCallback(callback);
-        }
-    }
-
-    @Override
-    public void setLockPatternUtils(LockPatternUtils utils) {
-        KeyguardSecurityView ksv = getSecurityView();
-        if (ksv != null) {
-            ksv.setLockPatternUtils(utils);
-        }
-    }
-
-    @Override
-    public void reset() {
-        KeyguardSecurityView ksv = getSecurityView();
-        if (ksv != null) {
-            ksv.reset();
-        }
-    }
-
-    @Override
-    public void onPause() {
-        KeyguardSecurityView ksv = getSecurityView();
-        if (ksv != null) {
-            ksv.onPause();
-        }
-    }
-
-    @Override
-    public void onResume(int reason) {
-        KeyguardSecurityView ksv = getSecurityView();
-        if (ksv != null) {
-            ksv.onResume(reason);
-        }
-    }
-
-    @Override
-    public boolean needsInput() {
-        KeyguardSecurityView ksv = getSecurityView();
-        return (ksv != null) ? ksv.needsInput() : false;
-    }
-
-    @Override
-    public KeyguardSecurityCallback getCallback() {
-        KeyguardSecurityView ksv = getSecurityView();
-        return (ksv != null) ? ksv.getCallback() : null;
-    }
-
-    @Override
-    public void showPromptReason(int reason) {
-        KeyguardSecurityView ksv = getSecurityView();
-        if (ksv != null) {
-            ksv.showPromptReason(reason);
-        }
-    }
-
-    @Override
-    public void showMessage(CharSequence message, ColorStateList colorState) {
-        KeyguardSecurityView ksv = getSecurityView();
-        if (ksv != null) {
-            ksv.showMessage(message, colorState);
-        }
-    }
-
-    @Override
-    public void showUsabilityHint() {
-        KeyguardSecurityView ksv = getSecurityView();
-        if (ksv != null) {
-            ksv.showUsabilityHint();
-        }
-    }
-
-    @Override
-    public void startAppearAnimation() {
-        KeyguardSecurityView ksv = getSecurityView();
-        if (ksv != null) {
-            ksv.startAppearAnimation();
-        }
-    }
-
-    @Override
-    public boolean startDisappearAnimation(Runnable finishRunnable) {
-        KeyguardSecurityView ksv = getSecurityView();
-        if (ksv != null) {
-            return ksv.startDisappearAnimation(finishRunnable);
-        } else {
-            return false;
-        }
-    }
-
-    @Override
     public CharSequence getTitle() {
-        KeyguardSecurityView ksv = getSecurityView();
+        KeyguardInputView ksv = getSecurityView();
         if (ksv != null) {
             return ksv.getTitle();
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
new file mode 100644
index 0000000..4953035
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -0,0 +1,148 @@
+/*
+ * 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.keyguard;
+
+import android.util.Log;
+import android.view.LayoutInflater;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.KeyguardInputViewController.Factory;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.dagger.KeyguardBouncerScope;
+import com.android.systemui.R;
+import com.android.systemui.util.ViewController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Controller for a {@link KeyguardSecurityViewFlipper}.
+ */
+@KeyguardBouncerScope
+public class KeyguardSecurityViewFlipperController
+        extends ViewController<KeyguardSecurityViewFlipper> {
+
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+    private static final String TAG = "KeyguardSecurityView";
+
+    private final List<KeyguardInputViewController<KeyguardInputView>> mChildren =
+            new ArrayList<>();
+    private final LayoutInflater mLayoutInflater;
+    private final Factory mKeyguardSecurityViewControllerFactory;
+
+    @Inject
+    protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view,
+            LayoutInflater layoutInflater,
+            KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory) {
+        super(view);
+        mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
+        mLayoutInflater = layoutInflater;
+    }
+
+    @Override
+    protected void onViewAttached() {
+
+    }
+
+    @Override
+    protected void onViewDetached() {
+
+    }
+
+    public void reset() {
+        for (KeyguardInputViewController<KeyguardInputView> child : mChildren) {
+            child.reset();
+        }
+    }
+
+    @VisibleForTesting
+    KeyguardInputViewController<KeyguardInputView> getSecurityView(SecurityMode securityMode,
+            KeyguardSecurityCallback keyguardSecurityCallback) {
+        KeyguardInputViewController<KeyguardInputView> childController = null;
+        for (KeyguardInputViewController<KeyguardInputView> child : mChildren) {
+            if (child.getSecurityMode() == securityMode) {
+                childController = child;
+                break;
+            }
+        }
+
+        if (childController == null
+                && securityMode != SecurityMode.None && securityMode != SecurityMode.Invalid) {
+
+            int layoutId = getLayoutIdFor(securityMode);
+            KeyguardInputView view = null;
+            if (layoutId != 0) {
+                if (DEBUG) Log.v(TAG, "inflating id = " + layoutId);
+                view = (KeyguardInputView) mLayoutInflater.inflate(
+                        layoutId, mView, false);
+                mView.addView(view);
+                childController = mKeyguardSecurityViewControllerFactory.create(
+                        view, securityMode, keyguardSecurityCallback);
+                childController.init();
+
+                mChildren.add(childController);
+            }
+        }
+
+        if (childController == null) {
+            childController = new NullKeyguardInputViewController(
+                    securityMode, keyguardSecurityCallback);
+        }
+
+        return childController;
+    }
+
+    private int getLayoutIdFor(SecurityMode securityMode) {
+        switch (securityMode) {
+            case Pattern: return com.android.systemui.R.layout.keyguard_pattern_view;
+            case PIN: return com.android.systemui.R.layout.keyguard_pin_view;
+            case Password: return com.android.systemui.R.layout.keyguard_password_view;
+            case SimPin: return com.android.systemui.R.layout.keyguard_sim_pin_view;
+            case SimPuk: return R.layout.keyguard_sim_puk_view;
+            default:
+                return 0;
+        }
+    }
+
+    /** Makes the supplied child visible if it is contained win this view, */
+    public void show(KeyguardInputViewController<KeyguardInputView> childController) {
+        int index = childController.getIndexIn(mView);
+        if (index != -1) {
+            mView.setDisplayedChild(index);
+        }
+    }
+
+    private static class NullKeyguardInputViewController
+            extends KeyguardInputViewController<KeyguardInputView> {
+        protected NullKeyguardInputViewController(SecurityMode securityMode,
+                KeyguardSecurityCallback keyguardSecurityCallback) {
+            super(null, securityMode, keyguardSecurityCallback);
+        }
+
+        @Override
+        public boolean needsInput() {
+            return false;
+        }
+
+        @Override
+        public void onStartingToHide() {
+
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index 1c47aa0..c0f9ce7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -16,66 +16,19 @@
 
 package com.android.keyguard;
 
-import android.annotation.NonNull;
-import android.app.AlertDialog;
-import android.app.AlertDialog.Builder;
-import android.app.Dialog;
-import android.app.ProgressDialog;
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.telephony.PinResult;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.View;
-import android.view.WindowManager;
-import android.widget.ImageView;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
 /**
  * Displays a PIN pad for unlocking.
  */
 public class KeyguardSimPinView extends KeyguardPinBasedInputView {
-    private static final String LOG_TAG = "KeyguardSimPinView";
-    private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES;
     public static final String TAG = "KeyguardSimPinView";
 
-    private ProgressDialog mSimUnlockProgressDialog = null;
-    private CheckSimPin mCheckSimPinThread;
-
-    // Below flag is set to true during power-up or when a new SIM card inserted on device.
-    // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would
-    // be displayed to inform user about the number of remaining PIN attempts left.
-    private boolean mShowDefaultMessage = true;
-    private int mRemainingAttempts = -1;
-    private AlertDialog mRemainingAttemptsDialog;
-    private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-    private ImageView mSimImageView;
-
-    KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
-        @Override
-        public void onSimStateChanged(int subId, int slotId, int simState) {
-            if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
-            switch(simState) {
-                case TelephonyManager.SIM_STATE_READY: {
-                    mRemainingAttempts = -1;
-                    resetState();
-                    break;
-                }
-                default:
-                    resetState();
-            }
-        }
-    };
-
     public KeyguardSimPinView(Context context) {
         this(context, null);
     }
@@ -84,81 +37,9 @@
         super(context, attrs);
     }
 
-    @Override
-    public void resetState() {
-        super.resetState();
-        if (DEBUG) Log.v(TAG, "Resetting state");
-        handleSubInfoChangeIfNeeded();
-        if (mShowDefaultMessage) {
-            showDefaultMessage();
-        }
-        boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
-
+    public void setEsimLocked(boolean locked) {
         KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area);
-        esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
-    }
-
-    private void setLockedSimMessage() {
-        boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
-        int count = 1;
-        TelephonyManager telephonyManager =
-            (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        if (telephonyManager != null) {
-            count = telephonyManager.getActiveModemCount();
-        }
-        Resources rez = getResources();
-        String msg;
-        TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor });
-        int color = array.getColor(0, Color.WHITE);
-        array.recycle();
-        if (count < 2) {
-            msg = rez.getString(R.string.kg_sim_pin_instructions);
-        } else {
-            SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
-                    .getSubscriptionInfoForSubId(mSubId);
-            CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
-            msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
-            if (info != null) {
-                color = info.getIconTint();
-            }
-        }
-        if (isEsimLocked) {
-            msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg);
-        }
-
-        if (mSecurityMessageDisplay != null && getVisibility() == VISIBLE) {
-            mSecurityMessageDisplay.setMessage(msg);
-        }
-        mSimImageView.setImageTintList(ColorStateList.valueOf(color));
-    }
-
-    private void showDefaultMessage() {
-        setLockedSimMessage();
-        if (mRemainingAttempts >= 0) {
-            return;
-        }
-
-        // Sending empty PIN here to query the number of remaining PIN attempts
-        new CheckSimPin("", mSubId) {
-            void onSimCheckResponse(final PinResult result) {
-                Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result "
-                        + result.toString());
-                if (result.getAttemptsRemaining() >= 0) {
-                    mRemainingAttempts = result.getAttemptsRemaining();
-                    setLockedSimMessage();
-                }
-            }
-        }.start();
-    }
-
-    private void handleSubInfoChangeIfNeeded() {
-        KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
-        int subId = monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED);
-        if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
-            mSubId = subId;
-            mShowDefaultMessage = true;
-            mRemainingAttempts = -1;
-        }
+        esimButton.setVisibility(locked ? View.VISIBLE : View.GONE);
     }
 
     @Override
@@ -173,35 +54,6 @@
         return 0;
     }
 
-    private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) {
-        String displayMessage;
-        int msgId;
-        if (attemptsRemaining == 0) {
-            displayMessage = getContext().getString(R.string.kg_password_wrong_pin_code_pukked);
-        } else if (attemptsRemaining > 0) {
-            msgId = isDefault ? R.plurals.kg_password_default_pin_message :
-                     R.plurals.kg_password_wrong_pin_code;
-            displayMessage = getContext().getResources()
-                    .getQuantityString(msgId, attemptsRemaining, attemptsRemaining);
-        } else {
-            msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed;
-            displayMessage = getContext().getString(msgId);
-        }
-        if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) {
-            displayMessage = getResources()
-                    .getString(R.string.kg_sim_lock_esim_instructions, displayMessage);
-        }
-        if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:"
-                + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
-        return displayMessage;
-    }
-
-    @Override
-    protected boolean shouldLockout(long deadline) {
-        // SIM PIN doesn't have a timed lockout
-        return false;
-    }
-
     @Override
     protected int getPasswordTextViewId() {
         return R.id.simPinEntry;
@@ -214,173 +66,6 @@
         if (mEcaView instanceof EmergencyCarrierArea) {
             ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true);
         }
-        mSimImageView = findViewById(R.id.keyguard_sim);
-    }
-
-    @Override
-    public void showUsabilityHint() {
-
-    }
-
-    @Override
-    public void onResume(int reason) {
-        super.onResume(reason);
-        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
-        resetState();
-    }
-
-    @Override
-    public void onPause() {
-        // dismiss the dialog.
-        if (mSimUnlockProgressDialog != null) {
-            mSimUnlockProgressDialog.dismiss();
-            mSimUnlockProgressDialog = null;
-        }
-        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback);
-    }
-
-    /**
-     * Since the IPC can block, we want to run the request in a separate thread
-     * with a callback.
-     */
-    private abstract class CheckSimPin extends Thread {
-        private final String mPin;
-        private int mSubId;
-
-        protected CheckSimPin(String pin, int subId) {
-            mPin = pin;
-            mSubId = subId;
-        }
-
-        abstract void onSimCheckResponse(@NonNull PinResult result);
-
-        @Override
-        public void run() {
-            if (DEBUG) {
-                Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")");
-            }
-            TelephonyManager telephonyManager =
-                    ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
-                            .createForSubscriptionId(mSubId);
-            final PinResult result = telephonyManager.supplyPinReportPinResult(mPin);
-            if (result == null) {
-                Log.e(TAG, "Error result for supplyPinReportResult.");
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        onSimCheckResponse(PinResult.getDefaultFailedResult());
-                    }
-                });
-            } else {
-                if (DEBUG) {
-                    Log.v(TAG, "supplyPinReportResult returned: " + result.toString());
-                }
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        onSimCheckResponse(result);
-                    }
-                });
-            }
-        }
-    }
-
-    private Dialog getSimUnlockProgressDialog() {
-        if (mSimUnlockProgressDialog == null) {
-            mSimUnlockProgressDialog = new ProgressDialog(mContext);
-            mSimUnlockProgressDialog.setMessage(
-                    mContext.getString(R.string.kg_sim_unlock_progress_dialog_message));
-            mSimUnlockProgressDialog.setIndeterminate(true);
-            mSimUnlockProgressDialog.setCancelable(false);
-            mSimUnlockProgressDialog.getWindow().setType(
-                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
-        }
-        return mSimUnlockProgressDialog;
-    }
-
-    private Dialog getSimRemainingAttemptsDialog(int remaining) {
-        String msg = getPinPasswordErrorMessage(remaining, false);
-        if (mRemainingAttemptsDialog == null) {
-            Builder builder = new AlertDialog.Builder(mContext);
-            builder.setMessage(msg);
-            builder.setCancelable(false);
-            builder.setNeutralButton(R.string.ok, null);
-            mRemainingAttemptsDialog = builder.create();
-            mRemainingAttemptsDialog.getWindow().setType(
-                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
-        } else {
-            mRemainingAttemptsDialog.setMessage(msg);
-        }
-        return mRemainingAttemptsDialog;
-    }
-
-    @Override
-    protected void verifyPasswordAndUnlock() {
-        String entry = mPasswordEntry.getText();
-
-        if (entry.length() < 4) {
-            // otherwise, display a message to the user, and don't submit.
-            mSecurityMessageDisplay.setMessage(R.string.kg_invalid_sim_pin_hint);
-            resetPasswordText(true /* animate */, true /* announce */);
-            mCallback.userActivity();
-            return;
-        }
-
-        getSimUnlockProgressDialog().show();
-
-        if (mCheckSimPinThread == null) {
-            mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) {
-                @Override
-                void onSimCheckResponse(final PinResult result) {
-                    post(new Runnable() {
-                        @Override
-                        public void run() {
-                            mRemainingAttempts = result.getAttemptsRemaining();
-                            if (mSimUnlockProgressDialog != null) {
-                                mSimUnlockProgressDialog.hide();
-                            }
-                            resetPasswordText(true /* animate */,
-                                    /* announce */
-                                    result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS);
-                            if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
-                                Dependency.get(KeyguardUpdateMonitor.class)
-                                        .reportSimUnlocked(mSubId);
-                                mRemainingAttempts = -1;
-                                mShowDefaultMessage = true;
-                                if (mCallback != null) {
-                                    mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
-                                }
-                            } else {
-                                mShowDefaultMessage = false;
-                                if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
-                                    if (result.getAttemptsRemaining() <= 2) {
-                                        // this is getting critical - show dialog
-                                        getSimRemainingAttemptsDialog(
-                                                result.getAttemptsRemaining()).show();
-                                    } else {
-                                        // show message
-                                        mSecurityMessageDisplay.setMessage(
-                                                getPinPasswordErrorMessage(
-                                                        result.getAttemptsRemaining(), false));
-                                    }
-                                } else {
-                                    // "PIN operation failed!" - no idea what this was and no way to
-                                    // find out. :/
-                                    mSecurityMessageDisplay.setMessage(getContext().getString(
-                                            R.string.kg_password_pin_failed));
-                                }
-                                if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock "
-                                        + " CheckSimPin.onSimCheckResponse: " + result
-                                        + " attemptsRemaining=" + result.getAttemptsRemaining());
-                            }
-                            mCallback.userActivity();
-                            mCheckSimPinThread = null;
-                        }
-                    });
-                }
-            };
-            mCheckSimPinThread.start();
-        }
     }
 
     @Override
@@ -389,11 +74,6 @@
     }
 
     @Override
-    public boolean startDisappearAnimation(Runnable finishRunnable) {
-        return false;
-    }
-
-    @Override
     public CharSequence getTitle() {
         return getContext().getString(
                 com.android.internal.R.string.keyguard_accessibility_sim_pin_unlock);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
new file mode 100644
index 0000000..cc8bf4f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -0,0 +1,350 @@
+/*
+ * 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.keyguard;
+
+import android.annotation.NonNull;
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.telephony.PinResult;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+
+public class KeyguardSimPinViewController
+        extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
+    public static final String TAG = "KeyguardSimPinView";
+    private static final String LOG_TAG = "KeyguardSimPinView";
+    private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final TelephonyManager mTelephonyManager;
+
+    private ProgressDialog mSimUnlockProgressDialog;
+    private CheckSimPin mCheckSimPinThread;
+    private int mRemainingAttempts;
+    // Below flag is set to true during power-up or when a new SIM card inserted on device.
+    // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would
+    // be displayed to inform user about the number of remaining PIN attempts left.
+    private boolean mShowDefaultMessage;
+    private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private AlertDialog mRemainingAttemptsDialog;
+    private ImageView mSimImageView;
+
+    KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
+        @Override
+        public void onSimStateChanged(int subId, int slotId, int simState) {
+            if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
+            if (simState == TelephonyManager.SIM_STATE_READY) {
+                mRemainingAttempts = -1;
+                resetState();
+            } else {
+                resetState();
+            }
+        }
+    };
+
+    protected KeyguardSimPinViewController(KeyguardSimPinView view,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SecurityMode securityMode, LockPatternUtils lockPatternUtils,
+            KeyguardSecurityCallback keyguardSecurityCallback,
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+            LatencyTracker latencyTracker,
+            LiftToActivateListener liftToActivateListener,
+            TelephonyManager telephonyManager) {
+        super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
+                messageAreaControllerFactory, latencyTracker, liftToActivateListener);
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mTelephonyManager = telephonyManager;
+        mSimImageView = mView.findViewById(R.id.keyguard_sim);
+    }
+
+    @Override
+    protected void onViewAttached() {
+        super.onViewAttached();
+    }
+
+    @Override
+    void resetState() {
+        super.resetState();
+        if (DEBUG) Log.v(TAG, "Resetting state");
+        handleSubInfoChangeIfNeeded();
+        mMessageAreaController.setMessage("");
+        if (mShowDefaultMessage) {
+            showDefaultMessage();
+        }
+
+        mView.setEsimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId));
+    }
+
+    @Override
+    public boolean startDisappearAnimation(Runnable finishRunnable) {
+        return false;
+    }
+
+    @Override
+    public void onResume(int reason) {
+        super.onResume(reason);
+        mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
+        mView.resetState();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
+
+        // dismiss the dialog.
+        if (mSimUnlockProgressDialog != null) {
+            mSimUnlockProgressDialog.dismiss();
+            mSimUnlockProgressDialog = null;
+        }
+    }
+
+    @Override
+    protected void verifyPasswordAndUnlock() {
+        String entry = mPasswordEntry.getText();
+
+        if (entry.length() < 4) {
+            // otherwise, display a message to the user, and don't submit.
+            mMessageAreaController.setMessage(
+                    com.android.systemui.R.string.kg_invalid_sim_pin_hint);
+            mView.resetPasswordText(true /* animate */, true /* announce */);
+            getKeyguardSecurityCallback().userActivity();
+            return;
+        }
+
+        getSimUnlockProgressDialog().show();
+
+        if (mCheckSimPinThread == null) {
+            mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) {
+                @Override
+                void onSimCheckResponse(final PinResult result) {
+                    mView.post(() -> {
+                        mRemainingAttempts = result.getAttemptsRemaining();
+                        if (mSimUnlockProgressDialog != null) {
+                            mSimUnlockProgressDialog.hide();
+                        }
+                        mView.resetPasswordText(true /* animate */,
+                                /* announce */
+                                result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS);
+                        if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
+                            mKeyguardUpdateMonitor.reportSimUnlocked(mSubId);
+                            mRemainingAttempts = -1;
+                            mShowDefaultMessage = true;
+                            getKeyguardSecurityCallback().dismiss(
+                                    true, KeyguardUpdateMonitor.getCurrentUser());
+                        } else {
+                            mShowDefaultMessage = false;
+                            if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
+                                if (result.getAttemptsRemaining() <= 2) {
+                                    // this is getting critical - show dialog
+                                    getSimRemainingAttemptsDialog(
+                                            result.getAttemptsRemaining()).show();
+                                } else {
+                                    // show message
+                                    mMessageAreaController.setMessage(
+                                            getPinPasswordErrorMessage(
+                                                    result.getAttemptsRemaining(), false));
+                                }
+                            } else {
+                                // "PIN operation failed!" - no idea what this was and no way to
+                                // find out. :/
+                                mMessageAreaController.setMessage(mView.getResources().getString(
+                                        R.string.kg_password_pin_failed));
+                            }
+                            if (DEBUG) {
+                                Log.d(LOG_TAG, "verifyPasswordAndUnlock "
+                                        + " CheckSimPin.onSimCheckResponse: " + result
+                                        + " attemptsRemaining=" + result.getAttemptsRemaining());
+                            }
+                        }
+                        getKeyguardSecurityCallback().userActivity();
+                        mCheckSimPinThread = null;
+                    });
+                }
+            };
+            mCheckSimPinThread.start();
+        }
+    }
+
+    private Dialog getSimUnlockProgressDialog() {
+        if (mSimUnlockProgressDialog == null) {
+            mSimUnlockProgressDialog = new ProgressDialog(mView.getContext());
+            mSimUnlockProgressDialog.setMessage(
+                    mView.getResources().getString(R.string.kg_sim_unlock_progress_dialog_message));
+            mSimUnlockProgressDialog.setIndeterminate(true);
+            mSimUnlockProgressDialog.setCancelable(false);
+            mSimUnlockProgressDialog.getWindow().setType(
+                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+        }
+        return mSimUnlockProgressDialog;
+    }
+
+
+    private Dialog getSimRemainingAttemptsDialog(int remaining) {
+        String msg = getPinPasswordErrorMessage(remaining, false);
+        if (mRemainingAttemptsDialog == null) {
+            Builder builder = new AlertDialog.Builder(mView.getContext());
+            builder.setMessage(msg);
+            builder.setCancelable(false);
+            builder.setNeutralButton(R.string.ok, null);
+            mRemainingAttemptsDialog = builder.create();
+            mRemainingAttemptsDialog.getWindow().setType(
+                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+        } else {
+            mRemainingAttemptsDialog.setMessage(msg);
+        }
+        return mRemainingAttemptsDialog;
+    }
+
+
+    private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) {
+        String displayMessage;
+        int msgId;
+        if (attemptsRemaining == 0) {
+            displayMessage = mView.getResources().getString(
+                    R.string.kg_password_wrong_pin_code_pukked);
+        } else if (attemptsRemaining > 0) {
+            msgId = isDefault ? R.plurals.kg_password_default_pin_message :
+                    R.plurals.kg_password_wrong_pin_code;
+            displayMessage = mView.getResources()
+                    .getQuantityString(msgId, attemptsRemaining, attemptsRemaining);
+        } else {
+            msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed;
+            displayMessage = mView.getResources().getString(msgId);
+        }
+        if (KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)) {
+            displayMessage = mView.getResources()
+                    .getString(R.string.kg_sim_lock_esim_instructions, displayMessage);
+        }
+        if (DEBUG) {
+            Log.d(LOG_TAG, "getPinPasswordErrorMessage: attemptsRemaining="
+                    + attemptsRemaining + " displayMessage=" + displayMessage);
+        }
+        return displayMessage;
+    }
+
+    private void showDefaultMessage() {
+        setLockedSimMessage();
+        if (mRemainingAttempts >= 0) {
+            return;
+        }
+
+        // Sending empty PIN here to query the number of remaining PIN attempts
+        new CheckSimPin("", mSubId) {
+            void onSimCheckResponse(final PinResult result) {
+                Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result "
+                        + result.toString());
+                if (result.getAttemptsRemaining() >= 0) {
+                    mRemainingAttempts = result.getAttemptsRemaining();
+                    setLockedSimMessage();
+                }
+            }
+        }.start();
+    }
+
+    /**
+     * Since the IPC can block, we want to run the request in a separate thread
+     * with a callback.
+     */
+    private abstract class CheckSimPin extends Thread {
+        private final String mPin;
+        private int mSubId;
+
+        protected CheckSimPin(String pin, int subId) {
+            mPin = pin;
+            mSubId = subId;
+        }
+
+        abstract void onSimCheckResponse(@NonNull PinResult result);
+
+        @Override
+        public void run() {
+            if (DEBUG) {
+                Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")");
+            }
+            TelephonyManager telephonyManager =
+                    mTelephonyManager.createForSubscriptionId(mSubId);
+            final PinResult result = telephonyManager.supplyPinReportPinResult(mPin);
+            if (result == null) {
+                Log.e(TAG, "Error result for supplyPinReportResult.");
+                mView.post(() -> onSimCheckResponse(PinResult.getDefaultFailedResult()));
+            } else {
+                if (DEBUG) {
+                    Log.v(TAG, "supplyPinReportResult returned: " + result.toString());
+                }
+                mView.post(() -> onSimCheckResponse(result));
+            }
+        }
+    }
+
+    private void setLockedSimMessage() {
+        boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId);
+        int count = 1;
+        if (mTelephonyManager != null) {
+            count = mTelephonyManager.getActiveModemCount();
+        }
+        Resources rez = mView.getResources();
+        String msg;
+        TypedArray array = mView.getContext().obtainStyledAttributes(
+                new int[] { R.attr.wallpaperTextColor });
+        int color = array.getColor(0, Color.WHITE);
+        array.recycle();
+        if (count < 2) {
+            msg = rez.getString(R.string.kg_sim_pin_instructions);
+        } else {
+            SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId);
+            CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
+            msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
+            if (info != null) {
+                color = info.getIconTint();
+            }
+        }
+        if (isEsimLocked) {
+            msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg);
+        }
+
+        if (mView.getVisibility() == View.VISIBLE) {
+            mMessageAreaController.setMessage(msg);
+        }
+        mSimImageView.setImageTintList(ColorStateList.valueOf(color));
+    }
+
+    private void handleSubInfoChangeIfNeeded() {
+        int subId = mKeyguardUpdateMonitor
+                .getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED);
+        if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
+            mSubId = subId;
+            mShowDefaultMessage = true;
+            mRemainingAttempts = -1;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 5148dd7..0d72c93 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -16,27 +16,10 @@
 
 package com.android.keyguard;
 
-import android.annotation.NonNull;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.ProgressDialog;
 import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.telephony.PinResult;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.ImageView;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
 
@@ -44,48 +27,9 @@
  * Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier.
  */
 public class KeyguardSimPukView extends KeyguardPinBasedInputView {
-    private static final String LOG_TAG = "KeyguardSimPukView";
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
     public static final String TAG = "KeyguardSimPukView";
 
-    private ProgressDialog mSimUnlockProgressDialog = null;
-    private CheckSimPuk mCheckSimPukThread;
-
-    // Below flag is set to true during power-up or when a new SIM card inserted on device.
-    // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would
-    // be displayed to inform user about the number of remaining PUK attempts left.
-    private boolean mShowDefaultMessage = true;
-    private int mRemainingAttempts = -1;
-    private String mPukText;
-    private String mPinText;
-    private StateMachine mStateMachine = new StateMachine();
-    private AlertDialog mRemainingAttemptsDialog;
-    private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-    private ImageView mSimImageView;
-
-    KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
-        @Override
-        public void onSimStateChanged(int subId, int slotId, int simState) {
-            if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
-            switch(simState) {
-                // If the SIM is unlocked via a key sequence through the emergency dialer, it will
-                // move into the READY state and the PUK lock keyguard should be removed.
-                case TelephonyManager.SIM_STATE_READY: {
-                    mRemainingAttempts = -1;
-                    mShowDefaultMessage = true;
-                    // mCallback can be null if onSimStateChanged callback is called when keyguard
-                    // isn't active.
-                    if (mCallback != null) {
-                        mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
-                    }
-                    break;
-                }
-                default:
-                    resetState();
-            }
-        }
-    };
-
     public KeyguardSimPukView(Context context) {
         this(context, null);
     }
@@ -94,136 +38,14 @@
         super(context, attrs);
     }
 
-    private class StateMachine {
-        final int ENTER_PUK = 0;
-        final int ENTER_PIN = 1;
-        final int CONFIRM_PIN = 2;
-        final int DONE = 3;
-        private int state = ENTER_PUK;
-
-        public void next() {
-            int msg = 0;
-            if (state == ENTER_PUK) {
-                if (checkPuk()) {
-                    state = ENTER_PIN;
-                    msg = R.string.kg_puk_enter_pin_hint;
-                } else {
-                    msg = R.string.kg_invalid_sim_puk_hint;
-                }
-            } else if (state == ENTER_PIN) {
-                if (checkPin()) {
-                    state = CONFIRM_PIN;
-                    msg = R.string.kg_enter_confirm_pin_hint;
-                } else {
-                    msg = R.string.kg_invalid_sim_pin_hint;
-                }
-            } else if (state == CONFIRM_PIN) {
-                if (confirmPin()) {
-                    state = DONE;
-                    msg = R.string.keyguard_sim_unlock_progress_dialog_message;
-                    updateSim();
-                } else {
-                    state = ENTER_PIN; // try again?
-                    msg = R.string.kg_invalid_confirm_pin_hint;
-                }
-            }
-            resetPasswordText(true /* animate */, true /* announce */);
-            if (msg != 0) {
-                mSecurityMessageDisplay.setMessage(msg);
-            }
-        }
-
-
-        void reset() {
-            mPinText="";
-            mPukText="";
-            state = ENTER_PUK;
-            handleSubInfoChangeIfNeeded();
-            if (mShowDefaultMessage) {
-                showDefaultMessage();
-            }
-            boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
-
-            KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area);
-            esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
-            mPasswordEntry.requestFocus();
-        }
-
-
-    }
-
-    private void showDefaultMessage() {
-        if (mRemainingAttempts >= 0) {
-            mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage(
-                    mRemainingAttempts, true));
-            return;
-        }
-
-        boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
-        int count = 1;
-        TelephonyManager telephonyManager =
-            (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        if (telephonyManager != null) {
-            count = telephonyManager.getActiveModemCount();
-        }
-        Resources rez = getResources();
-        String msg;
-        TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor });
-        int color = array.getColor(0, Color.WHITE);
-        array.recycle();
-        if (count < 2) {
-            msg = rez.getString(R.string.kg_puk_enter_puk_hint);
-        } else {
-            SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
-                    .getSubscriptionInfoForSubId(mSubId);
-            CharSequence displayName = info != null ? info.getDisplayName() : "";
-            msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
-            if (info != null) {
-                color = info.getIconTint();
-            }
-        }
-        if (isEsimLocked) {
-            msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg);
-        }
-        if (mSecurityMessageDisplay != null) {
-            mSecurityMessageDisplay.setMessage(msg);
-        }
-        mSimImageView.setImageTintList(ColorStateList.valueOf(color));
-
-        // Sending empty PUK here to query the number of remaining PIN attempts
-        new CheckSimPuk("", "", mSubId) {
-            void onSimLockChangedResponse(final PinResult result) {
-                if (result == null) Log.e(LOG_TAG, "onSimCheckResponse, pin result is NULL");
-                else {
-                    Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result "
-                            + result.toString());
-                    if (result.getAttemptsRemaining() >= 0) {
-                        mRemainingAttempts = result.getAttemptsRemaining();
-                        mSecurityMessageDisplay.setMessage(
-                                getPukPasswordErrorMessage(result.getAttemptsRemaining(), true));
-                    }
-                }
-            }
-        }.start();
-    }
-
-    private void handleSubInfoChangeIfNeeded() {
-        KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
-        int subId = monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PUK_REQUIRED);
-        if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
-            mSubId = subId;
-            mShowDefaultMessage = true;
-            mRemainingAttempts = -1;
-        }
-    }
-
     @Override
     protected int getPromptReasonStringRes(int reason) {
         // No message on SIM Puk
         return 0;
     }
 
-    private String getPukPasswordErrorMessage(int attemptsRemaining, boolean isDefault) {
+    String getPukPasswordErrorMessage(
+            int attemptsRemaining, boolean isDefault, boolean isEsimLocked) {
         String displayMessage;
 
         if (attemptsRemaining == 0) {
@@ -238,28 +60,19 @@
                     R.string.kg_password_puk_failed;
             displayMessage = getContext().getString(msgId);
         }
-        if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) {
+        if (isEsimLocked) {
             displayMessage = getResources()
                     .getString(R.string.kg_sim_lock_esim_instructions, displayMessage);
         }
-        if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:"
-                + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
+        if (DEBUG) {
+            Log.d(TAG, "getPukPasswordErrorMessage:"
+                    + " attemptsRemaining=" + attemptsRemaining
+                    + " displayMessage=" + displayMessage);
+        }
         return displayMessage;
     }
 
     @Override
-    public void resetState() {
-        super.resetState();
-        mStateMachine.reset();
-    }
-
-    @Override
-    protected boolean shouldLockout(long deadline) {
-        // SIM PUK doesn't have a timed lockout
-        return false;
-    }
-
-    @Override
     protected int getPasswordTextViewId() {
         return R.id.pukEntry;
     }
@@ -271,197 +84,6 @@
         if (mEcaView instanceof EmergencyCarrierArea) {
             ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true);
         }
-        mSimImageView = findViewById(R.id.keyguard_sim);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
-        resetState();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback);
-    }
-
-    @Override
-    public void showUsabilityHint() {
-    }
-
-    @Override
-    public void onPause() {
-        // dismiss the dialog.
-        if (mSimUnlockProgressDialog != null) {
-            mSimUnlockProgressDialog.dismiss();
-            mSimUnlockProgressDialog = null;
-        }
-    }
-
-    /**
-     * Since the IPC can block, we want to run the request in a separate thread
-     * with a callback.
-     */
-    private abstract class CheckSimPuk extends Thread {
-
-        private final String mPin, mPuk;
-        private final int mSubId;
-
-        protected CheckSimPuk(String puk, String pin, int subId) {
-            mPuk = puk;
-            mPin = pin;
-            mSubId = subId;
-        }
-
-        abstract void onSimLockChangedResponse(@NonNull PinResult result);
-
-        @Override
-        public void run() {
-            if (DEBUG) Log.v(TAG, "call supplyPukReportResult()");
-            TelephonyManager telephonyManager =
-                    ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
-                            .createForSubscriptionId(mSubId);
-            final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin);
-            if (result == null) {
-                Log.e(TAG, "Error result for supplyPukReportResult.");
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        onSimLockChangedResponse(PinResult.getDefaultFailedResult());
-                    }
-                });
-            } else {
-                if (DEBUG) {
-                    Log.v(TAG, "supplyPukReportResult returned: " + result.toString());
-                }
-                post(new Runnable() {
-                    @Override
-                    public void run() {
-                        onSimLockChangedResponse(result);
-                    }
-                });
-            }
-        }
-    }
-
-    private Dialog getSimUnlockProgressDialog() {
-        if (mSimUnlockProgressDialog == null) {
-            mSimUnlockProgressDialog = new ProgressDialog(mContext);
-            mSimUnlockProgressDialog.setMessage(
-                    mContext.getString(R.string.kg_sim_unlock_progress_dialog_message));
-            mSimUnlockProgressDialog.setIndeterminate(true);
-            mSimUnlockProgressDialog.setCancelable(false);
-            if (!(mContext instanceof Activity)) {
-                mSimUnlockProgressDialog.getWindow().setType(
-                        WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
-            }
-        }
-        return mSimUnlockProgressDialog;
-    }
-
-    private Dialog getPukRemainingAttemptsDialog(int remaining) {
-        String msg = getPukPasswordErrorMessage(remaining, false);
-        if (mRemainingAttemptsDialog == null) {
-            AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
-            builder.setMessage(msg);
-            builder.setCancelable(false);
-            builder.setNeutralButton(R.string.ok, null);
-            mRemainingAttemptsDialog = builder.create();
-            mRemainingAttemptsDialog.getWindow().setType(
-                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
-        } else {
-            mRemainingAttemptsDialog.setMessage(msg);
-        }
-        return mRemainingAttemptsDialog;
-    }
-
-    private boolean checkPuk() {
-        // make sure the puk is at least 8 digits long.
-        if (mPasswordEntry.getText().length() == 8) {
-            mPukText = mPasswordEntry.getText();
-            return true;
-        }
-        return false;
-    }
-
-    private boolean checkPin() {
-        // make sure the PIN is between 4 and 8 digits
-        int length = mPasswordEntry.getText().length();
-        if (length >= 4 && length <= 8) {
-            mPinText = mPasswordEntry.getText();
-            return true;
-        }
-        return false;
-    }
-
-    public boolean confirmPin() {
-        return mPinText.equals(mPasswordEntry.getText());
-    }
-
-    private void updateSim() {
-        getSimUnlockProgressDialog().show();
-
-        if (mCheckSimPukThread == null) {
-            mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) {
-                @Override
-                void onSimLockChangedResponse(final PinResult result) {
-                    post(new Runnable() {
-                        @Override
-                        public void run() {
-                            if (mSimUnlockProgressDialog != null) {
-                                mSimUnlockProgressDialog.hide();
-                            }
-                            resetPasswordText(true /* animate */,
-                                    /* announce */
-                                    result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS);
-                            if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
-                                Dependency.get(KeyguardUpdateMonitor.class)
-                                        .reportSimUnlocked(mSubId);
-                                mRemainingAttempts = -1;
-                                mShowDefaultMessage = true;
-                                if (mCallback != null) {
-                                    mCallback.dismiss(true,
-                                            KeyguardUpdateMonitor.getCurrentUser());
-                                }
-                            } else {
-                                mShowDefaultMessage = false;
-                                if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
-                                    // show message
-                                    mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage(
-                                            result.getAttemptsRemaining(), false));
-                                    if (result.getAttemptsRemaining() <= 2) {
-                                        // this is getting critical - show dialog
-                                        getPukRemainingAttemptsDialog(
-                                                result.getAttemptsRemaining()).show();
-                                    } else {
-                                        // show message
-                                        mSecurityMessageDisplay.setMessage(
-                                                getPukPasswordErrorMessage(
-                                                        result.getAttemptsRemaining(), false));
-                                    }
-                                } else {
-                                    mSecurityMessageDisplay.setMessage(getContext().getString(
-                                            R.string.kg_password_puk_failed));
-                                }
-                                if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock "
-                                        + " UpdateSim.onSimCheckResponse: "
-                                        + " attemptsRemaining=" + result.getAttemptsRemaining());
-                            }
-                            mStateMachine.reset();
-                            mCheckSimPukThread = null;
-                        }
-                    });
-                }
-            };
-            mCheckSimPukThread.start();
-        }
-    }
-
-    @Override
-    protected void verifyPasswordAndUnlock() {
-        mStateMachine.next();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
new file mode 100644
index 0000000..a873749
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -0,0 +1,413 @@
+/*
+ * 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.keyguard;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.telephony.PinResult;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+
+public class KeyguardSimPukViewController
+        extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+    public static final String TAG = "KeyguardSimPukView";
+
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final TelephonyManager mTelephonyManager;
+
+    private String mPukText;
+    private String mPinText;
+    private int mRemainingAttempts;
+    // Below flag is set to true during power-up or when a new SIM card inserted on device.
+    // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would
+    // be displayed to inform user about the number of remaining PUK attempts left.
+    private boolean mShowDefaultMessage;
+    private StateMachine mStateMachine = new StateMachine();
+    private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private CheckSimPuk mCheckSimPukThread;
+    private ProgressDialog mSimUnlockProgressDialog;
+
+    KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
+        @Override
+        public void onSimStateChanged(int subId, int slotId, int simState) {
+            if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
+            // If the SIM is unlocked via a key sequence through the emergency dialer, it will
+            // move into the READY state and the PUK lock keyguard should be removed.
+            if (simState == TelephonyManager.SIM_STATE_READY) {
+                mRemainingAttempts = -1;
+                mShowDefaultMessage = true;
+                getKeyguardSecurityCallback().dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+            } else {
+                resetState();
+            }
+        }
+    };
+    private ImageView mSimImageView;
+    private AlertDialog mRemainingAttemptsDialog;
+
+    protected KeyguardSimPukViewController(KeyguardSimPukView view,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SecurityMode securityMode, LockPatternUtils lockPatternUtils,
+            KeyguardSecurityCallback keyguardSecurityCallback,
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+            LatencyTracker latencyTracker,
+            LiftToActivateListener liftToActivateListener,
+            TelephonyManager telephonyManager) {
+        super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
+                messageAreaControllerFactory, latencyTracker, liftToActivateListener);
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mTelephonyManager = telephonyManager;
+        mSimImageView = mView.findViewById(R.id.keyguard_sim);
+    }
+
+    @Override
+    protected void onViewAttached() {
+        super.onViewAttached();
+        mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
+    }
+
+    @Override
+    protected void onViewDetached() {
+        super.onViewDetached();
+        mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
+    }
+
+    @Override
+    void resetState() {
+        super.resetState();
+        mStateMachine.reset();
+    }
+
+    @Override
+    protected void verifyPasswordAndUnlock() {
+        mStateMachine.next();
+    }
+
+    private class StateMachine {
+        static final int ENTER_PUK = 0;
+        static final int ENTER_PIN = 1;
+        static final int CONFIRM_PIN = 2;
+        static final int DONE = 3;
+
+        private int mState = ENTER_PUK;
+
+        public void next() {
+            int msg = 0;
+            if (mState == ENTER_PUK) {
+                if (checkPuk()) {
+                    mState = ENTER_PIN;
+                    msg = com.android.systemui.R.string.kg_puk_enter_pin_hint;
+                } else {
+                    msg = com.android.systemui.R.string.kg_invalid_sim_puk_hint;
+                }
+            } else if (mState == ENTER_PIN) {
+                if (checkPin()) {
+                    mState = CONFIRM_PIN;
+                    msg = com.android.systemui.R.string.kg_enter_confirm_pin_hint;
+                } else {
+                    msg = com.android.systemui.R.string.kg_invalid_sim_pin_hint;
+                }
+            } else if (mState == CONFIRM_PIN) {
+                if (confirmPin()) {
+                    mState = DONE;
+                    msg = com.android.systemui.R.string.keyguard_sim_unlock_progress_dialog_message;
+                    updateSim();
+                } else {
+                    mState = ENTER_PIN; // try again?
+                    msg = com.android.systemui.R.string.kg_invalid_confirm_pin_hint;
+                }
+            }
+            mView.resetPasswordText(true /* animate */, true /* announce */);
+            if (msg != 0) {
+                mMessageAreaController.setMessage(msg);
+            }
+        }
+
+
+        void reset() {
+            mPinText = "";
+            mPukText = "";
+            mState = ENTER_PUK;
+            handleSubInfoChangeIfNeeded();
+            if (mShowDefaultMessage) {
+                showDefaultMessage();
+            }
+            boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId);
+
+            KeyguardEsimArea esimButton = mView.findViewById(R.id.keyguard_esim_area);
+            esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
+            mPasswordEntry.requestFocus();
+        }
+    }
+
+    private void showDefaultMessage() {
+        if (mRemainingAttempts >= 0) {
+            mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage(
+                    mRemainingAttempts, true,
+                    KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)));
+            return;
+        }
+
+        boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId);
+        int count = 1;
+        if (mTelephonyManager != null) {
+            count = mTelephonyManager.getActiveModemCount();
+        }
+        Resources rez = mView.getResources();
+        String msg;
+        TypedArray array = mView.getContext().obtainStyledAttributes(
+                new int[] { R.attr.wallpaperTextColor });
+        int color = array.getColor(0, Color.WHITE);
+        array.recycle();
+        if (count < 2) {
+            msg = rez.getString(R.string.kg_puk_enter_puk_hint);
+        } else {
+            SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
+                    .getSubscriptionInfoForSubId(mSubId);
+            CharSequence displayName = info != null ? info.getDisplayName() : "";
+            msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
+            if (info != null) {
+                color = info.getIconTint();
+            }
+        }
+        if (isEsimLocked) {
+            msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg);
+        }
+        mMessageAreaController.setMessage(msg);
+        mSimImageView.setImageTintList(ColorStateList.valueOf(color));
+
+        // Sending empty PUK here to query the number of remaining PIN attempts
+        new CheckSimPuk("", "", mSubId) {
+            void onSimLockChangedResponse(final PinResult result) {
+                if (result == null) Log.e(TAG, "onSimCheckResponse, pin result is NULL");
+                else {
+                    Log.d(TAG, "onSimCheckResponse " + " empty One result "
+                            + result.toString());
+                    if (result.getAttemptsRemaining() >= 0) {
+                        mRemainingAttempts = result.getAttemptsRemaining();
+                        mMessageAreaController.setMessage(
+                                mView.getPukPasswordErrorMessage(
+                                        result.getAttemptsRemaining(), true,
+                                        KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)));
+                    }
+                }
+            }
+        }.start();
+    }
+
+    private boolean checkPuk() {
+        // make sure the puk is at least 8 digits long.
+        if (mPasswordEntry.getText().length() == 8) {
+            mPukText = mPasswordEntry.getText();
+            return true;
+        }
+        return false;
+    }
+
+    private boolean checkPin() {
+        // make sure the PIN is between 4 and 8 digits
+        int length = mPasswordEntry.getText().length();
+        if (length >= 4 && length <= 8) {
+            mPinText = mPasswordEntry.getText();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean confirmPin() {
+        return mPinText.equals(mPasswordEntry.getText());
+    }
+
+
+
+
+    private void updateSim() {
+        getSimUnlockProgressDialog().show();
+
+        if (mCheckSimPukThread == null) {
+            mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) {
+                @Override
+                void onSimLockChangedResponse(final PinResult result) {
+                    mView.post(() -> {
+                        if (mSimUnlockProgressDialog != null) {
+                            mSimUnlockProgressDialog.hide();
+                        }
+                        mView.resetPasswordText(true /* animate */,
+                                /* announce */
+                                result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS);
+                        if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
+                            mKeyguardUpdateMonitor.reportSimUnlocked(mSubId);
+                            mRemainingAttempts = -1;
+                            mShowDefaultMessage = true;
+
+                            getKeyguardSecurityCallback().dismiss(
+                                    true, KeyguardUpdateMonitor.getCurrentUser());
+                        } else {
+                            mShowDefaultMessage = false;
+                            if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
+                                // show message
+                                mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage(
+                                        result.getAttemptsRemaining(), false,
+                                        KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)));
+                                if (result.getAttemptsRemaining() <= 2) {
+                                    // this is getting critical - show dialog
+                                    getPukRemainingAttemptsDialog(
+                                            result.getAttemptsRemaining()).show();
+                                } else {
+                                    // show message
+                                    mMessageAreaController.setMessage(
+                                            mView.getPukPasswordErrorMessage(
+                                                    result.getAttemptsRemaining(), false,
+                                                    KeyguardEsimArea.isEsimLocked(
+                                                            mView.getContext(), mSubId)));
+                                }
+                            } else {
+                                mMessageAreaController.setMessage(mView.getResources().getString(
+                                        R.string.kg_password_puk_failed));
+                            }
+                            if (DEBUG) {
+                                Log.d(TAG, "verifyPasswordAndUnlock "
+                                        + " UpdateSim.onSimCheckResponse: "
+                                        + " attemptsRemaining=" + result.getAttemptsRemaining());
+                            }
+                        }
+                        mStateMachine.reset();
+                        mCheckSimPukThread = null;
+                    });
+                }
+            };
+            mCheckSimPukThread.start();
+        }
+    }
+
+    @Override
+    protected boolean shouldLockout(long deadline) {
+        // SIM PUK doesn't have a timed lockout
+        return false;
+    }
+
+    private Dialog getSimUnlockProgressDialog() {
+        if (mSimUnlockProgressDialog == null) {
+            mSimUnlockProgressDialog = new ProgressDialog(mView.getContext());
+            mSimUnlockProgressDialog.setMessage(
+                    mView.getResources().getString(R.string.kg_sim_unlock_progress_dialog_message));
+            mSimUnlockProgressDialog.setIndeterminate(true);
+            mSimUnlockProgressDialog.setCancelable(false);
+            if (!(mView.getContext() instanceof Activity)) {
+                mSimUnlockProgressDialog.getWindow().setType(
+                        WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+            }
+        }
+        return mSimUnlockProgressDialog;
+    }
+
+    private void handleSubInfoChangeIfNeeded() {
+        int subId = mKeyguardUpdateMonitor.getNextSubIdForState(
+                TelephonyManager.SIM_STATE_PUK_REQUIRED);
+        if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
+            mSubId = subId;
+            mShowDefaultMessage = true;
+            mRemainingAttempts = -1;
+        }
+    }
+
+
+    private Dialog getPukRemainingAttemptsDialog(int remaining) {
+        String msg = mView.getPukPasswordErrorMessage(remaining, false,
+                KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId));
+        if (mRemainingAttemptsDialog == null) {
+            AlertDialog.Builder builder = new AlertDialog.Builder(mView.getContext());
+            builder.setMessage(msg);
+            builder.setCancelable(false);
+            builder.setNeutralButton(R.string.ok, null);
+            mRemainingAttemptsDialog = builder.create();
+            mRemainingAttemptsDialog.getWindow().setType(
+                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+        } else {
+            mRemainingAttemptsDialog.setMessage(msg);
+        }
+        return mRemainingAttemptsDialog;
+    }
+
+    @Override
+    public void onPause() {
+        // dismiss the dialog.
+        if (mSimUnlockProgressDialog != null) {
+            mSimUnlockProgressDialog.dismiss();
+            mSimUnlockProgressDialog = null;
+        }
+    }
+
+    /**
+     * Since the IPC can block, we want to run the request in a separate thread
+     * with a callback.
+     */
+    private abstract class CheckSimPuk extends Thread {
+
+        private final String mPin, mPuk;
+        private final int mSubId;
+
+        protected CheckSimPuk(String puk, String pin, int subId) {
+            mPuk = puk;
+            mPin = pin;
+            mSubId = subId;
+        }
+
+        abstract void onSimLockChangedResponse(@NonNull PinResult result);
+
+        @Override
+        public void run() {
+            if (DEBUG) Log.v(TAG, "call supplyPukReportResult()");
+            TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
+            final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin);
+            if (result == null) {
+                Log.e(TAG, "Error result for supplyPukReportResult.");
+                mView.post(() -> onSimLockChangedResponse(PinResult.getDefaultFailedResult()));
+            } else {
+                if (DEBUG) {
+                    Log.v(TAG, "supplyPukReportResult returned: " + result.toString());
+                }
+                mView.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        onSimLockChangedResponse(result);
+                    }
+                });
+            }
+        }
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java
index e59602b..425e50e 100644
--- a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java
+++ b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java
@@ -16,11 +16,12 @@
 
 package com.android.keyguard;
 
-import android.content.Context;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
 
+import javax.inject.Inject;
+
 /**
  * Hover listener that implements lift-to-activate interaction for
  * accessibility. May be added to multiple views.
@@ -31,9 +32,9 @@
 
     private boolean mCachedClickableState;
 
-    public LiftToActivateListener(Context context) {
-        mAccessibilityManager = (AccessibilityManager) context.getSystemService(
-                Context.ACCESSIBILITY_SERVICE);
+    @Inject
+    LiftToActivateListener(AccessibilityManager accessibilityManager) {
+        mAccessibilityManager = accessibilityManager;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index b0457fc..2205fdd 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -26,6 +26,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.TextView;
 
 import com.android.internal.widget.LockPatternUtils;
@@ -90,7 +91,8 @@
         }
 
         setOnClickListener(mListener);
-        setOnHoverListener(new LiftToActivateListener(context));
+        setOnHoverListener(new LiftToActivateListener(
+                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE)));
 
         mLockPatternUtils = new LockPatternUtils(context);
         mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
index b6010c8..8811088 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
@@ -22,6 +22,7 @@
 import com.android.keyguard.KeyguardHostView;
 import com.android.keyguard.KeyguardMessageArea;
 import com.android.keyguard.KeyguardSecurityContainer;
+import com.android.keyguard.KeyguardSecurityViewFlipper;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 
@@ -58,7 +59,15 @@
     /** */
     @Provides
     @KeyguardBouncerScope
-    static KeyguardSecurityContainer preovidesKeyguardSecurityContainer(KeyguardHostView hostView) {
+    static KeyguardSecurityContainer providesKeyguardSecurityContainer(KeyguardHostView hostView) {
         return hostView.findViewById(R.id.keyguard_security_container);
     }
+
+    /** */
+    @Provides
+    @KeyguardBouncerScope
+    static KeyguardSecurityViewFlipper providesKeyguardSecurityViewFlipper(
+            KeyguardSecurityContainer containerView) {
+        return containerView.findViewById(R.id.view_flipper);
+    }
 }
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/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index c90e6b1..e303754 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -289,6 +289,7 @@
 
     /** */
     @Provides
+    @SysUISingleton
     public LockPatternUtils provideLockPatternUtils(Context context) {
         return new LockPatternUtils(context);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index b35579d..79925ba 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -62,6 +62,7 @@
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
+import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.statusbar.IStatusBarService;
@@ -183,6 +184,12 @@
 
     @Provides
     @Singleton
+    static InputMethodManager provideInputMethodManager(Context context) {
+        return context.getSystemService(InputMethodManager.class);
+    }
+
+    @Provides
+    @Singleton
     static IPackageManager provideIPackageManager() {
         return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
     }
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/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 22c735d..04f379e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -177,6 +177,16 @@
     }
 
     @Override
+    public void endFakeDrag() {
+        try {
+            super.endFakeDrag();
+        } catch (NullPointerException e) {
+            // Not sure what's going on. Let's log it
+            Log.e(TAG, "endFakeDrag called without velocityTracker", e);
+        }
+    }
+
+    @Override
     public void computeScroll() {
         if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
             if (!isFakeDragging()) {
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/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index eb8f065..a6cd350 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -23,7 +23,6 @@
 import android.view.LayoutInflater;
 import android.view.View;
 
-import com.android.keyguard.KeyguardMessageArea;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.QSFooterImpl;
 import com.android.systemui.qs.QSPanel;
@@ -108,11 +107,6 @@
         NotificationStackScrollLayout createNotificationStackScrollLayout();
 
         /**
-         * Creates the KeyguardMessageArea.
-         */
-        KeyguardMessageArea createKeyguardMessageArea();
-
-        /**
          * Creates the QSPanel.
          */
         QSPanel createQSPanel();
diff --git a/packages/SystemUI/src/com/android/systemui/util/ViewController.java b/packages/SystemUI/src/com/android/systemui/util/ViewController.java
index 64f8dbb..c7aa780fc 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/ViewController.java
@@ -23,7 +23,20 @@
  * Utility class that handles view lifecycle events for View Controllers.
  *
  * Implementations should handle setup and teardown related activities inside of
- * {@link #onViewAttached()} and {@link  #onViewDetached()}.
+ * {@link #onViewAttached()} and {@link  #onViewDetached()}. Be sure to call {@link #init()} on
+ * any child controllers that this uses. This can be done in {@link init()} if the controllers
+ * are injected, or right after creation time of the child controller.
+ *
+ * Tip: View "attachment" happens top down - parents are notified that they are attached before
+ * any children. That means that if you call a method on a child controller in
+ * {@link #onViewAttached()}, the child controller may not have had its onViewAttach method
+ * called, so it may not be fully set up.
+ *
+ * As such, make sure that methods on your controller are safe to call _before_ its {@link #init()}
+ * and {@link #onViewAttached()} methods are called. Specifically, if your controller must call
+ * {@link View#findViewById(int)} on its root view to setup member variables, do so in its
+ * constructor. Save {@link #onViewAttached()} for things that can happen post-construction - adding
+ * listeners, dynamically changing content, or other runtime decisions.
  *
  * @param <T> View class that this ViewController is for.
  */
@@ -54,10 +67,12 @@
         }
         mInited = true;
 
-        if (mView.isAttachedToWindow()) {
-            mOnAttachStateListener.onViewAttachedToWindow(mView);
+        if (mView != null) {
+            if (mView.isAttachedToWindow()) {
+                mOnAttachStateListener.onViewAttachedToWindow(mView);
+            }
+            mView.addOnAttachStateChangeListener(mOnAttachStateListener);
         }
-        mView.addOnAttachStateChangeListener(mOnAttachStateListener);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
index d6595b2..a50de45 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
@@ -26,6 +26,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.splitscreen.SplitScreen;
@@ -52,8 +53,9 @@
     static SplitScreen provideSplitScreen(Context context,
             DisplayController displayController, SystemWindows systemWindows,
             DisplayImeController displayImeController, @Main Handler handler,
-            TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer) {
+            TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer,
+            SyncTransactionQueue syncQueue) {
         return new SplitScreenController(context, displayController, systemWindows,
-                displayImeController, handler, transactionPool, shellTaskOrganizer);
+                displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index ae96829..dfb30b4 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;
@@ -38,6 +39,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.onehanded.OneHanded;
@@ -122,8 +124,15 @@
 
     @SysUISingleton
     @Provides
-    static ShellTaskOrganizer provideShellTaskOrganizer(TransactionPool transactionPool) {
-        ShellTaskOrganizer organizer = new ShellTaskOrganizer(transactionPool);
+    static SyncTransactionQueue provideSyncTransactionQueue(@Main Handler handler,
+            TransactionPool pool) {
+        return new SyncTransactionQueue(pool, handler);
+    }
+
+    @SysUISingleton
+    @Provides
+    static ShellTaskOrganizer provideShellTaskOrganizer(SyncTransactionQueue syncQueue) {
+        ShellTaskOrganizer organizer = new ShellTaskOrganizer(syncQueue);
         organizer.registerOrganizer();
         return organizer;
     }
@@ -148,5 +157,8 @@
     abstract SplitScreen optionalSplitScreen();
 
     @BindsOptionalOf
+    abstract Bubbles optionalBubbles();
+
+    @BindsOptionalOf
     abstract OneHanded optionalOneHanded();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 6ed836c..3142c1e 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -36,6 +36,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.onehanded.OneHanded;
@@ -84,9 +85,10 @@
     static SplitScreen provideSplitScreen(Context context,
             DisplayController displayController, SystemWindows systemWindows,
             DisplayImeController displayImeController, @Main Handler handler,
-            TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer) {
+            TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer,
+            SyncTransactionQueue syncQueue) {
         return new SplitScreenController(context, displayController, systemWindows,
-                displayImeController, handler, transactionPool, shellTaskOrganizer);
+                displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue);
     }
 
     @SysUISingleton
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index 9be2d12..dffad6c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -41,8 +41,6 @@
 import android.testing.ViewUtils;
 import android.view.SurfaceControlViewHost;
 import android.view.SurfaceView;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
 
 import androidx.test.filters.SmallTest;
 
@@ -67,7 +65,7 @@
     private ComponentName mComponentName;
     private Intent mServiceIntent;
     private TestableLooper mTestableLooper;
-    private ViewGroup mParent;
+    private KeyguardSecurityContainer mKeyguardSecurityContainer;
 
     @Mock
     private Handler mHandler;
@@ -84,8 +82,8 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mParent = spy(new FrameLayout(mContext));
-        ViewUtils.attachView(mParent);
+        mKeyguardSecurityContainer = spy(new KeyguardSecurityContainer(mContext));
+        ViewUtils.attachView(mKeyguardSecurityContainer);
 
         mTestableLooper = TestableLooper.get(this);
         mComponentName = new ComponentName(mContext, "FakeKeyguardClient.class");
@@ -96,13 +94,14 @@
         when(mKeyguardClient.queryLocalInterface(anyString())).thenReturn(mKeyguardClient);
         when(mKeyguardClient.asBinder()).thenReturn(mKeyguardClient);
 
-        mTestController = new AdminSecondaryLockScreenController(
-                mContext, mParent, mUpdateMonitor, mKeyguardCallback, mHandler);
+        mTestController = new AdminSecondaryLockScreenController.Factory(
+                mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler)
+                .create(mKeyguardCallback);
     }
 
     @After
     public void tearDown() {
-        ViewUtils.detachView(mParent);
+        ViewUtils.detachView(mKeyguardSecurityContainer);
     }
 
     @Test
@@ -146,7 +145,7 @@
         SurfaceView v = verifySurfaceReady();
 
         mTestController.hide();
-        verify(mParent).removeView(v);
+        verify(mKeyguardSecurityContainer).removeView(v);
         assertThat(mContext.isBound(mComponentName)).isFalse();
     }
 
@@ -154,7 +153,7 @@
     public void testHide_notShown() throws Exception {
         mTestController.hide();
         // Nothing should happen if trying to hide when the view isn't attached yet.
-        verify(mParent, never()).removeView(any(SurfaceView.class));
+        verify(mKeyguardSecurityContainer, never()).removeView(any(SurfaceView.class));
     }
 
     @Test
@@ -182,7 +181,7 @@
     private SurfaceView verifySurfaceReady() throws Exception {
         mTestableLooper.processAllMessages();
         ArgumentCaptor<SurfaceView> captor = ArgumentCaptor.forClass(SurfaceView.class);
-        verify(mParent).addView(captor.capture());
+        verify(mKeyguardSecurityContainer).addView(captor.capture());
 
         mTestableLooper.processAllMessages();
         verify(mKeyguardClient).onCreateKeyguardSurface(any(), any(IKeyguardCallback.class));
@@ -190,7 +189,7 @@
     }
 
     private void verifyViewDismissed(SurfaceView v) throws Exception {
-        verify(mParent).removeView(v);
+        verify(mKeyguardSecurityContainer).removeView(v);
         verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID, true);
         assertThat(mContext.isBound(mComponentName)).isFalse();
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
new file mode 100644
index 0000000..c2ade81
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.keyguard;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.KeyEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
+
+    @Mock
+    private KeyguardAbsKeyInputView mAbsKeyInputView;
+    @Mock
+    private PasswordTextView mPasswordEntry;
+    @Mock
+    private KeyguardMessageArea mKeyguardMessageArea;
+    @Mock
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock
+    private SecurityMode mSecurityMode;
+    @Mock
+    private LockPatternUtils mLockPatternUtils;
+    @Mock
+    private KeyguardSecurityCallback mKeyguardSecurityCallback;
+    @Mock
+    private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory;
+    @Mock
+    private KeyguardMessageAreaController mKeyguardMessageAreaController;
+    @Mock
+    private LatencyTracker mLatencyTracker;
+
+    private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class)))
+                .thenReturn(mKeyguardMessageAreaController);
+        when(mAbsKeyInputView.getPasswordTextViewId()).thenReturn(1);
+        when(mAbsKeyInputView.findViewById(1)).thenReturn(mPasswordEntry);
+        when(mAbsKeyInputView.isAttachedToWindow()).thenReturn(true);
+        when(mAbsKeyInputView.findViewById(R.id.keyguard_message_area))
+                .thenReturn(mKeyguardMessageArea);
+        mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
+                mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
+                mKeyguardMessageAreaControllerFactory, mLatencyTracker) {
+            @Override
+            void resetState() {
+            }
+
+            @Override
+            public void onResume(int reason) {
+                super.onResume(reason);
+            }
+        };
+        mKeyguardAbsKeyInputViewController.init();
+        reset(mKeyguardMessageAreaController);  // Clear out implicit call to init.
+    }
+
+    @Test
+    public void onKeyDown_clearsSecurityMessage() {
+        ArgumentCaptor<KeyDownListener> onKeyDownListenerArgumentCaptor =
+                ArgumentCaptor.forClass(KeyDownListener.class);
+        verify(mAbsKeyInputView).setKeyDownListener(onKeyDownListenerArgumentCaptor.capture());
+        onKeyDownListenerArgumentCaptor.getValue().onKeyDown(
+                KeyEvent.KEYCODE_0, mock(KeyEvent.class));
+        verify(mKeyguardSecurityCallback).userActivity();
+        verify(mKeyguardMessageAreaController).setMessage(eq(""));
+    }
+
+    @Test
+    public void onKeyDown_noSecurityMessageInteraction() {
+        ArgumentCaptor<KeyDownListener> onKeyDownListenerArgumentCaptor =
+                ArgumentCaptor.forClass(KeyDownListener.class);
+        verify(mAbsKeyInputView).setKeyDownListener(onKeyDownListenerArgumentCaptor.capture());
+        onKeyDownListenerArgumentCaptor.getValue().onKeyDown(
+                KeyEvent.KEYCODE_UNKNOWN, mock(KeyEvent.class));
+        verifyZeroInteractions(mKeyguardSecurityCallback);
+        verifyZeroInteractions(mKeyguardMessageAreaController);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 5999e2c..e793079 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -41,11 +41,9 @@
 import android.widget.TextClock;
 
 import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.util.InjectionInflationController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -78,12 +76,7 @@
         when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area))
                 .thenReturn(mMockKeyguardSliceView);
 
-        InjectionInflationController inflationController = new InjectionInflationController(
-                SystemUIFactory.getInstance()
-                        .getSysUIComponent()
-                        .createViewInstanceCreatorFactory());
-        LayoutInflater layoutInflater = inflationController
-                .injectable(LayoutInflater.from(getContext()));
+        LayoutInflater layoutInflater = LayoutInflater.from(getContext());
         layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
 
             @Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java
index 54e879e..64632af 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java
@@ -16,8 +16,10 @@
 
 package com.android.keyguard;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.media.AudioManager;
 import android.telephony.TelephonyManager;
@@ -47,13 +49,15 @@
     @Mock
     private KeyguardHostView mKeyguardHostView;
     @Mock
-    private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
-    @Mock
     private AudioManager mAudioManager;
     @Mock
     private TelephonyManager mTelephonyManager;
     @Mock
     private ViewMediatorCallback mViewMediatorCallback;
+    @Mock
+    KeyguardSecurityContainerController.Factory mKeyguardSecurityContainerControllerFactory;
+    @Mock
+    private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
 
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -62,9 +66,12 @@
 
     @Before
     public void setup() {
+        when(mKeyguardSecurityContainerControllerFactory.create(any(
+                KeyguardSecurityContainer.SecurityCallback.class)))
+                .thenReturn(mKeyguardSecurityContainerController);
         mKeyguardHostViewController = new KeyguardHostViewController(
-                mKeyguardHostView, mKeyguardUpdateMonitor, mKeyguardSecurityContainerController,
-                mAudioManager, mTelephonyManager, mViewMediatorCallback);
+                mKeyguardHostView, mKeyguardUpdateMonitor, mAudioManager, mTelephonyManager,
+                mViewMediatorCallback, mKeyguardSecurityContainerControllerFactory);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
new file mode 100644
index 0000000..a7197cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -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.
+ */
+
+package com.android.keyguard;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class KeyguardMessageAreaControllerTest extends SysuiTestCase {
+    @Mock
+    private ConfigurationController mConfigurationController;
+    @Mock
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock
+    private KeyguardMessageArea mKeyguardMessageArea;
+
+    private KeyguardMessageAreaController mMessageAreaController;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mMessageAreaController = new KeyguardMessageAreaController.Factory(
+                mKeyguardUpdateMonitor, mConfigurationController).create(mKeyguardMessageArea);
+    }
+
+    @Test
+    public void onAttachedToWindow_registersConfigurationCallback() {
+        ArgumentCaptor<ConfigurationListener> configurationListenerArgumentCaptor =
+                ArgumentCaptor.forClass(ConfigurationListener.class);
+
+        mMessageAreaController.onViewAttached();
+        verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
+
+        mMessageAreaController.onViewDetached();
+        verify(mConfigurationController).removeCallback(
+                eq(configurationListenerArgumentCaptor.getValue()));
+    }
+
+    @Test
+    public void onAttachedToWindow_registersKeyguardUpdateMontiorCallback() {
+        ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor =
+                ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+
+        mMessageAreaController.onViewAttached();
+        verify(mKeyguardUpdateMonitor).registerCallback(
+                keyguardUpdateMonitorCallbackArgumentCaptor.capture());
+
+        mMessageAreaController.onViewDetached();
+        verify(mKeyguardUpdateMonitor).removeCallback(
+                eq(keyguardUpdateMonitorCallbackArgumentCaptor.getValue()));
+    }
+
+    @Test
+    public void testClearsTextField() {
+        mMessageAreaController.setMessage("");
+        verify(mKeyguardMessageArea).setMessage("");
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java
index fc7b9a4..31fb25a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,65 +11,60 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.keyguard;
 
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 public class KeyguardMessageAreaTest extends SysuiTestCase {
-    @Mock
-    private ConfigurationController mConfigurationController;
-    @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private KeyguardMessageArea mMessageArea;
+    private KeyguardMessageArea mKeyguardMessageArea;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mMessageArea = new KeyguardMessageArea(mContext, null, mKeyguardUpdateMonitor,
-                mConfigurationController);
-        waitForIdleSync();
+        mKeyguardMessageArea = new KeyguardMessageArea(mContext, null);
+        mKeyguardMessageArea.setBouncerVisible(true);
     }
 
     @Test
-    public void onAttachedToWindow_registersConfigurationCallback() {
-        mMessageArea.onAttachedToWindow();
-        verify(mConfigurationController).addCallback(eq(mMessageArea));
-
-        mMessageArea.onDetachedFromWindow();
-        verify(mConfigurationController).removeCallback(eq(mMessageArea));
+    public void testShowsTextField() {
+        mKeyguardMessageArea.setVisibility(View.INVISIBLE);
+        mKeyguardMessageArea.setMessage("oobleck");
+        assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck");
     }
 
     @Test
-    public void clearFollowedByMessage_keepsMessage() {
-        mMessageArea.setMessage("");
-        mMessageArea.setMessage("test");
-
-        CharSequence[] messageText = new CharSequence[1];
-        messageText[0] = mMessageArea.getText();
-
-        assertEquals("test", messageText[0]);
+    public void testHiddenWhenBouncerHidden() {
+        mKeyguardMessageArea.setBouncerVisible(false);
+        mKeyguardMessageArea.setVisibility(View.INVISIBLE);
+        mKeyguardMessageArea.setMessage("oobleck");
+        assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE);
+        assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck");
     }
 
+    @Test
+    public void testClearsTextField() {
+        mKeyguardMessageArea.setVisibility(View.VISIBLE);
+        mKeyguardMessageArea.setMessage("");
+        assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE);
+        assertThat(mKeyguardMessageArea.getText()).isEqualTo("");
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
new file mode 100644
index 0000000..c69ec1a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.keyguard
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternView
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class KeyguardPatternViewControllerTest : SysuiTestCase() {
+    @Mock
+    private lateinit var mKeyguardPatternView: KeyguardPatternView
+    @Mock
+    private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock
+    private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
+    @Mock
+    private lateinit var mLockPatternUtils: LockPatternUtils
+    @Mock
+    private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
+    @Mock
+    private lateinit var mLatencyTracker: LatencyTracker
+    @Mock
+    private lateinit
+    var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
+    @Mock
+    private lateinit var mKeyguardMessageArea: KeyguardMessageArea
+    @Mock
+    private lateinit var mKeyguardMessageAreaController: KeyguardMessageAreaController
+    @Mock
+    private lateinit var mLockPatternView: LockPatternView
+
+    private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
+        `when`(mKeyguardPatternView.findViewById<KeyguardMessageArea>(R.id.keyguard_message_area))
+                .thenReturn(mKeyguardMessageArea)
+        `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
+                .thenReturn(mLockPatternView)
+        `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
+                .thenReturn(mKeyguardMessageAreaController)
+        mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView,
+        mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
+                mLatencyTracker, mKeyguardMessageAreaControllerFactory)
+    }
+
+    @Test
+    fun onPause_clearsTextField() {
+        mKeyguardPatternViewController.init()
+        mKeyguardPatternViewController.onPause()
+        verify(mKeyguardMessageAreaController).setMessage("")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt
deleted file mode 100644
index b4363cf..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.keyguard
-
-import androidx.test.filters.SmallTest
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.LayoutInflater
-
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.google.common.truth.Truth.assertThat
-
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class KeyguardPatternViewTest : SysuiTestCase() {
-
-    private lateinit var mKeyguardPatternView: KeyguardPatternView
-    private lateinit var mSecurityMessage: KeyguardMessageArea
-
-    @Before
-    fun setup() {
-        val inflater = LayoutInflater.from(context)
-        mDependency.injectMockDependency(KeyguardUpdateMonitor::class.java)
-        mKeyguardPatternView = inflater.inflate(R.layout.keyguard_pattern_view, null)
-                as KeyguardPatternView
-        mSecurityMessage = KeyguardMessageArea(mContext, null,
-                mock(KeyguardUpdateMonitor::class.java), mock(ConfigurationController::class.java))
-        mKeyguardPatternView.mSecurityMessageDisplay = mSecurityMessage
-    }
-
-    @Test
-    fun onPause_clearsTextField() {
-        mSecurityMessage.setMessage("an old message")
-        mKeyguardPatternView.onPause()
-        assertThat(mSecurityMessage.text).isEqualTo("")
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
new file mode 100644
index 0000000..4944284
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.keyguard;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
+
+    @Mock
+    private KeyguardPinBasedInputView mPinBasedInputView;
+    @Mock
+    private PasswordTextView mPasswordEntry;
+    @Mock
+    private KeyguardMessageArea mKeyguardMessageArea;
+    @Mock
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock
+    private SecurityMode mSecurityMode;
+    @Mock
+    private LockPatternUtils mLockPatternUtils;
+    @Mock
+    private KeyguardSecurityCallback mKeyguardSecurityCallback;
+    @Mock
+    private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory;
+    @Mock
+    private KeyguardMessageAreaController mKeyguardMessageAreaController;
+    @Mock
+    private LatencyTracker mLatencyTracker;
+    @Mock
+    private LiftToActivateListener mLiftToactivateListener;
+    @Mock
+    private View mDeleteButton;
+    @Mock
+    private View mOkButton;
+
+    private KeyguardPinBasedInputViewController mKeyguardPinViewController;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class)))
+                .thenReturn(mKeyguardMessageAreaController);
+        when(mPinBasedInputView.getPasswordTextViewId()).thenReturn(1);
+        when(mPinBasedInputView.findViewById(1)).thenReturn(mPasswordEntry);
+        when(mPinBasedInputView.isAttachedToWindow()).thenReturn(true);
+        when(mPinBasedInputView.findViewById(R.id.keyguard_message_area))
+                .thenReturn(mKeyguardMessageArea);
+        when(mPinBasedInputView.findViewById(R.id.delete_button))
+                .thenReturn(mDeleteButton);
+        when(mPinBasedInputView.findViewById(R.id.key_enter))
+                .thenReturn(mOkButton);
+        mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
+                mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
+                mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener) {
+            @Override
+            public void onResume(int reason) {
+                super.onResume(reason);
+            }
+        };
+        mKeyguardPinViewController.init();
+    }
+
+    @Test
+    public void onResume_requestsFocus() {
+        mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON);
+        verify(mPasswordEntry).requestFocus();
+    }
+}
+
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java
deleted file mode 100644
index 6666a92..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.keyguard;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public class KeyguardPinBasedInputViewTest extends SysuiTestCase {
-
-    @Mock
-    private PasswordTextView mPasswordEntry;
-    @Mock
-    private SecurityMessageDisplay mSecurityMessageDisplay;
-    @InjectMocks
-    private KeyguardPinBasedInputView mKeyguardPinView;
-
-    @Before
-    public void setup() {
-        LayoutInflater inflater = LayoutInflater.from(getContext());
-        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
-        mKeyguardPinView =
-                (KeyguardPinBasedInputView) inflater.inflate(R.layout.keyguard_pin_view, null);
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void onResume_requestsFocus() {
-        mKeyguardPinView.onResume(KeyguardSecurityView.SCREEN_ON);
-        verify(mPasswordEntry).requestFocus();
-    }
-
-    @Test
-    public void onKeyDown_clearsSecurityMessage() {
-        mKeyguardPinView.onKeyDown(KeyEvent.KEYCODE_0, mock(KeyEvent.class));
-        verify(mSecurityMessageDisplay).setMessage(eq(""));
-    }
-
-    @Test
-    public void onKeyDown_noSecurityMessageInteraction() {
-        mKeyguardPinView.onKeyDown(KeyEvent.KEYCODE_UNKNOWN, mock(KeyEvent.class));
-        verifyZeroInteractions(mSecurityMessageDisplay);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
index 559284a..ae159c7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
@@ -31,9 +31,7 @@
 import com.android.keyguard.KeyguardDisplayManager.KeyguardPresentation;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.InjectionInflationController;
 
 import org.junit.After;
 import org.junit.Before;
@@ -65,7 +63,6 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         when(mMockKeyguardClockSwitch.getContext()).thenReturn(mContext);
         when(mMockKeyguardSliceView.getContext()).thenReturn(mContext);
         when(mMockKeyguardStatusView.getContext()).thenReturn(mContext);
@@ -77,11 +74,7 @@
 
         allowTestableLooperAsMainThread();
 
-        InjectionInflationController inflationController = new InjectionInflationController(
-                SystemUIFactory.getInstance()
-                        .getSysUIComponent()
-                        .createViewInstanceCreatorFactory());
-        mLayoutInflater = inflationController.injectable(LayoutInflater.from(mContext));
+        mLayoutInflater = LayoutInflater.from(mContext);
         mLayoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
 
             @Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
new file mode 100644
index 0000000..eef38d3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.keyguard;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.WindowInsetsController;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper()
+public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
+
+    @Rule
+    public MockitoRule mRule = MockitoJUnit.rule();
+
+    @Mock
+    private KeyguardSecurityContainer mView;
+    @Mock
+    private AdminSecondaryLockScreenController.Factory mAdminSecondaryLockScreenControllerFactory;
+    @Mock
+    private AdminSecondaryLockScreenController mAdminSecondaryLockScreenController;
+    @Mock
+    private LockPatternUtils mLockPatternUtils;
+    @Mock
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock
+    private KeyguardSecurityModel mKeyguardSecurityModel;
+    @Mock
+    private MetricsLogger mMetricsLogger;
+    @Mock
+    private UiEventLogger mUiEventLogger;
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
+    private KeyguardInputViewController mInputViewController;
+    @Mock
+    private KeyguardSecurityContainer.SecurityCallback mSecurityCallback;
+    @Mock
+    private WindowInsetsController mWindowInsetsController;
+    @Mock
+    private KeyguardSecurityViewFlipper mSecurityViewFlipper;
+    @Mock
+    private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
+
+    private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
+
+    @Before
+    public void setup() {
+        when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
+                .thenReturn(mAdminSecondaryLockScreenController);
+        when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+
+        mKeyguardSecurityContainerController = new KeyguardSecurityContainerController.Factory(
+                mView,  mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
+                mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
+                mKeyguardStateController, mKeyguardSecurityViewFlipperController)
+                .create(mSecurityCallback);
+    }
+
+    @Test
+    public void showSecurityScreen_canInflateAllModes() {
+        SecurityMode[] modes = SecurityMode.values();
+        for (SecurityMode mode : modes) {
+            when(mInputViewController.getSecurityMode()).thenReturn(mode);
+            mKeyguardSecurityContainerController.showSecurityScreen(mode);
+            if (mode == SecurityMode.Invalid) {
+                verify(mKeyguardSecurityViewFlipperController, never()).getSecurityView(
+                        any(SecurityMode.class), any(KeyguardSecurityCallback.class));
+            } else {
+                verify(mKeyguardSecurityViewFlipperController).getSecurityView(
+                        eq(mode), any(KeyguardSecurityCallback.class));
+            }
+        }
+    }
+
+    @Test
+    public void startDisappearAnimation_animatesKeyboard() {
+        when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+                SecurityMode.Password);
+        when(mInputViewController.getSecurityMode()).thenReturn(
+                SecurityMode.Password);
+        when(mKeyguardSecurityViewFlipperController.getSecurityView(
+                eq(SecurityMode.Password), any(KeyguardSecurityCallback.class)))
+                .thenReturn(mInputViewController);
+        mKeyguardSecurityContainerController.showPrimarySecurityScreen(false /* turningOff */);
+
+        mKeyguardSecurityContainerController.startDisappearAnimation(null);
+        verify(mInputViewController).startDisappearAnimation(eq(null));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index a867825..854be1f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -19,23 +19,19 @@
 import static android.view.WindowInsets.Type.ime;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.LayoutInflater;
 import android.view.WindowInsetsController;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.R;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -50,68 +46,26 @@
 @TestableLooper.RunWithLooper()
 public class KeyguardSecurityContainerTest extends SysuiTestCase {
 
-    @Mock
-    private KeyguardSecurityModel mKeyguardSecurityModel;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-    @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
-    private KeyguardSecurityContainer.SecurityCallback mSecurityCallback;
-    @Mock
-    private KeyguardSecurityView mSecurityView;
+    @Rule
+    public MockitoRule mRule = MockitoJUnit.rule();
+
     @Mock
     private WindowInsetsController mWindowInsetsController;
     @Mock
     private KeyguardSecurityViewFlipper mSecurityViewFlipper;
-    @Rule
-    public MockitoRule mRule = MockitoJUnit.rule();
+
     private KeyguardSecurityContainer mKeyguardSecurityContainer;
 
     @Before
     public void setup() {
-        mDependency.injectTestDependency(KeyguardStateController.class, mKeyguardStateController);
-        mDependency.injectTestDependency(KeyguardSecurityModel.class, mKeyguardSecurityModel);
-        mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
-        mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext()) {
-            @Override
-            protected KeyguardSecurityView getSecurityView(
-                    KeyguardSecurityModel.SecurityMode securityMode) {
-                return mSecurityView;
-            }
-        };
-        mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper;
         when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
-        mKeyguardSecurityContainer.setSecurityCallback(mSecurityCallback);
-    }
-
-    @Test
-    public void showSecurityScreen_canInflateAllModes() {
-        Context context = getContext();
-
-        for (int theme : new int[] {R.style.Theme_SystemUI, R.style.Theme_SystemUI_Light}) {
-            context.setTheme(theme);
-            final LayoutInflater inflater = LayoutInflater.from(context);
-            KeyguardSecurityModel.SecurityMode[] modes =
-                    KeyguardSecurityModel.SecurityMode.values();
-            for (KeyguardSecurityModel.SecurityMode mode : modes) {
-                final int resId = mKeyguardSecurityContainer.getLayoutIdFor(mode);
-                if (resId == 0) {
-                    continue;
-                }
-                inflater.inflate(resId, null /* root */, false /* attach */);
-            }
-        }
+        mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext());
+        mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper;
     }
 
     @Test
     public void startDisappearAnimation_animatesKeyboard() {
-        when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
-                KeyguardSecurityModel.SecurityMode.Password);
-        mKeyguardSecurityContainer.showPrimarySecurityScreen(false /* turningOff */);
-
-        mKeyguardSecurityContainer.startDisappearAnimation(null);
-        verify(mSecurityView).startDisappearAnimation(eq(null));
+        mKeyguardSecurityContainer.startDisappearAnimation(SecurityMode.Password);
         verify(mWindowInsetsController).controlWindowInsetsAnimation(eq(ime()), anyLong(), any(),
                 any(), any());
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
new file mode 100644
index 0000000..3b7f4b8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.keyguard;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.WindowInsetsController;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper()
+public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase {
+
+    @Rule
+    public MockitoRule mRule = MockitoJUnit.rule();
+
+    @Mock
+    private KeyguardSecurityViewFlipper mView;
+    @Mock
+    private LayoutInflater mLayoutInflater;
+    @Mock
+    private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory;
+    @Mock
+    private KeyguardInputViewController mKeyguardInputViewController;
+    @Mock
+    private KeyguardInputView mInputView;
+    @Mock
+    private WindowInsetsController mWindowInsetsController;
+    @Mock
+    private KeyguardSecurityCallback mKeyguardSecurityCallback;
+
+    private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
+
+    @Before
+    public void setup() {
+        when(mKeyguardSecurityViewControllerFactory.create(
+                any(KeyguardInputView.class), any(SecurityMode.class),
+                any(KeyguardSecurityCallback.class)))
+                .thenReturn(mKeyguardInputViewController);
+        when(mView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+
+        mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView,
+                mLayoutInflater, mKeyguardSecurityViewControllerFactory);
+    }
+
+    @Test
+    public void showSecurityScreen_canInflateAllModes() {
+        SecurityMode[] modes = SecurityMode.values();
+        // Always return an invalid controller so that we're always making a new one.
+        when(mKeyguardInputViewController.getSecurityMode()).thenReturn(SecurityMode.Invalid);
+        for (SecurityMode mode : modes) {
+            reset(mLayoutInflater);
+            when(mLayoutInflater.inflate(anyInt(), eq(mView), eq(false)))
+                    .thenReturn(mInputView);
+            mKeyguardSecurityViewFlipperController.getSecurityView(mode, mKeyguardSecurityCallback);
+            if (mode == SecurityMode.Invalid || mode == SecurityMode.None) {
+                verify(mLayoutInflater, never()).inflate(
+                        anyInt(), any(ViewGroup.class), anyBoolean());
+            } else {
+                verify(mLayoutInflater).inflate(anyInt(), eq(mView), eq(false));
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
index 0431704..79ec4f2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
@@ -24,9 +24,7 @@
 import android.view.LayoutInflater;
 
 import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.InjectionInflationController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -50,13 +48,7 @@
     @Before
     public void setUp() {
         allowTestableLooperAsMainThread();
-        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
-        InjectionInflationController inflationController = new InjectionInflationController(
-                SystemUIFactory.getInstance()
-                        .getSysUIComponent()
-                        .createViewInstanceCreatorFactory());
-        LayoutInflater layoutInflater = inflationController
-                .injectable(LayoutInflater.from(getContext()));
+        LayoutInflater layoutInflater = LayoutInflater.from(getContext());
         mKeyguardStatusView =
                 (KeyguardStatusView) layoutInflater.inflate(R.layout.keyguard_status_view, null);
         org.mockito.MockitoAnnotations.initMocks(this);
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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 65334fd..4f056df 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8253,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;
 
@@ -8312,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
@@ -8721,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 */);
                 }
             }
         }
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/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 5d2f512..5076007 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -836,7 +836,7 @@
             return;
         }
         if (Settings.Global.getInt(getContext().getContentResolver(),
-                "clipboard_access_toast_enabled", 0) == 0) {
+                "clipboard_access_toast_enabled", 1) == 0) {
             return;
         }
         // Don't notify if the app accessing the clipboard is the same as the current owner.
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/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
new file mode 100644
index 0000000..f4f77db
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -0,0 +1,149 @@
+/*
+ * 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.display;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.display.DisplayManagerService.SyncRoot;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Container for all the display devices present in the system.  If an object wants to get events
+ * about all the DisplayDevices without needing to listen to all of the DisplayAdapters, they can
+ * listen and interact with the instance of this class.
+ * <p>
+ * The collection of {@link DisplayDevice}s and their usage is protected by the provided
+ * {@link DisplayManagerService.SyncRoot} lock object.
+ */
+class DisplayDeviceRepository implements DisplayAdapter.Listener {
+    private static final String TAG = "DisplayDeviceRepository";
+
+    public static final int DISPLAY_DEVICE_EVENT_ADDED = 1;
+    public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2;
+    public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3;
+
+    /**
+     * List of all currently connected display devices. Indexed by the displayId.
+     * TODO: multi-display - break the notion that this is indexed by displayId.
+     */
+    @GuardedBy("mSyncRoot")
+    private final List<DisplayDevice> mDisplayDevices = new ArrayList<>();
+
+    /** Listener for {link DisplayDevice} events. */
+    private final Listener mListener;
+
+    /** Global lock object from {@link DisplayManagerService}. */
+    private final SyncRoot mSyncRoot;
+
+    DisplayDeviceRepository(@NonNull SyncRoot syncRoot, @NonNull Listener listener) {
+        mSyncRoot = syncRoot;
+        mListener = listener;
+    }
+
+    @Override
+    public void onDisplayDeviceEvent(DisplayDevice device, int event) {
+        switch (event) {
+            case DISPLAY_DEVICE_EVENT_ADDED:
+                handleDisplayDeviceAdded(device);
+                break;
+
+            case DISPLAY_DEVICE_EVENT_CHANGED:
+                handleDisplayDeviceChanged(device);
+                break;
+
+            case DISPLAY_DEVICE_EVENT_REMOVED:
+                handleDisplayDeviceRemoved(device);
+                break;
+        }
+    }
+
+    @Override
+    public void onTraversalRequested() {
+        mListener.onTraversalRequested();
+    }
+
+    public boolean containsLocked(DisplayDevice d) {
+        return mDisplayDevices.contains(d);
+    }
+
+    public int sizeLocked() {
+        return mDisplayDevices.size();
+    }
+
+    public void forEachLocked(Consumer<DisplayDevice> consumer) {
+        final int count = mDisplayDevices.size();
+        for (int i = 0; i < count; i++) {
+            consumer.accept(mDisplayDevices.get(i));
+        }
+    }
+
+    private void handleDisplayDeviceAdded(DisplayDevice device) {
+        synchronized (mSyncRoot) {
+            DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+            if (mDisplayDevices.contains(device)) {
+                Slog.w(TAG, "Attempted to add already added display device: " + info);
+                return;
+            }
+            Slog.i(TAG, "Display device added: " + info);
+            device.mDebugLastLoggedDeviceInfo = info;
+
+            mDisplayDevices.add(device);
+            mListener.onDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
+        }
+    }
+
+    private void handleDisplayDeviceChanged(DisplayDevice device) {
+        synchronized (mSyncRoot) {
+            DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+            if (!mDisplayDevices.contains(device)) {
+                Slog.w(TAG, "Attempted to change non-existent display device: " + info);
+                return;
+            }
+            mListener.onDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
+        }
+    }
+
+    private void handleDisplayDeviceRemoved(DisplayDevice device) {
+        synchronized (mSyncRoot) {
+            DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+            if (!mDisplayDevices.remove(device)) {
+                Slog.w(TAG, "Attempted to remove non-existent display device: " + info);
+                return;
+            }
+
+            Slog.i(TAG, "Display device removed: " + info);
+            device.mDebugLastLoggedDeviceInfo = info;
+            mListener.onDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED);
+        }
+    }
+
+    /**
+     * Listens to {@link DisplayDevice} events from {@link DisplayDeviceRepository}.
+     */
+    interface Listener {
+        void onDisplayDeviceEventLocked(DisplayDevice device, int event);
+
+        // TODO: multi-display - Try to remove the need for requestTraversal...it feels like
+        // a shoe-horned method for a shoe-horned feature.
+        void onTraversalRequested();
+    };
+}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 97c4cf5..597f49c6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -131,7 +131,8 @@
  * </p><p>
  * Display adapters are only weakly coupled to the display manager service.
  * Display adapters communicate changes in display device state to the display manager
- * service asynchronously via a {@link DisplayAdapter.Listener} registered
+ * service asynchronously via a {@link DisplayAdapter.Listener}, and through
+ * the {@link DisplayDeviceRepository.Listener}, which is ultimately registered
  * by the display manager service.  This separation of concerns is important for
  * two main reasons.  First, it neatly encapsulates the responsibilities of these
  * two classes: display adapters handle individual display devices whereas
@@ -180,7 +181,7 @@
     private final Context mContext;
     private final DisplayManagerHandler mHandler;
     private final Handler mUiHandler;
-    private final DisplayAdapterListener mDisplayAdapterListener;
+    private final DisplayDeviceListener mDisplayDeviceListener;
     private final DisplayModeDirector mDisplayModeDirector;
     private WindowManagerInternal mWindowManagerInternal;
     private InputManagerInternal mInputManagerInternal;
@@ -215,8 +216,7 @@
     // List of all currently registered display adapters.
     private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>();
 
-    // List of all currently connected display devices.
-    private final ArrayList<DisplayDevice> mDisplayDevices = new ArrayList<DisplayDevice>();
+    private final DisplayDeviceRepository mDisplayDeviceRepo;
 
     // List of all logical displays indexed by logical display id.
     // Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache.
@@ -331,7 +331,8 @@
         mContext = context;
         mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper());
         mUiHandler = UiThread.getHandler();
-        mDisplayAdapterListener = new DisplayAdapterListener();
+        mDisplayDeviceListener = new DisplayDeviceListener();
+        mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mDisplayDeviceListener);
         mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
         mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
         Resources resources = mContext.getResources();
@@ -469,6 +470,11 @@
         return mHandler;
     }
 
+    @VisibleForTesting
+    DisplayDeviceRepository getDisplayDeviceRepository() {
+        return mDisplayDeviceRepo;
+    }
+
     private void loadStableDisplayValuesLocked() {
         final Point size = mPersistentDataStore.getStableDisplaySize();
         if (size.x > 0 && size.y > 0) {
@@ -818,7 +824,17 @@
                 return -1;
             }
 
-            handleDisplayDeviceAddedLocked(device);
+            // DisplayDevice events are handled manually for Virtual Displays.
+            // TODO: multi-display Fix this so that generic add/remove events are not handled in a
+            // different code path for virtual displays.  Currently this happens so that we can
+            // return a valid display ID synchronously upon successful Virtual Display creation.
+            // This code can run on any binder thread, while onDisplayDeviceAdded() callbacks are
+            // called on the DisplayThread (which we don't want to wait for?).
+            // One option would be to actually wait here on the binder thread
+            // to be notified when the virtual display is created (or failed).
+            mDisplayDeviceRepo.onDisplayDeviceEvent(device,
+                    DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+
             LogicalDisplay display = findLogicalDisplayForDeviceLocked(device);
             if (display != null) {
                 return display.getDisplayIdLocked();
@@ -828,7 +844,8 @@
             Slog.w(TAG, "Rejecting request to create virtual display "
                     + "because the logical display was not created.");
             mVirtualDisplayAdapter.releaseVirtualDisplayLocked(callback.asBinder());
-            handleDisplayDeviceRemovedLocked(device);
+            mDisplayDeviceRepo.onDisplayDeviceEvent(device,
+                    DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
         }
         return -1;
     }
@@ -863,7 +880,9 @@
             DisplayDevice device =
                     mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
             if (device != null) {
-                handleDisplayDeviceRemovedLocked(device);
+                // TODO - handle virtual displays the same as other display adapters.
+                mDisplayDeviceRepo.onDisplayDeviceEvent(device,
+                        DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
             }
         }
     }
@@ -883,7 +902,7 @@
         synchronized (mSyncRoot) {
             // main display adapter
             registerDisplayAdapterLocked(new LocalDisplayAdapter(
-                    mSyncRoot, mContext, mHandler, mDisplayAdapterListener));
+                    mSyncRoot, mContext, mHandler, mDisplayDeviceRepo));
 
             // Standalone VR devices rely on a virtual display as their primary display for
             // 2D UI. We register virtual display adapter along side the main display adapter
@@ -891,7 +910,7 @@
             // early apps like SetupWizard/Launcher. In particular, SUW is displayed using
             // the virtual display inside VR before any VR-specific apps even run.
             mVirtualDisplayAdapter = mInjector.getVirtualDisplayAdapter(mSyncRoot, mContext,
-                    mHandler, mDisplayAdapterListener);
+                    mHandler, mDisplayDeviceRepo);
             if (mVirtualDisplayAdapter != null) {
                 registerDisplayAdapterLocked(mVirtualDisplayAdapter);
             }
@@ -909,7 +928,7 @@
 
     private void registerOverlayDisplayAdapterLocked() {
         registerDisplayAdapterLocked(new OverlayDisplayAdapter(
-                mSyncRoot, mContext, mHandler, mDisplayAdapterListener, mUiHandler));
+                mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, mUiHandler));
     }
 
     private void registerWifiDisplayAdapterLocked() {
@@ -917,7 +936,7 @@
                 com.android.internal.R.bool.config_enableWifiDisplay)
                 || SystemProperties.getInt(FORCE_WIFI_DISPLAY_ENABLE, -1) == 1) {
             mWifiDisplayAdapter = new WifiDisplayAdapter(
-                    mSyncRoot, mContext, mHandler, mDisplayAdapterListener,
+                    mSyncRoot, mContext, mHandler, mDisplayDeviceRepo,
                     mPersistentDataStore);
             registerDisplayAdapterLocked(mWifiDisplayAdapter);
         }
@@ -946,15 +965,6 @@
     }
 
     private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
-        DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
-        if (mDisplayDevices.contains(device)) {
-            Slog.w(TAG, "Attempted to add already added display device: " + info);
-            return;
-        }
-        Slog.i(TAG, "Display device added: " + info);
-        device.mDebugLastLoggedDeviceInfo = info;
-
-        mDisplayDevices.add(device);
         LogicalDisplay display = addLogicalDisplayLocked(device);
         Runnable work = updateDisplayStateLocked(device);
         if (work != null) {
@@ -966,45 +976,45 @@
     @VisibleForTesting
     void handleDisplayDeviceChanged(DisplayDevice device) {
         synchronized (mSyncRoot) {
-            DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
-            if (!mDisplayDevices.contains(device)) {
-                Slog.w(TAG, "Attempted to change non-existent display device: " + info);
-                return;
-            }
+            handleDisplayDeviceChangedLocked(device);
+        }
+    }
 
-            int diff = device.mDebugLastLoggedDeviceInfo.diff(info);
-            if (diff == DisplayDeviceInfo.DIFF_STATE) {
-                Slog.i(TAG, "Display device changed state: \"" + info.name
-                        + "\", " + Display.stateToString(info.state));
-                final Optional<Integer> viewportType = getViewportType(info);
-                if (viewportType.isPresent()) {
-                    for (DisplayViewport d : mViewports) {
-                        if (d.type == viewportType.get() && info.uniqueId.equals(d.uniqueId)) {
-                            // Update display view port power state
-                            d.isActive = Display.isActiveState(info.state);
-                        }
-                    }
-                    if (mInputManagerInternal != null) {
-                        mHandler.sendEmptyMessage(MSG_UPDATE_VIEWPORT);
+    private void handleDisplayDeviceChangedLocked(DisplayDevice device) {
+        DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+
+        int diff = device.mDebugLastLoggedDeviceInfo.diff(info);
+        if (diff == DisplayDeviceInfo.DIFF_STATE) {
+            Slog.i(TAG, "Display device changed state: \"" + info.name
+                    + "\", " + Display.stateToString(info.state));
+            final Optional<Integer> viewportType = getViewportType(info);
+            if (viewportType.isPresent()) {
+                for (DisplayViewport d : mViewports) {
+                    if (d.type == viewportType.get() && info.uniqueId.equals(d.uniqueId)) {
+                        // Update display view port power state
+                        d.isActive = Display.isActiveState(info.state);
                     }
                 }
-            } else if (diff != 0) {
-                Slog.i(TAG, "Display device changed: " + info);
-            }
-            if ((diff & DisplayDeviceInfo.DIFF_COLOR_MODE) != 0) {
-                try {
-                    mPersistentDataStore.setColorMode(device, info.colorMode);
-                } finally {
-                    mPersistentDataStore.saveIfNeeded();
+                if (mInputManagerInternal != null) {
+                    mHandler.sendEmptyMessage(MSG_UPDATE_VIEWPORT);
                 }
             }
-            device.mDebugLastLoggedDeviceInfo = info;
-
-            device.applyPendingDisplayDeviceInfoChangesLocked();
-            if (updateLogicalDisplaysLocked()) {
-                scheduleTraversalLocked(false);
+        } else if (diff != 0) {
+            Slog.i(TAG, "Display device changed: " + info);
+        }
+        if ((diff & DisplayDeviceInfo.DIFF_COLOR_MODE) != 0) {
+            try {
+                mPersistentDataStore.setColorMode(device, info.colorMode);
+            } finally {
+                mPersistentDataStore.saveIfNeeded();
             }
         }
+        device.mDebugLastLoggedDeviceInfo = info;
+
+        device.applyPendingDisplayDeviceInfoChangesLocked();
+        if (updateLogicalDisplaysLocked()) {
+            scheduleTraversalLocked(false);
+        }
     }
 
     private void handleDisplayDeviceRemoved(DisplayDevice device) {
@@ -1014,15 +1024,6 @@
     }
 
     private void handleDisplayDeviceRemovedLocked(DisplayDevice device) {
-        DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
-        if (!mDisplayDevices.remove(device)) {
-            Slog.w(TAG, "Attempted to remove non-existent display device: " + info);
-            return;
-        }
-
-        Slog.i(TAG, "Display device removed: " + info);
-        device.mDebugLastLoggedDeviceInfo = info;
-
         updateLogicalDisplaysLocked();
         scheduleTraversalLocked(false);
     }
@@ -1043,14 +1044,12 @@
     }
 
     private void applyGlobalDisplayStateLocked(List<Runnable> workQueue) {
-        final int count = mDisplayDevices.size();
-        for (int i = 0; i < count; i++) {
-            DisplayDevice device = mDisplayDevices.get(i);
+        mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
             Runnable runnable = updateDisplayStateLocked(device);
             if (runnable != null) {
                 workQueue.add(runnable);
             }
-        }
+        });
     }
 
     private Runnable updateDisplayStateLocked(DisplayDevice device) {
@@ -1058,6 +1057,8 @@
         // by the display power controller (if known).
         DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
         if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
+            // TODO - multi-display - The rules regarding what display state to apply to each
+            // display will depend on the configuration/mapping of logical displays.
             return device.requestDisplayStateLocked(
                     mGlobalDisplayState, mGlobalDisplayBrightness);
         }
@@ -1085,7 +1086,7 @@
         final int layerStack = assignLayerStackLocked(displayId);
 
         LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
-        display.updateLocked(mDisplayDevices);
+        display.updateLocked(mDisplayDeviceRepo);
         if (!display.isValidLocked()) {
             // This should never happen currently.
             Slog.w(TAG, "Ignoring display device because the logical display "
@@ -1248,7 +1249,7 @@
 
             mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked());
             display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo);
-            display.updateLocked(mDisplayDevices);
+            display.updateLocked(mDisplayDeviceRepo);
             if (!display.isValidLocked()) {
                 mLogicalDisplays.removeAt(i);
                 handleLogicalDisplayRemoved(displayId);
@@ -1276,12 +1277,10 @@
         clearViewportsLocked();
 
         // Configure each display device.
-        final int count = mDisplayDevices.size();
-        for (int i = 0; i < count; i++) {
-            DisplayDevice device = mDisplayDevices.get(i);
+        mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
             configureDisplayLocked(t, device);
             device.performTraversalLocked(t);
-        }
+        });
 
         // Tell the input system about these new viewports.
         if (mInputManagerInternal != null) {
@@ -1714,11 +1713,11 @@
             }
 
             pw.println();
-            pw.println("Display Devices: size=" + mDisplayDevices.size());
-            for (DisplayDevice device : mDisplayDevices) {
+            pw.println("Display Devices: size=" + mDisplayDeviceRepo.sizeLocked());
+            mDisplayDeviceRepo.forEachLocked(device -> {
                 pw.println("  " + device.getDisplayDeviceInfoLocked());
                 device.dumpLocked(ipw);
-            }
+            });
 
             final int logicalDisplayCount = mLogicalDisplays.size();
             pw.println();
@@ -1864,20 +1863,20 @@
         }
     }
 
-    private final class DisplayAdapterListener implements DisplayAdapter.Listener {
+    private final class DisplayDeviceListener implements DisplayDeviceRepository.Listener {
         @Override
-        public void onDisplayDeviceEvent(DisplayDevice device, int event) {
+        public void onDisplayDeviceEventLocked(DisplayDevice device, int event) {
             switch (event) {
-                case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED:
-                    handleDisplayDeviceAdded(device);
+                case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_ADDED:
+                    handleDisplayDeviceAddedLocked(device);
                     break;
 
-                case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED:
-                    handleDisplayDeviceChanged(device);
+                case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_CHANGED:
+                    handleDisplayDeviceChangedLocked(device);
                     break;
 
-                case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED:
-                    handleDisplayDeviceRemoved(device);
+                case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_REMOVED:
+                    handleDisplayDeviceRemovedLocked(device);
                     break;
             }
         }
@@ -2573,9 +2572,10 @@
                         }
                     }
                 };
+                LogicalDisplay defaultDisplay = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
+                DisplayDevice defaultDevice = defaultDisplay.getPrimaryDisplayDeviceLocked();
                 mDisplayPowerController = new DisplayPowerController(
-                        mContext, callbacks, handler, sensorManager, blanker,
-                        mDisplayDevices.get(Display.DEFAULT_DISPLAY));
+                        mContext, callbacks, handler, sensorManager, blanker, defaultDevice);
                 mSensorManager = sensorManager;
             }
 
@@ -2689,9 +2689,7 @@
         @Override
         public void onOverlayChanged() {
             synchronized (mSyncRoot) {
-                for (int i = 0; i < mDisplayDevices.size(); i++) {
-                    mDisplayDevices.get(i).onOverlayChangedLocked();
-                }
+                mDisplayDeviceRepo.forEachLocked(DisplayDevice::onOverlayChangedLocked);
             }
         }
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 8556f08..bf8b891 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -28,7 +28,6 @@
 
 import java.io.PrintWriter;
 import java.util.Arrays;
-import java.util.List;
 import java.util.Objects;
 
 /**
@@ -220,16 +219,16 @@
      * The logical display might become invalid if it is attached to a display device
      * that no longer exists.
      *
-     * @param devices The list of all connected display devices.
+     * @param deviceRepo Repository of active {@link DisplayDevice}s.
      */
-    public void updateLocked(List<DisplayDevice> devices) {
+    public void updateLocked(DisplayDeviceRepository deviceRepo) {
         // Nothing to update if already invalid.
         if (mPrimaryDisplayDevice == null) {
             return;
         }
 
         // Check whether logical display has become invalid.
-        if (!devices.contains(mPrimaryDisplayDevice)) {
+        if (!deviceRepo.containsLocked(mPrimaryDisplayDevice)) {
             mPrimaryDisplayDevice = null;
             return;
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index bba248c..a44fabbe 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1702,7 +1702,8 @@
 
         Intent intent = new Intent(ACTION_SHOW_INPUT_METHOD_PICKER)
                 .setPackage(mContext.getPackageName());
-        mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent,
+                PendingIntent.FLAG_IMMUTABLE);
 
         mShowOngoingImeSwitcherForPhones = false;
 
@@ -2530,7 +2531,8 @@
         mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
                 com.android.internal.R.string.input_method_binding_label);
         mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
-                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
+                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS),
+                PendingIntent.FLAG_IMMUTABLE));
 
         if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) {
             mLastBindTime = SystemClock.uptimeMillis();
@@ -3463,6 +3465,9 @@
 
         final boolean sameWindowFocused = mCurFocusedWindow == windowToken;
         final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
+        final boolean startInputByWinGainedFocus =
+                (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0;
+
         if (sameWindowFocused && isTextEditor) {
             if (DEBUG) {
                 Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
@@ -3506,7 +3511,7 @@
         InputBindResult res = null;
         switch (softInputMode & LayoutParams.SOFT_INPUT_MASK_STATE) {
             case LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
-                if (!isTextEditor || !doAutoShow) {
+                if (!sameWindowFocused && (!isTextEditor || !doAutoShow)) {
                     if (LayoutParams.mayUseInputMethod(windowFlags)) {
                         // There is no focus view, and this window will
                         // be behind any soft input window, so hide the
@@ -3555,7 +3560,7 @@
                 }
                 break;
             case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
-                if (isImeVisible()) {
+                if (!sameWindowFocused) {
                     if (DEBUG) Slog.v(TAG, "Window asks to hide input");
                     hideCurrentInputLocked(mCurFocusedWindow, 0, null,
                             SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
@@ -3584,7 +3589,7 @@
                 if (DEBUG) Slog.v(TAG, "Window asks to always show input");
                 if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
                         unverifiedTargetSdkVersion, startInputFlags)) {
-                    if (!isImeVisible()) {
+                    if (!sameWindowFocused) {
                         if (attribute != null) {
                             res = startInputUncheckedLocked(cs, inputContext, missingMethods,
                                     attribute, startInputFlags, startInputReason);
@@ -3603,17 +3608,26 @@
 
         if (!didStart) {
             if (attribute != null) {
-                if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
+                if (sameWindowFocused) {
+                    // On previous platforms, when Dialogs re-gained focus, the Activity behind
+                    // would briefly gain focus first, and dismiss the IME.
+                    // On R that behavior has been fixed, but unfortunately apps have come
+                    // to rely on this behavior to hide the IME when the editor no longer has focus
+                    // To maintain compatibility, we are now hiding the IME when we don't have
+                    // an editor upon refocusing a window.
+                    if (startInputByWinGainedFocus) {
+                        hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                                SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
+                    }
+                    res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
+                            startInputFlags, startInputReason);
+                } else if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
                         || (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) {
                     res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
                             startInputFlags, startInputReason);
                 } else {
                     res = InputBindResult.NO_EDITOR;
                 }
-            } else if (sameWindowFocused) {
-                return new InputBindResult(
-                        InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
-                        null, null, null, -1, null);
             } else {
                 res = InputBindResult.NULL_EDITOR_INFO;
             }
diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS
index 25ef9fa..c09ade9 100644
--- a/services/core/java/com/android/server/inputmethod/OWNERS
+++ b/services/core/java/com/android/server/inputmethod/OWNERS
@@ -4,3 +4,4 @@
 yukawa@google.com
 tarandeep@google.com
 lumark@google.com
+roosa@google.com
diff --git a/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java
new file mode 100644
index 0000000..3801877
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java
@@ -0,0 +1,174 @@
+/*
+ * 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.location.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.location.timezone.LocationTimeZoneEvent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.timezone.ILocationTimeZoneProvider;
+import com.android.internal.location.timezone.ILocationTimeZoneProviderManager;
+import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
+import com.android.server.ServiceWatcher;
+
+import java.util.Objects;
+
+/**
+ * System server-side proxy for ILocationTimeZoneProvider implementations, i.e. this provides the
+ * system server object used to communicate with a remote LocationTimeZoneProvider over Binder,
+ * which could be running in a different process. As "remote" LocationTimeZoneProviders are bound /
+ * unbound this proxy will rebind to the "best" available remote process.
+ */
+class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
+
+    /**
+     * Creates and registers this proxy. If no suitable service is available for the proxy, returns
+     * null.
+     */
+    @Nullable
+    static LocationTimeZoneProviderProxy createAndRegister(
+            @NonNull Context context, @NonNull ThreadingDomain threadingDomain,
+            @NonNull String action, int enableOverlayResId, int nonOverlayPackageResId) {
+        RealLocationTimeZoneProviderProxy proxy = new RealLocationTimeZoneProviderProxy(
+                context, threadingDomain, action, enableOverlayResId, nonOverlayPackageResId);
+        if (proxy.register()) {
+            return proxy;
+        } else {
+            return null;
+        }
+    }
+
+    @NonNull private final ServiceWatcher mServiceWatcher;
+
+    @GuardedBy("mProxyLock")
+    @Nullable private ManagerProxy mManagerProxy;
+
+    @GuardedBy("mProxyLock")
+    @NonNull private LocationTimeZoneProviderRequest mRequest;
+
+    private RealLocationTimeZoneProviderProxy(
+            @NonNull Context context, @NonNull ThreadingDomain threadingDomain,
+            @NonNull String action, int enableOverlayResId,
+            int nonOverlayPackageResId) {
+        super(context, threadingDomain);
+        mManagerProxy = null;
+        mRequest = LocationTimeZoneProviderRequest.EMPTY_REQUEST;
+        mServiceWatcher = new ServiceWatcher(context, action, this::onBind, this::onUnbind,
+                enableOverlayResId, nonOverlayPackageResId);
+    }
+
+    private boolean register() {
+        return mServiceWatcher.register();
+    }
+
+    private void onBind(IBinder binder, ComponentName componentName) throws RemoteException {
+        processServiceWatcherCallbackOnThreadingDomainThread(() -> onBindOnHandlerThread(binder));
+    }
+
+    private void onUnbind() {
+        processServiceWatcherCallbackOnThreadingDomainThread(this::onUnbindOnHandlerThread);
+    }
+
+    private void processServiceWatcherCallbackOnThreadingDomainThread(@NonNull Runnable runnable) {
+        // For simplicity, this code just post()s the runnable to the mThreadingDomain Thread in all
+        // cases. This adds a delay if ServiceWatcher and ThreadingDomain happen to be using the
+        // same thread, but nothing here should be performance critical.
+        mThreadingDomain.post(runnable);
+    }
+
+    private void onBindOnHandlerThread(@NonNull IBinder binder) {
+        mThreadingDomain.assertCurrentThread();
+
+        ILocationTimeZoneProvider provider = ILocationTimeZoneProvider.Stub.asInterface(binder);
+
+        synchronized (mSharedLock) {
+            try {
+                mManagerProxy = new ManagerProxy();
+                provider.setLocationTimeZoneProviderManager(mManagerProxy);
+                trySendCurrentRequest();
+                mListener.onProviderBound();
+            } catch (RemoteException e) {
+                // This is not expected to happen.
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private void onUnbindOnHandlerThread() {
+        mThreadingDomain.assertCurrentThread();
+
+        synchronized (mSharedLock) {
+            mManagerProxy = null;
+            mListener.onProviderUnbound();
+        }
+    }
+
+    @Override
+    final void setRequest(@NonNull LocationTimeZoneProviderRequest request) {
+        mThreadingDomain.assertCurrentThread();
+
+        Objects.requireNonNull(request);
+        synchronized (mSharedLock) {
+            mRequest = request;
+
+            trySendCurrentRequest();
+        }
+    }
+
+    @GuardedBy("mProxyLock")
+    private void trySendCurrentRequest() {
+        LocationTimeZoneProviderRequest request = mRequest;
+        mServiceWatcher.runOnBinder(binder -> {
+            ILocationTimeZoneProvider service =
+                    ILocationTimeZoneProvider.Stub.asInterface(binder);
+            service.setRequest(request);
+        });
+    }
+
+    @Override
+    public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
+        synchronized (mSharedLock) {
+            ipw.println("mRequest=" + mRequest);
+            mServiceWatcher.dump(null, ipw, args);
+        }
+    }
+
+    /**
+     * A system Server-side proxy for the ILocationTimeZoneProviderManager, i.e. this is a local
+     * binder stub. Each "remote" LocationTimeZoneProvider is passed a binder instance that it
+     * then uses to communicate back with the system server, invoking the logic here.
+     */
+    private class ManagerProxy extends ILocationTimeZoneProviderManager.Stub {
+
+        // executed on binder thread
+        @Override
+        public void onLocationTimeZoneEvent(LocationTimeZoneEvent locationTimeZoneEvent) {
+            synchronized (mSharedLock) {
+                if (mManagerProxy != this) {
+                    return;
+                }
+            }
+            handleLocationTimeZoneEvent(locationTimeZoneEvent);
+        }
+    }
+}
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/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
index 18da33c..7257f52 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
@@ -40,15 +40,12 @@
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.attribute.BasicFileAttributes;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 import java.util.Iterator;
 import java.util.LinkedList;
-import java.util.concurrent.TimeUnit;
+import java.util.Set;
 
 /**
  * Provides an interface to write and query for notification history data for a user from a Protocol
@@ -173,8 +170,8 @@
         mFileWriteHandler.post(rnr);
     }
 
-    public void deleteConversation(String pkg, String conversationId) {
-        RemoveConversationRunnable rcr = new RemoveConversationRunnable(pkg, conversationId);
+    public void deleteConversations(String pkg, Set<String> conversationIds) {
+        RemoveConversationRunnable rcr = new RemoveConversationRunnable(pkg, conversationIds);
         mFileWriteHandler.post(rcr);
     }
 
@@ -467,12 +464,12 @@
 
     final class RemoveConversationRunnable implements Runnable {
         private String mPkg;
-        private String mConversationId;
+        private Set<String> mConversationIds;
         private NotificationHistory mNotificationHistory;
 
-        public RemoveConversationRunnable(String pkg, String conversationId) {
+        public RemoveConversationRunnable(String pkg, Set<String> conversationIds) {
             mPkg = pkg;
-            mConversationId = conversationId;
+            mConversationIds = conversationIds;
         }
 
         @VisibleForTesting
@@ -482,10 +479,10 @@
 
         @Override
         public void run() {
-            if (DEBUG) Slog.d(TAG, "RemoveConversationRunnable " + mPkg + " "  + mConversationId);
+            if (DEBUG) Slog.d(TAG, "RemoveConversationRunnable " + mPkg + " "  + mConversationIds);
             synchronized (mLock) {
                 // Remove from pending history
-                mBuffer.removeConversationFromWrite(mPkg, mConversationId);
+                mBuffer.removeConversationsFromWrite(mPkg, mConversationIds);
 
                 Iterator<AtomicFile> historyFileItr = mHistoryFiles.iterator();
                 while (historyFileItr.hasNext()) {
@@ -496,7 +493,8 @@
                                 : new NotificationHistory();
                         readLocked(af, notificationHistory,
                                 new NotificationHistoryFilter.Builder().build());
-                        if(notificationHistory.removeConversationFromWrite(mPkg, mConversationId)) {
+                        if (notificationHistory.removeConversationsFromWrite(
+                                mPkg, mConversationIds)) {
                             writeLocked(af, notificationHistory);
                         }
                     } catch (Exception e) {
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryManager.java b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
index 69a7ce90..cf3530b 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryManager.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
@@ -38,12 +38,12 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FunctionalUtils;
 import com.android.server.IoThread;
 
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Keeps track of per-user notification histories.
@@ -167,7 +167,7 @@
         }
     }
 
-    public void deleteConversation(String pkg, int uid, String conversationId) {
+    public void deleteConversations(String pkg, int uid, Set<String> conversationIds) {
         synchronized (mLock) {
             int userId = UserHandle.getUserId(uid);
             final NotificationHistoryDatabase userHistory =
@@ -179,7 +179,7 @@
                         + userId);
                 return;
             }
-            userHistory.deleteConversation(pkg, conversationId);
+            userHistory.deleteConversations(pkg, conversationIds);
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index c301cd2..affdcea 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -19,6 +19,8 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 
+import java.util.Set;
+
 public interface NotificationManagerInternal {
     NotificationChannel getNotificationChannel(String pkg, int uid, String channelId);
     void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid,
@@ -28,5 +30,5 @@
 
     void removeForegroundServiceFlagFromNotification(String pkg, int notificationId, int userId);
 
-    void onConversationRemoved(String pkg, int uid, String conversationId);
+    void onConversationRemoved(String pkg, int uid, Set<String> shortcuts);
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1fd7a73..b4c98e0 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5605,8 +5605,8 @@
         }
 
         @Override
-        public void onConversationRemoved(String pkg, int uid, String conversationId) {
-            onConversationRemovedInternal(pkg, uid, conversationId);
+        public void onConversationRemoved(String pkg, int uid, Set<String> shortcuts) {
+            onConversationRemovedInternal(pkg, uid, shortcuts);
         }
 
         @GuardedBy("mNotificationLock")
@@ -5835,14 +5835,13 @@
         mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));
     }
 
-    private void onConversationRemovedInternal(String pkg, int uid, String conversationId) {
+    private void onConversationRemovedInternal(String pkg, int uid, Set<String> shortcuts) {
         checkCallerIsSystem();
         Preconditions.checkStringNotEmpty(pkg);
-        Preconditions.checkStringNotEmpty(conversationId);
 
-        mHistoryManager.deleteConversation(pkg, uid, conversationId);
+        mHistoryManager.deleteConversations(pkg, uid, shortcuts);
         List<String> deletedChannelIds =
-                mPreferencesHelper.deleteConversation(pkg, uid, conversationId);
+                mPreferencesHelper.deleteConversations(pkg, uid, shortcuts);
         for (String channelId : deletedChannelIds) {
             cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
                     UserHandle.getUserId(uid), REASON_CHANNEL_BANNED,
@@ -8019,7 +8018,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 +8041,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/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 9cf9545..bdf98f4 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -79,6 +79,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class PreferencesHelper implements RankingConfig {
@@ -1428,7 +1429,8 @@
         }
     }
 
-    public @NonNull List<String> deleteConversation(String pkg, int uid, String conversationId) {
+    public @NonNull List<String> deleteConversations(String pkg, int uid,
+            Set<String> conversationIds) {
         synchronized (mPackagePreferences) {
             List<String> deletedChannelIds = new ArrayList<>();
             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
@@ -1438,7 +1440,8 @@
             int N = r.channels.size();
             for (int i = 0; i < N; i++) {
                 final NotificationChannel nc = r.channels.valueAt(i);
-                if (conversationId.equals(nc.getConversationId())) {
+                if (nc.getConversationId() != null
+                        && conversationIds.contains(nc.getConversationId())) {
                     nc.setDeleted(true);
                     LogMaker lm = getChannelLog(nc, pkg);
                     lm.setType(
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/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 4d11295..52fec33 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -74,6 +74,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -757,14 +758,16 @@
                     Slog.e(TAG, "Package not found: " + packageName, e);
                 }
                 PackageData packageData = getPackage(packageName, user.getIdentifier());
+                Set<String> shortcutIds = new HashSet<>();
                 for (ShortcutInfo shortcutInfo : shortcuts) {
                     if (packageData != null) {
                         packageData.deleteDataForConversation(shortcutInfo.getId());
                     }
-                    if (uid != Process.INVALID_UID) {
-                        mNotificationManagerInternal.onConversationRemoved(
-                                shortcutInfo.getPackage(), uid, shortcutInfo.getId());
-                    }
+                    shortcutIds.add(shortcutInfo.getId());
+                }
+                if (uid != Process.INVALID_UID) {
+                    mNotificationManagerInternal.onConversationRemoved(
+                            packageName, uid, shortcutIds);
                 }
             });
         }
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 73dda07..da25fd6 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -309,7 +309,8 @@
                 zeroRect, new Rect(0, 0, 10, 10), zeroRect, zeroRect);
         displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY;
         displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
-        displayManager.handleDisplayDeviceAdded(displayDevice);
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
 
         // Find the display id of the added FakeDisplayDevice
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index 301a9fe..b312e52 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -28,8 +28,6 @@
 import org.junit.Before;
 import org.junit.Test;
 
-import java.util.ArrayList;
-
 public class LogicalDisplayTest {
     private static final int DISPLAY_ID = 0;
     private static final int LAYER_STACK = 0;
@@ -51,9 +49,16 @@
         mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
         when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(displayDeviceInfo);
 
-        ArrayList<DisplayDevice> displayDevices = new ArrayList<>();
-        displayDevices.add(mDisplayDevice);
-        mLogicalDisplay.updateLocked(displayDevices);
+        DisplayDeviceRepository repo = new DisplayDeviceRepository(
+                new DisplayManagerService.SyncRoot(), new DisplayDeviceRepository.Listener() {
+                    @Override
+                    public void onDisplayDeviceEventLocked(DisplayDevice device, int event) {}
+
+                    @Override
+                    public void onTraversalRequested() {}
+                });
+        repo.onDisplayDeviceEvent(mDisplayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+        mLogicalDisplay.updateLocked(repo);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index b2f7abb..0a6cd51 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -623,17 +623,19 @@
     }
 
     @Test
-    public void testShortcutDeleted() {
+    public void testShortcutsDeleted() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
 
         ShortcutInfo shortcut1 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc1",
                 buildPerson());
         ShortcutInfo shortcut2 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc2",
                 buildPerson());
+        ShortcutInfo shortcut3 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc3",
+                buildPerson());
         mShortcutChangeCallback.onShortcutsAddedOrUpdated(TEST_PKG_NAME,
-                Arrays.asList(shortcut1, shortcut2), UserHandle.of(USER_ID_PRIMARY));
+                Arrays.asList(shortcut1, shortcut2, shortcut3), UserHandle.of(USER_ID_PRIMARY));
         mShortcutChangeCallback.onShortcutsRemoved(TEST_PKG_NAME,
-                Collections.singletonList(shortcut1), UserHandle.of(USER_ID_PRIMARY));
+                List.of(shortcut1, shortcut3), UserHandle.of(USER_ID_PRIMARY));
 
         List<ConversationInfo> conversations = getConversationsInPrimary();
 
@@ -641,7 +643,7 @@
         assertEquals("sc2", conversations.get(0).getShortcutId());
 
         verify(mNotificationManagerInternal)
-                .onConversationRemoved(TEST_PKG_NAME, TEST_PKG_UID, "sc1");
+                .onConversationRemoved(TEST_PKG_NAME, TEST_PKG_UID, Set.of("sc1", "sc3"));
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
index a2d987fb..f6d6624 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
@@ -44,15 +44,13 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.internal.matchers.Not;
 
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
+import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
 public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
@@ -309,22 +307,22 @@
     public void testRemoveConversationRunnable() throws Exception {
         NotificationHistory nh = mock(NotificationHistory.class);
         NotificationHistoryDatabase.RemoveConversationRunnable rcr =
-                mDataBase.new RemoveConversationRunnable("pkg", "convo");
+                mDataBase.new RemoveConversationRunnable("pkg", Set.of("convo", "another"));
         rcr.setNotificationHistory(nh);
 
         AtomicFile af = mock(AtomicFile.class);
         when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
         mDataBase.mHistoryFiles.addLast(af);
 
-        when(nh.removeConversationFromWrite("pkg", "convo")).thenReturn(true);
+        when(nh.removeConversationsFromWrite("pkg", Set.of("convo", "another"))).thenReturn(true);
 
         mDataBase.mBuffer = mock(NotificationHistory.class);
 
         rcr.run();
 
-        verify(mDataBase.mBuffer).removeConversationFromWrite("pkg", "convo");
+        verify(mDataBase.mBuffer).removeConversationsFromWrite("pkg",Set.of("convo", "another"));
         verify(af).openRead();
-        verify(nh).removeConversationFromWrite("pkg", "convo");
+        verify(nh).removeConversationsFromWrite("pkg",Set.of("convo", "another"));
         verify(af).startWrite();
     }
 
@@ -332,22 +330,22 @@
     public void testRemoveConversationRunnable_noChanges() throws Exception {
         NotificationHistory nh = mock(NotificationHistory.class);
         NotificationHistoryDatabase.RemoveConversationRunnable rcr =
-                mDataBase.new RemoveConversationRunnable("pkg", "convo");
+                mDataBase.new RemoveConversationRunnable("pkg", Set.of("convo"));
         rcr.setNotificationHistory(nh);
 
         AtomicFile af = mock(AtomicFile.class);
         when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
         mDataBase.mHistoryFiles.addLast(af);
 
-        when(nh.removeConversationFromWrite("pkg", "convo")).thenReturn(false);
+        when(nh.removeConversationsFromWrite("pkg", Set.of("convo"))).thenReturn(false);
 
         mDataBase.mBuffer = mock(NotificationHistory.class);
 
         rcr.run();
 
-        verify(mDataBase.mBuffer).removeConversationFromWrite("pkg", "convo");
+        verify(mDataBase.mBuffer).removeConversationsFromWrite("pkg", Set.of("convo"));
         verify(af).openRead();
-        verify(nh).removeConversationFromWrite("pkg", "convo");
+        verify(nh).removeConversationsFromWrite("pkg", Set.of("convo"));
         verify(af, never()).startWrite();
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
index 2341c10..a0293b7 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
@@ -47,6 +47,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 
 @RunWith(AndroidJUnit4.class)
@@ -365,15 +366,15 @@
     @Test
     public void testDeleteConversation_userUnlocked() {
         String pkg = "pkg";
-        String convo  = "convo";
+        Set<String> convos  = Set.of("convo", "another");
         NotificationHistoryDatabase userHistory = mock(NotificationHistoryDatabase.class);
 
         mHistoryManager.onUserUnlocked(USER_SYSTEM);
         mHistoryManager.replaceNotificationHistoryDatabase(USER_SYSTEM, userHistory);
 
-        mHistoryManager.deleteConversation(pkg, 1, convo);
+        mHistoryManager.deleteConversations(pkg, 1, convos);
 
-        verify(userHistory, times(1)).deleteConversation(pkg, convo);
+        verify(userHistory, times(1)).deleteConversations(pkg, convos);
     }
 
     @Test
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/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 0be1bf3..3e779a9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -127,6 +127,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ThreadLocalRandom;
 
 @SmallTest
@@ -3502,8 +3503,9 @@
     }
 
     @Test
-    public void testDeleteConversation() {
+    public void testDeleteConversations() {
         String convoId = "convo";
+        String convoIdC = "convoC";
         NotificationChannel messages =
                 new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
         mHelper.createNotificationChannel(PKG_O, UID_O, messages, true, false);
@@ -3526,10 +3528,16 @@
         channel2.setConversationId(calls.getId(), convoId);
         mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false);
 
+        NotificationChannel channel3 =
+                new NotificationChannel("C person msgs", "msgs from C", IMPORTANCE_DEFAULT);
+        channel3.setConversationId(messages.getId(), convoIdC);
+        mHelper.createNotificationChannel(PKG_O, UID_O, channel3, true, false);
+
         assertEquals(channel, mHelper.getNotificationChannel(PKG_O, UID_O, channel.getId(), false));
         assertEquals(channel2,
                 mHelper.getNotificationChannel(PKG_O, UID_O, channel2.getId(), false));
-        assertEquals(2, mHelper.deleteConversation(PKG_O, UID_O, convoId).size());
+        List<String> deleted = mHelper.deleteConversations(PKG_O, UID_O, Set.of(convoId, convoIdC));
+        assertEquals(3, deleted.size());
 
         assertEquals(messages,
                 mHelper.getNotificationChannel(PKG_O, UID_O, messages.getId(), false));
@@ -3542,7 +3550,7 @@
         assertEquals(channel2,
                 mHelper.getNotificationChannel(PKG_O, UID_O, channel2.getId(), true));
 
-        assertEquals(7, mLogger.getCalls().size());
+        assertEquals(9, mLogger.getCalls().size());
         assertEquals(
                 NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_CREATED,
                 mLogger.get(0).event);  // Channel messages
@@ -3563,12 +3571,20 @@
                 mLogger.get(4).event);  // Channel channel2 - Conversation A person calls
         assertEquals(
                 NotificationChannelLogger.NotificationChannelEvent
-                        .NOTIFICATION_CHANNEL_CONVERSATION_DELETED,
-                mLogger.get(5).event);  // Delete Channel channel - Conversation A person msgs
+                        .NOTIFICATION_CHANNEL_CONVERSATION_CREATED,
+                mLogger.get(5).event);  // Channel channel3 - Conversation C person msgs
         assertEquals(
                 NotificationChannelLogger.NotificationChannelEvent
                         .NOTIFICATION_CHANNEL_CONVERSATION_DELETED,
-                mLogger.get(6).event);  // Delete Channel channel2 - Conversation A person calls
+                mLogger.get(6).event);  // Delete Channel channel - Conversation A person msgs
+        assertEquals(
+                NotificationChannelLogger.NotificationChannelEvent
+                        .NOTIFICATION_CHANNEL_CONVERSATION_DELETED,
+                mLogger.get(7).event);  // Delete Channel channel2 - Conversation A person calls
+        assertEquals(
+                NotificationChannelLogger.NotificationChannelEvent
+                        .NOTIFICATION_CHANNEL_CONVERSATION_DELETED,
+                mLogger.get(8).event);  // Delete Channel channel3 - Conversation C person msgs
     }
 
     @Test
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/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