Merge "Update Ethiopic fonts to a variable format"
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/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 7a8d1a1..f66d12a 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -11,6 +11,7 @@
                libs/input/
                services/core/jni/
                services/incremental/
+               tests/
                tools/
 
 [Hook Scripts]
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/hardware/camera2/impl/FrameNumberTracker.java b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
index 27f8a61..7b6a457 100644
--- a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
+++ b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
@@ -37,12 +37,16 @@
     /** the completed frame number for each type of capture results */
     private long[] mCompletedFrameNumber = new long[CaptureRequest.REQUEST_TYPE_COUNT];
 
-    /** the skipped frame numbers that don't belong to each type of capture results */
-    private final LinkedList<Long>[] mSkippedOtherFrameNumbers =
+    /** the frame numbers that don't belong to each type of capture results and are yet to be seen
+     * through an updateTracker() call. Each list holds a list of frame numbers that should appear
+     * with request types other than that, to which the list corresponds.
+     */
+    private final LinkedList<Long>[] mPendingFrameNumbersWithOtherType =
             new LinkedList[CaptureRequest.REQUEST_TYPE_COUNT];
 
-    /** the skipped frame numbers that belong to each type of capture results */
-    private final LinkedList<Long>[] mSkippedFrameNumbers =
+    /** the frame numbers that belong to each type of capture results which should appear, but
+     * haven't yet.*/
+    private final LinkedList<Long>[] mPendingFrameNumbers =
             new LinkedList[CaptureRequest.REQUEST_TYPE_COUNT];
 
     /** frame number -> request type */
@@ -53,8 +57,8 @@
     public FrameNumberTracker() {
         for (int i = 0; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) {
             mCompletedFrameNumber[i] = CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED;
-            mSkippedOtherFrameNumbers[i] = new LinkedList<Long>();
-            mSkippedFrameNumbers[i] = new LinkedList<Long>();
+            mPendingFrameNumbersWithOtherType[i] = new LinkedList<Long>();
+            mPendingFrameNumbers[i] = new LinkedList<Long>();
         }
     }
 
@@ -66,29 +70,29 @@
             int requestType = (int) pair.getValue();
             Boolean removeError = false;
             if (errorFrameNumber == mCompletedFrameNumber[requestType] + 1) {
-                mCompletedFrameNumber[requestType] = errorFrameNumber;
                 removeError = true;
+            }
+            // The error frame number could have also either been in the pending list or one of the
+            // 'other' pending lists.
+            if (!mPendingFrameNumbers[requestType].isEmpty()) {
+                if (errorFrameNumber == mPendingFrameNumbers[requestType].element()) {
+                    mPendingFrameNumbers[requestType].remove();
+                    removeError = true;
+                }
             } else {
-                if (!mSkippedFrameNumbers[requestType].isEmpty()) {
-                    if (errorFrameNumber == mSkippedFrameNumbers[requestType].element()) {
-                        mCompletedFrameNumber[requestType] = errorFrameNumber;
-                        mSkippedFrameNumbers[requestType].remove();
+                for (int i = 1; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) {
+                    int otherType = (requestType + i) % CaptureRequest.REQUEST_TYPE_COUNT;
+                    if (!mPendingFrameNumbersWithOtherType[otherType].isEmpty() && errorFrameNumber
+                            == mPendingFrameNumbersWithOtherType[otherType].element()) {
+                        mPendingFrameNumbersWithOtherType[otherType].remove();
                         removeError = true;
-                    }
-                } else {
-                    for (int i = 1; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) {
-                        int otherType = (requestType + i) % CaptureRequest.REQUEST_TYPE_COUNT;
-                        if (!mSkippedOtherFrameNumbers[otherType].isEmpty() && errorFrameNumber
-                                == mSkippedOtherFrameNumbers[otherType].element()) {
-                            mCompletedFrameNumber[requestType] = errorFrameNumber;
-                            mSkippedOtherFrameNumbers[otherType].remove();
-                            removeError = true;
-                            break;
-                        }
+                        break;
                     }
                 }
             }
             if (removeError) {
+                mCompletedFrameNumber[requestType] = errorFrameNumber;
+                mPartialResults.remove(errorFrameNumber);
                 iter.remove();
             }
         }
@@ -182,7 +186,7 @@
      * It validates that all previous frames of the same category have arrived.
      *
      * If there is a gap since previous frame number of the same category, assume the frames in
-     * the gap are other categories and store them in the skipped frame number queue to check
+     * the gap are other categories and store them in the pending frame number queue to check
      * against when frames of those categories arrive.
      */
     private void updateCompletedFrameNumber(long frameNumber,
@@ -199,25 +203,29 @@
         if (frameNumber < maxOtherFrameNumberSeen) {
             // if frame number is smaller than completed frame numbers of other categories,
             // it must be:
-            // - the head of mSkippedFrameNumbers for this category, or
-            // - in one of other mSkippedOtherFrameNumbers
-            if (!mSkippedFrameNumbers[requestType].isEmpty()) {
-                // frame number must be head of current type of mSkippedFrameNumbers if
-                // mSkippedFrameNumbers isn't empty.
-                if (frameNumber < mSkippedFrameNumbers[requestType].element()) {
+            // - the head of mPendingFrameNumbers for this category, or
+            // - in one of other mPendingFrameNumbersWithOtherType
+            if (!mPendingFrameNumbers[requestType].isEmpty()) {
+                // frame number must be head of current type of mPendingFrameNumbers if
+                // mPendingFrameNumbers isn't empty.
+                Long pendingFrameNumberSameType = mPendingFrameNumbers[requestType].element();
+                if (frameNumber == pendingFrameNumberSameType) {
+                    // frame number matches the head of the pending frame number queue.
+                    // Do this before the inequality checks since this is likely to be the common
+                    // case.
+                    mPendingFrameNumbers[requestType].remove();
+                } else if (frameNumber < pendingFrameNumberSameType) {
                     throw new IllegalArgumentException("frame number " + frameNumber
                             + " is a repeat");
-                } else if (frameNumber > mSkippedFrameNumbers[requestType].element()) {
+                } else {
                     throw new IllegalArgumentException("frame number " + frameNumber
                             + " comes out of order. Expecting "
-                            + mSkippedFrameNumbers[requestType].element());
+                            + pendingFrameNumberSameType);
                 }
-                // frame number matches the head of the skipped frame number queue.
-                mSkippedFrameNumbers[requestType].remove();
             } else {
-                // frame number must be in one of the other mSkippedOtherFrameNumbers.
-                int index1 = mSkippedOtherFrameNumbers[otherType1].indexOf(frameNumber);
-                int index2 = mSkippedOtherFrameNumbers[otherType2].indexOf(frameNumber);
+                // frame number must be in one of the other mPendingFrameNumbersWithOtherType.
+                int index1 = mPendingFrameNumbersWithOtherType[otherType1].indexOf(frameNumber);
+                int index2 = mPendingFrameNumbersWithOtherType[otherType2].indexOf(frameNumber);
                 boolean inSkippedOther1 = index1 != -1;
                 boolean inSkippedOther2 = index2 != -1;
                 if (!(inSkippedOther1 ^ inSkippedOther2)) {
@@ -225,33 +233,39 @@
                             + " is a repeat or invalid");
                 }
 
-                // We know the category of frame numbers in skippedOtherFrameNumbers leading up
-                // to the current frame number. Move them into the correct skippedFrameNumbers.
+                // We know the category of frame numbers in pendingFrameNumbersWithOtherType leading
+                // up to the current frame number. The destination is the type which isn't the
+                // requestType* and isn't the src. Move them into the correct pendingFrameNumbers.
+                // * : This is since frameNumber is the first frame of requestType that we've
+                // received in the 'others' list, since for each request type frames come in order.
+                // All the frames before frameNumber are of the same type. They're not of
+                // 'requestType', neither of the type of the 'others' list they were found in. The
+                // remaining option is the 3rd type.
                 LinkedList<Long> srcList, dstList;
                 int index;
                 if (inSkippedOther1) {
-                    srcList = mSkippedOtherFrameNumbers[otherType1];
-                    dstList = mSkippedFrameNumbers[otherType2];
+                    srcList = mPendingFrameNumbersWithOtherType[otherType1];
+                    dstList = mPendingFrameNumbers[otherType2];
                     index = index1;
                 } else {
-                    srcList = mSkippedOtherFrameNumbers[otherType2];
-                    dstList = mSkippedFrameNumbers[otherType1];
+                    srcList = mPendingFrameNumbersWithOtherType[otherType2];
+                    dstList = mPendingFrameNumbers[otherType1];
                     index = index2;
                 }
                 for (int i = 0; i < index; i++) {
                     dstList.add(srcList.removeFirst());
                 }
 
-                // Remove current frame number from skippedOtherFrameNumbers
+                // Remove current frame number from pendingFrameNumbersWithOtherType
                 srcList.remove();
             }
         } else {
             // there is a gap of unseen frame numbers which should belong to the other
-            // 2 categories. Put all the skipped frame numbers in the queue.
+            // 2 categories. Put all the pending frame numbers in the queue.
             for (long i =
                     Math.max(maxOtherFrameNumberSeen, mCompletedFrameNumber[requestType]) + 1;
                     i < frameNumber; i++) {
-                mSkippedOtherFrameNumbers[requestType].add(i);
+                mPendingFrameNumbersWithOtherType[requestType].add(i);
             }
         }
 
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index df1f1b2..52ee04c 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -29,6 +29,7 @@
 import android.content.res.AssetFileDescriptor;
 import android.content.res.AssetManager;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Log;
 import android.widget.Toast;
 
@@ -43,11 +44,14 @@
 import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
-/** @hide */
+/**
+ * GraphicsEnvironment sets up necessary properties for the graphics environment of the
+ * application process.
+ *
+ * @hide
+ */
 public class GraphicsEnvironment {
 
     private static final GraphicsEnvironment sInstance = new GraphicsEnvironment();
@@ -64,27 +68,35 @@
     private static final String SYSTEM_DRIVER_NAME = "system";
     private static final String SYSTEM_DRIVER_VERSION_NAME = "";
     private static final long SYSTEM_DRIVER_VERSION_CODE = 0;
+
+    // System properties related to updatable graphics drivers.
     private static final String PROPERTY_GFX_DRIVER_PRODUCTION = "ro.gfx.driver.0";
     private static final String PROPERTY_GFX_DRIVER_PRERELEASE = "ro.gfx.driver.1";
     private static final String PROPERTY_GFX_DRIVER_BUILD_TIME = "ro.gfx.driver_build_time";
+
+    // Metadata flags within the <application> tag in the AndroidManifest.xml file.
     private static final String METADATA_DRIVER_BUILD_TIME =
             "com.android.graphics.driver.build_time";
     private static final String METADATA_DEVELOPER_DRIVER_ENABLE =
             "com.android.graphics.developerdriver.enable";
     private static final String METADATA_INJECT_LAYERS_ENABLE =
             "com.android.graphics.injectLayers.enable";
+
+    private static final String UPDATABLE_DRIVER_ALLOWLIST_ALL = "*";
+    private static final String UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt";
+
+    // ANGLE related properties.
     private static final String ANGLE_RULES_FILE = "a4a_rules.json";
     private static final String ANGLE_TEMP_RULES = "debug.angle.rules";
     private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID";
     private static final String ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE =
             "android.app.action.ANGLE_FOR_ANDROID_TOAST_MESSAGE";
     private static final String INTENT_KEY_A4A_TOAST_MESSAGE = "A4A Toast Message";
-    private static final String UPDATABLE_DRIVER_ALLOWLIST_ALL = "*";
-    private static final String UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt";
+
     private static final int VULKAN_1_0 = 0x00400000;
     private static final int VULKAN_1_1 = 0x00401000;
 
-    // UPDATABLE_DRIVER_ALL_APPS
+    // Values for UPDATABLE_DRIVER_ALL_APPS
     // 0: Default (Invalid values fallback to default as well)
     // 1: All apps use updatable production driver
     // 2: All apps use updatable prerelease driver
@@ -94,6 +106,15 @@
     private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER = 2;
     private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_OFF = 3;
 
+    // Values for GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE
+    private static final int ANGLE_GL_DRIVER_ALL_ANGLE_ON = 1;
+    private static final int ANGLE_GL_DRIVER_ALL_ANGLE_OFF = 0;
+
+    // Values for GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES
+    private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default";
+    private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle";
+    private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native";
+
     private ClassLoader mClassLoader;
     private String mLibrarySearchPaths;
     private String mLibraryPermittedPaths;
@@ -106,12 +127,15 @@
         final String packageName = context.getPackageName();
         final ApplicationInfo appInfoWithMetaData =
                 getAppInfoWithMetadata(context, pm, packageName);
+
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers");
         setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData);
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle");
         setupAngle(context, coreSettings, pm, packageName);
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "chooseDriver");
         if (!chooseDriver(context, coreSettings, pm, packageName, appInfoWithMetaData)) {
             setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME, SYSTEM_DRIVER_VERSION_CODE,
@@ -132,36 +156,30 @@
      */
     public static boolean shouldUseAngle(Context context, Bundle coreSettings,
             String packageName) {
-        if (packageName.isEmpty()) {
-            Log.v(TAG, "No package name available yet, ANGLE should not be used");
+        if (TextUtils.isEmpty(packageName)) {
+            Log.v(TAG, "No package name specified, ANGLE should not be used");
             return false;
         }
 
-        final String devOptIn = getDriverForPkg(context, coreSettings, packageName);
-        if (DEBUG) {
-            Log.v(TAG, "ANGLE Developer option for '" + packageName + "' "
-                    + "set to: '" + devOptIn + "'");
-        }
+        final String devOptIn = getDriverForPackage(context, coreSettings, packageName);
+        Log.v(TAG, "ANGLE Developer option for '" + packageName + "' "
+                + "set to: '" + devOptIn + "'");
 
-        // We only want to use ANGLE if the app is allowlisted or the developer has
+        // We only want to use ANGLE if the app is in the allowlist, or the developer has
         // explicitly chosen something other than default driver.
         // The allowlist will be generated by the ANGLE APK at both boot time and
         // ANGLE update time. It will only include apps mentioned in the rules file.
-        final boolean allowlisted = checkAngleAllowlist(context, coreSettings, packageName);
-        final boolean requested = devOptIn.equals(sDriverMap.get(OpenGlDriverChoice.ANGLE));
-        final boolean useAngle = (allowlisted || requested);
-        if (!useAngle) {
-            return false;
-        }
+        final boolean allowed = checkAngleAllowlist(context, coreSettings, packageName);
+        final boolean requested = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE);
 
-        if (allowlisted) {
+        if (allowed) {
             Log.v(TAG, "ANGLE allowlist includes " + packageName);
         }
         if (requested) {
             Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn);
         }
 
-        return true;
+        return allowed || requested;
     }
 
     private static int getVulkanVersion(PackageManager pm) {
@@ -315,23 +333,6 @@
         setLayerPaths(mClassLoader, layerPaths);
     }
 
-    enum OpenGlDriverChoice {
-        DEFAULT,
-        NATIVE,
-        ANGLE
-    }
-
-    private static final Map<OpenGlDriverChoice, String> sDriverMap = buildMap();
-    private static Map<OpenGlDriverChoice, String> buildMap() {
-        final Map<OpenGlDriverChoice, String> map = new HashMap<>();
-        map.put(OpenGlDriverChoice.DEFAULT, "default");
-        map.put(OpenGlDriverChoice.ANGLE, "angle");
-        map.put(OpenGlDriverChoice.NATIVE, "native");
-
-        return map;
-    }
-
-
     private static List<String> getGlobalSettingsString(ContentResolver contentResolver,
                                                         Bundle bundle,
                                                         String globalSetting) {
@@ -378,18 +379,25 @@
         return ai;
     }
 
-    private static String getDriverForPkg(Context context, Bundle bundle, String packageName) {
-        final String allUseAngle;
+    private static String getDriverForPackage(Context context, Bundle bundle, String packageName) {
+        final int allUseAngle;
         if (bundle != null) {
             allUseAngle =
-                    bundle.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE);
+                    bundle.getInt(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE);
         } else {
             ContentResolver contentResolver = context.getContentResolver();
-            allUseAngle = Settings.Global.getString(contentResolver,
-                    Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE);
+            allUseAngle = Settings.Global.getInt(contentResolver,
+                    Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE,
+                    ANGLE_GL_DRIVER_ALL_ANGLE_OFF);
         }
-        if ((allUseAngle != null) && allUseAngle.equals("1")) {
-            return sDriverMap.get(OpenGlDriverChoice.ANGLE);
+        if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) {
+            Log.v(TAG, "Turn on ANGLE for all applications.");
+            return ANGLE_GL_DRIVER_CHOICE_ANGLE;
+        }
+
+        // Make sure we have a good package name
+        if (TextUtils.isEmpty(packageName)) {
+            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
 
         final ContentResolver contentResolver = context.getContentResolver();
@@ -400,25 +408,21 @@
                 getGlobalSettingsString(contentResolver, bundle,
                         Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES);
 
-        // Make sure we have a good package name
-        if ((packageName == null) || (packageName.isEmpty())) {
-            return sDriverMap.get(OpenGlDriverChoice.DEFAULT);
-        }
         // Make sure we have good settings to use
         if (globalSettingsDriverPkgs.size() != globalSettingsDriverValues.size()) {
             Log.w(TAG,
                     "Global.Settings values are invalid: "
-                        + "globalSettingsDriverPkgs.size = "
+                        + "number of packages: "
                             + globalSettingsDriverPkgs.size() + ", "
-                        + "globalSettingsDriverValues.size = "
+                        + "number of values: "
                             + globalSettingsDriverValues.size());
-            return sDriverMap.get(OpenGlDriverChoice.DEFAULT);
+            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
 
         final int pkgIndex = getGlobalSettingsPkgIndex(packageName, globalSettingsDriverPkgs);
 
         if (pkgIndex < 0) {
-            return sDriverMap.get(OpenGlDriverChoice.DEFAULT);
+            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
 
         return globalSettingsDriverValues.get(pkgIndex);
@@ -446,27 +450,28 @@
     }
 
     /**
-     * Check for ANGLE debug package, but only for apps that can load them (dumpable)
+     * Check for ANGLE debug package, but only for apps that can load them.
+     * An application can load ANGLE debug package if it is a debuggable application, or
+     * the device is debuggable.
      */
     private String getAngleDebugPackage(Context context, Bundle coreSettings) {
-        if (isDebuggable()) {
-            String debugPackage;
-
-            if (coreSettings != null) {
-                debugPackage =
-                        coreSettings.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE);
-            } else {
-                ContentResolver contentResolver = context.getContentResolver();
-                debugPackage = Settings.Global.getString(contentResolver,
-                        Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE);
-            }
-
-            if ((debugPackage != null) && (!debugPackage.isEmpty())) {
-                return debugPackage;
-            }
+        if (!isDebuggable()) {
+            return "";
         }
+        final String debugPackage;
 
-        return "";
+        if (coreSettings != null) {
+            debugPackage =
+                    coreSettings.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE);
+        } else {
+            ContentResolver contentResolver = context.getContentResolver();
+            debugPackage = Settings.Global.getString(contentResolver,
+                    Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE);
+        }
+        if (TextUtils.isEmpty(debugPackage)) {
+            return "";
+        }
+        return debugPackage;
     }
 
     /**
@@ -491,7 +496,7 @@
 
         final String angleTempRules = SystemProperties.get(ANGLE_TEMP_RULES);
 
-        if ((angleTempRules == null) || angleTempRules.isEmpty()) {
+        if (TextUtils.isEmpty(angleTempRules)) {
             Log.v(TAG, "System property '" + ANGLE_TEMP_RULES + "' is not set or is empty");
             return false;
         }
@@ -583,11 +588,12 @@
      *
      * @param context
      * @param bundle
-     * @param packageName
+     * @param pm
+     * @param packageName - package name of the application.
      * @return true: ANGLE setup successfully
      *         false: ANGLE not setup (not on allowlist, ANGLE not present, etc.)
      */
-    public boolean setupAngle(Context context, Bundle bundle, PackageManager pm,
+    private boolean setupAngle(Context context, Bundle bundle, PackageManager pm,
             String packageName) {
 
         if (!shouldUseAngle(context, bundle, packageName)) {
@@ -612,18 +618,18 @@
         // Otherwise, check to see if ANGLE is properly installed
         if (angleInfo == null) {
             anglePkgName = getAnglePackageName(pm);
-            if (!anglePkgName.isEmpty()) {
-                Log.i(TAG, "ANGLE package enabled: " + anglePkgName);
-                try {
-                    // Production ANGLE libraries must be pre-installed as a system app
-                    angleInfo = pm.getApplicationInfo(anglePkgName,
-                            PackageManager.MATCH_SYSTEM_ONLY);
-                } catch (PackageManager.NameNotFoundException e) {
-                    Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed");
-                    return false;
-                }
-            } else {
-                Log.e(TAG, "Failed to find ANGLE package.");
+            if (TextUtils.isEmpty(anglePkgName)) {
+                Log.w(TAG, "Failed to find ANGLE package.");
+                return false;
+            }
+
+            Log.i(TAG, "ANGLE package enabled: " + anglePkgName);
+            try {
+                // Production ANGLE libraries must be pre-installed as a system app
+                angleInfo = pm.getApplicationInfo(anglePkgName,
+                        PackageManager.MATCH_SYSTEM_ONLY);
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed");
                 return false;
             }
         }
@@ -645,7 +651,7 @@
         // load a driver, GraphicsEnv::getShouldUseAngle() has seen the package name before
         // and can confidently answer yes/no based on the previously set developer
         // option value.
-        final String devOptIn = getDriverForPkg(context, bundle, packageName);
+        final String devOptIn = getDriverForPackage(context, bundle, packageName);
 
         if (setupAngleWithTempRulesFile(context, packageName, paths, devOptIn)) {
             // We setup ANGLE with a temp rules file, so we're done here.
@@ -730,18 +736,18 @@
         final boolean hasPrereleaseDriver = prereleaseDriver != null && !prereleaseDriver.isEmpty();
 
         if (!hasProductionDriver && !hasPrereleaseDriver) {
-            if (DEBUG) {
-                Log.v(TAG,
-                        "Neither updatable production driver nor prerelease driver is supported.");
-            }
+            Log.v(TAG, "Neither updatable production driver nor prerelease driver is supported.");
             return null;
         }
 
-        // To minimize risk of driver updates crippling the device beyond user repair, never use an
-        // updated driver for privileged or non-updated system apps. Presumably pre-installed apps
-        // were tested thoroughly with the pre-installed driver.
+        // To minimize risk of driver updates crippling the device beyond user repair, never use the
+        // updatable drivers for privileged or non-updated system apps. Presumably pre-installed
+        // apps were tested thoroughly with the system driver.
         if (ai.isPrivilegedApp() || (ai.isSystemApp() && !ai.isUpdatedSystemApp())) {
-            if (DEBUG) Log.v(TAG, "Ignoring driver package for privileged/non-updated system app.");
+            if (DEBUG) {
+                Log.v(TAG,
+                        "Ignore updatable driver package for privileged/non-updated system app.");
+            }
             return null;
         }
 
@@ -758,13 +764,13 @@
         // 6. UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST
         switch (coreSettings.getInt(Settings.Global.UPDATABLE_DRIVER_ALL_APPS, 0)) {
             case UPDATABLE_DRIVER_GLOBAL_OPT_IN_OFF:
-                if (DEBUG) Log.v(TAG, "updatable driver is turned off on this device.");
+                Log.v(TAG, "The updatable driver is turned off on this device.");
                 return null;
             case UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRODUCTION_DRIVER:
-                if (DEBUG) Log.v(TAG, "All apps opt in to use updatable production driver.");
+                Log.v(TAG, "All apps opt in to use updatable production driver.");
                 return hasProductionDriver ? productionDriver : null;
             case UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER:
-                if (DEBUG) Log.v(TAG, "All apps opt in to use updatable prerelease driver.");
+                Log.v(TAG, "All apps opt in to use updatable prerelease driver.");
                 return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null;
             case UPDATABLE_DRIVER_GLOBAL_OPT_IN_DEFAULT:
             default:
@@ -775,20 +781,20 @@
         if (getGlobalSettingsString(null, coreSettings,
                                     Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS)
                         .contains(appPackageName)) {
-            if (DEBUG) Log.v(TAG, "App opts out for updatable production driver.");
+            Log.v(TAG, "App opts out for updatable production driver.");
             return null;
         }
 
         if (getGlobalSettingsString(
                     null, coreSettings, Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS)
                         .contains(appPackageName)) {
-            if (DEBUG) Log.v(TAG, "App opts in for updatable prerelease driver.");
+            Log.v(TAG, "App opts in for updatable prerelease driver.");
             return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null;
         }
 
         // Early return here since the rest logic is only for updatable production Driver.
         if (!hasProductionDriver) {
-            if (DEBUG) Log.v(TAG, "Updatable production driver is not supported on the device.");
+            Log.v(TAG, "Updatable production driver is not supported on the device.");
             return null;
         }
 
@@ -801,7 +807,7 @@
                                         Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST);
         if (!isOptIn && allowlist.indexOf(UPDATABLE_DRIVER_ALLOWLIST_ALL) != 0
                 && !allowlist.contains(appPackageName)) {
-            if (DEBUG) Log.v(TAG, "App is not on the allowlist for updatable production driver.");
+            Log.v(TAG, "App is not on the allowlist for updatable production driver.");
             return null;
         }
 
@@ -811,7 +817,7 @@
                 && getGlobalSettingsString(
                         null, coreSettings, Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST)
                            .contains(appPackageName)) {
-            if (DEBUG) Log.v(TAG, "App is on the denylist for updatable production driver.");
+            Log.v(TAG, "App is on the denylist for updatable production driver.");
             return null;
         }
 
@@ -834,7 +840,7 @@
             driverPackageInfo = pm.getPackageInfo(driverPackageName,
                     PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA);
         } catch (PackageManager.NameNotFoundException e) {
-            Log.w(TAG, "driver package '" + driverPackageName + "' not installed");
+            Log.w(TAG, "updatable driver package '" + driverPackageName + "' not installed");
             return false;
         }
 
@@ -843,7 +849,7 @@
         final ApplicationInfo driverAppInfo = driverPackageInfo.applicationInfo;
         if (driverAppInfo.targetSdkVersion < Build.VERSION_CODES.O) {
             if (DEBUG) {
-                Log.w(TAG, "updated driver package is not known to be compatible with O");
+                Log.w(TAG, "updatable driver package is not compatible with O");
             }
             return false;
         }
@@ -853,7 +859,7 @@
             if (DEBUG) {
                 // This is the normal case for the pre-installed empty driver package, don't spam
                 if (driverAppInfo.isUpdatedSystemApp()) {
-                    Log.w(TAG, "updated driver package has no compatible native libraries");
+                    Log.w(TAG, "Updatable driver package has no compatible native libraries");
                 }
             }
             return false;
@@ -867,11 +873,8 @@
           .append(abi);
         final String paths = sb.toString();
         final String sphalLibraries = getSphalLibraries(context, driverPackageName);
-        if (DEBUG) {
-            Log.v(TAG,
-                    "gfx driver package search path: " + paths
-                            + ", required sphal libraries: " + sphalLibraries);
-        }
+        Log.v(TAG, "Updatable driver package search path: " + paths
+                + ", required sphal libraries: " + sphalLibraries);
         setDriverPathAndSphalLibraries(paths, sphalLibraries);
 
         if (driverAppInfo.metaData == null) {
@@ -880,7 +883,7 @@
 
         String driverBuildTime = driverAppInfo.metaData.getString(METADATA_DRIVER_BUILD_TIME);
         if (driverBuildTime == null || driverBuildTime.length() <= 1) {
-            Log.v(TAG, "com.android.graphics.driver.build_time is not set");
+            Log.w(TAG, "com.android.graphics.driver.build_time is not set");
             driverBuildTime = "L0";
         }
         // driver_build_time in the meta-data is in "L<Unix epoch timestamp>" format. e.g. L123456.
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index b654707..35e7bad 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -235,6 +235,21 @@
     }
 
     /**
+     * Returns the list of declared instances for an interface.
+     *
+     * @return true if the service is declared somewhere (eg. VINTF manifest) and
+     * waitForService should always be able to return the service.
+     */
+    public static String[] getDeclaredInstances(@NonNull String iface) {
+        try {
+            return getIServiceManager().getDeclaredInstances(iface);
+        } catch (RemoteException e) {
+            Log.e(TAG, "error in getDeclaredInstances", e);
+            return null;
+        }
+    }
+
+    /**
      * Returns the specified service from the service manager.
      *
      * If the service is not running, servicemanager will attempt to start it, and this function
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 91b56fb..b70b6b5 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -90,6 +90,10 @@
         return mServiceManager.isDeclared(name);
     }
 
+    public String[] getDeclaredInstances(String iface) throws RemoteException {
+        return mServiceManager.getDeclaredInstances(iface);
+    }
+
     public void registerClientCallback(String name, IBinder service, IClientCallback cb)
             throws RemoteException {
         throw new RemoteException();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a7055ec..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 a3c95a9..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 4cfc205..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/errorprone/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceChecker.java
new file mode 100644
index 0000000..d524316
--- /dev/null
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceChecker.java
@@ -0,0 +1,123 @@
+/*
+ * 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.google.errorprone.bugpatterns.android;
+
+import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
+import static com.google.errorprone.matchers.Matchers.allOf;
+import static com.google.errorprone.matchers.Matchers.enclosingClass;
+import static com.google.errorprone.matchers.Matchers.enclosingMethod;
+import static com.google.errorprone.matchers.Matchers.instanceMethod;
+import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
+import static com.google.errorprone.matchers.Matchers.methodInvocation;
+import static com.google.errorprone.matchers.Matchers.methodIsNamed;
+
+import com.google.auto.service.AutoService;
+import com.google.errorprone.BugPattern;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.bugpatterns.BugChecker;
+import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
+import com.google.errorprone.matchers.Description;
+import com.google.errorprone.matchers.Matcher;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree;
+
+/**
+ * Parcelable data can be transported in many ways (some of which can be very
+ * inefficient) so this checker guides developers towards using high-performance
+ * best-practices.
+ */
+@AutoService(BugChecker.class)
+@BugPattern(
+    name = "AndroidFrameworkParcelablePerformance",
+    summary = "Verifies Parcelable performance best-practices",
+    severity = WARNING)
+public final class ParcelablePerformanceChecker extends BugChecker
+        implements MethodInvocationTreeMatcher {
+    private static final Matcher<Tree> INSIDE_WRITE_TO_PARCEL = allOf(
+            enclosingClass(isSubtypeOf("android.os.Parcelable")),
+            enclosingMethod(methodIsNamed("writeToParcel")));
+
+    private static final Matcher<ExpressionTree> WRITE_STRING = methodInvocation(
+            instanceMethod().onExactClass("android.os.Parcel").named("writeString"));
+    private static final Matcher<ExpressionTree> WRITE_STRING_ARRAY = methodInvocation(
+            instanceMethod().onExactClass("android.os.Parcel").named("writeStringArray"));
+
+    private static final Matcher<ExpressionTree> WRITE_VALUE = methodInvocation(
+            instanceMethod().onExactClass("android.os.Parcel").named("writeValue"));
+    private static final Matcher<ExpressionTree> WRITE_PARCELABLE = methodInvocation(
+            instanceMethod().onExactClass("android.os.Parcel").named("writeParcelable"));
+
+    private static final Matcher<ExpressionTree> WRITE_LIST = methodInvocation(
+            instanceMethod().onExactClass("android.os.Parcel").named("writeList"));
+    private static final Matcher<ExpressionTree> WRITE_PARCELABLE_LIST = methodInvocation(
+            instanceMethod().onExactClass("android.os.Parcel").named("writeParcelableList"));
+    private static final Matcher<ExpressionTree> WRITE_PARCELABLE_ARRAY = methodInvocation(
+            instanceMethod().onExactClass("android.os.Parcel").named("writeParcelableArray"));
+
+    @Override
+    public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
+        if (INSIDE_WRITE_TO_PARCEL.matches(tree, state)) {
+            if (WRITE_STRING.matches(tree, state)) {
+                return buildDescription(tree)
+                        .setMessage("Recommended to use 'writeString8()' to improve "
+                                + "efficiency; sending as UTF-8 can double throughput")
+                        .build();
+            }
+            if (WRITE_STRING_ARRAY.matches(tree, state)) {
+                return buildDescription(tree)
+                        .setMessage("Recommended to use 'writeString8Array()' to improve "
+                                + "efficiency; sending as UTF-8 can double throughput")
+                        .build();
+            }
+
+            if (WRITE_VALUE.matches(tree, state)) {
+                return buildDescription(tree)
+                        .setMessage("Recommended to use strongly-typed methods to improve "
+                                + "efficiency; saves 4 bytes for type and overhead of "
+                                + "Parcelable class name")
+                        .build();
+            }
+            if (WRITE_PARCELABLE.matches(tree, state)) {
+                return buildDescription(tree)
+                        .setMessage("Recommended to use 'item.writeToParcel()' to improve "
+                                + "efficiency; saves overhead of Parcelable class name")
+                        .build();
+            }
+
+            if (WRITE_LIST.matches(tree, state)) {
+                return buildDescription(tree)
+                        .setMessage("Recommended to use 'writeTypedList()' to improve "
+                                + "efficiency; saves overhead of repeated Parcelable class name")
+                        .build();
+            }
+            if (WRITE_PARCELABLE_LIST.matches(tree, state)) {
+                return buildDescription(tree)
+                        .setMessage("Recommended to use 'writeTypedList()' to improve "
+                                + "efficiency; saves overhead of repeated Parcelable class name")
+                        .build();
+            }
+            if (WRITE_PARCELABLE_ARRAY.matches(tree, state)) {
+                return buildDescription(tree)
+                        .setMessage("Recommended to use 'writeTypedArray()' to improve "
+                                + "efficiency; saves overhead of repeated Parcelable class name")
+                        .build();
+            }
+        }
+        return Description.NO_MATCH;
+    }
+}
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceCheckerTest.java
new file mode 100644
index 0000000..75c76e3
--- /dev/null
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceCheckerTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.google.errorprone.bugpatterns.android;
+
+import com.google.errorprone.CompilationTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ParcelablePerformanceCheckerTest {
+    private CompilationTestHelper compilationHelper;
+
+    @Before
+    public void setUp() {
+        compilationHelper = CompilationTestHelper.newInstance(
+                ParcelablePerformanceChecker.class, getClass());
+    }
+
+    @Test
+    public void testString() {
+        compilationHelper
+                .addSourceFile("/android/os/Parcel.java")
+                .addSourceFile("/android/os/Parcelable.java")
+                .addSourceLines("FooInfo.java",
+                        "import android.os.Parcel;",
+                        "import android.os.Parcelable;",
+                        "public class FooInfo implements Parcelable {",
+                        "  public void writeToParcel(Parcel dest, int flags) {",
+                        "    // BUG: Diagnostic contains:",
+                        "    dest.writeString(toString());",
+                        "    dest.writeString8(toString());",
+                        "    // BUG: Diagnostic contains:",
+                        "    dest.writeStringArray(new String[0]);",
+                        "    dest.writeString8Array(new String[0]);",
+                        "  }",
+                        "}")
+                .doTest();
+    }
+
+    @Test
+    public void testSingle() {
+        compilationHelper
+                .addSourceFile("/android/os/Parcel.java")
+                .addSourceFile("/android/os/Parcelable.java")
+                .addSourceLines("FooInfo.java",
+                        "import android.os.Parcel;",
+                        "import android.os.Parcelable;",
+                        "public class FooInfo implements Parcelable {",
+                        "  public void writeToParcel(Parcel dest, int flags) {",
+                        "    // BUG: Diagnostic contains:",
+                        "    dest.writeValue(this);",
+                        "    this.writeToParcel(dest, flags);",
+                        "    // BUG: Diagnostic contains:",
+                        "    dest.writeParcelable(this, flags);",
+                        "    this.writeToParcel(dest, flags);",
+                        "  }",
+                        "}")
+                .doTest();
+    }
+
+    @Test
+    public void testList() {
+        compilationHelper
+                .addSourceFile("/android/os/Parcel.java")
+                .addSourceFile("/android/os/Parcelable.java")
+                .addSourceLines("FooInfo.java",
+                        "import android.os.Parcel;",
+                        "import android.os.Parcelable;",
+                        "import java.util.List;",
+                        "import java.util.ArrayList;",
+                        "public class FooInfo implements Parcelable {",
+                        "  public void writeToParcel(Parcel dest, int flags) {",
+                        "    List<Parcelable> list = new ArrayList<Parcelable>();",
+                        "    Parcelable[] array = new Parcelable[0];",
+                        "    // BUG: Diagnostic contains:",
+                        "    dest.writeList(list);",
+                        "    dest.writeTypedList(list, flags);",
+                        "    // BUG: Diagnostic contains:",
+                        "    dest.writeParcelableList(list, flags);",
+                        "    dest.writeTypedList(list, flags);",
+                        "    // BUG: Diagnostic contains:",
+                        "    dest.writeParcelableArray(array, flags);",
+                        "    dest.writeTypedArray(array, flags);",
+                        "  }",
+                        "}")
+                .doTest();
+    }
+}
diff --git a/errorprone/tests/res/android/os/Parcel.java b/errorprone/tests/res/android/os/Parcel.java
new file mode 100644
index 0000000..bafa236
--- /dev/null
+++ b/errorprone/tests/res/android/os/Parcel.java
@@ -0,0 +1,57 @@
+/*
+ * 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 java.util.List;
+
+public class Parcel {
+    public void writeString(String val) {
+        throw new UnsupportedOperationException();
+    }
+    public void writeString8(String val) {
+        throw new UnsupportedOperationException();
+    }
+    public final void writeStringArray(String[] val) {
+        throw new UnsupportedOperationException();
+    }
+    public final void writeString8Array(String[] val) {
+        throw new UnsupportedOperationException();
+    }
+
+    public final void writeValue(Object v) {
+        throw new UnsupportedOperationException();
+    }
+    public final void writeParcelable(Parcelable p, int flags) {
+        throw new UnsupportedOperationException();
+    }
+
+    public final void writeList(List val) {
+        throw new UnsupportedOperationException();
+    }
+    public final <T extends Parcelable> void writeParcelableList(List<T> val, int flags) {
+        throw new UnsupportedOperationException();
+    }
+    public <T extends Parcelable> void writeTypedList(List<T> val, int flags) {
+        throw new UnsupportedOperationException();
+    }
+    public final <T extends Parcelable> void writeParcelableArray(T[] value, int flags) {
+        throw new UnsupportedOperationException();
+    }
+    public final <T extends Parcelable> void writeTypedArray(T[] val, int flags) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java b/errorprone/tests/res/android/os/Parcelable.java
similarity index 60%
copy from services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
copy to errorprone/tests/res/android/os/Parcelable.java
index e0806ff..217690d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
+++ b/errorprone/tests/res/android/os/Parcelable.java
@@ -14,14 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.server.biometrics.sensors.fingerprint;
+package android.os;
 
-/**
- * Interface for under-display fingerprint sensors.
- * {@link com.android.server.biometrics.sensors.ClientMonitor} subclass that require knowledge of
- * finger position (e.g. enroll, authenticate) should implement this.
- */
-public interface Udfps {
-    void onFingerDown(int x, int y, float minor, float major);
-    void onFingerUp();
+public interface Parcelable {
+    public void writeToParcel(Parcel dest, int flags);
 }
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/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 e7e33a5..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/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
new file mode 100644
index 0000000..27cb4f6
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -0,0 +1,71 @@
+/*
+ * 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.shared.system;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.view.View;
+
+import com.android.internal.jank.InteractionJankMonitor;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public final class InteractionJankMonitorWrapper {
+    // Launcher journeys.
+    public static final int CUJ_APP_LAUNCH_FROM_RECENTS =
+            InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS;
+    public static final int CUJ_APP_LAUNCH_FROM_ICON =
+            InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON;
+    public static final int CUJ_APP_CLOSE_TO_HOME =
+            InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME;
+    public static final int CUJ_APP_CLOSE_TO_PIP =
+            InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP;
+    public static final int CUJ_QUICK_SWITCH =
+            InteractionJankMonitor.CUJ_LAUNCHER_QUICK_SWITCH;
+
+    @IntDef({
+            CUJ_APP_LAUNCH_FROM_RECENTS,
+            CUJ_APP_LAUNCH_FROM_ICON,
+            CUJ_APP_CLOSE_TO_HOME,
+            CUJ_APP_CLOSE_TO_PIP,
+            CUJ_QUICK_SWITCH,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CujType {
+    }
+
+    public static void init(@NonNull View view) {
+        InteractionJankMonitor.getInstance().init(view);
+    }
+
+    public static boolean begin(@CujType int cujType) {
+        return InteractionJankMonitor.getInstance().begin(cujType);
+    }
+
+    public static boolean begin(@CujType int cujType, long timeout) {
+        return InteractionJankMonitor.getInstance().begin(cujType, timeout);
+    }
+
+    public static boolean end(@CujType int cujType) {
+        return InteractionJankMonitor.getInstance().end(cujType);
+    }
+
+    public static boolean cancel(@CujType int cujType) {
+        return InteractionJankMonitor.getInstance().cancel(cujType);
+    }
+}
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..7fa4311 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/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index cbce4d8..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;
 
@@ -520,6 +521,7 @@
     /**
      * See {@link NotifCallback}.
      */
+    @Override
     public void addNotifCallback(NotifCallback callback) {
         mCallbacks.add(callback);
     }
@@ -701,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;
     }
@@ -709,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
@@ -726,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();
     }
 
@@ -956,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) {
@@ -988,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)
@@ -1022,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);
@@ -1042,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;
@@ -1075,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;
@@ -1095,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();
@@ -1162,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();
@@ -1209,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);
         }
@@ -1457,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;
@@ -1589,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/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/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/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 95edfe3..d7a8202 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -29,6 +29,7 @@
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.FooterView;
@@ -687,15 +688,27 @@
             AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
         float childrenOnTop = 0.0f;
+
+        int topHunIndex = -1;
+        for (int i = 0; i < childCount; i++) {
+            ExpandableView child = algorithmState.visibleChildren.get(i);
+            if (child instanceof ActivatableNotificationView
+                    && (child.isAboveShelf() || child.showingPulsing())) {
+                topHunIndex = i;
+                break;
+            }
+        }
+
         for (int i = childCount - 1; i >= 0; i--) {
             childrenOnTop = updateChildZValue(i, childrenOnTop,
-                    algorithmState, ambientState);
+                    algorithmState, ambientState, i == topHunIndex);
         }
     }
 
     protected float updateChildZValue(int i, float childrenOnTop,
             StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+            AmbientState ambientState,
+            boolean shouldElevateHun) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
         ExpandableViewState childViewState = child.getViewState();
         int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
@@ -713,8 +726,7 @@
             }
             childViewState.zTranslation = baseZ
                     + childrenOnTop * zDistanceBetweenElements;
-        } else if (child == ambientState.getTrackedHeadsUpRow()
-                || (i == 0 && (child.isAboveShelf() || child.showingPulsing()))) {
+        } else if (shouldElevateHun) {
             // In case this is a new view that has never been measured before, we don't want to
             // elevate if we are currently expanded more then the notification
             int shelfHeight = ambientState.getShelf() == null ? 0 :
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..c7aa780 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/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/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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f1561cab..7ad0f21 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -297,6 +297,7 @@
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
     //   and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
     private static final int MSG_DISABLE_AUDIO_FOR_UID = 100;
+    private static final int MSG_INIT_STREAMS_VOLUMES = 101;
     // end of messages handled under wakelock
 
     // retry delay in case of failure to indicate system ready to AudioFlinger
@@ -822,7 +823,34 @@
         updateStreamVolumeAlias(false /*updateVolumes*/, TAG);
         readPersistedSettings();
         readUserRestrictions();
-        mSettingsObserver = new SettingsObserver();
+
+        mPlaybackMonitor =
+                new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
+        mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
+
+        readAndSetLowRamDevice();
+
+        mIsCallScreeningModeSupported = AudioSystem.isCallScreeningModeSupported();
+
+        if (mSystemServer.isPrivileged()) {
+            LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal());
+
+            mUserManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener);
+
+            mRecordMonitor.initMonitor();
+        }
+
+        mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false);
+
+        // done with service initialization, continue additional work in our Handler thread
+        queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES,
+                0 /* arg1 */,  0 /* arg2 */, null /* obj */,  0 /* delay */);
+    }
+
+    /**
+     * Called by handling of MSG_INIT_STREAMS_VOLUMES
+     */
+    private void onInitStreamsAndVolumes() {
         createStreamStates();
 
         // must be called after createStreamStates() as it uses MUSIC volume as default if no
@@ -833,53 +861,11 @@
         // relies on audio policy having correct ranges for volume indexes.
         mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
 
-        mPlaybackMonitor =
-                new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
-
-        mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
-
-        readAndSetLowRamDevice();
-
-        mIsCallScreeningModeSupported = AudioSystem.isCallScreeningModeSupported();
-
         // Call setRingerModeInt() to apply correct mute
         // state on streams affected by ringer mode.
         mRingerAndZenModeMutedStreams = 0;
         setRingerModeInt(getRingerModeInternal(), false);
 
-        // Register for device connection intent broadcasts.
-        IntentFilter intentFilter =
-                new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
-        intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
-        intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
-        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
-        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
-        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
-        intentFilter.addAction(Intent.ACTION_USER_BACKGROUND);
-        intentFilter.addAction(Intent.ACTION_USER_FOREGROUND);
-        intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
-        intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
-        intentFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
-
-        intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
-        mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false);
-        if (mMonitorRotation) {
-            RotationHelper.init(mContext, mAudioHandler);
-        }
-
-        intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
-        intentFilter.addAction(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
-
-        context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
-
-        if (mSystemServer.isPrivileged()) {
-            LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal());
-
-            mUserManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener);
-
-            mRecordMonitor.initMonitor();
-        }
-
         final float[] preScale = new float[3];
         preScale[0] = mContext.getResources().getFraction(
                 com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index1,
@@ -896,10 +882,47 @@
             }
         }
 
+        initExternalEventReceivers();
+
         // check on volume initialization
         checkVolumeRangeInitialization("AudioService()");
     }
 
+    /**
+     * Initialize intent receives and settings observers for this service.
+     * Must be called after createStreamStates() as the handling of some events
+     * may affect or need volumes, e.g. BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED
+     * (for intent receiver), or Settings.Global.ZEN_MODE (for settings observer)
+     */
+    private void initExternalEventReceivers() {
+        mSettingsObserver = new SettingsObserver();
+
+        // Register for device connection intent broadcasts.
+        IntentFilter intentFilter =
+                new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
+        intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
+        intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
+        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+        intentFilter.addAction(Intent.ACTION_USER_BACKGROUND);
+        intentFilter.addAction(Intent.ACTION_USER_FOREGROUND);
+        intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+        intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+        intentFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
+
+        intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+        if (mMonitorRotation) {
+            RotationHelper.init(mContext, mAudioHandler);
+        }
+
+        intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
+        intentFilter.addAction(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
+
+        mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
+
+    }
+
     public void systemReady() {
         sendMsg(mAudioHandler, MSG_SYSTEM_READY, SENDMSG_QUEUE,
                 0, 0, null, 0);
@@ -2593,7 +2616,7 @@
 
     // StreamVolumeCommand contains the information needed to defer the process of
     // setStreamVolume() in case the user has to acknowledge the safe volume warning message.
-    class StreamVolumeCommand {
+    static class StreamVolumeCommand {
         public final int mStreamType;
         public final int mIndex;
         public final int mFlags;
@@ -2612,7 +2635,7 @@
                     .append(mIndex).append(",flags=").append(mFlags).append(",device=")
                     .append(mDevice).append('}').toString();
         }
-    };
+    }
 
     private int getNewRingerMode(int stream, int index, int flags) {
         // setRingerMode does nothing if the device is single volume,so the value would be unchanged
@@ -3321,7 +3344,7 @@
     }
 
     private int mRmtSbmxFullVolRefCount = 0;
-    private ArrayList<RmtSbmxFullVolDeathHandler> mRmtSbmxFullVolDeathHandlers =
+    private final ArrayList<RmtSbmxFullVolDeathHandler> mRmtSbmxFullVolDeathHandlers =
             new ArrayList<RmtSbmxFullVolDeathHandler>();
 
     public void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb) {
@@ -5882,7 +5905,6 @@
         private final Intent mStreamDevicesChanged;
 
         private VolumeStreamState(String settingName, int streamType) {
-
             mVolumeIndexSettingName = settingName;
 
             mStreamType = streamType;
@@ -6658,6 +6680,11 @@
                     mAudioEventWakeLock.release();
                     break;
 
+                case MSG_INIT_STREAMS_VOLUMES:
+                    onInitStreamsAndVolumes();
+                    mAudioEventWakeLock.release();
+                    break;
+
                 case MSG_CHECK_MUSIC_ACTIVE:
                     onCheckMusicActive((String) msg.obj);
                     break;
@@ -9216,7 +9243,7 @@
         }
     }
 
-    private HashMap<IBinder, AsdProxy> mAudioServerStateListeners =
+    private final HashMap<IBinder, AsdProxy> mAudioServerStateListeners =
             new HashMap<IBinder, AsdProxy>();
 
     private void checkMonitorAudioServerStatePermission() {
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/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 99dc58e..14b3478 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -77,6 +77,7 @@
 import android.net.ipsec.ike.IkeSession;
 import android.net.ipsec.ike.IkeSessionCallback;
 import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.os.Binder;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
@@ -142,6 +143,7 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -2301,7 +2303,7 @@
         void onChildTransformCreated(
                 @NonNull Network network, @NonNull IpSecTransform transform, int direction);
 
-        void onSessionLost(@NonNull Network network);
+        void onSessionLost(@NonNull Network network, @Nullable Exception exception);
     }
 
     /**
@@ -2458,7 +2460,7 @@
                 networkAgent.sendLinkProperties(lp);
             } catch (Exception e) {
                 Log.d(TAG, "Error in ChildOpened for network " + network, e);
-                onSessionLost(network);
+                onSessionLost(network, e);
             }
         }
 
@@ -2488,7 +2490,7 @@
                 mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
             } catch (IOException e) {
                 Log.d(TAG, "Transform application failed for network " + network, e);
-                onSessionLost(network);
+                onSessionLost(network, e);
             }
         }
 
@@ -2546,11 +2548,20 @@
                     Log.d(TAG, "Ike Session started for network " + network);
                 } catch (Exception e) {
                     Log.i(TAG, "Setup failed for network " + network + ". Aborting", e);
-                    onSessionLost(network);
+                    onSessionLost(network, e);
                 }
             });
         }
 
+        /** Marks the state as FAILED, and disconnects. */
+        private void markFailedAndDisconnect(Exception exception) {
+            synchronized (Vpn.this) {
+                updateState(DetailedState.FAILED, exception.getMessage());
+            }
+
+            disconnectVpnRunner();
+        }
+
         /**
          * Handles loss of a session
          *
@@ -2560,7 +2571,7 @@
          * <p>This method MUST always be called on the mExecutor thread in order to ensure
          * consistency of the Ikev2VpnRunner fields.
          */
-        public void onSessionLost(@NonNull Network network) {
+        public void onSessionLost(@NonNull Network network, @Nullable Exception exception) {
             if (!isActiveNetwork(network)) {
                 Log.d(TAG, "onSessionLost() called for obsolete network " + network);
 
@@ -2572,6 +2583,27 @@
                 return;
             }
 
+            if (exception instanceof IkeProtocolException) {
+                final IkeProtocolException ikeException = (IkeProtocolException) exception;
+
+                switch (ikeException.getErrorType()) {
+                    case IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN: // Fallthrough
+                    case IkeProtocolException.ERROR_TYPE_INVALID_KE_PAYLOAD: // Fallthrough
+                    case IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED: // Fallthrough
+                    case IkeProtocolException.ERROR_TYPE_SINGLE_PAIR_REQUIRED: // Fallthrough
+                    case IkeProtocolException.ERROR_TYPE_FAILED_CP_REQUIRED: // Fallthrough
+                    case IkeProtocolException.ERROR_TYPE_TS_UNACCEPTABLE:
+                        // All the above failures are configuration errors, and are terminal
+                        markFailedAndDisconnect(exception);
+                        return;
+                    // All other cases possibly recoverable.
+                }
+            } else if (exception instanceof IllegalArgumentException) {
+                // Failed to build IKE/ChildSessionParams; fatal profile configuration error
+                markFailedAndDisconnect(exception);
+                return;
+            }
+
             mActiveNetwork = null;
 
             // Close all obsolete state, but keep VPN alive incase a usable network comes up.
@@ -2621,12 +2653,18 @@
         }
 
         /**
-         * Cleans up all Ikev2VpnRunner internal state
+         * Disconnects and shuts down this VPN.
+         *
+         * <p>This method resets all internal Ikev2VpnRunner state, but unless called via
+         * VpnRunner#exit(), this Ikev2VpnRunner will still be listed as the active VPN of record
+         * until the next VPN is started, or the Ikev2VpnRunner is explicitly exited. This is
+         * necessary to ensure that the detailed state is shown in the Settings VPN menus; if the
+         * active VPN is cleared, Settings VPNs will not show the resultant state or errors.
          *
          * <p>This method MUST always be called on the mExecutor thread in order to ensure
          * consistency of the Ikev2VpnRunner fields.
          */
-        private void shutdownVpnRunner() {
+        private void disconnectVpnRunner() {
             mActiveNetwork = null;
             mIsRunning = false;
 
@@ -2640,9 +2678,13 @@
 
         @Override
         public void exitVpnRunner() {
-            mExecutor.execute(() -> {
-                shutdownVpnRunner();
-            });
+            try {
+                mExecutor.execute(() -> {
+                    disconnectVpnRunner();
+                });
+            } catch (RejectedExecutionException ignored) {
+                // The Ikev2VpnRunner has already shut down.
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
index 103f659..62630300 100644
--- a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
+++ b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
@@ -270,13 +270,13 @@
         @Override
         public void onClosed() {
             Log.d(mTag, "IkeClosed for network " + mNetwork);
-            mCallback.onSessionLost(mNetwork); // Server requested session closure. Retry?
+            mCallback.onSessionLost(mNetwork, null); // Server requested session closure. Retry?
         }
 
         @Override
         public void onClosedExceptionally(@NonNull IkeException exception) {
             Log.d(mTag, "IkeClosedExceptionally for network " + mNetwork, exception);
-            mCallback.onSessionLost(mNetwork);
+            mCallback.onSessionLost(mNetwork, exception);
         }
 
         @Override
@@ -306,13 +306,13 @@
         @Override
         public void onClosed() {
             Log.d(mTag, "ChildClosed for network " + mNetwork);
-            mCallback.onSessionLost(mNetwork);
+            mCallback.onSessionLost(mNetwork, null);
         }
 
         @Override
         public void onClosedExceptionally(@NonNull IkeException exception) {
             Log.d(mTag, "ChildClosedExceptionally for network " + mNetwork, exception);
-            mCallback.onSessionLost(mNetwork);
+            mCallback.onSessionLost(mNetwork, exception);
         }
 
         @Override
@@ -349,7 +349,7 @@
         @Override
         public void onLost(@NonNull Network network) {
             Log.d(mTag, "Tearing down; lost network: " + network);
-            mCallback.onSessionLost(network);
+            mCallback.onSessionLost(network, null);
         }
     }
 
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 69a7ce9..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/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b451eaf1..3e7304b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -11291,6 +11291,8 @@
                 mSettings.addRenamedPackageLPw(parsedPackage.getRealPackage(),
                         originalPkgSetting.name);
                 mTransferredPackages.add(originalPkgSetting.name);
+            } else {
+                mSettings.removeRenamedPackageLPw(parsedPackage.getPackageName());
             }
         }
         if (pkgSetting.sharedUser != null) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index bae36b2a..a922d76 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -485,6 +485,10 @@
         return mRenamedPackages.put(pkgName, origPkgName);
     }
 
+    void removeRenamedPackageLPw(String pkgName) {
+        mRenamedPackages.remove(pkgName);
+    }
+
     public boolean canPropagatePermissionToInstantApp(String permName) {
         return mPermissions.canPropagatePermissionToInstantApp(permName);
     }
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
diff --git a/tests/SurfaceViewBufferTests/Android.bp b/tests/SurfaceViewBufferTests/Android.bp
new file mode 100644
index 0000000..647da2a
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/Android.bp
@@ -0,0 +1,58 @@
+// 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: "SurfaceViewBufferTests",
+    srcs: ["**/*.java","**/*.kt"],
+    manifest: "AndroidManifest.xml",
+    test_config: "AndroidTest.xml",
+    platform_apis: true,
+    certificate: "platform",
+    use_embedded_native_libs: true,
+    jni_libs: [
+        "libsurface_jni",
+    ],
+
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "androidx.test.rules",
+        "androidx.test.runner",
+        "androidx.test.ext.junit",
+        "kotlin-stdlib",
+        "kotlinx-coroutines-android",
+        "flickerlib",
+        "truth-prebuilt",
+    ],
+}
+
+cc_library_shared {
+    name: "libsurface_jni",
+    srcs: [
+        "cpp/SurfaceProxy.cpp",
+    ],
+    shared_libs: [
+        "libutils",
+        "libgui",
+        "liblog",
+        "libandroid",
+    ],
+    include_dirs: [
+        "system/core/include"
+    ],
+    stl: "libc++_static",
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+}
diff --git a/tests/SurfaceViewBufferTests/AndroidManifest.xml b/tests/SurfaceViewBufferTests/AndroidManifest.xml
new file mode 100644
index 0000000..95885c1
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.test">
+
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
+    <!-- Enable / Disable tracing !-->
+    <uses-permission android:name="android.permission.DUMP" />
+    <!-- Enable / Disable sv blast adapter !-->
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
+    <application android:allowBackup="false"
+         android:supportsRtl="true">
+        <activity android:name=".MainActivity"
+                  android:taskAffinity="com.android.test.MainActivity"
+                  android:theme="@style/AppTheme"
+                  android:label="SurfaceViewBufferTestApp"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.test"
+                     android:label="SurfaceViewBufferTests">
+    </instrumentation>
+</manifest>
diff --git a/tests/SurfaceViewBufferTests/AndroidTest.xml b/tests/SurfaceViewBufferTests/AndroidTest.xml
new file mode 100644
index 0000000..b73fe48
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<configuration description="Runs SurfaceView Buffer Tests">
+    <option name="test-tag" value="SurfaceViewBufferTests" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <!-- keeps the screen on during tests -->
+        <option name="screen-always-on" value="on" />
+        <!-- prevents the phone from restarting -->
+        <option name="force-skip-system-props" value="true" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="false"/>
+        <option name="test-file-name" value="SurfaceViewBufferTests.apk"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.test"/>
+        <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
+        <option name="shell-timeout" value="6600s" />
+        <option name="test-timeout" value="6000s" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
new file mode 100644
index 0000000..0c86524
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
@@ -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.
+ */
+
+#include <android/log.h>
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+#include <android/window.h>
+#include <gui/Surface.h>
+#include <jni.h>
+#include <system/window.h>
+#include <utils/RefBase.h>
+#include <cassert>
+#include <chrono>
+#include <thread>
+
+#define TAG "SurfaceViewBufferTests"
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
+
+extern "C" {
+int i = 0;
+static ANativeWindow* sAnw;
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_setSurface(JNIEnv* env, jclass,
+                                                                     jobject surfaceObject) {
+    sAnw = ANativeWindow_fromSurface(env, surfaceObject);
+    assert(sAnw);
+    android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+    surface->enableFrameTimestamps(true);
+    return 0;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_waitUntilBufferDisplayed(
+        JNIEnv*, jclass, jint jFrameNumber, jint timeoutSec) {
+    using namespace std::chrono_literals;
+    assert(sAnw);
+    android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+
+    uint64_t frameNumber = static_cast<uint64_t>(jFrameNumber);
+    nsecs_t outRequestedPresentTime, outAcquireTime, outLatchTime, outFirstRefreshStartTime;
+    nsecs_t outLastRefreshStartTime, outGlCompositionDoneTime, outDequeueReadyTime;
+    nsecs_t outDisplayPresentTime = -1;
+    nsecs_t outReleaseTime;
+
+    auto start = std::chrono::steady_clock::now();
+    while (outDisplayPresentTime < 0) {
+        std::this_thread::sleep_for(8ms);
+        surface->getFrameTimestamps(frameNumber, &outRequestedPresentTime, &outAcquireTime,
+                                    &outLatchTime, &outFirstRefreshStartTime,
+                                    &outLastRefreshStartTime, &outGlCompositionDoneTime,
+                                    &outDisplayPresentTime, &outDequeueReadyTime, &outReleaseTime);
+        if (outDisplayPresentTime < 0) {
+            auto end = std::chrono::steady_clock::now();
+            if (std::chrono::duration_cast<std::chrono::seconds>(end - start).count() >
+                timeoutSec) {
+                return -1;
+            }
+        }
+    }
+    return 0;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_draw(JNIEnv*, jclass) {
+    assert(sAnw);
+    ANativeWindow_Buffer outBuffer;
+    ANativeWindow_lock(sAnw, &outBuffer, nullptr);
+    return 0;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowLock(JNIEnv*, jclass) {
+    assert(sAnw);
+    ANativeWindow_Buffer outBuffer;
+    ANativeWindow_lock(sAnw, &outBuffer, nullptr);
+    return 0;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowUnlockAndPost(JNIEnv*,
+                                                                                     jclass) {
+    assert(sAnw);
+    ANativeWindow_unlockAndPost(sAnw);
+    return 0;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowSetBuffersGeometry(
+        JNIEnv* /* env */, jclass /* clazz */, jobject /* surfaceObject */, jint w, jint h,
+        jint format) {
+    assert(sAnw);
+    return ANativeWindow_setBuffersGeometry(sAnw, w, h, format);
+}
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/res/values/styles.xml b/tests/SurfaceViewBufferTests/res/values/styles.xml
new file mode 100644
index 0000000..8b50738
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/res/values/styles.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+    <item name="windowNoTitle">true</item>
+    <item name="windowActionBar">false</item>
+    <item name="android:windowFullscreen">true</item>
+    <item name="android:windowContentOverlay">@null</item>
+    <item name="android:windowDisablePreview">true</item>
+</style>
+</resources>
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt b/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt
new file mode 100644
index 0000000..b1e1336
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt
@@ -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.test
+
+import android.app.Activity
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import android.os.Bundle
+import android.view.Gravity
+import android.view.Surface
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+import android.widget.FrameLayout
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+
+class MainActivity : Activity() {
+    val mSurfaceProxy = SurfaceProxy()
+    private var mSurfaceHolder: SurfaceHolder? = null
+    private val mDrawLock = ReentrantLock()
+
+    val surface: Surface? get() = mSurfaceHolder?.surface
+
+    public override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        addSurfaceView(Rect(0, 0, 500, 200))
+    }
+
+    fun addSurfaceView(size: Rect): CountDownLatch {
+        val layout = findViewById<FrameLayout>(android.R.id.content)
+        val surfaceReadyLatch = CountDownLatch(1)
+        val surfaceView = createSurfaceView(applicationContext, size, surfaceReadyLatch)
+        layout.addView(surfaceView,
+                FrameLayout.LayoutParams(size.width(), size.height(), Gravity.TOP or Gravity.LEFT)
+                        .also { it.setMargins(100, 100, 0, 0) })
+        return surfaceReadyLatch
+    }
+
+    private fun createSurfaceView(
+        context: Context,
+        size: Rect,
+        surfaceReadyLatch: CountDownLatch
+    ): SurfaceView {
+        val surfaceView = SurfaceView(context)
+        surfaceView.setWillNotDraw(false)
+        surfaceView.holder.setFixedSize(size.width(), size.height())
+        surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
+            override fun surfaceCreated(holder: SurfaceHolder) {
+                mDrawLock.withLock {
+                    mSurfaceHolder = holder
+                    mSurfaceProxy.setSurface(holder.surface)
+                }
+                surfaceReadyLatch.countDown()
+            }
+
+            override fun surfaceChanged(
+                holder: SurfaceHolder,
+                format: Int,
+                width: Int,
+                height: Int
+            ) {
+            }
+
+            override fun surfaceDestroyed(holder: SurfaceHolder) {
+                mDrawLock.withLock {
+                    mSurfaceHolder = null
+                }
+            }
+        })
+        return surfaceView
+    }
+
+    fun drawFrame(): Rect {
+        mDrawLock.withLock {
+            val holder = mSurfaceHolder ?: return Rect()
+            val canvas = holder.lockCanvas()
+            val canvasSize = Rect(0, 0, canvas.width, canvas.height)
+            canvas.drawColor(Color.GREEN)
+            val p = Paint()
+            p.color = Color.RED
+            canvas.drawRect(canvasSize, p)
+            holder.unlockCanvasAndPost(canvas)
+            return canvasSize
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
new file mode 100644
index 0000000..884aae4
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.test
+
+class SurfaceProxy {
+    init {
+        System.loadLibrary("surface_jni")
+    }
+
+    external fun setSurface(surface: Any)
+    external fun waitUntilBufferDisplayed(frameNumber: Int, timeoutSec: Int)
+    external fun draw()
+
+    // android/native_window.h functions
+    external fun ANativeWindowLock()
+    external fun ANativeWindowUnlockAndPost()
+    external fun ANativeWindowSetBuffersGeometry(surface: Any, width: Int, height: Int, format: Int)
+}
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt
new file mode 100644
index 0000000..b48a91d
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt
@@ -0,0 +1,185 @@
+/*
+ * 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.test
+
+import android.app.Instrumentation
+import android.graphics.Rect
+import android.provider.Settings
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.monitor.LayersTraceMonitor
+import com.android.server.wm.flicker.monitor.withSFTracing
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.util.concurrent.CountDownLatch
+import kotlin.properties.Delegates
+
+@RunWith(Parameterized::class)
+class SurfaceViewBufferTest(val useBlastAdapter: Boolean) {
+    private var mInitialUseBlastConfig by Delegates.notNull<Int>()
+
+    @get:Rule
+    var scenarioRule: ActivityScenarioRule<MainActivity> =
+            ActivityScenarioRule<MainActivity>(MainActivity::class.java)
+
+    protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    val defaultBufferSize = Rect(0, 0, 640, 480)
+
+    @Before
+    fun setup() {
+        mInitialUseBlastConfig = Settings.Global.getInt(instrumentation.context.contentResolver,
+                "use_blast_adapter_sv", 0)
+        val enable = if (useBlastAdapter) 1 else 0
+        Settings.Global.putInt(instrumentation.context.contentResolver, "use_blast_adapter_sv",
+                enable)
+        val tmpDir = instrumentation.targetContext.dataDir.toPath()
+        LayersTraceMonitor(tmpDir).stop()
+
+        lateinit var surfaceReadyLatch: CountDownLatch
+        scenarioRule.getScenario().onActivity {
+            surfaceReadyLatch = it.addSurfaceView(defaultBufferSize)
+        }
+        surfaceReadyLatch.await()
+    }
+
+    @After
+    fun teardown() {
+        scenarioRule.getScenario().close()
+        Settings.Global.putInt(instrumentation.context.contentResolver,
+                "use_blast_adapter_sv", mInitialUseBlastConfig)
+    }
+
+    @Test
+    fun testSetBuffersGeometry_0x0_resetsBufferSize() {
+        val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
+            scenarioRule.getScenario().onActivity {
+                it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0,
+                        R8G8B8A8_UNORM)
+                it.mSurfaceProxy.ANativeWindowLock()
+                it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+                it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */)
+            }
+        }
+
+        // verify buffer size is reset to default buffer size
+        assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+    }
+
+    @Test
+    fun testSetBuffersGeometry_0x0_rejectsBuffer() {
+        val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
+            scenarioRule.getScenario().onActivity {
+                it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100,
+                        R8G8B8A8_UNORM)
+                it.mSurfaceProxy.ANativeWindowLock()
+                it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+                it.mSurfaceProxy.ANativeWindowLock()
+                it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0, R8G8B8A8_UNORM)
+                // Submit buffer one with a different size which should be rejected
+                it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+
+                // submit a buffer with the default buffer size
+                it.mSurfaceProxy.ANativeWindowLock()
+                it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+                it.mSurfaceProxy.waitUntilBufferDisplayed(3, 1 /* sec */)
+            }
+        }
+        // Verify we reject buffers since scaling mode == NATIVE_WINDOW_SCALING_MODE_FREEZE
+        assertThat(trace).layer("SurfaceView", 2).doesNotExist()
+
+        // Verify the next buffer is submitted with the correct size
+        assertThat(trace).layer("SurfaceView", 3).also {
+            it.hasBufferSize(defaultBufferSize)
+            it.hasScalingMode(0 /* NATIVE_WINDOW_SCALING_MODE_FREEZE */)
+        }
+    }
+
+    @Test
+    fun testSetBuffersGeometry_smallerThanBuffer() {
+        val bufferSize = Rect(0, 0, 300, 200)
+        val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
+            scenarioRule.getScenario().onActivity {
+                it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize.width(),
+                        bufferSize.height(), R8G8B8A8_UNORM)
+                it.drawFrame()
+                it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */)
+            }
+        }
+
+        assertThat(trace).layer("SurfaceView", 1).also {
+            it.hasBufferSize(bufferSize)
+            it.hasLayerSize(defaultBufferSize)
+            it.hasScalingMode(1 /* NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW */)
+        }
+    }
+
+    @Test
+    fun testSetBuffersGeometry_largerThanBuffer() {
+        val bufferSize = Rect(0, 0, 3000, 2000)
+        val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
+            scenarioRule.getScenario().onActivity {
+                it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize.width(),
+                        bufferSize.height(), R8G8B8A8_UNORM)
+                it.drawFrame()
+                it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */)
+            }
+        }
+
+        assertThat(trace).layer("SurfaceView", 1).also {
+            it.hasBufferSize(bufferSize)
+            it.hasLayerSize(defaultBufferSize)
+            it.hasScalingMode(1 /* NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW */)
+        }
+    }
+
+    /** Submit buffers as fast as possible and make sure they are queued */
+    @Test
+    fun testQueueBuffers() {
+        val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
+            scenarioRule.getScenario().onActivity {
+                it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100,
+                        R8G8B8A8_UNORM)
+                for (i in 0..100) {
+                    it.mSurfaceProxy.ANativeWindowLock()
+                    it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+                }
+                it.mSurfaceProxy.waitUntilBufferDisplayed(100, 1 /* sec */)
+            }
+        }
+        for (frameNumber in 1..100) {
+            assertThat(trace).layer("SurfaceView", frameNumber.toLong())
+        }
+    }
+
+    companion object {
+        private const val TRACE_FLAGS = 0x1 // TRACE_CRITICAL
+        private const val R8G8B8A8_UNORM = 1
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "blast={0}")
+        fun data(): Collection<Array<Any>> {
+            return listOf(
+                    arrayOf(false), // First test:  submit buffers via bufferqueue
+                    arrayOf(true)   // Second test: submit buffers via blast adapter
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index 124b660..0fe84ab 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -63,6 +63,7 @@
         "services.net",
     ],
     libs: [
+        "android.net.ipsec.ike.stubs.module_lib",
         "android.test.runner",
         "android.test.base",
         "android.test.mock",
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index c76b4cd..c3f1549 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -20,6 +20,7 @@
 import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
 import static android.content.pm.UserInfo.FLAG_PRIMARY;
 import static android.content.pm.UserInfo.FLAG_RESTRICTED;
+import static android.net.ConnectivityManager.NetworkCallback;
 import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
@@ -45,7 +46,9 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -66,6 +69,7 @@
 import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.IpSecManager;
+import android.net.IpSecTunnelInterfaceResponse;
 import android.net.LinkProperties;
 import android.net.LocalSocket;
 import android.net.Network;
@@ -75,6 +79,8 @@
 import android.net.UidRange;
 import android.net.VpnManager;
 import android.net.VpnService;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.os.ConditionVariable;
@@ -101,6 +107,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -150,6 +157,11 @@
     private static final String TEST_VPN_IDENTITY = "identity";
     private static final byte[] TEST_VPN_PSK = "psk".getBytes();
 
+    private static final Network TEST_NETWORK = new Network(Integer.MAX_VALUE);
+    private static final String TEST_IFACE_NAME = "TEST_IFACE";
+    private static final int TEST_TUNNEL_RESOURCE_ID = 0x2345;
+    private static final long TEST_TIMEOUT_MS = 500L;
+
     /**
      * Names and UIDs for some fake packages. Important points:
      *  - UID is ordered increasing.
@@ -227,6 +239,13 @@
         // Deny all appops by default.
         when(mAppOps.noteOpNoThrow(anyInt(), anyInt(), anyString()))
                 .thenReturn(AppOpsManager.MODE_IGNORED);
+
+        // Setup IpSecService
+        final IpSecTunnelInterfaceResponse tunnelResp =
+                new IpSecTunnelInterfaceResponse(
+                        IpSecManager.Status.OK, TEST_TUNNEL_RESOURCE_ID, TEST_IFACE_NAME);
+        when(mIpSecService.createTunnelInterface(any(), any(), any(), any(), any()))
+                .thenReturn(tunnelResp);
     }
 
     @Test
@@ -988,6 +1007,52 @@
                         eq(AppOpsManager.MODE_IGNORED));
     }
 
+    private NetworkCallback triggerOnAvailableAndGetCallback() {
+        final ArgumentCaptor<NetworkCallback> networkCallbackCaptor =
+                ArgumentCaptor.forClass(NetworkCallback.class);
+        verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
+                .requestNetwork(any(), networkCallbackCaptor.capture());
+
+        final NetworkCallback cb = networkCallbackCaptor.getValue();
+        cb.onAvailable(TEST_NETWORK);
+        return cb;
+    }
+
+    @Test
+    public void testStartPlatformVpnAuthenticationFailed() throws Exception {
+        final ArgumentCaptor<IkeSessionCallback> captor =
+                ArgumentCaptor.forClass(IkeSessionCallback.class);
+        final IkeProtocolException exception = mock(IkeProtocolException.class);
+        when(exception.getErrorType())
+                .thenReturn(IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED);
+
+        final Vpn vpn = startLegacyVpn(mVpnProfile);
+        final NetworkCallback cb = triggerOnAvailableAndGetCallback();
+
+        // Wait for createIkeSession() to be called before proceeding in order to ensure consistent
+        // state
+        verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
+                .createIkeSession(any(), any(), any(), any(), captor.capture(), any());
+        final IkeSessionCallback ikeCb = captor.getValue();
+        ikeCb.onClosedExceptionally(exception);
+
+        verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb));
+        assertEquals(DetailedState.FAILED, vpn.getNetworkInfo().getDetailedState());
+    }
+
+    @Test
+    public void testStartPlatformVpnIllegalArgumentExceptionInSetup() throws Exception {
+        when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any()))
+                .thenThrow(new IllegalArgumentException());
+        final Vpn vpn = startLegacyVpn(mVpnProfile);
+        final NetworkCallback cb = triggerOnAvailableAndGetCallback();
+
+        // Wait for createIkeSession() to be called before proceeding in order to ensure consistent
+        // state
+        verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb));
+        assertEquals(DetailedState.FAILED, vpn.getNetworkInfo().getDetailedState());
+    }
+
     private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) {
         assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null, mKeyStore));