Merge "free weakref to linearblock when it is destroy"
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 42725c5..bf5781b 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -101,10 +101,15 @@
      * version {@link android.os.Build.VERSION_CODES#Q}. As such, the system may throttle calls to
      * this API if calls are made too frequently in a short amount of time.
      *
+     * <p>Note: The JobService component needs to be enabled in order to successfully schedule a
+     * job.
+     *
      * @param job The job you wish scheduled. See
      * {@link android.app.job.JobInfo.Builder JobInfo.Builder} for more detail on the sorts of jobs
      * you can schedule.
      * @return the result of the schedule request.
+     * @throws IllegalArgumentException if the specified {@link JobService} doesn't exist or is
+     * disabled.
      */
     public abstract @Result int schedule(@NonNull JobInfo job);
 
@@ -137,11 +142,21 @@
      * work you are enqueue, since currently this will always be treated as a different JobInfo,
      * even if the ClipData contents are exactly the same.</p>
      *
+     * <p class="caution"><strong>Note:</strong> Scheduling a job can have a high cost, even if it's
+     * rescheduling the same job and the job didn't execute, especially on platform versions before
+     * version {@link android.os.Build.VERSION_CODES#Q}. As such, the system may throttle calls to
+     * this API if calls are made too frequently in a short amount of time.
+     *
+     * <p>Note: The JobService component needs to be enabled in order to successfully schedule a
+     * job.
+     *
      * @param job The job you wish to enqueue work for. See
      * {@link android.app.job.JobInfo.Builder JobInfo.Builder} for more detail on the sorts of jobs
      * you can schedule.
      * @param work New work to enqueue.  This will be available later when the job starts running.
      * @return the result of the enqueue request.
+     * @throws IllegalArgumentException if the specified {@link JobService} doesn't exist or is
+     * disabled.
      */
     public abstract @Result int enqueue(@NonNull JobInfo job, @NonNull JobWorkItem work);
 
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index ec7e99e..a1be7cb 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -2722,6 +2722,14 @@
                 mPendingBackgroundAlarms.removeAt(i);
             }
         }
+        for (int i = mPendingNonWakeupAlarms.size() - 1; i >= 0; i--) {
+            final Alarm a = mPendingNonWakeupAlarms.get(i);
+            if (a.matches(operation, directReceiver)) {
+                // Don't set didRemove, since this doesn't impact the scheduled alarms.
+                mPendingNonWakeupAlarms.remove(i);
+                decrementAlarmCount(a.uid, 1);
+            }
+        }
         if (didRemove) {
             if (DEBUG_BATCH) {
                 Slog.v(TAG, "remove(operation) changed bounds; rebatching");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index cf4caea..b638fef 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -87,11 +87,11 @@
 import com.android.server.DeviceIdleInternal;
 import com.android.server.JobSchedulerBackgroundThread;
 import com.android.server.LocalServices;
-import com.android.server.SystemService.TargetUser;
 import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
 import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
 import com.android.server.job.controllers.BackgroundJobsController;
 import com.android.server.job.controllers.BatteryController;
+import com.android.server.job.controllers.ComponentController;
 import com.android.server.job.controllers.ConnectivityController;
 import com.android.server.job.controllers.ContentObserverController;
 import com.android.server.job.controllers.DeviceIdleJobsController;
@@ -1484,6 +1484,7 @@
         mControllers.add(mDeviceIdleJobsController);
         mQuotaController = new QuotaController(this);
         mControllers.add(mQuotaController);
+        mControllers.add(new ComponentController(this));
 
         mRestrictiveControllers = new ArrayList<>();
         mRestrictiveControllers.add(mBatteryController);
@@ -2300,21 +2301,12 @@
             return false;
         }
 
-        // The expensive check: validate that the defined package+service is
-        // still present & viable.
+        // Validate that the defined package+service is still present & viable.
         return isComponentUsable(job);
     }
 
     private boolean isComponentUsable(@NonNull JobStatus job) {
-        final ServiceInfo service;
-        try {
-            // TODO: cache result until we're notified that something in the package changed.
-            service = AppGlobals.getPackageManager().getServiceInfo(
-                    job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
-                    job.getUserId());
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
+        final ServiceInfo service = job.serviceInfo;
 
         if (service == null) {
             if (DEBUG) {
@@ -3104,7 +3096,7 @@
                 try {
                     componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
                             js.getServiceComponent(),
-                            PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
+                            PackageManager.MATCH_DIRECT_BOOT_AUTO,
                             js.getUserId()) != null);
                 } catch (RemoteException e) {
                 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
new file mode 100644
index 0000000..b47a210
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
@@ -0,0 +1,192 @@
+/*
+ * 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.job.controllers;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArrayMap;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.job.JobSchedulerService;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * Controller that tracks changes in the service component's enabled state.
+ */
+public class ComponentController extends StateController {
+    private static final String TAG = "JobScheduler.Component";
+    private static final boolean DEBUG = JobSchedulerService.DEBUG
+            || Log.isLoggable(TAG, Log.DEBUG);
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (action == null) {
+                Slog.wtf(TAG, "Intent action was null");
+                return;
+            }
+            switch (action) {
+                case Intent.ACTION_PACKAGE_CHANGED:
+                    final Uri uri = intent.getData();
+                    final String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
+                    final String[] changedComponents = intent.getStringArrayExtra(
+                            Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+                    if (pkg != null && changedComponents != null && changedComponents.length > 0) {
+                        updateComponentStateForPackage(pkg);
+                    }
+                    break;
+                case Intent.ACTION_USER_UNLOCKED:
+                case Intent.ACTION_USER_STOPPED:
+                    final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+                    updateComponentStateForUser(userId);
+                    break;
+            }
+        }
+    };
+
+    private final ComponentStateUpdateFunctor mComponentStateUpdateFunctor =
+            new ComponentStateUpdateFunctor();
+
+    public ComponentController(JobSchedulerService service) {
+        super(service);
+
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addDataScheme("package");
+        mContext.registerReceiverAsUser(
+                mBroadcastReceiver, UserHandle.ALL, filter, null, null);
+        final IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_UNLOCKED);
+        userFilter.addAction(Intent.ACTION_USER_STOPPED);
+        mContext.registerReceiverAsUser(
+                mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
+
+    }
+
+    @Override
+    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
+        updateComponentEnabledStateLocked(jobStatus, null);
+    }
+
+    @Override
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
+            boolean forUpdate) {
+
+    }
+
+    @Nullable
+    private ServiceInfo getServiceInfo(JobStatus jobStatus,
+            @Nullable SparseArrayMap<ComponentName, ServiceInfo> cache) {
+        final ComponentName cn = jobStatus.getServiceComponent();
+        ServiceInfo si = null;
+        if (cache != null) {
+            si = cache.get(jobStatus.getUserId(), cn);
+        }
+        if (si == null) {
+            try {
+                si = AppGlobals.getPackageManager().getServiceInfo(
+                        cn, PackageManager.MATCH_DIRECT_BOOT_AUTO, jobStatus.getUserId());
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+            if (cache != null) {
+                cache.add(jobStatus.getUserId(), cn, si);
+            }
+        }
+        return si;
+    }
+
+    private boolean updateComponentEnabledStateLocked(JobStatus jobStatus,
+            @Nullable SparseArrayMap<ComponentName, ServiceInfo> cache) {
+        final ServiceInfo service = getServiceInfo(jobStatus, cache);
+
+        if (DEBUG && service == null) {
+            Slog.v(TAG, jobStatus.toShortString() + " component not present");
+        }
+        final ServiceInfo ogService = jobStatus.serviceInfo;
+        jobStatus.serviceInfo = service;
+        return !Objects.equals(ogService, service);
+    }
+
+    private void updateComponentStateForPackage(final String pkg) {
+        updateComponentStates(
+                jobStatus -> jobStatus.getServiceComponent().getPackageName().equals(pkg));
+    }
+
+    private void updateComponentStateForUser(final int userId) {
+        updateComponentStates(jobStatus -> {
+            // Using user ID instead of source user ID because the service will run under the
+            // user ID, not source user ID.
+            return jobStatus.getUserId() == userId;
+        });
+    }
+
+    private void updateComponentStates(@NonNull Predicate<JobStatus> filter) {
+        synchronized (mLock) {
+            mComponentStateUpdateFunctor.reset();
+            mService.getJobStore().forEachJob(filter, mComponentStateUpdateFunctor);
+            if (mComponentStateUpdateFunctor.mChanged) {
+                mStateChangedListener.onControllerStateChanged();
+            }
+        }
+    }
+
+    final class ComponentStateUpdateFunctor implements Consumer<JobStatus> {
+        boolean mChanged;
+        final SparseArrayMap<ComponentName, ServiceInfo> mTempCache = new SparseArrayMap<>();
+
+        @Override
+        public void accept(JobStatus jobStatus) {
+            mChanged |= updateComponentEnabledStateLocked(jobStatus, mTempCache);
+        }
+
+        private void reset() {
+            mChanged = false;
+            mTempCache.clear();
+        }
+    }
+
+    @Override
+    public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
+
+    }
+
+    @Override
+    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
+            Predicate<JobStatus> predicate) {
+
+    }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index d7be259..c7cc2f0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -27,6 +27,7 @@
 import android.app.job.JobWorkItem;
 import android.content.ClipData;
 import android.content.ComponentName;
+import android.content.pm.ServiceInfo;
 import android.net.Network;
 import android.net.Uri;
 import android.os.RemoteException;
@@ -296,6 +297,7 @@
     public ArraySet<Uri> changedUris;
     public ArraySet<String> changedAuthorities;
     public Network network;
+    public ServiceInfo serviceInfo;
 
     public int lastEvaluatedPriority;
 
@@ -1284,8 +1286,8 @@
         // run if its constraints are satisfied).
         // DeviceNotDozing implicit constraint must be satisfied
         // NotRestrictedInBackground implicit constraint must be satisfied
-        return mReadyNotDozing && mReadyNotRestrictedInBg && (mReadyDeadlineSatisfied
-                || isConstraintsSatisfied(satisfiedConstraints));
+        return mReadyNotDozing && mReadyNotRestrictedInBg && (serviceInfo != null)
+                && (mReadyDeadlineSatisfied || isConstraintsSatisfied(satisfiedConstraints));
     }
 
     /** All constraints besides implicit and deadline. */
@@ -1767,6 +1769,9 @@
         pw.print(prefix);
         pw.print("  readyDynamicSatisfied: ");
         pw.println(mReadyDynamicSatisfied);
+        pw.print(prefix);
+        pw.print("  readyComponentEnabled: ");
+        pw.println(serviceInfo != null);
 
         if (changedAuthorities != null) {
             pw.print(prefix); pw.println("Changed authorities:");
diff --git a/api/current.txt b/api/current.txt
index 922e89c..2b662eb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -27325,6 +27325,12 @@
     field public static final android.media.MediaTimestamp TIMESTAMP_UNKNOWN;
   }
 
+  public class MediaTranscodingException extends java.lang.Exception {
+  }
+
+  public static final class MediaTranscodingException.ServiceNotAvailableException extends android.media.MediaTranscodingException {
+  }
+
   public interface MicrophoneDirection {
     method public boolean setPreferredMicrophoneDirection(int);
     method public boolean setPreferredMicrophoneFieldDimension(@FloatRange(from=-1.0, to=1.0) float);
@@ -46760,6 +46766,7 @@
     field public static final String KEY_APN_EXPAND_BOOL = "apn_expand_bool";
     field public static final String KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY = "apn_settings_default_apn_types_string_array";
     field public static final String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool";
+    field public static final String KEY_CALL_BARRING_DEFAULT_SERVICE_CLASS_INT = "call_barring_default_service_class_int";
     field public static final String KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL = "call_barring_supports_deactivate_all_bool";
     field public static final String KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL = "call_barring_supports_password_change_bool";
     field public static final String KEY_CALL_BARRING_VISIBILITY_BOOL = "call_barring_visibility_bool";
@@ -46987,6 +46994,8 @@
     field public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING = "wfc_emergency_address_carrier_app_string";
     field public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool";
     field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
+    field public static final int SERVICE_CLASS_NONE = 0; // 0x0
+    field public static final int SERVICE_CLASS_VOICE = 1; // 0x1
   }
 
   public static final class CarrierConfigManager.Apn {
diff --git a/api/system-current.txt b/api/system-current.txt
index b8fa299..65fe85b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1592,12 +1592,16 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setBluetoothTethering(boolean);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
+    field public static final String ACTION_TETHERING_STATE_CHANGED = "android.bluetooth.action.TETHERING_STATE_CHANGED";
     field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
+    field public static final String EXTRA_TETHERING_STATE = "android.bluetooth.extra.TETHERING_STATE";
     field public static final int LOCAL_NAP_ROLE = 1; // 0x1
     field public static final int LOCAL_PANU_ROLE = 2; // 0x2
     field public static final int PAN_ROLE_NONE = 0; // 0x0
     field public static final int REMOTE_NAP_ROLE = 1; // 0x1
     field public static final int REMOTE_PANU_ROLE = 2; // 0x2
+    field public static final int TETHERING_STATE_OFF = 1; // 0x1
+    field public static final int TETHERING_STATE_ON = 2; // 0x2
   }
 
   public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
@@ -4363,7 +4367,7 @@
   }
 
   public final class MediaTranscodeManager {
-    method @NonNull public android.media.MediaTranscodeManager.TranscodingJob enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener) throws java.io.FileNotFoundException;
+    method @NonNull public android.media.MediaTranscodeManager.TranscodingJob enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener) throws java.io.FileNotFoundException, android.media.MediaTranscodingException.ServiceNotAvailableException;
     field public static final int PRIORITY_REALTIME = 1; // 0x1
     field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1
   }
@@ -4378,7 +4382,6 @@
     method @IntRange(from=0, to=100) public int getProgress();
     method public int getResult();
     method public int getStatus();
-    method public boolean retry();
     method public void setOnProgressUpdateListener(@NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener);
     method public void setOnProgressUpdateListener(int, @NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener);
     field public static final int RESULT_CANCELED = 4; // 0x4
@@ -7205,6 +7208,7 @@
     method public int getBandwidth();
     method @Nullable public android.net.MacAddress getBssid();
     method public int getFrequency();
+    method public int getWifiStandard();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int CHANNEL_WIDTH_160MHZ = 6; // 0x6
     field public static final int CHANNEL_WIDTH_20MHZ = 2; // 0x2
@@ -9374,6 +9378,7 @@
     field public static final String APN_SET_ID = "apn_set_id";
     field public static final int CARRIER_EDITED = 4; // 0x4
     field public static final String EDITED_STATUS = "edited";
+    field public static final int MATCH_ALL_APN_SET_ID = -1; // 0xffffffff
     field public static final String MAX_CONNECTIONS = "max_conns";
     field public static final String MODEM_PERSIST = "modem_cognitive";
     field public static final String MTU = "mtu";
@@ -10931,7 +10936,8 @@
     method public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
     method @Deprecated public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
     method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
-    method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
+    method @Deprecated public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
+    method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber, int);
     method public void onPhysicalChannelConfigurationChanged(@NonNull java.util.List<android.telephony.PhysicalChannelConfig>);
     method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
     method public void onRadioPowerStateChanged(int);
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
index 9c40b6c..3a9f49c 100644
--- a/api/system-lint-baseline.txt
+++ b/api/system-lint-baseline.txt
@@ -1,43 +1,48 @@
 // Baseline format: 1.0
 AcronymName: android.net.NetworkCapabilities#setSSID(String):
-    Acronyms should not be capitalized in method names: was `setSSID`, should this be `setSsid`?
+    
 
 
 ActionValue: android.location.Location#EXTRA_NO_GPS_LOCATION:
     
+ActionValue: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+    
+ActionValue: android.net.TetheringManager#EXTRA_ACTIVE_TETHER:
+    
+ActionValue: android.net.TetheringManager#EXTRA_AVAILABLE_TETHER:
+    
+ActionValue: android.net.TetheringManager#EXTRA_ERRORED_TETHER:
+    
 ActionValue: android.net.wifi.WifiManager#ACTION_LINK_CONFIGURATION_CHANGED:
     
 
-// Tethering broadcast action / extras cannot change name for backwards compatibility
-ActionValue: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
-    Inconsistent action value; expected `android.net.action.TETHER_STATE_CHANGED`, was `android.net.conn.TETHER_STATE_CHANGED`
-ActionValue: android.net.TetheringManager#EXTRA_ACTIVE_TETHER:
-    Inconsistent extra value; expected `android.net.extra.ACTIVE_TETHER`, was `tetherArray`
-ActionValue: android.net.TetheringManager#EXTRA_AVAILABLE_TETHER:
-    Inconsistent extra value; expected `android.net.extra.AVAILABLE_TETHER`, was `availableArray`
-ActionValue: android.net.TetheringManager#EXTRA_ERRORED_TETHER:
-    Inconsistent extra value; expected `android.net.extra.ERRORED_TETHER`, was `erroredArray`
 
 ArrayReturn: android.bluetooth.BluetoothCodecStatus#BluetoothCodecStatus(android.bluetooth.BluetoothCodecConfig, android.bluetooth.BluetoothCodecConfig[], android.bluetooth.BluetoothCodecConfig[]) parameter #1:
-    Method parameter should be Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
+    
 ArrayReturn: android.bluetooth.BluetoothCodecStatus#BluetoothCodecStatus(android.bluetooth.BluetoothCodecConfig, android.bluetooth.BluetoothCodecConfig[], android.bluetooth.BluetoothCodecConfig[]) parameter #2:
-    Method parameter should be Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
+    
 ArrayReturn: android.bluetooth.BluetoothCodecStatus#getCodecsLocalCapabilities():
-    Method should return Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
+    
 ArrayReturn: android.bluetooth.BluetoothCodecStatus#getCodecsSelectableCapabilities():
-    Method should return Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
+    
 ArrayReturn: android.bluetooth.BluetoothUuid#containsAnyUuid(android.os.ParcelUuid[], android.os.ParcelUuid[]) parameter #0:
-    Method parameter should be Collection<ParcelUuid> (or subclass) instead of raw array; was `android.os.ParcelUuid[]`
+    
 ArrayReturn: android.bluetooth.BluetoothUuid#containsAnyUuid(android.os.ParcelUuid[], android.os.ParcelUuid[]) parameter #1:
-    Method parameter should be Collection<ParcelUuid> (or subclass) instead of raw array; was `android.os.ParcelUuid[]`
+    
 ArrayReturn: android.media.tv.tuner.Tuner.FilterCallback#onFilterEvent(android.media.tv.tuner.Tuner.Filter, android.media.tv.tuner.filter.FilterEvent[]) parameter #1:
-    Method parameter should be Collection<FilterEvent> (or subclass) instead of raw array; was `android.media.tv.tuner.filter.FilterEvent[]`
+    
 ArrayReturn: android.net.NetworkScoreManager#requestScores(android.net.NetworkKey[]) parameter #0:
-    Method parameter should be Collection<NetworkKey> (or subclass) instead of raw array; was `android.net.NetworkKey[]`
+    
 ArrayReturn: android.view.contentcapture.ViewNode#getAutofillOptions():
     
 
 
+BuilderSetStyle: android.net.IpSecTransform.Builder#buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex):
+    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.IpSecTransform.Builder.buildTunnelModeTransform(java.net.InetAddress,android.net.IpSecManager.SecurityParameterIndex)
+
+
+ExecutorRegistration: android.media.MediaPlayer#setOnImsRxNoticeListener(android.media.MediaPlayer.OnImsRxNoticeListener, android.os.Handler):
+    Registration methods should have overload that accepts delivery Executor: `setOnImsRxNoticeListener`
 ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#deletePersistentGroup(android.net.wifi.p2p.WifiP2pManager.Channel, int, android.net.wifi.p2p.WifiP2pManager.ActionListener):
     
 ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#factoryReset(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener):
@@ -65,7 +70,7 @@
 
 
 IntentBuilderName: android.content.Context#registerReceiverForAllUsers(android.content.BroadcastReceiver, android.content.IntentFilter, String, android.os.Handler):
-    Methods creating an Intent should be named `create<Foo>Intent()`, was `registerReceiverForAllUsers`
+    
 
 
 KotlinKeyword: android.app.Notification#when:
@@ -73,7 +78,19 @@
 
 
 KotlinOperator: android.telephony.CbGeoUtils.Geometry#contains(android.telephony.CbGeoUtils.LatLng):
-    Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
+    
+
+
+MissingGetterMatchingBuilder: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig):
+    android.net.wifi.rtt.RangingRequest does not declare a `getResponders()` method matching method android.net.wifi.rtt.RangingRequest.Builder.addResponder(android.net.wifi.rtt.ResponderConfig)
+MissingGetterMatchingBuilder: android.security.keystore.KeyGenParameterSpec.Builder#setUid(int):
+    android.security.keystore.KeyGenParameterSpec does not declare a `getUid()` method matching method android.security.keystore.KeyGenParameterSpec.Builder.setUid(int)
+MissingGetterMatchingBuilder: android.service.autofill.Dataset.Builder#setFieldInlinePresentation(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.service.autofill.InlinePresentation):
+    android.service.autofill.Dataset does not declare a `getFieldInlinePresentation()` method matching method android.service.autofill.Dataset.Builder.setFieldInlinePresentation(android.view.autofill.AutofillId,android.view.autofill.AutofillValue,java.util.regex.Pattern,android.service.autofill.InlinePresentation)
+MissingGetterMatchingBuilder: android.telecom.CallScreeningService.CallResponse.Builder#setShouldScreenCallViaAudioProcessing(boolean):
+    android.telecom.CallScreeningService.CallResponse does not declare a `shouldScreenCallViaAudioProcessing()` method matching method android.telecom.CallScreeningService.CallResponse.Builder.setShouldScreenCallViaAudioProcessing(boolean)
+MissingGetterMatchingBuilder: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String):
+    android.telephony.mbms.DownloadRequest does not declare a `getServiceId()` method matching method android.telephony.mbms.DownloadRequest.Builder.setServiceId(String)
 
 
 MissingNullability: android.hardware.soundtrigger.SoundTrigger.ModuleProperties#toString():
@@ -217,28 +234,27 @@
 
 
 NoClone: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
-
+    
 
 
 NoSettingsProvider: android.provider.Settings.Global#TETHER_OFFLOAD_DISABLED:
-    New setting keys are not allowed. (Field: TETHER_OFFLOAD_DISABLED)
+    
 NoSettingsProvider: android.provider.Settings.Global#TETHER_SUPPORTED:
-    New setting keys are not allowed. (Field: TETHER_SUPPORTED)
     
 
 
 NotCloseable: android.bluetooth.BluetoothA2dpSink:
-    Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothA2dpSink
+    
 NotCloseable: android.bluetooth.BluetoothMap:
-    Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothMap
+    
 NotCloseable: android.bluetooth.BluetoothPan:
-    Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothPan
+    
 NotCloseable: android.bluetooth.BluetoothPbap:
-    Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothPbap
+    
 
 
 OnNameExpected: android.content.ContentProvider#checkUriPermission(android.net.Uri, int, int):
-    If implemented by developer, should follow the on<Something> style; otherwise consider marking final
+    
 
 
 PairedRegistration: android.net.wifi.nl80211.WifiNl80211Manager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback):
@@ -300,7 +316,7 @@
 SamShouldBeLast: android.app.WallpaperInfo#dump(android.util.Printer, String):
     
 SamShouldBeLast: android.app.WallpaperManager#addOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener, android.os.Handler):
-    SAM-compatible parameters (such as parameter 1, "listener", in android.app.WallpaperManager.addOnColorsChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+    
 SamShouldBeLast: android.app.admin.DevicePolicyManager#installSystemUpdate(android.content.ComponentName, android.net.Uri, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback):
     
 SamShouldBeLast: android.content.Context#bindIsolatedService(android.content.Intent, int, String, java.util.concurrent.Executor, android.content.ServiceConnection):
@@ -334,19 +350,19 @@
 SamShouldBeLast: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback):
     
 SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, android.location.LocationListener, android.os.Looper):
-    SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+    
 SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, java.util.concurrent.Executor, android.location.LocationListener):
     
 SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener):
     
 SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, android.location.LocationListener, android.os.Looper):
-    SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+    
 SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, java.util.concurrent.Executor, android.location.LocationListener):
     
 SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(String, android.location.LocationListener, android.os.Looper):
-    SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+    
 SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(android.location.Criteria, android.location.LocationListener, android.os.Looper):
-    SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+    
 SamShouldBeLast: android.media.AudioFocusRequest.Builder#setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler):
     
 SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int):
@@ -359,6 +375,10 @@
     
 SamShouldBeLast: android.media.AudioRouting#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
     
+SamShouldBeLast: android.media.AudioTrack#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioTrack.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.MediaPlayer#setOnImsRxNoticeListener(android.media.MediaPlayer.OnImsRxNoticeListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaPlayer.setOnImsRxNoticeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.media.MediaRecorder#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
     
 SamShouldBeLast: android.media.MediaRecorder#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
@@ -510,8 +530,8 @@
 
 
 UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociated(String, android.net.MacAddress, android.os.UserHandle):
-    When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added
+    
 
 
 UserHandleName: android.telephony.CellBroadcastIntents#sendOrderedBroadcastForBackgroundReceivers(android.content.Context, android.os.UserHandle, android.content.Intent, String, String, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle):
-    Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `sendOrderedBroadcastForBackgroundReceivers`
+    
diff --git a/api/test-current.txt b/api/test-current.txt
index d57269e..7a7cdd1 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1817,7 +1817,7 @@
   }
 
   public final class MediaTranscodeManager {
-    method @NonNull public android.media.MediaTranscodeManager.TranscodingJob enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener) throws java.io.FileNotFoundException;
+    method @NonNull public android.media.MediaTranscodeManager.TranscodingJob enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener) throws java.io.FileNotFoundException, android.media.MediaTranscodingException.ServiceNotAvailableException;
     field public static final int PRIORITY_REALTIME = 1; // 0x1
     field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1
   }
@@ -1832,7 +1832,6 @@
     method @IntRange(from=0, to=100) public int getProgress();
     method public int getResult();
     method public int getStatus();
-    method public boolean retry();
     method public void setOnProgressUpdateListener(@NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener);
     method public void setOnProgressUpdateListener(int, @NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener);
     field public static final int RESULT_CANCELED = 4; // 0x4
@@ -4139,7 +4138,8 @@
   public class PhoneStateListener {
     method @Deprecated public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
     method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
-    method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
+    method @Deprecated public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
+    method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber, int);
     field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000
     field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
   }
diff --git a/api/test-lint-baseline.txt b/api/test-lint-baseline.txt
index 63ba4aa..91a09e3 100644
--- a/api/test-lint-baseline.txt
+++ b/api/test-lint-baseline.txt
@@ -74,11 +74,11 @@
 ArrayReturn: android.app.UiAutomation#executeShellCommandRw(String):
     
 ArrayReturn: android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel#KeyphraseSoundModel(java.util.UUID, java.util.UUID, byte[], android.hardware.soundtrigger.SoundTrigger.Keyphrase[]) parameter #3:
-    Method parameter should be Collection<Keyphrase> (or subclass) instead of raw array; was `android.hardware.soundtrigger.SoundTrigger.Keyphrase[]`
+    
 ArrayReturn: android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel#KeyphraseSoundModel(java.util.UUID, java.util.UUID, byte[], android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int) parameter #3:
-    Method parameter should be Collection<Keyphrase> (or subclass) instead of raw array; was `android.hardware.soundtrigger.SoundTrigger.Keyphrase[]`
+    
 ArrayReturn: android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel#getKeyphrases():
-    Method should return Collection<Keyphrase> (or subclass) instead of raw array; was `android.hardware.soundtrigger.SoundTrigger.Keyphrase[]`
+    
 ArrayReturn: android.location.GnssMeasurementsEvent#GnssMeasurementsEvent(android.location.GnssClock, android.location.GnssMeasurement[]) parameter #1:
     
 ArrayReturn: android.media.AudioRecordingConfiguration#AudioRecordingConfiguration(int, int, int, android.media.AudioFormat, android.media.AudioFormat, int, String, int, boolean, int, android.media.audiofx.AudioEffect.Descriptor[], android.media.audiofx.AudioEffect.Descriptor[]) parameter #10:
@@ -218,33 +218,33 @@
 
 
 BuilderSetStyle: android.media.audiopolicy.AudioMixingRule.Builder#allowPrivilegedPlaybackCapture(boolean):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.media.audiopolicy.AudioMixingRule.Builder.allowPrivilegedPlaybackCapture(boolean)
+    
 BuilderSetStyle: android.media.audiopolicy.AudioMixingRule.Builder#excludeMixRule(int, Object):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.media.audiopolicy.AudioMixingRule.Builder.excludeMixRule(int,Object)
+    
 BuilderSetStyle: android.media.audiopolicy.AudioMixingRule.Builder#excludeRule(android.media.AudioAttributes, int):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.media.audiopolicy.AudioMixingRule.Builder.excludeRule(android.media.AudioAttributes,int)
+    
 BuilderSetStyle: android.net.NetworkCapabilities.Builder#removeCapability(int):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.NetworkCapabilities.Builder.removeCapability(int)
+    
 BuilderSetStyle: android.net.NetworkCapabilities.Builder#removeTransportType(int):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.NetworkCapabilities.Builder.removeTransportType(int)
+    
 BuilderSetStyle: android.net.metrics.RaEvent.Builder#updateDnsslLifetime(long):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.metrics.RaEvent.Builder.updateDnsslLifetime(long)
+    
 BuilderSetStyle: android.net.metrics.RaEvent.Builder#updatePrefixPreferredLifetime(long):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.metrics.RaEvent.Builder.updatePrefixPreferredLifetime(long)
+    
 BuilderSetStyle: android.net.metrics.RaEvent.Builder#updatePrefixValidLifetime(long):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.metrics.RaEvent.Builder.updatePrefixValidLifetime(long)
+    
 BuilderSetStyle: android.net.metrics.RaEvent.Builder#updateRdnssLifetime(long):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.metrics.RaEvent.Builder.updateRdnssLifetime(long)
+    
 BuilderSetStyle: android.net.metrics.RaEvent.Builder#updateRouteInfoLifetime(long):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.metrics.RaEvent.Builder.updateRouteInfoLifetime(long)
+    
 BuilderSetStyle: android.net.metrics.RaEvent.Builder#updateRouterLifetime(long):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.metrics.RaEvent.Builder.updateRouterLifetime(long)
+    
 BuilderSetStyle: android.os.StrictMode.ThreadPolicy.Builder#detectExplicitGc():
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.os.StrictMode.ThreadPolicy.Builder.detectExplicitGc()
+    
 BuilderSetStyle: android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse():
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.os.StrictMode.VmPolicy.Builder.detectIncorrectContextUse()
+    
 BuilderSetStyle: android.os.StrictMode.VmPolicy.Builder#permitIncorrectContextUse():
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.os.StrictMode.VmPolicy.Builder.permitIncorrectContextUse()
+    
 
 
 CallbackInterface: android.app.prediction.AppPredictor.Callback:
@@ -382,11 +382,11 @@
 ExecutorRegistration: android.os.RemoteCallback#RemoteCallback(android.os.RemoteCallback.OnResultListener, android.os.Handler):
     
 ExecutorRegistration: android.permission.PermissionControllerManager#countPermissionApps(java.util.List<java.lang.String>, int, android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, android.os.Handler):
-    Registration methods should have overload that accepts delivery Executor: `countPermissionApps`
+    
 ExecutorRegistration: android.permission.PermissionControllerManager#getAppPermissions(String, android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, android.os.Handler):
     
 ExecutorRegistration: android.service.watchdog.ExplicitHealthCheckService#setCallback(android.os.RemoteCallback):
-    Registration methods should have overload that accepts delivery Executor: `setCallback`
+    
 ExecutorRegistration: android.telephony.ims.stub.ImsCallSessionImplBase#setListener(android.telephony.ims.ImsCallSessionListener):
     
 ExecutorRegistration: android.telephony.ims.stub.ImsUtImplBase#setListener(android.telephony.ims.ImsUtListener):
@@ -406,7 +406,7 @@
 ExecutorRegistration: android.telephony.mbms.vendor.MbmsStreamingServiceBase#startStreaming(int, String, android.telephony.mbms.StreamingServiceCallback):
     
 ExecutorRegistration: android.window.WindowOrganizer#applySyncTransaction(android.window.WindowContainerTransaction, android.window.WindowContainerTransactionCallback):
-    Registration methods should have overload that accepts delivery Executor: `applySyncTransaction`
+    
 
 
 ForbiddenSuperClass: android.app.AppDetailsActivity:
@@ -428,9 +428,9 @@
 
 
 GetterOnBuilder: android.hardware.display.BrightnessConfiguration.Builder#getMaxCorrectionsByCategory():
-    Getter should be on the built object, not the builder: method android.hardware.display.BrightnessConfiguration.Builder.getMaxCorrectionsByCategory()
+    
 GetterOnBuilder: android.hardware.display.BrightnessConfiguration.Builder#getMaxCorrectionsByPackageName():
-    Getter should be on the built object, not the builder: method android.hardware.display.BrightnessConfiguration.Builder.getMaxCorrectionsByPackageName()
+    
 
 
 GetterSetterNames: android.app.NotificationChannel#isBlockableSystem():
@@ -510,7 +510,7 @@
 IntentBuilderName: android.app.backup.BackupManager#getDataManagementIntent(String):
     
 IntentBuilderName: android.hardware.soundtrigger.KeyphraseEnrollmentInfo#getManageKeyphraseIntent(int, String, java.util.Locale):
-    Methods creating an Intent should be named `create<Foo>Intent()`, was `getManageKeyphraseIntent`
+    
 
 
 IntentName: android.provider.Settings.Secure#VOICE_INTERACTION_SERVICE:
@@ -560,7 +560,7 @@
 ListenerLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper) parameter #2:
     
 ListenerLast: android.permission.PermissionControllerManager#countPermissionApps(java.util.List<java.lang.String>, int, android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, android.os.Handler) parameter #3:
-    Listeners should always be at end of argument list (method `countPermissionApps`)
+    
 ListenerLast: android.permission.PermissionControllerManager#getAppPermissions(String, android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, android.os.Handler) parameter #2:
     
 ListenerLast: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#initialize(android.telephony.mbms.MbmsGroupCallSessionCallback, int) parameter #1:
@@ -593,140 +593,152 @@
     
 
 
+MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setAttributeSet(android.util.AttributeSet):
+    android.app.ActivityView does not declare a `getAttributeSet()` method matching method android.app.ActivityView.Builder.setAttributeSet(android.util.AttributeSet)
+MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setDefaultStyle(int):
+    android.app.ActivityView does not declare a `getDefaultStyle()` method matching method android.app.ActivityView.Builder.setDefaultStyle(int)
+MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setDisableSurfaceViewBackgroundLayer(boolean):
+    android.app.ActivityView does not declare a `isDisableSurfaceViewBackgroundLayer()` method matching method android.app.ActivityView.Builder.setDisableSurfaceViewBackgroundLayer(boolean)
+MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setSingleInstance(boolean):
+    android.app.ActivityView does not declare a `isSingleInstance()` method matching method android.app.ActivityView.Builder.setSingleInstance(boolean)
+MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setUsePublicVirtualDisplay(boolean):
+    android.app.ActivityView does not declare a `isUsePublicVirtualDisplay()` method matching method android.app.ActivityView.Builder.setUsePublicVirtualDisplay(boolean)
+MissingGetterMatchingBuilder: android.app.ActivityView.Builder#setUseTrustedDisplay(boolean):
+    android.app.ActivityView does not declare a `isUseTrustedDisplay()` method matching method android.app.ActivityView.Builder.setUseTrustedDisplay(boolean)
 MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setAttributionTag(String):
-    android.app.AppOpsManager.HistoricalOpsRequest does not declare a `getAttributionTag()` method matching method android.app.AppOpsManager.HistoricalOpsRequest.Builder.setAttributionTag(String)
+    
 MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setFlags(int):
-    android.app.AppOpsManager.HistoricalOpsRequest does not declare a `getFlags()` method matching method android.app.AppOpsManager.HistoricalOpsRequest.Builder.setFlags(int)
+    
 MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setOpNames(java.util.List<java.lang.String>):
-    android.app.AppOpsManager.HistoricalOpsRequest does not declare a `getOpNames()` method matching method android.app.AppOpsManager.HistoricalOpsRequest.Builder.setOpNames(java.util.List<java.lang.String>)
+    
 MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setPackageName(String):
-    android.app.AppOpsManager.HistoricalOpsRequest does not declare a `getPackageName()` method matching method android.app.AppOpsManager.HistoricalOpsRequest.Builder.setPackageName(String)
+    
 MissingGetterMatchingBuilder: android.app.AppOpsManager.HistoricalOpsRequest.Builder#setUid(int):
-    android.app.AppOpsManager.HistoricalOpsRequest does not declare a `getUid()` method matching method android.app.AppOpsManager.HistoricalOpsRequest.Builder.setUid(int)
+    
 MissingGetterMatchingBuilder: android.content.integrity.RuleSet.Builder#addRules(java.util.List<android.content.integrity.Rule>):
-    android.content.integrity.RuleSet does not declare a `getRuless()` method matching method android.content.integrity.RuleSet.Builder.addRules(java.util.List<android.content.integrity.Rule>)
+    
 MissingGetterMatchingBuilder: android.hardware.display.BrightnessConfiguration.Builder#addCorrectionByCategory(int, android.hardware.display.BrightnessCorrection):
-    android.hardware.display.BrightnessConfiguration does not declare a `getCorrectionByCategorys()` method matching method android.hardware.display.BrightnessConfiguration.Builder.addCorrectionByCategory(int,android.hardware.display.BrightnessCorrection)
+    
 MissingGetterMatchingBuilder: android.hardware.display.BrightnessConfiguration.Builder#addCorrectionByPackageName(String, android.hardware.display.BrightnessCorrection):
-    android.hardware.display.BrightnessConfiguration does not declare a `getCorrectionByPackageNames()` method matching method android.hardware.display.BrightnessConfiguration.Builder.addCorrectionByPackageName(String,android.hardware.display.BrightnessCorrection)
+    
 MissingGetterMatchingBuilder: android.hardware.display.BrightnessConfiguration.Builder#setDescription(String):
-    android.hardware.display.BrightnessConfiguration does not declare a `getDescription()` method matching method android.hardware.display.BrightnessConfiguration.Builder.setDescription(String)
+    
 MissingGetterMatchingBuilder: android.hardware.lights.LightsRequest.Builder#setLight(android.hardware.lights.Light, android.hardware.lights.LightState):
-    android.hardware.lights.LightsRequest does not declare a `getLight()` method matching method android.hardware.lights.LightsRequest.Builder.setLight(android.hardware.lights.Light,android.hardware.lights.LightState)
+    
 MissingGetterMatchingBuilder: android.media.VolumeShaper.Configuration.Builder#setOptionFlags(int):
-    android.media.VolumeShaper.Configuration does not declare a `getOptionFlags()` method matching method android.media.VolumeShaper.Configuration.Builder.setOptionFlags(int)
+    
 MissingGetterMatchingBuilder: android.media.audiopolicy.AudioMix.Builder#setDevice(android.media.AudioDeviceInfo):
-    android.media.audiopolicy.AudioMix does not declare a `getDevice()` method matching method android.media.audiopolicy.AudioMix.Builder.setDevice(android.media.AudioDeviceInfo)
+    
 MissingGetterMatchingBuilder: android.media.audiopolicy.AudioMix.Builder#setFormat(android.media.AudioFormat):
-    android.media.audiopolicy.AudioMix does not declare a `getFormat()` method matching method android.media.audiopolicy.AudioMix.Builder.setFormat(android.media.AudioFormat)
+    
 MissingGetterMatchingBuilder: android.media.audiopolicy.AudioMix.Builder#setRouteFlags(int):
-    android.media.audiopolicy.AudioMix does not declare a `getRouteFlags()` method matching method android.media.audiopolicy.AudioMix.Builder.setRouteFlags(int)
+    
 MissingGetterMatchingBuilder: android.media.audiopolicy.AudioMixingRule.Builder#addMixRule(int, Object):
-    android.media.audiopolicy.AudioMixingRule does not declare a `getMixRules()` method matching method android.media.audiopolicy.AudioMixingRule.Builder.addMixRule(int,Object)
+    
 MissingGetterMatchingBuilder: android.media.audiopolicy.AudioMixingRule.Builder#addRule(android.media.AudioAttributes, int):
-    android.media.audiopolicy.AudioMixingRule does not declare a `getRules()` method matching method android.media.audiopolicy.AudioMixingRule.Builder.addRule(android.media.AudioAttributes,int)
+    
 MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#addMix(android.media.audiopolicy.AudioMix):
-    android.media.audiopolicy.AudioPolicy does not declare a `getMixs()` method matching method android.media.audiopolicy.AudioPolicy.Builder.addMix(android.media.audiopolicy.AudioMix)
+    
 MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyFocusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener):
-    android.media.audiopolicy.AudioPolicy does not declare a `getAudioPolicyFocusListener()` method matching method android.media.audiopolicy.AudioPolicy.Builder.setAudioPolicyFocusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener)
+    
 MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyStatusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListener):
-    android.media.audiopolicy.AudioPolicy does not declare a `getAudioPolicyStatusListener()` method matching method android.media.audiopolicy.AudioPolicy.Builder.setAudioPolicyStatusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListener)
+    
 MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setAudioPolicyVolumeCallback(android.media.audiopolicy.AudioPolicy.AudioPolicyVolumeCallback):
-    android.media.audiopolicy.AudioPolicy does not declare a `getAudioPolicyVolumeCallback()` method matching method android.media.audiopolicy.AudioPolicy.Builder.setAudioPolicyVolumeCallback(android.media.audiopolicy.AudioPolicy.AudioPolicyVolumeCallback)
+    
 MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setIsAudioFocusPolicy(boolean):
-    android.media.audiopolicy.AudioPolicy does not declare a `isIsAudioFocusPolicy()` method matching method android.media.audiopolicy.AudioPolicy.Builder.setIsAudioFocusPolicy(boolean)
+    
 MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setIsTestFocusPolicy(boolean):
-    android.media.audiopolicy.AudioPolicy does not declare a `isIsTestFocusPolicy()` method matching method android.media.audiopolicy.AudioPolicy.Builder.setIsTestFocusPolicy(boolean)
+    
 MissingGetterMatchingBuilder: android.media.audiopolicy.AudioPolicy.Builder#setLooper(android.os.Looper):
-    android.media.audiopolicy.AudioPolicy does not declare a `getLooper()` method matching method android.media.audiopolicy.AudioPolicy.Builder.setLooper(android.os.Looper)
+    
 MissingGetterMatchingBuilder: android.net.CaptivePortalData.Builder#setBytesRemaining(long):
-    android.net.CaptivePortalData does not declare a `getBytesRemaining()` method matching method android.net.CaptivePortalData.Builder.setBytesRemaining(long)
+    
 MissingGetterMatchingBuilder: android.net.CaptivePortalData.Builder#setExpiryTime(long):
-    android.net.CaptivePortalData does not declare a `getExpiryTime()` method matching method android.net.CaptivePortalData.Builder.setExpiryTime(long)
+    
 MissingGetterMatchingBuilder: android.net.CaptivePortalData.Builder#setRefreshTime(long):
-    android.net.CaptivePortalData does not declare a `getRefreshTime()` method matching method android.net.CaptivePortalData.Builder.setRefreshTime(long)
+    
 MissingGetterMatchingBuilder: android.net.NetworkCapabilities.Builder#addCapability(int):
-    android.net.NetworkCapabilities does not declare a `getCapabilitys()` method matching method android.net.NetworkCapabilities.Builder.addCapability(int)
+    
 MissingGetterMatchingBuilder: android.net.NetworkCapabilities.Builder#setRequestorPackageName(String):
-    android.net.NetworkCapabilities does not declare a `getRequestorPackageName()` method matching method android.net.NetworkCapabilities.Builder.setRequestorPackageName(String)
+    
 MissingGetterMatchingBuilder: android.net.NetworkCapabilities.Builder#setRequestorUid(int):
-    android.net.NetworkCapabilities does not declare a `getRequestorUid()` method matching method android.net.NetworkCapabilities.Builder.setRequestorUid(int)
+    
 MissingGetterMatchingBuilder: android.net.TetheringManager.TetheringRequest.Builder#setShouldShowEntitlementUi(boolean):
-    android.net.TetheringManager.TetheringRequest does not declare a `shouldShowEntitlementUi()` method matching method android.net.TetheringManager.TetheringRequest.Builder.setShouldShowEntitlementUi(boolean)
+    
 MissingGetterMatchingBuilder: android.net.TetheringManager.TetheringRequest.Builder#setStaticIpv4Addresses(android.net.LinkAddress, android.net.LinkAddress):
-    android.net.TetheringManager.TetheringRequest does not declare a `getStaticIpv4Addresses()` method matching method android.net.TetheringManager.TetheringRequest.Builder.setStaticIpv4Addresses(android.net.LinkAddress,android.net.LinkAddress)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setActualLifetime(long):
-    android.net.metrics.ApfProgramEvent does not declare a `getActualLifetime()` method matching method android.net.metrics.ApfProgramEvent.Builder.setActualLifetime(long)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setCurrentRas(int):
-    android.net.metrics.ApfProgramEvent does not declare a `getCurrentRas()` method matching method android.net.metrics.ApfProgramEvent.Builder.setCurrentRas(int)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setFilteredRas(int):
-    android.net.metrics.ApfProgramEvent does not declare a `getFilteredRas()` method matching method android.net.metrics.ApfProgramEvent.Builder.setFilteredRas(int)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setFlags(boolean, boolean):
-    android.net.metrics.ApfProgramEvent does not declare a `isFlags()` method matching method android.net.metrics.ApfProgramEvent.Builder.setFlags(boolean,boolean)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setLifetime(long):
-    android.net.metrics.ApfProgramEvent does not declare a `getLifetime()` method matching method android.net.metrics.ApfProgramEvent.Builder.setLifetime(long)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfProgramEvent.Builder#setProgramLength(int):
-    android.net.metrics.ApfProgramEvent does not declare a `getProgramLength()` method matching method android.net.metrics.ApfProgramEvent.Builder.setProgramLength(int)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setDroppedRas(int):
-    android.net.metrics.ApfStats does not declare a `getDroppedRas()` method matching method android.net.metrics.ApfStats.Builder.setDroppedRas(int)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setDurationMs(long):
-    android.net.metrics.ApfStats does not declare a `getDurationMs()` method matching method android.net.metrics.ApfStats.Builder.setDurationMs(long)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setMatchingRas(int):
-    android.net.metrics.ApfStats does not declare a `getMatchingRas()` method matching method android.net.metrics.ApfStats.Builder.setMatchingRas(int)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setMaxProgramSize(int):
-    android.net.metrics.ApfStats does not declare a `getMaxProgramSize()` method matching method android.net.metrics.ApfStats.Builder.setMaxProgramSize(int)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setParseErrors(int):
-    android.net.metrics.ApfStats does not declare a `getParseErrors()` method matching method android.net.metrics.ApfStats.Builder.setParseErrors(int)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setProgramUpdates(int):
-    android.net.metrics.ApfStats does not declare a `getProgramUpdates()` method matching method android.net.metrics.ApfStats.Builder.setProgramUpdates(int)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setProgramUpdatesAll(int):
-    android.net.metrics.ApfStats does not declare a `getProgramUpdatesAll()` method matching method android.net.metrics.ApfStats.Builder.setProgramUpdatesAll(int)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setProgramUpdatesAllowingMulticast(int):
-    android.net.metrics.ApfStats does not declare a `getProgramUpdatesAllowingMulticast()` method matching method android.net.metrics.ApfStats.Builder.setProgramUpdatesAllowingMulticast(int)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setReceivedRas(int):
-    android.net.metrics.ApfStats does not declare a `getReceivedRas()` method matching method android.net.metrics.ApfStats.Builder.setReceivedRas(int)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ApfStats.Builder#setZeroLifetimeRas(int):
-    android.net.metrics.ApfStats does not declare a `getZeroLifetimeRas()` method matching method android.net.metrics.ApfStats.Builder.setZeroLifetimeRas(int)
+    
 MissingGetterMatchingBuilder: android.net.metrics.DhcpClientEvent.Builder#setDurationMs(int):
-    android.net.metrics.DhcpClientEvent does not declare a `getDurationMs()` method matching method android.net.metrics.DhcpClientEvent.Builder.setDurationMs(int)
+    
 MissingGetterMatchingBuilder: android.net.metrics.DhcpClientEvent.Builder#setMsg(String):
-    android.net.metrics.DhcpClientEvent does not declare a `getMsg()` method matching method android.net.metrics.DhcpClientEvent.Builder.setMsg(String)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ValidationProbeEvent.Builder#setDurationMs(long):
-    android.net.metrics.ValidationProbeEvent does not declare a `getDurationMs()` method matching method android.net.metrics.ValidationProbeEvent.Builder.setDurationMs(long)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ValidationProbeEvent.Builder#setProbeType(int, boolean):
-    android.net.metrics.ValidationProbeEvent does not declare a `getProbeType()` method matching method android.net.metrics.ValidationProbeEvent.Builder.setProbeType(int,boolean)
+    
 MissingGetterMatchingBuilder: android.net.metrics.ValidationProbeEvent.Builder#setReturnCode(int):
-    android.net.metrics.ValidationProbeEvent does not declare a `getReturnCode()` method matching method android.net.metrics.ValidationProbeEvent.Builder.setReturnCode(int)
+    
 MissingGetterMatchingBuilder: android.security.keystore.KeyGenParameterSpec.Builder#setUniqueIdIncluded(boolean):
-    android.security.keystore.KeyGenParameterSpec does not declare a `isUniqueIdIncluded()` method matching method android.security.keystore.KeyGenParameterSpec.Builder.setUniqueIdIncluded(boolean)
+    
 MissingGetterMatchingBuilder: android.service.autofill.Dataset.Builder#setFieldInlinePresentation(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.service.autofill.InlinePresentation):
-    android.service.autofill.Dataset does not declare a `getFieldInlinePresentation()` method matching method android.service.autofill.Dataset.Builder.setFieldInlinePresentation(android.view.autofill.AutofillId,android.view.autofill.AutofillValue,java.util.regex.Pattern,android.service.autofill.InlinePresentation)
+    
 MissingGetterMatchingBuilder: android.service.autofill.augmented.FillResponse.Builder#setClientState(android.os.Bundle):
-    android.service.autofill.augmented.FillResponse does not declare a `getClientState()` method matching method android.service.autofill.augmented.FillResponse.Builder.setClientState(android.os.Bundle)
+    
 MissingGetterMatchingBuilder: android.service.autofill.augmented.FillResponse.Builder#setFillWindow(android.service.autofill.augmented.FillWindow):
-    android.service.autofill.augmented.FillResponse does not declare a `getFillWindow()` method matching method android.service.autofill.augmented.FillResponse.Builder.setFillWindow(android.service.autofill.augmented.FillWindow)
+    
 MissingGetterMatchingBuilder: android.service.autofill.augmented.FillResponse.Builder#setInlineSuggestions(java.util.List<android.service.autofill.Dataset>):
-    android.service.autofill.augmented.FillResponse does not declare a `getInlineSuggestions()` method matching method android.service.autofill.augmented.FillResponse.Builder.setInlineSuggestions(java.util.List<android.service.autofill.Dataset>)
+    
 MissingGetterMatchingBuilder: android.telecom.CallScreeningService.CallResponse.Builder#setShouldScreenCallViaAudioProcessing(boolean):
-    android.telecom.CallScreeningService.CallResponse does not declare a `shouldScreenCallViaAudioProcessing()` method matching method android.telecom.CallScreeningService.CallResponse.Builder.setShouldScreenCallViaAudioProcessing(boolean)
+    
 MissingGetterMatchingBuilder: android.telecom.ConnectionRequest.Builder#setIsAdhocConferenceCall(boolean):
-    android.telecom.ConnectionRequest does not declare a `isIsAdhocConferenceCall()` method matching method android.telecom.ConnectionRequest.Builder.setIsAdhocConferenceCall(boolean)
+    
 MissingGetterMatchingBuilder: android.telecom.ConnectionRequest.Builder#setRttPipeFromInCall(android.os.ParcelFileDescriptor):
-    android.telecom.ConnectionRequest does not declare a `getRttPipeFromInCall()` method matching method android.telecom.ConnectionRequest.Builder.setRttPipeFromInCall(android.os.ParcelFileDescriptor)
+    
 MissingGetterMatchingBuilder: android.telecom.ConnectionRequest.Builder#setRttPipeToInCall(android.os.ParcelFileDescriptor):
-    android.telecom.ConnectionRequest does not declare a `getRttPipeToInCall()` method matching method android.telecom.ConnectionRequest.Builder.setRttPipeToInCall(android.os.ParcelFileDescriptor)
+    
 MissingGetterMatchingBuilder: android.telecom.ConnectionRequest.Builder#setShouldShowIncomingCallUi(boolean):
-    android.telecom.ConnectionRequest does not declare a `shouldShowIncomingCallUi()` method matching method android.telecom.ConnectionRequest.Builder.setShouldShowIncomingCallUi(boolean)
+    
 MissingGetterMatchingBuilder: android.telecom.PhoneAccount.Builder#setGroupId(String):
-    android.telecom.PhoneAccount does not declare a `getGroupId()` method matching method android.telecom.PhoneAccount.Builder.setGroupId(String)
+    
 MissingGetterMatchingBuilder: android.telephony.NetworkRegistrationInfo.Builder#setEmergencyOnly(boolean):
-    android.telephony.NetworkRegistrationInfo does not declare a `isEmergencyOnly()` method matching method android.telephony.NetworkRegistrationInfo.Builder.setEmergencyOnly(boolean)
+    
 MissingGetterMatchingBuilder: android.telephony.ims.ImsSsData.Builder#setCallForwardingInfo(java.util.List<android.telephony.ims.ImsCallForwardInfo>):
-    android.telephony.ims.ImsSsData does not declare a `getCallForwardingInfo()` method matching method android.telephony.ims.ImsSsData.Builder.setCallForwardingInfo(java.util.List<android.telephony.ims.ImsCallForwardInfo>)
+    
 MissingGetterMatchingBuilder: android.telephony.ims.stub.ImsFeatureConfiguration.Builder#addFeature(int, int):
-    android.telephony.ims.stub.ImsFeatureConfiguration does not declare a `getFeatures()` method matching method android.telephony.ims.stub.ImsFeatureConfiguration.Builder.addFeature(int,int)
+    
 MissingGetterMatchingBuilder: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String):
-    android.telephony.mbms.DownloadRequest does not declare a `getServiceId()` method matching method android.telephony.mbms.DownloadRequest.Builder.setServiceId(String)
+    
 
 
 MissingNullability: android.app.Activity#onMovedToDisplay(int, android.content.res.Configuration) parameter #1:
@@ -2590,7 +2602,7 @@
 OnNameExpected: android.service.quicksettings.TileService#isQuickSettingsSupported():
     
 OnNameExpected: android.service.watchdog.ExplicitHealthCheckService#setCallback(android.os.RemoteCallback):
-    If implemented by developer, should follow the on<Something> style; otherwise consider marking final
+    
 OnNameExpected: android.telephony.ims.ImsService#createMmTelFeature(int):
     
 OnNameExpected: android.telephony.ims.ImsService#createRcsFeature(int):
@@ -2620,11 +2632,11 @@
 
 
 OptionalBuilderConstructorArgument: android.app.prediction.AppTargetEvent.Builder#Builder(android.app.prediction.AppTarget, int) parameter #0:
-    Builder constructor arguments must be mandatory (i.e. not @Nullable): parameter target in android.app.prediction.AppTargetEvent.Builder(android.app.prediction.AppTarget target, int actionType)
+    
 OptionalBuilderConstructorArgument: android.net.CaptivePortalData.Builder#Builder(android.net.CaptivePortalData) parameter #0:
-    Builder constructor arguments must be mandatory (i.e. not @Nullable): parameter data in android.net.CaptivePortalData.Builder(android.net.CaptivePortalData data)
+    
 OptionalBuilderConstructorArgument: android.os.VibrationAttributes.Builder#Builder(android.media.AudioAttributes, android.os.VibrationEffect) parameter #1:
-    Builder constructor arguments must be mandatory (i.e. not @Nullable): parameter effect in android.os.VibrationAttributes.Builder(android.media.AudioAttributes audio, android.os.VibrationEffect effect)
+    
 
 
 PackageLayering: android.util.FeatureFlagUtils:
@@ -2824,7 +2836,7 @@
 SamShouldBeLast: android.os.StrictMode.ViolationInfo#dump(android.util.Printer, String):
     
 SamShouldBeLast: android.permission.PermissionControllerManager#countPermissionApps(java.util.List<java.lang.String>, int, android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, android.os.Handler):
-    SAM-compatible parameters (such as parameter 3, "callback", in android.permission.PermissionControllerManager.countPermissionApps) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+    
 SamShouldBeLast: android.permission.PermissionControllerManager#getAppPermissions(String, android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, android.os.Handler):
     
 SamShouldBeLast: android.permission.PermissionControllerManager#revokeRuntimePermissions(java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, java.util.concurrent.Executor, android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback):
@@ -2870,21 +2882,21 @@
 
 
 StaticFinalBuilder: android.content.integrity.RuleSet.Builder:
-    Builder must be final: android.content.integrity.RuleSet.Builder
+    
 StaticFinalBuilder: android.hardware.display.BrightnessConfiguration.Builder:
-    Builder must be final: android.hardware.display.BrightnessConfiguration.Builder
+    
 StaticFinalBuilder: android.media.audiopolicy.AudioMix.Builder:
-    Builder must be final: android.media.audiopolicy.AudioMix.Builder
+    
 StaticFinalBuilder: android.media.audiopolicy.AudioMixingRule.Builder:
-    Builder must be final: android.media.audiopolicy.AudioMixingRule.Builder
+    
 StaticFinalBuilder: android.media.audiopolicy.AudioPolicy.Builder:
-    Builder must be final: android.media.audiopolicy.AudioPolicy.Builder
+    
 StaticFinalBuilder: android.net.CaptivePortalData.Builder:
-    Builder must be final: android.net.CaptivePortalData.Builder
+    
 StaticFinalBuilder: android.net.TetheringManager.TetheringRequest.Builder:
-    Builder must be final: android.net.TetheringManager.TetheringRequest.Builder
+    
 StaticFinalBuilder: android.telephony.ims.stub.ImsFeatureConfiguration.Builder:
-    Builder must be final: android.telephony.ims.stub.ImsFeatureConfiguration.Builder
+    
 
 
 StaticUtils: android.os.health.HealthKeys:
@@ -2912,15 +2924,15 @@
 
 
 UseIcu: android.hardware.soundtrigger.KeyphraseEnrollmentInfo#getKeyphraseMetadata(String, java.util.Locale) parameter #1:
-    Type `java.util.Locale` should be replaced with richer ICU type `android.icu.util.ULocale`
+    
 UseIcu: android.hardware.soundtrigger.KeyphraseEnrollmentInfo#getManageKeyphraseIntent(int, String, java.util.Locale) parameter #2:
-    Type `java.util.Locale` should be replaced with richer ICU type `android.icu.util.ULocale`
+    
 UseIcu: android.hardware.soundtrigger.KeyphraseMetadata#supportsLocale(java.util.Locale) parameter #0:
-    Type `java.util.Locale` should be replaced with richer ICU type `android.icu.util.ULocale`
+    
 UseIcu: android.hardware.soundtrigger.SoundTrigger.Keyphrase#Keyphrase(int, int, java.util.Locale, String, int[]) parameter #2:
-    Type `java.util.Locale` should be replaced with richer ICU type `android.icu.util.ULocale`
+    
 UseIcu: android.hardware.soundtrigger.SoundTrigger.Keyphrase#getLocale():
-    Type `java.util.Locale` should be replaced with richer ICU type `android.icu.util.ULocale`
+    
 
 
 UseParcelFileDescriptor: android.util.proto.ProtoOutputStream#ProtoOutputStream(java.io.FileDescriptor) parameter #0:
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index fc1455b..0a09801 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -38,6 +38,7 @@
 import "frameworks/base/core/proto/android/os/enums.proto";
 import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto";
 import "frameworks/base/core/proto/android/server/enums.proto";
+import "frameworks/base/core/proto/android/stats/hdmi/enums.proto";
 import "frameworks/base/core/proto/android/server/job/enums.proto";
 import "frameworks/base/core/proto/android/server/location/enums.proto";
 import "frameworks/base/core/proto/android/service/procstats_enum.proto";
@@ -490,6 +491,8 @@
         UIActionLatencyReported ui_action_latency_reported = 306 [(module) = "framework"];
         WifiDisconnectReported wifi_disconnect_reported = 307 [(module) = "wifi"];
         WifiConnectionStateChanged wifi_connection_state_changed = 308 [(module) = "wifi"];
+        HdmiCecActiveSourceChanged hdmi_cec_active_source_changed = 309 [(module) = "framework"];
+        HdmiCecMessageReported hdmi_cec_message_reported = 310 [(module) = "framework"];
 
         // StatsdStats tracks platform atoms with ids upto 500.
         // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value.
@@ -11433,4 +11436,65 @@
 
     // List of leasees of this Blob
     optional BlobLeaseeListProto leasees = 5 [(log_mode) = MODE_BYTES];
-}
\ No newline at end of file
+}
+
+/**
+ * Logs when the HDMI CEC active source changes.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
+ */
+message HdmiCecActiveSourceChanged {
+    // The logical address of the active source.
+    optional android.stats.hdmi.LogicalAddress active_source_logical_address = 1;
+
+    // The physical address of the active source. Consists of four hexadecimal nibbles.
+    // Examples: 0x1234, 0x0000 (root device). 0xFFFF represents an unknown or invalid address.
+    // See section 8.7 in the HDMI 1.4b spec for details.
+    optional int32 active_source_physical_address = 2;
+
+    // The relationship between this device and the active source.
+    optional android.stats.hdmi.PathRelationship local_relationship = 3;
+}
+
+/**
+ * Logs when an HDMI CEC message is sent or received.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
+ */
+message HdmiCecMessageReported {
+    // The calling uid of the application that caused this atom to be written.
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Whether a HDMI CEC message is sent from this device, to this device, or neither.
+    optional android.stats.hdmi.MessageDirection direction = 2;
+
+    // The HDMI CEC logical address of the initiator.
+    optional android.stats.hdmi.LogicalAddress initiator_logical_address = 3;
+
+    // The HDMI CEC logical address of the destination.
+    optional android.stats.hdmi.LogicalAddress destination_logical_address = 4;
+
+    // The opcode of the message. Ranges from 0x00 to 0xFF.
+    // For all values, see section "CEC 15 Message Descriptions" in the HDMI CEC 1.4b spec.
+    optional int32 opcode = 5;
+
+    // The result of attempting to send the message on its final retransmission attempt.
+    // Only applicable to outgoing messages; set to SEND_MESSAGE_RESULT_UNKNOWN otherwise.
+    optional android.stats.hdmi.SendMessageResult send_message_result = 6;
+
+    // Fields specific to <User Control Pressed> messages
+
+    // The user control command that was received.
+    optional android.stats.hdmi.UserControlPressedCommand user_control_pressed_command = 7;
+
+    // Fields specific to <Feature Abort> messages
+
+    // The opcode of the message that was feature aborted.
+    // Set to 0x100 when unknown or not applicable.
+    optional int32 feature_abort_opcode = 8;
+
+    // The reason for the feature abort.
+    optional android.stats.hdmi.FeatureAbortReason feature_abort_reason = 9;
+}
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 4139b2f..b0307d1 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -70,7 +70,7 @@
             "name": "CtsAutoFillServiceTestCases",
             "options": [
                 {
-                    "include-filter": "android.autofillservice.cts.PreSimpleSaveActivityTest"
+                    "include-filter": "android.autofillservice.cts.saveui.PreSimpleSaveActivityTest"
                 },
                 {
                     "exclude-annotation": "androidx.test.filters.FlakyTest"
@@ -82,7 +82,7 @@
             "name": "CtsAutoFillServiceTestCases",
             "options": [
                 {
-                    "include-filter": "android.autofillservice.cts.SimpleSaveActivityTest"
+                    "include-filter": "android.autofillservice.cts.saveui.SimpleSaveActivityTest"
                 },
                 {
                     "exclude-annotation": "androidx.test.filters.FlakyTest"
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 73c38cb..ce3c7d2 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -89,6 +89,33 @@
     @SuppressLint("ActionValue")
     public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
 
+    /**
+     * Intent used to broadcast the change in tethering state of the Pan
+     * Profile
+     *
+     * <p>This intent will have 1 extra:
+     * <ul>
+     * <li> {@link #EXTRA_TETHERING_STATE} - The current state of Bluetooth
+     * tethering. </li>
+     * </ul>
+     *
+     * <p> {@link #EXTRA_TETHERING_STATE} can be any of {@link #TETHERING_STATE_OFF} or
+     * {@link #TETHERING_STATE_ON}
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_TETHERING_STATE_CHANGED =
+            "android.bluetooth.action.TETHERING_STATE_CHANGED";
+
+    /**
+     * Extra for {@link #ACTION_TETHERING_STATE_CHANGED} intent
+     * The tethering state of the PAN profile.
+     * It can be one of {@link #TETHERING_STATE_OFF} or {@link #TETHERING_STATE_ON}.
+     */
+    public static final String EXTRA_TETHERING_STATE =
+            "android.bluetooth.extra.TETHERING_STATE";
+
     /** @hide */
     @IntDef({PAN_ROLE_NONE, LOCAL_NAP_ROLE, LOCAL_PANU_ROLE})
     @Retention(RetentionPolicy.SOURCE)
@@ -114,6 +141,14 @@
 
     public static final int REMOTE_PANU_ROLE = 2;
 
+    /** @hide **/
+    @IntDef({TETHERING_STATE_OFF, TETHERING_STATE_ON})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TetheringState{}
+
+    public static final int TETHERING_STATE_OFF = 1;
+
+    public static final int TETHERING_STATE_ON = 2;
     /**
      * Return codes for the connect and disconnect Bluez / Dbus calls.
      *
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 6673cb9..d5f2c12 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -146,8 +146,15 @@
     public int uiOptions = 0;
 
     /**
-     * Value for {@link #flags}: if set, this application is installed in the
-     * device's system image.
+     * Value for {@link #flags}: if set, this application is installed in the device's system image.
+     * This should not be used to make security decisions. Instead, rely on
+     * {@linkplain android.content.pm.PackageManager#checkSignatures(java.lang.String,java.lang.String)
+     * signature checks} or
+     * <a href="https://developer.android.com/training/articles/security-tips#Permissions">permissions</a>.
+     *
+     * <p><b>Warning:</b> Note that does flag not behave the same as
+     * {@link android.R.attr#protectionLevel android:protectionLevel} {@code system} or
+     * {@code signatureOrSystem}.
      */
     public static final int FLAG_SYSTEM = 1<<0;
     
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 0cdad9f..400b312 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -46,6 +46,8 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.SomeArgs;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Turns a {@link SeekBar} into a volume control.
  * @hide
@@ -64,9 +66,15 @@
         void onSampleStarting(SeekBarVolumizer sbv);
         void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch);
         void onMuted(boolean muted, boolean zenMuted);
+        /**
+         * Callback reporting that the seek bar is start tracking.
+         * @param sbv - The seek bar that start tracking
+         */
+        void onStartTrackingTouch(SeekBarVolumizer sbv);
     }
 
     private static final int MSG_GROUP_VOLUME_CHANGED = 1;
+    private static long sStopVolumeTime = 0L;
     private final Handler mVolumeHandler = new VolumeHandler();
     private AudioAttributes mAttributes;
     private int mVolumeGroupId;
@@ -126,6 +134,9 @@
     private static final int MSG_STOP_SAMPLE = 2;
     private static final int MSG_INIT_SAMPLE = 3;
     private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
+    private static final long SET_STREAM_VOLUME_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500);
+    private static final long START_SAMPLE_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500);
+    private static final long DURATION_TO_START_DELAYING = TimeUnit.MILLISECONDS.toMillis(2000);
 
     private NotificationManager.Policy mNotificationPolicy;
     private boolean mAllowAlarms;
@@ -314,7 +325,29 @@
         if (mHandler == null) return;
         mHandler.removeMessages(MSG_START_SAMPLE);
         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
-                isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
+                isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS
+                        : isDelay() ? START_SAMPLE_DELAY_MS : 0);
+    }
+
+    // After stop volume it needs to add a small delay when playing volume or set stream.
+    // It is because the call volume is from the earpiece and the alarm/ring/media
+    // is from the speaker. If play the alarm volume or set alarm stream right after stop
+    // call volume, the alarm volume on earpiece is returned then cause the volume value incorrect.
+    // It needs a small delay after stop call volume to get alarm volume on speaker.
+    // e.g. : If the ring volume has adjusted right after call volume stopped in 2 second
+    // then delay 0.5 second to set stream or play volume ringtone.
+    private boolean isDelay() {
+        final long durationTime = java.lang.System.currentTimeMillis() - sStopVolumeTime;
+        return durationTime >= 0 && durationTime < DURATION_TO_START_DELAYING;
+    }
+
+    private void setStopVolumeTime() {
+        // set the time of stop volume
+        if ((mStreamType == AudioManager.STREAM_VOICE_CALL
+                || mStreamType == AudioManager.STREAM_RING
+                || mStreamType == AudioManager.STREAM_ALARM)) {
+            sStopVolumeTime = java.lang.System.currentTimeMillis();
+        }
     }
 
     private void onStartSample() {
@@ -341,6 +374,7 @@
 
     private void postStopSample() {
         if (mHandler == null) return;
+        setStopVolumeTime();
         // remove pending delayed start messages
         mHandler.removeMessages(MSG_START_SAMPLE);
         mHandler.removeMessages(MSG_STOP_SAMPLE);
@@ -404,10 +438,15 @@
         // Do the volume changing separately to give responsive UI
         mLastProgress = progress;
         mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
+        mHandler.removeMessages(MSG_START_SAMPLE);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME),
+                isDelay() ? SET_STREAM_VOLUME_DELAY_MS : 0);
     }
 
     public void onStartTrackingTouch(SeekBar seekBar) {
+        if (mCallback != null) {
+            mCallback.onStartTrackingTouch(this);
+        }
     }
 
     public void onStopTrackingTouch(SeekBar seekBar) {
@@ -539,7 +578,7 @@
             if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
                 int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
-                if (hasAudioProductStrategies()) {
+                if (hasAudioProductStrategies() && !isDelay()) {
                     updateVolumeSlider(streamType, streamValue);
                 }
             } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
@@ -551,7 +590,7 @@
                 }
             } else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) {
                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
-                if (hasAudioProductStrategies()) {
+                if (hasAudioProductStrategies() && !isDelay()) {
                     int streamVolume = mAudioManager.getStreamVolume(streamType);
                     updateVolumeSlider(streamType, streamVolume);
                 } else {
@@ -559,7 +598,9 @@
                     if (volumeGroup != AudioVolumeGroup.DEFAULT_VOLUME_GROUP
                             && volumeGroup == mVolumeGroupId) {
                         int streamVolume = mAudioManager.getStreamVolume(streamType);
-                        updateVolumeSlider(streamType, streamVolume);
+                        if (!isDelay()) {
+                            updateVolumeSlider(streamType, streamVolume);
+                        }
                     }
                 }
             } else if (NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED.equals(action)) {
diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java
index 6eb524a..563bc46 100644
--- a/core/java/android/preference/VolumePreference.java
+++ b/core/java/android/preference/VolumePreference.java
@@ -176,6 +176,11 @@
     }
 
     @Override
+    public void onStartTrackingTouch(SeekBarVolumizer sbv) {
+        // noop
+    }
+
+    @Override
     protected Parcelable onSaveInstanceState() {
         final Parcelable superState = super.onSaveInstanceState();
         if (isPersistent()) {
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 9f52142..79d6bb4 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3956,9 +3956,7 @@
         public static final String APN_SET_ID = "apn_set_id";
 
         /**
-         * Possible value for the {@link #APN_SET_ID} field. By default APNs will not belong to a
-         * set. If the user manually selects an APN without apn set id, there is no need to
-         * prioritize any specific APN set ids.
+         * Possible value for the {@link #APN_SET_ID} field. By default APNs are added to set 0.
          * <p>Type: INTEGER</p>
          * @hide
          */
@@ -3966,6 +3964,16 @@
         public static final int NO_APN_SET_ID = 0;
 
         /**
+         * Possible value for the {@link #APN_SET_ID} field.
+         * APNs with MATCH_ALL_APN_SET_ID will be used regardless of any set ids of
+         * the selected APN.
+         * <p>Type: INTEGER</p>
+         * @hide
+         */
+        @SystemApi
+        public static final int MATCH_ALL_APN_SET_ID = -1;
+
+        /**
          * A unique carrier id associated with this APN
          * {@see TelephonyManager#getSimCarrierId()}
          * <p>Type: STRING</p>
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 0827fef..49bc65b 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1326,7 +1326,8 @@
             line2 = res.getString(R.string.zen_mode_until, formattedTime);
         } else {
             // display as day/time
-            summary = line1 = line2 = res.getString(R.string.zen_mode_until, formattedTime);
+            summary = line1 = line2 = res.getString(R.string.zen_mode_until_next_day,
+                    formattedTime);
         }
         final Uri id = toCountdownConditionId(time, false);
         return new Condition(id, summary, line1, line2, 0, Condition.STATE_TRUE,
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 8f31d77..1682686 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -1001,16 +1001,43 @@
     /**
      * Callback invoked when an outgoing SMS is placed to an emergency number.
      *
+     * This method will be called when an emergency sms is sent on any subscription.
      * @param sentEmergencyNumber the emergency number {@link EmergencyNumber} the SMS is sent to.
+     *
+     * @deprecated Use {@link #onOutgoingEmergencySms(EmergencyNumber, int)}.
      * @hide
      */
     @SystemApi
     @TestApi
+    @Deprecated
     public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber) {
         // default implementation empty
     }
 
     /**
+     * Smsback invoked when an outgoing sms is sent to an emergency number.
+     *
+     * This method will be called when an emergency sms is sent on any subscription,
+     * regardless of which subscription this listener was registered on.
+     *
+     * The default implementation of this method calls
+     * {@link #onOutgoingEmergencySms(EmergencyNumber)} for backwards compatibility purposes. Do
+     * not call {@code super(...)} from within your implementation unless you want
+     * {@link #onOutgoingEmergencySms(EmergencyNumber)} to be called as well.
+     *
+     * @param sentEmergencyNumber The {@link EmergencyNumber} the emergency sms was sent to.
+     * @param subscriptionId The subscription ID used to send the emergency sms.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber,
+            int subscriptionId) {
+        // Default implementation for backwards compatibility
+        onOutgoingEmergencySms(sentEmergencyNumber);
+    }
+
+    /**
      * Callback invoked when OEM hook raw event is received on the registered subscription.
      * Note, the registration subId comes from {@link TelephonyManager} object which registers
      * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
@@ -1399,13 +1426,14 @@
                                     subscriptionId)));
         }
 
-        public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber) {
+        public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber,
+                int subscriptionId) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
 
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(
-                            () -> psl.onOutgoingEmergencySms(sentEmergencyNumber)));
+                            () -> psl.onOutgoingEmergencySms(sentEmergencyNumber, subscriptionId)));
         }
 
         public void onPhoneCapabilityChanged(PhoneCapability capability) {
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 9cc6b9f..9244647 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -44,6 +44,8 @@
     /** @hide */
     public static final String SETTINGS_DO_NOT_RESTORE_PRESERVED =
             "settings_do_not_restore_preserved";
+    /** @hide */
+    public static final String SETTINGS_PROVIDER_MODEL = "settings_provider_model";
 
     private static final Map<String, String> DEFAULT_FLAGS;
 
@@ -67,6 +69,7 @@
         DEFAULT_FLAGS.put("settings_tether_all_in_one", "false");
         DEFAULT_FLAGS.put("settings_silky_home", "false");
         DEFAULT_FLAGS.put("settings_contextual_home", "false");
+        DEFAULT_FLAGS.put(SETTINGS_PROVIDER_MODEL, "false");
     }
 
     /**
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 987edf7..042808a4 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -660,8 +660,7 @@
         ThreadedRenderer.setFPSDivisor(divisor);
     }
 
-    @UnsupportedAppUsage
-    void doFrame(long frameTimeNanos, int frame) {
+    void doFrame(long frameTimeNanos, int frame, long frameTimelineVsyncId) {
         final long startNanos;
         synchronized (mLock) {
             if (!mFrameScheduled) {
@@ -711,7 +710,7 @@
                 }
             }
 
-            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
+            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, frameTimelineVsyncId);
             mFrameScheduled = false;
             mLastFrameTimeNanos = frameTimeNanos;
         }
@@ -897,7 +896,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_DO_FRAME:
-                    doFrame(System.nanoTime(), 0);
+                    doFrame(System.nanoTime(), 0, FrameInfo.INVALID_VSYNC_ID);
                     break;
                 case MSG_DO_SCHEDULE_VSYNC:
                     doScheduleVsync();
@@ -914,6 +913,7 @@
         private boolean mHavePendingVsync;
         private long mTimestampNanos;
         private int mFrame;
+        private long mFrameTimelineVsyncId;
 
         public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
             super(looper, vsyncSource, CONFIG_CHANGED_EVENT_SUPPRESS);
@@ -923,7 +923,8 @@
         // the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
         // for the internal display implicitly.
         @Override
-        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
+        public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
+                long frameTimelineVsyncId) {
             // Post the vsync event to the Handler.
             // The idea is to prevent incoming vsync events from completely starving
             // the message queue.  If there are no messages in the queue with timestamps
@@ -946,6 +947,7 @@
 
             mTimestampNanos = timestampNanos;
             mFrame = frame;
+            mFrameTimelineVsyncId = frameTimelineVsyncId;
             Message msg = Message.obtain(mHandler, this);
             msg.setAsynchronous(true);
             mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
@@ -954,7 +956,7 @@
         @Override
         public void run() {
             mHavePendingVsync = false;
-            doFrame(mTimestampNanos, mFrame);
+            doFrame(mTimestampNanos, mFrame, mFrameTimelineVsyncId);
         }
     }
 
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index eaf297c..51474d3 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -154,9 +154,11 @@
      * timebase.
      * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
      * @param frame The frame number.  Increases by one for each vertical sync interval.
+     * @param frameTimelineVsyncId The frame timeline vsync id, used to correlate a frame
+     * produced by HWUI with the timeline data stored in Surface Flinger.
      */
-    @UnsupportedAppUsage
-    public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
+    public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
+            long frameTimelineVsyncId) {
     }
 
     /**
@@ -198,9 +200,9 @@
 
     // Called from native code.
     @SuppressWarnings("unused")
-    @UnsupportedAppUsage
-    private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
-        onVsync(timestampNanos, physicalDisplayId, frame);
+    private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
+            long frameTimelineVsyncId) {
+        onVsync(timestampNanos, physicalDisplayId, frame, frameTimelineVsyncId);
     }
 
     // Called from native code.
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index 32cc30be8..280a1c0 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -189,6 +189,7 @@
      */
     @IntDef ({
             Index.FLAGS,
+            Index.FRAME_TIMELINE_VSYNC_ID,
             Index.INTENDED_VSYNC,
             Index.VSYNC,
             Index.OLDEST_INPUT_EVENT,
@@ -206,21 +207,22 @@
     @Retention(RetentionPolicy.SOURCE)
     private @interface Index {
         int FLAGS = 0;
-        int INTENDED_VSYNC = 1;
-        int VSYNC = 2;
-        int OLDEST_INPUT_EVENT = 3;
-        int NEWEST_INPUT_EVENT = 4;
-        int HANDLE_INPUT_START = 5;
-        int ANIMATION_START = 6;
-        int PERFORM_TRAVERSALS_START = 7;
-        int DRAW_START = 8;
-        int SYNC_QUEUED = 9;
-        int SYNC_START = 10;
-        int ISSUE_DRAW_COMMANDS_START = 11;
-        int SWAP_BUFFERS = 12;
-        int FRAME_COMPLETED = 13;
+        int FRAME_TIMELINE_VSYNC_ID = 1;
+        int INTENDED_VSYNC = 2;
+        int VSYNC = 3;
+        int OLDEST_INPUT_EVENT = 4;
+        int NEWEST_INPUT_EVENT = 5;
+        int HANDLE_INPUT_START = 6;
+        int ANIMATION_START = 7;
+        int PERFORM_TRAVERSALS_START = 8;
+        int DRAW_START = 9;
+        int SYNC_QUEUED = 10;
+        int SYNC_START = 11;
+        int ISSUE_DRAW_COMMANDS_START = 12;
+        int SWAP_BUFFERS = 13;
+        int FRAME_COMPLETED = 14;
 
-        int FRAME_STATS_COUNT = 17; // must always be last
+        int FRAME_STATS_COUNT = 18; // must always be last
     }
 
     /*
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index c5f2299..104bc43 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -22,13 +22,14 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.os.LocaleList;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
-import android.text.Editable;
 import android.text.InputType;
+import android.text.ParcelableSpan;
 import android.text.TextUtils;
 import android.util.Printer;
 import android.view.View;
@@ -543,6 +544,9 @@
      * {@code #getInitialSelectedText}, and {@code #getInitialTextBeforeCursor}. System is allowed
      * to trim {@code sourceText} for various reasons while keeping the most valuable data to IMEs.
      *
+     * Starting from {@link VERSION_CODES#S}, spans that do not implement {@link Parcelable} will
+     * be automatically dropped.
+     *
      * <p><strong>Editor authors: </strong>Providing the initial input text helps reducing IPC calls
      * for IMEs to provide many modern features right after the connection setup. We recommend
      * calling this method in your implementation.
@@ -562,14 +566,16 @@
      * try to include the selected text within {@code subText} to give the system best flexibility
      * to choose where and how to trim {@code subText} when necessary.
      *
+     * Starting from {@link VERSION_CODES#S}, spans that do not implement {@link Parcelable} will
+     * be automatically dropped.
+     *
      * @param subText The input text. When it was trimmed, {@code subTextStart} must be provided
      *                correctly.
      * @param subTextStart  The position that the input text got trimmed. For example, when the
      *                      editor wants to trim out the first 10 chars, subTextStart should be 10.
      */
     public void setInitialSurroundingSubText(@NonNull CharSequence subText, int subTextStart) {
-        CharSequence newSubText = Editable.Factory.getInstance().newEditable(subText);
-        Objects.requireNonNull(newSubText);
+        Objects.requireNonNull(subText);
 
         // Swap selection start and end if necessary.
         final int subTextSelStart = initialSelStart > initialSelEnd
@@ -577,7 +583,7 @@
         final int subTextSelEnd = initialSelStart > initialSelEnd
                 ? initialSelStart - subTextStart : initialSelEnd - subTextStart;
 
-        final int subTextLength = newSubText.length();
+        final int subTextLength = subText.length();
         // Unknown or invalid selection.
         if (subTextStart < 0 || subTextSelStart < 0 || subTextSelEnd > subTextLength) {
             mInitialSurroundingText = new InitialSurroundingText();
@@ -591,12 +597,12 @@
         }
 
         if (subTextLength <= MEMORY_EFFICIENT_TEXT_LENGTH) {
-            mInitialSurroundingText = new InitialSurroundingText(newSubText, subTextSelStart,
+            mInitialSurroundingText = new InitialSurroundingText(subText, subTextSelStart,
                     subTextSelEnd);
             return;
         }
 
-        trimLongSurroundingText(newSubText, subTextSelStart, subTextSelEnd);
+        trimLongSurroundingText(subText, subTextSelStart, subTextSelEnd);
     }
 
     /**
@@ -901,7 +907,9 @@
 
         InitialSurroundingText(@Nullable CharSequence surroundingText, int selectionHead,
                 int selectionEnd) {
-            mSurroundingText = surroundingText;
+            // Copy the original text (without NoCopySpan) in case the original text is updated
+            // later.
+            mSurroundingText = copyWithParcelableSpans(surroundingText);
             mSelectionHead = selectionHead;
             mSelectionEnd = selectionEnd;
         }
@@ -975,5 +983,30 @@
                         return new InitialSurroundingText[size];
                     }
                 };
+
+        /**
+         * Create a copy of the given {@link CharSequence} object, with completely copy
+         * {@link ParcelableSpan} instances.
+         *
+         * @param source the original {@link CharSequence} to be copied.
+         * @return the copied {@link CharSequence}. {@code null} if {@code source} is {@code null}.
+         */
+        @Nullable
+        private static CharSequence copyWithParcelableSpans(@Nullable CharSequence source) {
+            if (source == null) {
+                return null;
+            }
+            Parcel parcel = null;
+            try {
+                parcel = Parcel.obtain();
+                TextUtils.writeToParcel(source, parcel, /* parcelableFlags= */ 0);
+                parcel.setDataPosition(0);
+                return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+            } finally {
+                if (parcel != null) {
+                    parcel.recycle();
+                }
+            }
+        }
     }
 }
diff --git a/core/java/com/android/internal/infra/AbstractRemoteService.java b/core/java/com/android/internal/infra/AbstractRemoteService.java
index b2852ea..722e5c1 100644
--- a/core/java/com/android/internal/infra/AbstractRemoteService.java
+++ b/core/java/com/android/internal/infra/AbstractRemoteService.java
@@ -225,6 +225,7 @@
         if (mService != null) {
             mService.asBinder().unlinkToDeath(this, 0);
         }
+        mBinding = false;
         mService = null;
         mServiceDied = true;
         cancelScheduledUnbind();
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 25d1ae6..44dca9b 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -63,7 +63,7 @@
     void onCallAttributesChanged(in CallAttributes callAttributes);
     void onEmergencyNumberListChanged(in Map emergencyNumberList);
     void onOutgoingEmergencyCall(in EmergencyNumber placedEmergencyNumber, int subscriptionId);
-    void onOutgoingEmergencySms(in EmergencyNumber sentEmergencyNumber);
+    void onOutgoingEmergencySms(in EmergencyNumber sentEmergencyNumber, int subscriptionId);
     void onCallDisconnectCauseChanged(in int disconnectCause, in int preciseDisconnectCause);
     void onImsCallDisconnectCauseChanged(in ImsReasonInfo imsReasonInfo);
     void onRegistrationFailed(in CellIdentity cellIdentity,
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 50a557bb..3acd15a 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -59,7 +59,8 @@
     jobject mReceiverWeakGlobal;
     sp<MessageQueue> mMessageQueue;
 
-    void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count) override;
+    void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
+                       int64_t sharedTimelineFrameCount) override;
     void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
     void dispatchConfigChanged(nsecs_t timestamp, PhysicalDisplayId displayId,
                                int32_t configId, nsecs_t vsyncPeriod) override;
@@ -90,14 +91,14 @@
 }
 
 void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId,
-                                               uint32_t count) {
+                                               uint32_t count, int64_t frameTimelineVsyncId) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
 
     ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal));
     if (receiverObj.get()) {
         ALOGV("receiver %p ~ Invoking vsync handler.", this);
         env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync,
-                            timestamp, displayId.value, count);
+                            timestamp, displayId.value, count, frameTimelineVsyncId);
         ALOGV("receiver %p ~ Returned from vsync handler.", this);
     }
 
@@ -196,8 +197,8 @@
     jclass clazz = FindClassOrDie(env, "android/view/DisplayEventReceiver");
     gDisplayEventReceiverClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
 
-    gDisplayEventReceiverClassInfo.dispatchVsync = GetMethodIDOrDie(env,
-            gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JJI)V");
+    gDisplayEventReceiverClassInfo.dispatchVsync =
+            GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JJIJ)V");
     gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env,
             gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
     gDisplayEventReceiverClassInfo.dispatchConfigChanged = GetMethodIDOrDie(env,
diff --git a/core/proto/android/stats/hdmi/enums.proto b/core/proto/android/stats/hdmi/enums.proto
new file mode 100644
index 0000000..acb8899
--- /dev/null
+++ b/core/proto/android/stats/hdmi/enums.proto
@@ -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.
+ */
+
+syntax = "proto2";
+
+package android.stats.hdmi;
+option java_multiple_files = true;
+option java_outer_classname = "HdmiStatsEnums";
+
+// HDMI CEC logical addresses.
+// Values correspond to "CEC Table 5 Logical Addresses" in the HDMI CEC 1.4b spec.
+enum LogicalAddress {
+    LOGICAL_ADDRESS_UNKNOWN = -1;
+    TV = 0;
+    RECORDING_DEVICE_1 = 1;
+    RECORDING_DEVICE_2 = 2;
+    TUNER_1 = 3;
+    PLAYBACK_DEVICE_1 = 4;
+    AUDIO_SYSTEM = 5;
+    TUNER_2 = 6;
+    TUNER_3 = 7;
+    PLAYBACK_DEVICE_2 = 8;
+    RECORDING_DEVICE_3 = 9;
+    TUNER_4 = 10;
+    PLAYBACK_DEVICE_3 = 11;
+    RESERVED_1 = 12;
+    RESERVED_2 = 13;
+    SPECIFIC_USE = 14;
+    UNREGISTERED_OR_BROADCAST = 15;
+}
+
+// The relationship between two paths.
+// Values correspond exactly to PathRelationship in com.android.server.hdmi.Constants.
+enum PathRelationship {
+    RELATIONSHIP_TO_ACTIVE_SOURCE_UNKNOWN = 0;
+    DIFFERENT_BRANCH = 1;
+    ANCESTOR = 2;
+    DESCENDANT = 3;
+    SIBLING = 4;
+    SAME = 5;
+}
+
+// The result of attempting to send a HDMI CEC message.
+// Values correspond to the constants in android.hardware.tv.cec.V1_0.SendMessageResult,
+// offset by 10.
+enum SendMessageResult {
+    SEND_MESSAGE_RESULT_UNKNOWN = 0;
+    SUCCESS = 10;
+    NACK = 11;
+    BUSY = 12;
+    FAIL = 13;
+}
+
+// Whether a HDMI CEC message is sent from this device, to this device, or neither.
+enum MessageDirection {
+    MESSAGE_DIRECTION_UNKNOWN = 0;
+    MESSAGE_DIRECTION_OTHER = 1;    // None of the other options.
+    OUTGOING = 2;                   // Sent from this device.
+    INCOMING = 3;                   // Sent to this device.
+    TO_SELF = 4;                    // Sent from this device, to this device. Indicates a bug.
+}
+
+// User control commands. Each value can represent an individual command, or a set of commands.
+// Values correspond to "CEC Table 30 UI Command Codes" in the HDMI CEC 1.4b spec, offset by 0x100.
+enum UserControlPressedCommand {
+    USER_CONTROL_PRESSED_COMMAND_UNKNOWN = 0;
+
+    // Represents all codes that are not represented by another value.
+    USER_CONTROL_PRESSED_COMMAND_OTHER = 1;
+
+    // Represents all number codes (codes 0x1E through 0x29).
+    NUMBER = 2;
+
+    // Navigation
+    SELECT = 0x100;
+    UP = 0x101;
+    DOWN = 0x102;
+    LEFT = 0x103;
+    RIGHT = 0x104;
+    RIGHT_UP = 0x105;
+    RIGHT_DOWN = 0x106;
+    LEFT_UP = 0x107;
+    LEFT_DOWN = 0x108;
+    EXIT = 0x10D;
+
+    // Volume
+    VOLUME_UP = 0x141;
+    VOLUME_DOWN = 0x142;
+    VOLUME_MUTE = 0x143;
+
+    // Power
+    POWER = 0x140;
+    POWER_TOGGLE = 0x16B;
+    POWER_OFF = 0x16C;
+    POWER_ON = 0x16D;
+}
+
+// Reason parameter of the <Feature Abort> message.
+// Values correspond to "CEC Table 29 Operand Descriptions" in the HDMI CEC 1.4b spec,
+// offset by 10.
+enum FeatureAbortReason {
+    FEATURE_ABORT_REASON_UNKNOWN = 0;
+    UNRECOGNIZED_OPCODE = 10;
+    NOT_IN_CORRECT_MODE_TO_RESPOND = 11;
+    CANNOT_PROVIDE_SOURCE = 12;
+    INVALID_OPERAND = 13;
+    REFUSED = 14;
+    UNABLE_TO_DETERMINE = 15;
+}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 68e2891..c9a4ab0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -238,6 +238,8 @@
         android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" />
     <protected-broadcast
         android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
+    <protected-broadcast
+        android:name="android.bluetooth.action.TETHERING_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED" />
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index fbbdaa3..ed15970 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -239,7 +239,9 @@
         <!-- Old synonym for "privileged". Deprecated in API level 23. -->
         <flag name="system" value="0x10" />
         <!-- Additional flag from base permission type: this permission can also
-             (optionally) be granted to development applications. -->
+             (optionally) be granted to development applications. Although undocumented, the
+              permission state used to be shared by all users (including future users), but it is
+              managed per-user since API level 31. -->
         <flag name="development" value="0x20" />
         <!-- Additional flag from base permission type: this permission is closely
              associated with an app op for controlling access. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ee6267a..592afa7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3267,13 +3267,12 @@
 
     <!-- Array of values used in Gesture Navigation settings page to reduce/increase the back
      gesture's inset size. These values will be multiplied into the default width, read from the
-     gesture navigation overlay package, in order to create 4 different sizes which are selectable
+     gesture navigation overlay package, in order to create 3 different sizes which are selectable
      via a slider component. -->
     <array name="config_backGestureInsetScales">
-        <item>0.75</item>
+        <item>0.60</item>
         <item>1.00</item>
         <item>1.33</item>
-        <item>1.66</item>
     </array>
 
     <!-- Controls whether the navbar needs a scrim with
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a5e5fba..03975da 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4888,6 +4888,9 @@
         <item quantity="other">For %d hr</item>
     </plurals>
 
+    <!-- Zen mode condition - line two: ending time indicating the next day. [CHAR LIMIT=NONE] -->
+    <string name="zen_mode_until_next_day">Until <xliff:g id="formattedTime" example="Tue, 10 PM">%1$s</xliff:g></string>
+
     <!-- Zen mode condition - line two: ending time. [CHAR LIMIT=NONE] -->
     <string name="zen_mode_until">Until <xliff:g id="formattedTime" example="10:00 PM">%1$s</xliff:g></string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index fdcd39a..8cd4b48 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2393,6 +2393,7 @@
   <java-symbol type="plurals" name="zen_mode_duration_hours_short" />
   <java-symbol type="plurals" name="zen_mode_duration_minutes_summary_short" />
   <java-symbol type="plurals" name="zen_mode_duration_hours_summary_short" />
+  <java-symbol type="string" name="zen_mode_until_next_day" />
   <java-symbol type="string" name="zen_mode_until" />
   <java-symbol type="string" name="zen_mode_feature_name" />
   <java-symbol type="string" name="zen_mode_downtime_feature_name" />
diff --git a/core/tests/PackageInstallerSessions/Android.bp b/core/tests/PackageInstallerSessions/Android.bp
index e74f30e..055249d 100644
--- a/core/tests/PackageInstallerSessions/Android.bp
+++ b/core/tests/PackageInstallerSessions/Android.bp
@@ -36,6 +36,11 @@
         "framework-res",
     ],
 
+    java_resources: [
+        // Borrow an arbitrary test app from another module
+        ":PackageManagerTestAppVersion1",
+    ],
+
     platform_apis: true,
     sdk_version: "core_platform",
     test_suites: ["device-tests"],
diff --git a/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
index 494c92a..b410189 100644
--- a/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
+++ b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
@@ -16,7 +16,12 @@
 
 package android.content.pm
 
+import android.app.Instrumentation
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
 import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
 import android.content.pm.PackageInstaller.SessionParams
 import android.platform.test.annotations.Presubmit
 import androidx.test.InstrumentationRegistry
@@ -27,6 +32,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.testng.Assert.assertThrows
+import java.util.concurrent.ArrayBlockingQueue
+import java.util.concurrent.TimeUnit
 import kotlin.random.Random
 
 /**
@@ -53,15 +60,49 @@
                 "android.permission.WRITE_CALL_LOG",
                 "android.permission.PROCESS_OUTGOING_CALLS"
         )
+
+        private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app"
+        private const val INTENT_ACTION = "com.android.server.pm.test.test_app.action"
     }
 
     private val context: Context = InstrumentationRegistry.getContext()
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
 
     private val installer = context.packageManager.packageInstaller
 
+    private val receiver = object : BroadcastReceiver() {
+        private val results = ArrayBlockingQueue<Intent>(1)
+
+        override fun onReceive(context: Context, intent: Intent) {
+            results.add(intent)
+        }
+
+        fun makeIntentSender(sessionId: Int) = PendingIntent.getBroadcast(context, sessionId,
+                Intent(INTENT_ACTION), PendingIntent.FLAG_UPDATE_CURRENT).intentSender
+
+        fun getResult(unit: TimeUnit, timeout: Long) = results.poll(timeout, unit)
+
+        fun clear() = results.clear()
+    }
+
+    @Before
+    fun registerReceiver() {
+        receiver.clear()
+        context.registerReceiver(receiver, IntentFilter(INTENT_ACTION))
+    }
+
+    @After
+    fun unregisterReceiver() {
+        context.unregisterReceiver(receiver)
+    }
+
     @Before
     @After
     fun abandonAllSessions() {
+        instrumentation.uiAutomation
+                .executeShellCommand("pm uninstall com.android.server.pm.test.test_app")
+                .close()
+
         installer.mySessions.asSequence()
                 .map { it.sessionId }
                 .forEach {
@@ -82,7 +123,7 @@
             setAppLabel(longLabel)
         }
 
-        createSession(params) {
+        createAndAbandonSession(params) {
             assertThat(installer.getSessionInfo(it)?.appLabel)
                     .isEqualTo(longLabel.take(PackageItemInfo.MAX_SAFE_LABEL_LENGTH))
         }
@@ -95,7 +136,7 @@
             setAppPackageName(longName)
         }
 
-        createSession(params) {
+        createAndAbandonSession(params) {
             assertThat(installer.getSessionInfo(it)?.appPackageName)
                     .isEqualTo(null)
         }
@@ -108,7 +149,7 @@
             setInstallerPackageName(longName)
         }
 
-        createSession(params) {
+        createAndAbandonSession(params) {
             // If a custom installer name is dropped, it defaults to the caller
             assertThat(installer.getSessionInfo(it)?.installerPackageName)
                     .isEqualTo(context.packageName)
@@ -121,12 +162,39 @@
             setWhitelistedRestrictedPermissions(invalidPermissions())
         }
 
-        createSession(params) {
+        createAndAbandonSession(params) {
             assertThat(installer.getSessionInfo(it)?.whitelistedRestrictedPermissions!!)
                     .containsExactlyElementsIn(RESTRICTED_PERMISSIONS)
         }
     }
 
+    @Test
+    fun fillPackageNameWithParsedValue() {
+        val params = SessionParams(SessionParams.MODE_FULL_INSTALL)
+        val sessionId = installer.createSession(params)
+        val session = installer.openSession(sessionId)
+
+        javaClass.classLoader.getResourceAsStream("PackageManagerTestAppVersion1.apk")!!
+                .use { input ->
+                    session.openWrite("base", 0, -1)
+                            .use { output -> input.copyTo(output) }
+                }
+
+        // Test instrumentation doesn't have install permissions, so use shell
+        ShellIdentityUtils.invokeWithShellPermissions {
+            session.commit(receiver.makeIntentSender(sessionId))
+        }
+        session.close()
+
+        // The actual contents aren't verified as part of this test. Only care about it finishing.
+        val result = receiver.getResult(TimeUnit.SECONDS, 30)
+        assertThat(result).isNotNull()
+
+        val sessionInfo = installer.getSessionInfo(sessionId)
+        assertThat(sessionInfo).isNotNull()
+        assertThat(sessionInfo!!.getAppPackageName()).isEqualTo(TEST_PKG_NAME)
+    }
+
     @LargeTest
     @Test
     fun allocateMaxSessionsWithPermission() {
@@ -177,7 +245,7 @@
                 repeat(10) { add(invalidPackageName(300)) }
             }
 
-    private fun createSession(params: SessionParams, block: (Int) -> Unit = {}) {
+    private fun createAndAbandonSession(params: SessionParams, block: (Int) -> Unit = {}) {
         val sessionId = installer.createSession(params)
         try {
             block(sessionId)
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
index 93de03a..5c41087 100644
--- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
@@ -274,13 +274,10 @@
         editorInfo.inputType = EditorInfo.TYPE_CLASS_TEXT;
 
         editorInfo.setInitialSurroundingText(sb);
-        sb.setLength(0);
-        editorInfo.writeToParcel(parcel, 0);
+        sb.setLength(/* newLength= */ 0);
+        editorInfo.writeToParcel(parcel, /* flags= */ 0);
 
-        try {
-            editorInfo.getInitialTextBeforeCursor(60, 1);
-            fail("Test shouldn't have exception");
-        } catch (AssertionError e) { }
+        editorInfo.getInitialTextBeforeCursor(/* length= */ 60, /* flags= */ 1);
     }
 
     private static void assertExpectedTextLength(EditorInfo editorInfo,
diff --git a/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml b/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml
index b1c2a63..9cecaed 100644
--- a/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml
+++ b/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml
@@ -22,6 +22,7 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.BATTERY_STATS"/>
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.DEVICE_POWER"/>
     <uses-permission android:name="android.permission.INTERNET"/>
 
     <instrumentation
diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java
index 0cdb404..a71559b 100644
--- a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java
+++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java
@@ -16,6 +16,7 @@
 
 package com.android.frameworks.core.powerstatsloadtests;
 
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.app.Instrumentation;
@@ -34,6 +35,7 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.internal.os.BatteryStatsHelper;
 import com.android.internal.os.LoggingPrintStream;
 
@@ -45,6 +47,8 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 public class PowerMetricsCollector implements TestRule {
     private final String mTag;
@@ -55,6 +59,7 @@
     private final UserManager mUserManager;
     private final int mUid;
     private final BatteryStatsHelper mStatsHelper;
+    private final CountDownLatch mSuspendingBatteryInput = new CountDownLatch(1);
 
     private long mStartTime;
     private volatile float mInitialBatteryLevel;
@@ -63,12 +68,21 @@
     private PowerMetrics mInitialPowerMetrics;
     private PowerMetrics mFinalPowerMetrics;
     private List<PowerMetrics.Metric> mPowerMetricsDelta;
+    private Intent mBatteryStatus;
 
     @Override
     public Statement apply(Statement base, Description description) {
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
+                BroadcastReceiver batteryBroadcastReceiver = new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        handleBatteryStatus(intent);
+                    }
+                };
+                mBatteryStatus = mContext.registerReceiver(batteryBroadcastReceiver,
+                        new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
                 disableCharger();
                 try {
                     prepareBatteryLevelMonitor();
@@ -76,6 +90,7 @@
                     base.evaluate();
                     captureFinalPowerStatsData();
                 } finally {
+                    mContext.unregisterReceiver(batteryBroadcastReceiver);
                     enableCharger();
                 }
             }
@@ -95,12 +110,14 @@
         mStatsHelper.create((Bundle) null);
     }
 
-    private void disableCharger() {
-        // TODO(b/167636754): implement this method once the charger suspension API is available
+    private void disableCharger() throws InterruptedException {
+        SystemUtil.runShellCommand("dumpsys battery suspend_input");
+        final boolean success = mSuspendingBatteryInput.await(10, TimeUnit.SECONDS);
+        assertTrue("Timed out waiting for battery input to be suspended", success);
     }
 
     private void enableCharger() {
-        // TODO(b/167636754): implement this method once the charger suspension API is available
+        SystemUtil.runShellCommand("dumpsys battery reset");
     }
 
     private PowerMetrics readBatteryStatsData() {
@@ -111,19 +128,25 @@
     }
 
     protected void prepareBatteryLevelMonitor() {
-        Intent batteryStatus = mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                handleBatteryStatus(intent);
-            }
-        }, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
-
-        handleBatteryStatus(batteryStatus);
+        handleBatteryStatus(mBatteryStatus);
         mInitialBatteryLevel = mCurrentBatteryLevel;
     }
 
     protected void handleBatteryStatus(Intent intent) {
-        if (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) != 0) {
+        if (mFinalPowerMetrics != null) {
+            return;
+        }
+
+        final boolean isCharging = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) != 0;
+
+        if (mSuspendingBatteryInput.getCount() > 0) {
+            if (!isCharging) {
+                mSuspendingBatteryInput.countDown();
+            }
+            return;
+        }
+
+        if (isCharging) {
             fail("Device must remain disconnected from the power source "
                     + "for the duration of the test");
         }
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 602ccfe..102c933 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -226,6 +226,7 @@
         <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
         <permission name="android.permission.UPDATE_DEVICE_STATS"/>
+        <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.providers.media.module">
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index 42a5cc4..163823f 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -40,7 +40,7 @@
  */
 public final class FrameInfo {
 
-    public long[] frameInfo = new long[9];
+    public long[] frameInfo = new long[10];
 
     // Various flags set to provide extra metadata about the current frame
     private static final int FLAGS = 0;
@@ -51,38 +51,44 @@
     // A renderer associated with just a Surface, not with a ViewRootImpl instance.
     public static final long FLAG_SURFACE_CANVAS = 1 << 2;
 
+    // An invalid vsync id to be used when FRAME_TIMELINE_VSYNC_ID is unknown
+    public static final long INVALID_VSYNC_ID = -1;
+
     @LongDef(flag = true, value = {
             FLAG_WINDOW_LAYOUT_CHANGED, FLAG_SURFACE_CANVAS })
     @Retention(RetentionPolicy.SOURCE)
     public @interface FrameInfoFlags {}
 
+    private static final int FRAME_TIMELINE_VSYNC_ID = 1;
+
     // The intended vsync time, unadjusted by jitter
-    private static final int INTENDED_VSYNC = 1;
+    private static final int INTENDED_VSYNC = 2;
 
     // Jitter-adjusted vsync time, this is what was used as input into the
     // animation & drawing system
-    private static final int VSYNC = 2;
+    private static final int VSYNC = 3;
 
     // The time of the oldest input event
-    private static final int OLDEST_INPUT_EVENT = 3;
+    private static final int OLDEST_INPUT_EVENT = 4;
 
     // The time of the newest input event
-    private static final int NEWEST_INPUT_EVENT = 4;
+    private static final int NEWEST_INPUT_EVENT = 5;
 
     // When input event handling started
-    private static final int HANDLE_INPUT_START = 5;
+    private static final int HANDLE_INPUT_START = 6;
 
     // When animation evaluations started
-    private static final int ANIMATION_START = 6;
+    private static final int ANIMATION_START = 7;
 
     // When ViewRootImpl#performTraversals() started
-    private static final int PERFORM_TRAVERSALS_START = 7;
+    private static final int PERFORM_TRAVERSALS_START = 8;
 
     // When View:draw() started
-    private static final int DRAW_START = 8;
+    private static final int DRAW_START = 9;
 
     /** checkstyle */
-    public void setVsync(long intendedVsync, long usedVsync) {
+    public void setVsync(long intendedVsync, long usedVsync, long frameTimelineVsyncId) {
+        frameInfo[FRAME_TIMELINE_VSYNC_ID] = frameTimelineVsyncId;
         frameInfo[INTENDED_VSYNC] = intendedVsync;
         frameInfo[VSYNC] = usedVsync;
         frameInfo[OLDEST_INPUT_EVENT] = Long.MAX_VALUE;
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 0452933..fd5916c 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -354,7 +354,8 @@
          * @return this instance
          */
         public @NonNull FrameRenderRequest setVsyncTime(long vsyncTime) {
-            mFrameInfo.setVsync(vsyncTime, vsyncTime);
+            // TODO(b/168552873): populate vsync Id once available to Choreographer public API
+            mFrameInfo.setVsync(vsyncTime, vsyncTime, FrameInfo.INVALID_VSYNC_ID);
             mFrameInfo.addFlags(FrameInfo.FLAG_SURFACE_CANVAS);
             return this;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index c84b478..d060f64 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -172,8 +172,10 @@
                 context, displayController);
         OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
                 context, displayController, animationController, tutorialHandler);
+        IOverlayManager overlayManager = IOverlayManager.Stub.asInterface(
+                ServiceManager.getService(Context.OVERLAY_SERVICE));
         return new OneHandedController(context, displayController, organizer, touchHandler,
-                tutorialHandler, gestureHandler);
+                tutorialHandler, gestureHandler, overlayManager);
     }
 
     @VisibleForTesting
@@ -182,7 +184,8 @@
             OneHandedDisplayAreaOrganizer displayAreaOrganizer,
             OneHandedTouchHandler touchHandler,
             OneHandedTutorialHandler tutorialHandler,
-            OneHandedGestureHandler gestureHandler) {
+            OneHandedGestureHandler gestureHandler,
+            IOverlayManager overlayManager) {
         mHasOneHandedFeature = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
         if (!mHasOneHandedFeature) {
             Log.i(TAG, "Device config SUPPORT_ONE_HANDED_MODE off");
@@ -194,32 +197,32 @@
             mGestureHandler = null;
             mTimeoutHandler = null;
             mOverlayManager = null;
-            return;
+        } else {
+            mContext = context;
+            mDisplayAreaOrganizer = displayAreaOrganizer;
+            mDisplayController = displayController;
+            mTouchHandler = touchHandler;
+            mTutorialHandler = tutorialHandler;
+            mGestureHandler = gestureHandler;
+            mOverlayManager = overlayManager;
+
+            mOffSetFraction = SystemProperties.getInt(ONE_HANDED_MODE_OFFSET_PERCENTAGE, 50)
+                    / 100.0f;
+            mIsOneHandedEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+                    context.getContentResolver());
+            mIsSwipeToNotificationEnabled =
+                    OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+                            context.getContentResolver());
+            mTimeoutHandler = OneHandedTimeoutHandler.get();
+
+            mDisplayController.addDisplayChangingController(mRotationController);
+
+            setupCallback();
+            setupSettingObservers();
+            setupTimeoutListener();
+            setupGesturalOverlay();
+            updateSettings();
         }
-
-        mContext = context;
-        mDisplayAreaOrganizer = displayAreaOrganizer;
-        mDisplayController = displayController;
-        mTouchHandler = touchHandler;
-        mTutorialHandler = tutorialHandler;
-        mGestureHandler = gestureHandler;
-
-        mOverlayManager = IOverlayManager.Stub.asInterface(
-                ServiceManager.getService(Context.OVERLAY_SERVICE));
-        mOffSetFraction = SystemProperties.getInt(ONE_HANDED_MODE_OFFSET_PERCENTAGE, 50) / 100.0f;
-        mIsOneHandedEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
-                context.getContentResolver());
-        mIsSwipeToNotificationEnabled = OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
-                context.getContentResolver());
-        mTimeoutHandler = OneHandedTimeoutHandler.get();
-
-        mDisplayController.addDisplayChangingController(mRotationController);
-
-        setupCallback();
-        setupSettingObservers();
-        setupTimeoutListener();
-        setupGesturalOverlay();
-        updateSettings();
     }
 
     /**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 1ce8b54..3645f1e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -18,13 +18,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.om.IOverlayManager;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -62,6 +62,8 @@
     OneHandedGestureHandler mMockGestureHandler;
     @Mock
     OneHandedTimeoutHandler mMockTimeoutHandler;
+    @Mock
+    IOverlayManager mMockOverlayManager;
 
     @Before
     public void setUp() throws Exception {
@@ -73,7 +75,8 @@
                 mMockDisplayAreaOrganizer,
                 mMockTouchHandler,
                 mMockTutorialHandler,
-                mMockGestureHandler);
+                mMockGestureHandler,
+                mMockOverlayManager);
         mOneHandedController = Mockito.spy(oneHandedController);
         mTimeoutHandler = Mockito.spy(OneHandedTimeoutHandler.get());
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
index 4a133d39..3341c9c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
@@ -18,6 +18,7 @@
 
 import static org.mockito.Mockito.verify;
 
+import android.content.om.IOverlayManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -44,6 +45,8 @@
     DisplayController mMockDisplayController;
     @Mock
     OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer;
+    @Mock
+    IOverlayManager mMockOverlayManager;
 
     @Before
     public void setUp() {
@@ -56,11 +59,12 @@
                 mMockDisplayAreaOrganizer,
                 mTouchHandler,
                 mTutorialHandler,
-                mGestureHandler);
+                mGestureHandler,
+                mMockOverlayManager);
     }
 
     @Test
-    public void testOneHandedManager_registerForDisplayAreaOrganizer() {
+    public void testRegisterForDisplayAreaOrganizer() {
         verify(mMockDisplayAreaOrganizer).registerTransitionCallback(mTutorialHandler);
     }
 }
diff --git a/libs/androidfw/include/androidfw/ConfigDescription.h b/libs/androidfw/include/androidfw/ConfigDescription.h
index acf413a..61d10cd 100644
--- a/libs/androidfw/include/androidfw/ConfigDescription.h
+++ b/libs/androidfw/include/androidfw/ConfigDescription.h
@@ -177,9 +177,8 @@
   return *this;
 }
 
-inline bool ConfigDescription::MatchWithDensity(
-    const ConfigDescription& o) const {
-  return match(o) && (density == 0 || density == o.density);
+inline bool ConfigDescription::MatchWithDensity(const ConfigDescription& o) const {
+  return match(o) && (density == 0 || o.density != 0);
 }
 
 inline bool ConfigDescription::operator<(const ConfigDescription& o) const {
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index 0698775..30ce537 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -22,6 +22,7 @@
 
 const std::string FrameInfoNames[] = {
         "Flags",
+        "FrameTimelineVsyncId",
         "IntendedVsync",
         "Vsync",
         "OldestInputEvent",
@@ -44,7 +45,7 @@
                       static_cast<int>(FrameInfoIndex::NumIndexes),
               "size mismatch: FrameInfoNames doesn't match the enum!");
 
-static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 17,
+static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 18,
               "Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
 
 void FrameInfo::importUiThreadInfo(int64_t* info) {
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index dc30617..f5bfedd 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -27,10 +27,11 @@
 namespace android {
 namespace uirenderer {
 
-#define UI_THREAD_FRAME_INFO_SIZE 9
+#define UI_THREAD_FRAME_INFO_SIZE 10
 
 enum class FrameInfoIndex {
     Flags = 0,
+    FrameTimelineVsyncId,
     IntendedVsync,
     Vsync,
     OldestInputEvent,
@@ -71,11 +72,15 @@
 
 class UiFrameInfoBuilder {
 public:
+    static constexpr int64_t INVALID_VSYNC_ID = -1;
+
     explicit UiFrameInfoBuilder(int64_t* buffer) : mBuffer(buffer) {
         memset(mBuffer, 0, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
+        set(FrameInfoIndex::FrameTimelineVsyncId) = INVALID_VSYNC_ID;
     }
 
-    UiFrameInfoBuilder& setVsync(nsecs_t vsyncTime, nsecs_t intendedVsync) {
+    UiFrameInfoBuilder& setVsync(nsecs_t vsyncTime, nsecs_t intendedVsync, int64_t vsyncId) {
+        set(FrameInfoIndex::FrameTimelineVsyncId) = vsyncId;
         set(FrameInfoIndex::Vsync) = vsyncTime;
         set(FrameInfoIndex::IntendedVsync) = intendedVsync;
         // Pretend the other fields are all at vsync, too, so that naive
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index e817ca7..c89463b 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -514,7 +514,7 @@
         proxy.setLightGeometry((Vector3){0, 0, 0}, 0);
         nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC);
         UiFrameInfoBuilder(proxy.frameInfo())
-                .setVsync(vsync, vsync)
+                .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID)
                 .addFlag(FrameInfoFlags::SurfaceCanvas);
         proxy.syncAndDrawFrame();
     }
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 13d544c..c7560b2 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -484,6 +484,14 @@
 
     waitOnFences();
 
+    if (mNativeSurface) {
+        // TODO(b/165985262): measure performance impact
+        if (const auto vsyncId = mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
+                vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
+            native_window_set_frame_timeline_vsync(mNativeSurface->getNativeWindow(), vsyncId);
+        }
+    }
+
     bool requireSwap = false;
     int error = OK;
     bool didSwap =
@@ -617,8 +625,11 @@
     ATRACE_CALL();
 
     nsecs_t vsync = mRenderThread.timeLord().computeFrameTimeNanos();
+    int64_t vsyncId = mRenderThread.timeLord().lastVsyncId();
     int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
-    UiFrameInfoBuilder(frameInfo).addFlag(FrameInfoFlags::RTAnimation).setVsync(vsync, vsync);
+    UiFrameInfoBuilder(frameInfo)
+        .addFlag(FrameInfoFlags::RTAnimation)
+        .setVsync(vsync, vsync, vsyncId);
 
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *this);
     prepareTree(info, frameInfo, systemTime(SYSTEM_TIME_MONOTONIC), node);
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 1e59338..1ea595d 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -128,7 +128,9 @@
 bool DrawFrameTask::syncFrameState(TreeInfo& info) {
     ATRACE_CALL();
     int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)];
-    mRenderThread->timeLord().vsyncReceived(vsync);
+    int64_t intendedVsync = mFrameInfo[static_cast<int>(FrameInfoIndex::IntendedVsync)];
+    int64_t vsyncId = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameTimelineVsyncId)];
+    mRenderThread->timeLord().vsyncReceived(vsync, intendedVsync, vsyncId);
     bool canDraw = mContext->makeCurrent();
     mContext->unpinImages();
 
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 4dcbc44..9371656 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -51,8 +51,10 @@
 
 void RenderThread::frameCallback(int64_t frameTimeNanos, void* data) {
     RenderThread* rt = reinterpret_cast<RenderThread*>(data);
+    int64_t vsyncId = AChoreographer_getVsyncId(rt->mChoreographer);
     rt->mVsyncRequested = false;
-    if (rt->timeLord().vsyncReceived(frameTimeNanos) && !rt->mFrameCallbackTaskPending) {
+    if (rt->timeLord().vsyncReceived(frameTimeNanos, frameTimeNanos, vsyncId) &&
+            !rt->mFrameCallbackTaskPending) {
         ATRACE_NAME("queue mFrameCallbackTask");
         rt->mFrameCallbackTaskPending = true;
         nsecs_t runAt = (frameTimeNanos + rt->mDispatchFrameDelay);
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index d7dc00b..4fbb0716 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -19,8 +19,8 @@
 
 #include <GrDirectContext.h>
 #include <SkBitmap.h>
-#include <apex/choreographer.h>
 #include <cutils/compiler.h>
+#include <private/android/choreographer.h>
 #include <thread/ThreadBase.h>
 #include <utils/Looper.h>
 #include <utils/Thread.h>
diff --git a/libs/hwui/renderthread/TimeLord.cpp b/libs/hwui/renderthread/TimeLord.cpp
index 784068f..7dc36c4 100644
--- a/libs/hwui/renderthread/TimeLord.cpp
+++ b/libs/hwui/renderthread/TimeLord.cpp
@@ -19,9 +19,17 @@
 namespace uirenderer {
 namespace renderthread {
 
-TimeLord::TimeLord() : mFrameIntervalNanos(milliseconds_to_nanoseconds(16)), mFrameTimeNanos(0) {}
+TimeLord::TimeLord() : mFrameIntervalNanos(milliseconds_to_nanoseconds(16)),
+                       mFrameTimeNanos(0),
+                       mFrameIntendedTimeNanos(0),
+                       mFrameVsyncId(-1) {}
 
-bool TimeLord::vsyncReceived(nsecs_t vsync) {
+bool TimeLord::vsyncReceived(nsecs_t vsync, nsecs_t intendedVsync, int64_t vsyncId) {
+    if (intendedVsync > mFrameIntendedTimeNanos) {
+        mFrameIntendedTimeNanos = intendedVsync;
+        mFrameVsyncId = vsyncId;
+    }
+
     if (vsync > mFrameTimeNanos) {
         mFrameTimeNanos = vsync;
         return true;
@@ -36,6 +44,8 @@
     if (jitterNanos >= mFrameIntervalNanos) {
         nsecs_t lastFrameOffset = jitterNanos % mFrameIntervalNanos;
         mFrameTimeNanos = now - lastFrameOffset;
+        // mFrameVsyncId is not adjusted here as we still want to send
+        // the vsync id that started this frame to the Surface Composer
     }
     return mFrameTimeNanos;
 }
diff --git a/libs/hwui/renderthread/TimeLord.h b/libs/hwui/renderthread/TimeLord.h
index 68a0f7f..23c1e51 100644
--- a/libs/hwui/renderthread/TimeLord.h
+++ b/libs/hwui/renderthread/TimeLord.h
@@ -32,9 +32,10 @@
     nsecs_t frameIntervalNanos() const { return mFrameIntervalNanos; }
 
     // returns true if the vsync is newer, false if it was rejected for staleness
-    bool vsyncReceived(nsecs_t vsync);
+    bool vsyncReceived(nsecs_t vsync, nsecs_t indendedVsync, int64_t vsyncId);
     nsecs_t latestVsync() { return mFrameTimeNanos; }
     nsecs_t computeFrameTimeNanos();
+    int64_t lastVsyncId() const { return mFrameVsyncId; }
 
 private:
     friend class RenderThread;
@@ -44,6 +45,8 @@
 
     nsecs_t mFrameIntervalNanos;
     nsecs_t mFrameTimeNanos;
+    nsecs_t mFrameIntendedTimeNanos;
+    int64_t mFrameVsyncId;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index 801cb7d..ed89c59 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -145,7 +145,8 @@
     for (int i = 0; i < warmupFrameCount; i++) {
         testContext.waitForVsync();
         nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC);
-        UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
+        UiFrameInfoBuilder(proxy->frameInfo())
+            .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID);
         proxy->syncAndDrawFrame();
     }
 
@@ -165,7 +166,8 @@
         nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC);
         {
             ATRACE_NAME("UI-Draw Frame");
-            UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
+            UiFrameInfoBuilder(proxy->frameInfo())
+                .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID);
             scene->doFrame(i);
             proxy->syncAndDrawFrame();
         }
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index 6dc0c6f..cf2f0f0 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -4,21 +4,13 @@
       "name": "GtsMediaTestCases",
       "options" : [
         {
-	  "include-annotation": "android.platform.test.annotations.Presubmit"
+          "include-annotation": "android.platform.test.annotations.Presubmit"
         },
         {
           "include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
-        }
-      ]
-    },
-    {
-      "name": "GtsExoPlayerTestCases",
-      "options" : [
-        {
-	  "include-annotation": "android.platform.test.annotations.SocPresubmit"
         },
         {
-          "include-filter": "com.google.android.exoplayer.gts.DashTest#testWidevine23FpsH264Fixed"
+          "include-filter": "com.google.android.media.gts.WidevineYouTubePerformanceTests"
         }
       ]
     }
diff --git a/media/java/android/media/CamcorderProfile.java b/media/java/android/media/CamcorderProfile.java
index f898931..45f1ca0 100644
--- a/media/java/android/media/CamcorderProfile.java
+++ b/media/java/android/media/CamcorderProfile.java
@@ -19,6 +19,8 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.Camera;
 import android.hardware.Camera.CameraInfo;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
 import android.os.Build;
 
 /**
@@ -418,7 +420,11 @@
      * resolution and higher audio sampling rate, etc, than those with lower quality
      * level.
      *
-     * @param cameraId the id for the camera
+     * @param cameraId the id for the camera. Integer camera ids parsed from the list received by
+     *                 invoking {@link CameraManager#getCameraIdList} can be used as long as they
+     *                 are {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE}
+     *                 and not
+     *                 {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL EXTERNAL}.
      * @param quality the target quality level for the camcorder profile.
      * @see #QUALITY_LOW
      * @see #QUALITY_HIGH
@@ -512,7 +518,11 @@
      * @see android.hardware.camera2.CameraManager
      * @see android.hardware.camera2.CameraCharacteristics
      *
-     * @param cameraId the id for the camera
+     * @param cameraId the id for the camera. Integer camera ids parsed from the list received by
+     *                 invoking {@link CameraManager#getCameraIdList} can be used as long as they
+     *                 are {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE}
+     *                 and not
+     *                 {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL EXTERNAL}.
      * @param quality the target quality level for the camcorder profile
      */
     public static boolean hasProfile(int cameraId, int quality) {
diff --git a/media/java/android/media/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java
index 451677f..e092411 100644
--- a/media/java/android/media/MediaTranscodeManager.java
+++ b/media/java/android/media/MediaTranscodeManager.java
@@ -274,7 +274,7 @@
 
     private static IMediaTranscodingService getService(boolean retry) {
         int retryCount = !retry ? 1 :  CONNECT_SERVICE_RETRY_COUNT;
-        Log.i(TAG, "get service with rety " + retryCount);
+        Log.i(TAG, "get service with retry " + retryCount);
         for (int count = 1;  count <= retryCount; count++) {
             Log.d(TAG, "Trying to connect to service. Try count: " + count);
             IMediaTranscodingService service = IMediaTranscodingService.Stub.asInterface(
@@ -356,7 +356,15 @@
             for (TranscodingJob job : retryJobs) {
                 // Notify the job failure if we fails to connect to the service or fail
                 // to retry the job.
-                if (!haveTranscodingClient || !job.retry()) {
+                if (!haveTranscodingClient) {
+                    // TODO(hkuang): Return correct error code to the client.
+                    handleTranscodingFailed(job.getJobId(), 0 /*unused */);
+                }
+
+                try {
+                    // Do not set hasRetried for retry initiated by MediaTranscodeManager.
+                    job.retryInternal(false /*setHasRetried*/);
+                } catch (Exception re) {
                     // TODO(hkuang): Return correct error code to the client.
                     handleTranscodingFailed(job.getJobId(), 0 /*unused */);
                 }
@@ -1010,9 +1018,9 @@
                     @IntRange(from = 0, to = 100) int progress);
         }
 
-        private final ITranscodingClient mJobOwner;
-        private final Executor mListenerExecutor;
-        private final OnTranscodingFinishedListener mListener;
+        private final MediaTranscodeManager mManager;
+        private Executor mListenerExecutor;
+        private OnTranscodingFinishedListener mListener;
         private int mJobId = -1;
         // Lock for internal state.
         private final Object mLock = new Object();
@@ -1028,20 +1036,26 @@
         private @Status int mStatus = STATUS_PENDING;
         @GuardedBy("mLock")
         private @Result int mResult = RESULT_NONE;
+        @GuardedBy("mLock")
+        private boolean mHasRetried = false;
+        // The original request that associated with this job.
+        private final TranscodingRequest mRequest;
 
         private TranscodingJob(
-                @NonNull ITranscodingClient jobOwner,
+                @NonNull MediaTranscodeManager manager,
+                @NonNull TranscodingRequest request,
                 @NonNull TranscodingJobParcel parcel,
                 @NonNull @CallbackExecutor Executor executor,
                 @NonNull OnTranscodingFinishedListener listener) {
-            Objects.requireNonNull(jobOwner, "JobOwner must not be null");
-            Objects.requireNonNull(parcel, "TranscodingJobParcel must not be null");
+            Objects.requireNonNull(manager, "manager must not be null");
+            Objects.requireNonNull(parcel, "parcel must not be null");
             Objects.requireNonNull(executor, "listenerExecutor must not be null");
             Objects.requireNonNull(listener, "listener must not be null");
-            mJobOwner = jobOwner;
+            mManager = manager;
             mJobId = parcel.jobId;
             mListenerExecutor = executor;
             mListener = listener;
+            mRequest = request;
         }
 
         /**
@@ -1085,14 +1099,61 @@
 
         /**
          * Resubmit the transcoding job to the service.
+         * Note that only the job that fails or gets cancelled could be retried and each job could
+         * be retried only once. After that, Client need to enqueue a new request if they want to
+         * try again.
          *
-         * @return true if successfully resubmit the job to the service. False otherwise.
+         * @throws MediaTranscodingException.ServiceNotAvailableException if the service
+         *         is temporarily unavailable due to internal service rebooting. Client could retry
+         *         again after receiving this exception.
+         * @throws UnsupportedOperationException if the retry could not be fulfilled.
+         * @hide
          */
-        public boolean retry() {
+        public void retry() throws MediaTranscodingException.ServiceNotAvailableException {
+            retryInternal(true /*setHasRetried*/);
+        }
+
+        // TODO(hkuang): Add more test for it.
+        private void retryInternal(boolean setHasRetried)
+                throws MediaTranscodingException.ServiceNotAvailableException {
             synchronized (mLock) {
-                // TODO(hkuang): Implement this.
+                if (mStatus == STATUS_PENDING || mStatus == STATUS_RUNNING) {
+                    throw new UnsupportedOperationException(
+                            "Failed to retry as job is in processing");
+                }
+
+                if (mHasRetried) {
+                    throw new UnsupportedOperationException("Job has been retried already");
+                }
+
+                // Get the client interface.
+                ITranscodingClient client = mManager.getTranscodingClient();
+                if (client == null) {
+                    throw new MediaTranscodingException.ServiceNotAvailableException(
+                            "Service rebooting. Try again later");
+                }
+
+                synchronized (mManager.mPendingTranscodingJobs) {
+                    try {
+                        // Submits the request to MediaTranscoding service.
+                        TranscodingJobParcel jobParcel = new TranscodingJobParcel();
+                        if (!client.submitRequest(mRequest.writeToParcel(), jobParcel)) {
+                            mHasRetried = true;
+                            throw new UnsupportedOperationException("Failed to enqueue request");
+                        }
+
+                        // Replace the old job id wit the new one.
+                        mJobId = jobParcel.jobId;
+                        // Adds the new job back into pending jobs.
+                        mManager.mPendingTranscodingJobs.put(mJobId, this);
+                    } catch (RemoteException re) {
+                        throw new MediaTranscodingException.ServiceNotAvailableException(
+                                "Failed to resubmit request to Transcoding service");
+                    }
+                    mStatus = STATUS_PENDING;
+                    mHasRetried = setHasRetried ? true : false;
+                }
             }
-            return true;
         }
 
         /**
@@ -1105,7 +1166,11 @@
                 // Check if the job is finished already.
                 if (mStatus != STATUS_FINISHED) {
                     try {
-                        mJobOwner.cancelJob(mJobId);
+                        ITranscodingClient client = mManager.getTranscodingClient();
+                        // The client may be gone.
+                        if (client != null) {
+                            client.cancelJob(mJobId);
+                        }
                     } catch (RemoteException re) {
                         //TODO(hkuang): Find out what to do if failing to cancel the job.
                         Log.e(TAG, "Failed to cancel the job due to exception:  " + re);
@@ -1173,6 +1238,12 @@
         }
     }
 
+    private ITranscodingClient getTranscodingClient() {
+        synchronized (mLock) {
+            return mTranscodingClient;
+        }
+    }
+
     /**
      * Enqueues a TranscodingRequest for execution.
      * <p> Upon successfully accepting the request, MediaTranscodeManager will return a
@@ -1185,13 +1256,17 @@
      * @return A TranscodingJob for this operation.
      * @throws FileNotFoundException if the source Uri or destination Uri could not be opened.
      * @throws UnsupportedOperationException if the request could not be fulfilled.
+     * @throws MediaTranscodingException.ServiceNotAvailableException if the service
+     *         is temporarily unavailable due to internal service rebooting. Client could retry
+     *         again after receiving this exception.
      */
     @NonNull
     public TranscodingJob enqueueRequest(
             @NonNull TranscodingRequest transcodingRequest,
             @NonNull @CallbackExecutor Executor listenerExecutor,
             @NonNull OnTranscodingFinishedListener listener)
-            throws FileNotFoundException {
+            throws FileNotFoundException,
+            MediaTranscodingException.ServiceNotAvailableException {
         Log.i(TAG, "enqueueRequest called.");
         Objects.requireNonNull(transcodingRequest, "transcodingRequest must not be null");
         Objects.requireNonNull(listenerExecutor, "listenerExecutor must not be null");
@@ -1208,7 +1283,14 @@
             synchronized (mPendingTranscodingJobs) {
                 synchronized (mLock) {
                     if (mTranscodingClient == null) {
-                        // TODO(hkuang): Handle the case if client is temporarily unavailable.
+                        // Try to register with the service again.
+                        IMediaTranscodingService service = getService(false /*retry*/);
+                        mTranscodingClient = registerClient(service);
+                        // If still fails, throws an exception to tell client to try later.
+                        if (mTranscodingClient == null) {
+                            throw new MediaTranscodingException.ServiceNotAvailableException(
+                                    "Service rebooting. Try again later");
+                        }
                     }
 
                     if (!mTranscodingClient.submitRequest(requestParcel, jobParcel)) {
@@ -1218,7 +1300,8 @@
 
                 // Wraps the TranscodingJobParcel into a TranscodingJob and returns it to client for
                 // tracking.
-                TranscodingJob job = new TranscodingJob(mTranscodingClient, jobParcel,
+                TranscodingJob job = new TranscodingJob(this, transcodingRequest,
+                        jobParcel,
                         listenerExecutor,
                         listener);
 
diff --git a/media/java/android/media/MediaTranscodingException.java b/media/java/android/media/MediaTranscodingException.java
new file mode 100644
index 0000000..50cc9c4
--- /dev/null
+++ b/media/java/android/media/MediaTranscodingException.java
@@ -0,0 +1,38 @@
+/*
+ * 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.media;
+
+/**
+ * Base class for MediaTranscoding exceptions
+ */
+public class MediaTranscodingException extends Exception {
+    private MediaTranscodingException(String detailMessage) {
+        super(detailMessage);
+    }
+
+    /**
+     * Exception thrown when the service is rebooting and MediaTranscodeManager is temporarily
+     * unavailable for accepting new request. It's likely that retrying will be successful.
+     */
+    public static final class ServiceNotAvailableException extends
+            MediaTranscodingException {
+        /** @hide */
+        public ServiceNotAvailableException(String detailMessage) {
+            super(detailMessage);
+        }
+    }
+}
diff --git a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerDiedTest.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerDiedTest.java
index f00c14d..6b5abcc 100644
--- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerDiedTest.java
+++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerDiedTest.java
@@ -22,6 +22,7 @@
 import android.media.MediaTranscodeManager;
 import android.media.MediaTranscodeManager.TranscodingJob;
 import android.media.MediaTranscodeManager.TranscodingRequest;
+import android.media.MediaTranscodingException;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.FileUtils;
@@ -250,6 +251,27 @@
                 TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
         assertTrue("Invalid job status", job.getStatus() == TranscodingJob.STATUS_FINISHED);
         assertTrue("Invalid job result", job.getResult()== TranscodingJob.RESULT_ERROR);
+
+
+        boolean retryJob = false;
+        // Wait till service is available again.
+        Log.d(TAG, "Retry the failed transcoding job");
+        while (!retryJob) {
+            try {
+                job.retry();
+                // Break out when job retry succeeds.
+                break;
+            } catch (MediaTranscodingException.ServiceNotAvailableException ex) {
+                // Sleep for 10 milliseconds to wait.
+                Thread.sleep(10);
+            }
+        }
+
+        finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
+                TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        // Check the make sure job is successfully finished after retry.
+        assertTrue("Invalid job status", job.getStatus() == TranscodingJob.STATUS_FINISHED);
+        assertTrue("Invalid job result", job.getResult() == TranscodingJob.RESULT_SUCCESS);
     }
 }
 
diff --git a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java
index 73e1d98..10262d9 100644
--- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java
+++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java
@@ -22,6 +22,7 @@
 import android.media.MediaTranscodeManager;
 import android.media.MediaTranscodeManager.TranscodingJob;
 import android.media.MediaTranscodeManager.TranscodingRequest;
+import android.media.MediaTranscodingException;
 import android.net.Uri;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
@@ -101,7 +102,8 @@
      * Transcode the sourceFileName to destinationFileName with LOOP_COUNT.
      */
     private void transcode(final String sourceFileName, final String destinationFileName)
-            throws IOException, InterruptedException {
+            throws IOException, InterruptedException,
+            MediaTranscodingException.ServiceNotAvailableException {
         AtomicLong totalTimeMs = new AtomicLong();
         AtomicLong transcodingTime = new AtomicLong();
         Uri srcUri = getUri(sourceFileName);
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index 8a2976f..480e494 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -27138,6 +27138,12 @@
     field public static final android.media.MediaTimestamp TIMESTAMP_UNKNOWN;
   }
 
+  public class MediaTranscodingException extends java.lang.Exception {
+  }
+
+  public static final class MediaTranscodingException.ServiceNotAvailableException extends android.media.MediaTranscodingException {
+  }
+
   public interface MicrophoneDirection {
     method public boolean setPreferredMicrophoneDirection(int);
     method public boolean setPreferredMicrophoneFieldDimension(@FloatRange(from=-1.0, to=1.0) float);
@@ -44903,6 +44909,7 @@
     field public static final String KEY_APN_EXPAND_BOOL = "apn_expand_bool";
     field public static final String KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY = "apn_settings_default_apn_types_string_array";
     field public static final String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool";
+    field public static final String KEY_CALL_BARRING_DEFAULT_SERVICE_CLASS_INT = "call_barring_default_service_class_int";
     field public static final String KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL = "call_barring_supports_deactivate_all_bool";
     field public static final String KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL = "call_barring_supports_password_change_bool";
     field public static final String KEY_CALL_BARRING_VISIBILITY_BOOL = "call_barring_visibility_bool";
@@ -45130,6 +45137,8 @@
     field public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING = "wfc_emergency_address_carrier_app_string";
     field public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool";
     field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
+    field public static final int SERVICE_CLASS_NONE = 0; // 0x0
+    field public static final int SERVICE_CLASS_VOICE = 1; // 0x1
   }
 
   public static final class CarrierConfigManager.Apn {
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index 38f745e..82f2021 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -1532,12 +1532,16 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setBluetoothTethering(boolean);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
+    field public static final String ACTION_TETHERING_STATE_CHANGED = "android.bluetooth.action.TETHERING_STATE_CHANGED";
     field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
+    field public static final String EXTRA_TETHERING_STATE = "android.bluetooth.extra.TETHERING_STATE";
     field public static final int LOCAL_NAP_ROLE = 1; // 0x1
     field public static final int LOCAL_PANU_ROLE = 2; // 0x2
     field public static final int PAN_ROLE_NONE = 0; // 0x0
     field public static final int REMOTE_NAP_ROLE = 1; // 0x1
     field public static final int REMOTE_PANU_ROLE = 2; // 0x2
+    field public static final int TETHERING_STATE_OFF = 1; // 0x1
+    field public static final int TETHERING_STATE_ON = 2; // 0x2
   }
 
   public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
@@ -4303,7 +4307,7 @@
   }
 
   public final class MediaTranscodeManager {
-    method @NonNull public android.media.MediaTranscodeManager.TranscodingJob enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener) throws java.io.FileNotFoundException;
+    method @NonNull public android.media.MediaTranscodeManager.TranscodingJob enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener) throws java.io.FileNotFoundException, android.media.MediaTranscodingException.ServiceNotAvailableException;
     field public static final int PRIORITY_REALTIME = 1; // 0x1
     field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1
   }
@@ -4318,7 +4322,6 @@
     method @IntRange(from=0, to=100) public int getProgress();
     method public int getResult();
     method public int getStatus();
-    method public boolean retry();
     method public void setOnProgressUpdateListener(@NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener);
     method public void setOnProgressUpdateListener(int, @NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener);
     field public static final int RESULT_CANCELED = 4; // 0x4
@@ -8238,6 +8241,7 @@
     field public static final String APN_SET_ID = "apn_set_id";
     field public static final int CARRIER_EDITED = 4; // 0x4
     field public static final String EDITED_STATUS = "edited";
+    field public static final int MATCH_ALL_APN_SET_ID = -1; // 0xffffffff
     field public static final String MAX_CONNECTIONS = "max_conns";
     field public static final String MODEM_PERSIST = "modem_cognitive";
     field public static final String MTU = "mtu";
@@ -9795,7 +9799,8 @@
     method public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
     method @Deprecated public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
     method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
-    method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
+    method @Deprecated public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
+    method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber, int);
     method public void onPhysicalChannelConfigurationChanged(@NonNull java.util.List<android.telephony.PhysicalChannelConfig>);
     method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
     method public void onRadioPowerStateChanged(int);
diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
index 8b235e6..d57875f 100644
--- a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -53,7 +53,7 @@
             android:textColor="?attr/wallpaperTextColor"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:imeOptions="flagForceAscii|actionDone"
-            android:maxLength="@integer/password_text_view_scale"
+            android:maxLength="@integer/password_max_length"
          />
 
         <TextView
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 9c92b46..3ccb1f4 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -22,6 +22,8 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
 
 import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX;
 
@@ -41,6 +43,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.om.IOverlayManager;
+import android.content.om.OverlayInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
@@ -3414,7 +3417,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 191;
+            private static final int SETTINGS_VERSION = 192;
 
             private final int mUserId;
 
@@ -4783,6 +4786,20 @@
                     currentVersion = 191;
                 }
 
+                if (currentVersion == 191) {
+                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
+                    int mode = getContext().getResources().getInteger(
+                            com.android.internal.R.integer.config_navBarInteractionMode);
+                    if (mode == NAV_BAR_MODE_GESTURAL) {
+                        switchToDefaultGestureNavBackInset(userId, secureSettings);
+                    }
+                    migrateBackGestureSensitivity(Secure.BACK_GESTURE_INSET_SCALE_LEFT, userId,
+                            secureSettings);
+                    migrateBackGestureSensitivity(Secure.BACK_GESTURE_INSET_SCALE_RIGHT, userId,
+                            secureSettings);
+                    currentVersion = 192;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
@@ -4801,6 +4818,83 @@
             }
         }
 
+        /**
+         * Previously, We were using separate overlay packages for different back inset sizes. Now,
+         * we have a single overlay package for gesture navigation mode, and set the inset size via
+         * a secure.settings field.
+         *
+         * If a non-default overlay package is enabled, then enable the default overlay exclusively,
+         * and set the calculated inset size difference as a scale value in secure.settings.
+         */
+        private void switchToDefaultGestureNavBackInset(int userId, SettingsState secureSettings) {
+            try {
+                final IOverlayManager om = IOverlayManager.Stub.asInterface(
+                        ServiceManager.getService(Context.OVERLAY_SERVICE));
+                final OverlayInfo info = om.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, userId);
+                if (info != null && !info.isEnabled()) {
+                    final int curInset = getContext().getResources().getDimensionPixelSize(
+                            com.android.internal.R.dimen.config_backGestureInset);
+                    om.setEnabledExclusiveInCategory(NAV_BAR_MODE_GESTURAL_OVERLAY, userId);
+                    final int defInset = getContext().getResources().getDimensionPixelSize(
+                            com.android.internal.R.dimen.config_backGestureInset);
+
+                    final float scale = defInset == 0 ? 1.0f : ((float) curInset) / defInset;
+                    if (scale != 1.0f) {
+                        secureSettings.insertSettingLocked(
+                                Secure.BACK_GESTURE_INSET_SCALE_LEFT,
+                                Float.toString(scale), null /* tag */, false /* makeDefault */,
+                                SettingsState.SYSTEM_PACKAGE_NAME);
+                        secureSettings.insertSettingLocked(
+                                Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
+                                Float.toString(scale), null /* tag */, false /* makeDefault */,
+                                SettingsState.SYSTEM_PACKAGE_NAME);
+                        if (DEBUG) {
+                            Slog.v(LOG_TAG, "Moved back sensitivity for user " + userId
+                                    + " to scale " + scale);
+                        }
+                    }
+                }
+            } catch (SecurityException | IllegalStateException | RemoteException e) {
+                Slog.e(LOG_TAG, "Failed to switch to default gesture nav overlay for user "
+                        + userId);
+            }
+        }
+
+        private void migrateBackGestureSensitivity(String side, int userId,
+                SettingsState secureSettings) {
+            final Setting currentScale = secureSettings.getSettingLocked(side);
+            if (currentScale.isNull()) {
+                return;
+            }
+            float current = 1.0f;
+            try {
+                current = Float.parseFloat(currentScale.getValue());
+            } catch (NumberFormatException e) {
+                // Do nothing. Overwrite with default value.
+            }
+
+            // Inset scale migration across all devices
+            //     Old(24dp): 0.66  0.75  0.83  1.00  1.08  1.33  1.66
+            //     New(30dp): 0.60  0.60  1.00  1.00  1.00  1.00  1.33
+            final float low = 0.76f;   // Values smaller than this will map to 0.6
+            final float high = 1.65f;  // Values larger than this will map to 1.33
+            float newScale;
+            if (current < low) {
+                newScale = 0.6f;
+            } else if (current < high) {
+                newScale = 1.0f;
+            } else {
+                newScale = 1.33f;
+            }
+            secureSettings.insertSettingLocked(side, Float.toString(newScale),
+                    null /* tag */, false /* makeDefault */,
+                    SettingsState.SYSTEM_PACKAGE_NAME);
+            if (DEBUG) {
+                Slog.v(LOG_TAG, "Changed back sensitivity from " + current + " to " + newScale
+                        + " for user " + userId + " on " + side);
+            }
+        }
+
         private void ensureLegacyDefaultValueAndSystemSetUpdatedLocked(SettingsState settings,
                 int userId) {
             List<String> names = settings.getSettingNamesLocked();
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index e246917..26cead2 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -86,6 +86,10 @@
      */
     InstanceId getInstanceId();
 
+    default boolean isTileReady() {
+        return false;
+    }
+
     @ProvidesInterface(version = Callback.VERSION)
     public interface Callback {
         public static final int VERSION = 1;
diff --git a/packages/SystemUI/res/drawable/ic_move.xml b/packages/SystemUI/res/drawable/ic_move_magnification.xml
similarity index 97%
rename from packages/SystemUI/res/drawable/ic_move.xml
rename to packages/SystemUI/res/drawable/ic_move_magnification.xml
index e82c9d0..ed97d0cc 100644
--- a/packages/SystemUI/res/drawable/ic_move.xml
+++ b/packages/SystemUI/res/drawable/ic_move_magnification.xml
@@ -16,7 +16,7 @@
 
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
     <item>
-        <shape android:shape="oval">
+        <shape android:shape="rectangle">
             <solid
                 android:color="@android:color/black" />
             <size
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
index 25d63e3..6ced978 100644
--- a/packages/SystemUI/res/layout/window_magnifier_view.xml
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -23,13 +23,13 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_margin="@dimen/magnification_outer_border_margin"
-        android:background="@android:color/black" />
+        android:background="@android:color/black"/>
 
     <View
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_margin="@dimen/magnification_inner_border_margin"
-        android:background="@color/magnification_border_color" />
+        android:background="@color/magnification_border_color"/>
 
     <RelativeLayout
         android:layout_width="match_parent"
@@ -40,32 +40,32 @@
             android:id="@+id/left_handle"
             android:layout_width="@dimen/magnification_border_drag_size"
             android:layout_height="match_parent"
-            android:layout_above="@+id/bottom_handle" />
+            android:layout_above="@+id/bottom_handle"/>
 
         <View
             android:id="@+id/top_handle"
             android:layout_width="match_parent"
             android:layout_height="@dimen/magnification_border_drag_size"
-            android:layout_alignParentTop="true" />
+            android:layout_alignParentTop="true"/>
 
         <View
             android:id="@+id/right_handle"
             android:layout_width="@dimen/magnification_border_drag_size"
             android:layout_height="match_parent"
             android:layout_above="@+id/bottom_handle"
-            android:layout_alignParentEnd="true" />
+            android:layout_alignParentEnd="true"/>
 
         <View
             android:id="@+id/bottom_handle"
             android:layout_width="match_parent"
             android:layout_height="@dimen/magnification_border_drag_size"
-            android:layout_alignParentBottom="true" />
+            android:layout_alignParentBottom="true"/>
 
         <SurfaceView
             android:id="@+id/surface_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:layout_margin="@dimen/magnification_mirror_surface_margin" />
+            android:layout_margin="@dimen/magnification_mirror_surface_margin"/>
 
     </RelativeLayout>
 
@@ -73,8 +73,9 @@
         android:id="@+id/drag_handle"
         android:layout_width="@dimen/magnification_drag_view_size"
         android:layout_height="@dimen/magnification_drag_view_size"
+        android:layout_margin="@dimen/magnification_outer_border_margin"
         android:layout_gravity="right|bottom"
         android:scaleType="center"
-        android:src="@drawable/ic_move" />
+        android:src="@drawable/ic_move_magnification"/>
 
 </FrameLayout>
\ No newline at end of file
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..d957628
--- /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 {
+            mView.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..9ffa658 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -178,18 +178,18 @@
     /** 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.setSecurityCallback(mSecurityCallback);
+        mKeyguardSecurityContainerController.showPrimarySecurityScreen(false);
     }
 
     @Override
@@ -350,7 +350,7 @@
     }
 
     public boolean handleBackKey() {
-        if (mKeyguardSecurityContainerController.getCurrentSecuritySelection()
+        if (mKeyguardSecurityContainerController.getCurrentSecurityMode()
                 != SecurityMode.None) {
             mKeyguardSecurityContainerController.dismiss(
                     false, KeyguardUpdateMonitor.getCurrentUser());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
new file mode 100644
index 0000000..d42a53c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A Base class for all Keyguard password/pattern/pin related inputs.
+ */
+public abstract class KeyguardInputView extends LinearLayout {
+
+    public KeyguardInputView(Context context) {
+        super(context);
+    }
+
+    public KeyguardInputView(Context context,
+            @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public KeyguardInputView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    abstract CharSequence getTitle();
+
+    boolean disallowInterceptTouch(MotionEvent event) {
+        return false;
+    }
+
+    void startAppearAnimation() {}
+
+    boolean startDisappearAnimation(Runnable finishRunnable) {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
new file mode 100644
index 0000000..fbda818
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.telephony.TelephonyManager;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.ViewController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import javax.inject.Inject;
+
+
+/** Controller for a {@link KeyguardSecurityView}. */
+public abstract class KeyguardInputViewController<T extends KeyguardInputView>
+        extends ViewController<T> implements KeyguardSecurityView {
+
+    private final SecurityMode mSecurityMode;
+    private final KeyguardSecurityCallback mKeyguardSecurityCallback;
+    private boolean mPaused;
+
+
+    // The following is used to ignore callbacks from SecurityViews that are no longer current
+    // (e.g. face unlock). This avoids unwanted asynchronous events from messing with the
+    // state for the current security method.
+    private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {
+        @Override
+        public void userActivity() { }
+        @Override
+        public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { }
+        @Override
+        public boolean isVerifyUnlockOnly() {
+            return false;
+        }
+        @Override
+        public void dismiss(boolean securityVerified, int targetUserId) { }
+        @Override
+        public void dismiss(boolean authenticated, int targetId,
+                boolean bypassSecondaryLockScreen) { }
+        @Override
+        public void onUserInput() { }
+        @Override
+        public void reset() {}
+    };
+
+    protected KeyguardInputViewController(T view, SecurityMode securityMode,
+            KeyguardSecurityCallback keyguardSecurityCallback) {
+        super(view);
+        mSecurityMode = securityMode;
+        mKeyguardSecurityCallback = keyguardSecurityCallback;
+    }
+
+    @Override
+    protected void onViewAttached() {
+    }
+
+    @Override
+    protected void onViewDetached() {
+    }
+
+    SecurityMode getSecurityMode() {
+        return mSecurityMode;
+    }
+
+    protected KeyguardSecurityCallback getKeyguardSecurityCallback() {
+        if (mPaused) {
+            return mNullCallback;
+        }
+
+        return mKeyguardSecurityCallback;
+    }
+
+    @Override
+    public void reset() {
+    }
+
+    @Override
+    public void onPause() {
+        mPaused = true;
+    }
+
+    @Override
+    public void onResume(int reason) {
+        mPaused = false;
+    }
+
+    @Override
+    public void showPromptReason(int reason) {
+    }
+
+    @Override
+    public void showMessage(CharSequence message, ColorStateList colorState) {
+    }
+
+    public void startAppearAnimation() {
+        mView.startAppearAnimation();
+    }
+
+    public boolean startDisappearAnimation(Runnable finishRunnable) {
+        return mView.startDisappearAnimation(finishRunnable);
+    }
+
+    @Override
+    public CharSequence getTitle() {
+        return mView.getTitle();
+    }
+
+    /** Finds the index of this view in the suppplied parent view. */
+    public int getIndexIn(KeyguardSecurityViewFlipper view) {
+        return view.indexOfChild(mView);
+    }
+
+    /** Factory for a {@link KeyguardInputViewController}. */
+    public static class Factory {
+        private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+        private final LockPatternUtils mLockPatternUtils;
+        private final LatencyTracker mLatencyTracker;
+        private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
+        private final InputMethodManager mInputMethodManager;
+        private final DelayableExecutor mMainExecutor;
+        private final Resources mResources;
+        private LiftToActivateListener mLiftToActivateListener;
+        private TelephonyManager mTelephonyManager;
+
+        @Inject
+        public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
+                LockPatternUtils lockPatternUtils,
+                LatencyTracker latencyTracker,
+                KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+                InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor,
+                @Main Resources resources, LiftToActivateListener liftToActivateListener,
+                TelephonyManager telephonyManager) {
+            mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+            mLockPatternUtils = lockPatternUtils;
+            mLatencyTracker = latencyTracker;
+            mMessageAreaControllerFactory = messageAreaControllerFactory;
+            mInputMethodManager = inputMethodManager;
+            mMainExecutor = mainExecutor;
+            mResources = resources;
+            mLiftToActivateListener = liftToActivateListener;
+            mTelephonyManager = telephonyManager;
+        }
+
+        /** Create a new {@link KeyguardInputViewController}. */
+        public KeyguardInputViewController create(KeyguardInputView keyguardInputView,
+                SecurityMode securityMode, KeyguardSecurityCallback keyguardSecurityCallback) {
+            if (keyguardInputView instanceof KeyguardPatternView) {
+                return new KeyguardPatternViewController((KeyguardPatternView) keyguardInputView,
+                        mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
+                        keyguardSecurityCallback, mLatencyTracker, mMessageAreaControllerFactory);
+            } else if (keyguardInputView instanceof KeyguardPasswordView) {
+                return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
+                        mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
+                        keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
+                        mInputMethodManager, mMainExecutor, mResources);
+            } else if (keyguardInputView instanceof KeyguardPINView) {
+                return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
+                        mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
+                        keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
+                        mLiftToActivateListener);
+            } else if (keyguardInputView instanceof KeyguardSimPinView) {
+                return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
+                        mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
+                        keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
+                        mLiftToActivateListener, mTelephonyManager);
+            } else if (keyguardInputView instanceof KeyguardSimPukView) {
+                return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
+                        mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
+                        keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
+                        mLiftToActivateListener, mTelephonyManager);
+            }
+
+            throw new RuntimeException("Unable to find controller for " + keyguardInputView);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index a8b1451..1a0a437 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -16,8 +16,6 @@
 
 package com.android.keyguard;
 
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
@@ -31,20 +29,14 @@
 import android.view.View;
 import android.widget.TextView;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.lang.ref.WeakReference;
 
-import javax.inject.Inject;
-import javax.inject.Named;
-
 /***
  * Manages a number of views inside of the given layout. See below for a list of widgets.
  */
-public class KeyguardMessageArea extends TextView implements SecurityMessageDisplay,
-        ConfigurationController.ConfigurationListener {
+public class KeyguardMessageArea extends TextView implements SecurityMessageDisplay {
     /** Handler token posted with accessibility announcement runnables. */
     private static final Object ANNOUNCE_TOKEN = new Object();
 
@@ -56,71 +48,26 @@
     private static final int DEFAULT_COLOR = -1;
 
     private final Handler mHandler;
-    private final ConfigurationController mConfigurationController;
 
     private ColorStateList mDefaultColorState;
     private CharSequence mMessage;
     private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR);
     private boolean mBouncerVisible;
 
-    private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
-        public void onFinishedGoingToSleep(int why) {
-            setSelected(false);
-        }
-
-        public void onStartedWakingUp() {
-            setSelected(true);
-        }
-
-        @Override
-        public void onKeyguardBouncerChanged(boolean bouncer) {
-            mBouncerVisible = bouncer;
-            update();
-        }
-    };
-
-    public KeyguardMessageArea(Context context) {
-        super(context, null);
-        throw new IllegalStateException("This constructor should never be invoked");
-    }
-
-    @Inject
-    public KeyguardMessageArea(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
-            ConfigurationController configurationController) {
-        this(context, attrs, Dependency.get(KeyguardUpdateMonitor.class), configurationController);
-    }
-
-    public KeyguardMessageArea(Context context, AttributeSet attrs, KeyguardUpdateMonitor monitor,
-            ConfigurationController configurationController) {
+    public KeyguardMessageArea(Context context, AttributeSet attrs) {
         super(context, attrs);
         setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
 
-        monitor.registerCallback(mInfoCallback);
         mHandler = new Handler(Looper.myLooper());
-        mConfigurationController = configurationController;
         onThemeChanged();
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mConfigurationController.addCallback(this);
-        onThemeChanged();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mConfigurationController.removeCallback(this);
-    }
-
-    @Override
     public void setNextMessageColor(ColorStateList colorState) {
         mNextMessageColorState = colorState;
     }
 
-    @Override
-    public void onThemeChanged() {
+    void onThemeChanged() {
         TypedArray array = mContext.obtainStyledAttributes(new int[] {
                 R.attr.wallpaperTextColor
         });
@@ -130,8 +77,7 @@
         update();
     }
 
-    @Override
-    public void onDensityOrFontScaleChanged() {
+    void onDensityOrFontScaleChanged() {
         TypedArray array = mContext.obtainStyledAttributes(R.style.Keyguard_TextView, new int[] {
                 android.R.attr.textSize
         });
@@ -177,12 +123,6 @@
         return messageArea;
     }
 
-    @Override
-    protected void onFinishInflate() {
-        boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
-        setSelected(shouldMarquee); // This is required to ensure marquee works
-    }
-
     private void securityMessageChanged(CharSequence message) {
         mMessage = message;
         update();
@@ -196,7 +136,7 @@
         update();
     }
 
-    private void update() {
+    void update() {
         CharSequence status = mMessage;
         setVisibility(TextUtils.isEmpty(status) || !mBouncerVisible ? INVISIBLE : VISIBLE);
         setText(status);
@@ -208,6 +148,9 @@
         setTextColor(colorState);
     }
 
+    public void setBouncerVisible(boolean bouncerVisible) {
+        mBouncerVisible = bouncerVisible;
+    }
 
     /**
      * Runnable used to delay accessibility announcements.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index f056bdb..1618e8e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -16,7 +16,10 @@
 
 package com.android.keyguard;
 
+import android.content.res.ColorStateList;
+
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
@@ -26,6 +29,35 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final ConfigurationController mConfigurationController;
 
+
+    private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
+        public void onFinishedGoingToSleep(int why) {
+            mView.setSelected(false);
+        }
+
+        public void onStartedWakingUp() {
+            mView.setSelected(true);
+        }
+
+        @Override
+        public void onKeyguardBouncerChanged(boolean bouncer) {
+            mView.setBouncerVisible(bouncer);
+            mView.update();
+        }
+    };
+
+    private ConfigurationListener mConfigurationListener = new ConfigurationListener() {
+        @Override
+        public void onThemeChanged() {
+            mView.onThemeChanged();
+        }
+
+        @Override
+        public void onDensityOrFontScaleChanged() {
+            mView.onDensityOrFontScaleChanged();
+        }
+    };
+
     private KeyguardMessageAreaController(KeyguardMessageArea view,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             ConfigurationController configurationController) {
@@ -37,17 +69,31 @@
 
     @Override
     protected void onViewAttached() {
-        //mConfigurationController.addCallback();
-        //mKeyguardUpdateMonitor.registerCallback();
+        mConfigurationController.addCallback(mConfigurationListener);
+        mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
+        mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive());
+        mView.onThemeChanged();
     }
 
     @Override
     protected void onViewDetached() {
-        //mConfigurationController.removeCallback();
-        //mKeyguardUpdateMonitor.removeCallback();
+        mConfigurationController.removeCallback(mConfigurationListener);
+        mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
     }
 
-    /** Factory for createing {@link com.android.keyguard.KeyguardMessageAreaController}. */
+    public void setMessage(CharSequence s) {
+        mView.setMessage(s);
+    }
+
+    public void setMessage(int resId) {
+        mView.setMessage(resId);
+    }
+
+    public void setNextMessageColor(ColorStateList colorState) {
+        mView.setNextMessageColor(colorState);
+    }
+
+    /** Factory for creating {@link com.android.keyguard.KeyguardMessageAreaController}. */
     public static class Factory {
         private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
         private final ConfigurationController mConfigurationController;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 12ea1d5..580d704 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -24,7 +24,6 @@
 
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
 /**
@@ -40,10 +39,8 @@
     private ViewGroup mRow1;
     private ViewGroup mRow2;
     private ViewGroup mRow3;
-    private View mDivider;
     private int mDisappearYTranslation;
     private View[][] mViews;
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     public KeyguardPINView(Context context) {
         this(context, null);
@@ -63,15 +60,10 @@
                         mContext, android.R.interpolator.fast_out_linear_in));
         mDisappearYTranslation = getResources().getDimensionPixelSize(
                 R.dimen.disappear_y_translation);
-        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
     }
 
     @Override
     protected void resetState() {
-        super.resetState();
-        if (mSecurityMessageDisplay != null) {
-            mSecurityMessageDisplay.setMessage("");
-        }
     }
 
     @Override
@@ -88,7 +80,6 @@
         mRow1 = findViewById(R.id.row1);
         mRow2 = findViewById(R.id.row2);
         mRow3 = findViewById(R.id.row3);
-        mDivider = findViewById(R.id.divider);
         mViews = new View[][]{
                 new View[]{
                         mRow0, null, null
@@ -112,18 +103,6 @@
                 new View[]{
                         null, mEcaView, null
                 }};
-
-        View cancelBtn = findViewById(R.id.cancel_button);
-        if (cancelBtn != null) {
-            cancelBtn.setOnClickListener(view -> {
-                mCallback.reset();
-                mCallback.onCancelClicked();
-            });
-        }
-    }
-
-    @Override
-    public void showUsabilityHint() {
     }
 
     @Override
@@ -147,24 +126,21 @@
                 });
     }
 
-    @Override
-    public boolean startDisappearAnimation(final Runnable finishRunnable) {
+    public boolean startDisappearAnimation(boolean needsSlowUnlockTransition,
+            final Runnable finishRunnable) {
+
         enableClipping(false);
         setTranslationY(0);
         AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 280 /* duration */,
                 mDisappearYTranslation, mDisappearAnimationUtils.getInterpolator());
-        DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor
-                .needsSlowUnlockTransition()
+        DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
                         ? mDisappearAnimationUtilsLocked
                         : mDisappearAnimationUtils;
         disappearAnimationUtils.startAnimation2d(mViews,
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        enableClipping(true);
-                        if (finishRunnable != null) {
-                            finishRunnable.run();
-                        }
+                () -> {
+                    enableClipping(true);
+                    if (finishRunnable != null) {
+                        finishRunnable.run();
                     }
                 });
         return true;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 97317cf..aaa5efe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -16,50 +16,37 @@
 
 package com.android.keyguard;
 
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+
 import android.content.Context;
 import android.graphics.Rect;
-import android.os.UserHandle;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.text.method.TextKeyListener;
 import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.View;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
 import android.widget.TextView;
-import android.widget.TextView.OnEditorActionListener;
 
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.TextViewInputDisabler;
 import com.android.systemui.R;
-
-import java.util.List;
 /**
  * Displays an alphanumeric (latin-1) key entry for the user to enter
  * an unlock password
  */
-public class KeyguardPasswordView extends KeyguardAbsKeyInputView
-        implements KeyguardSecurityView, OnEditorActionListener, TextWatcher {
+public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
 
-    private final boolean mShowImeAtScreenOn;
     private final int mDisappearYTranslation;
 
     // A delay constant to be used in a workaround for the situation where InputMethodManagerService
     // is not switched to the new user yet.
     // TODO: Remove this by ensuring such a race condition never happens.
-    private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500;  // 500ms
 
-    InputMethodManager mImm;
     private TextView mPasswordEntry;
     private TextViewInputDisabler mPasswordEntryDisabler;
-    private View mSwitchImeButton;
 
     private Interpolator mLinearOutSlowInInterpolator;
     private Interpolator mFastOutLinearInInterpolator;
@@ -70,8 +57,6 @@
 
     public KeyguardPasswordView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mShowImeAtScreenOn = context.getResources().
-                getBoolean(R.bool.kg_show_ime_at_screen_on);
         mDisappearYTranslation = getResources().getDimensionPixelSize(
                 R.dimen.disappear_y_translation);
         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
@@ -82,20 +67,6 @@
 
     @Override
     protected void resetState() {
-        mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
-        if (mSecurityMessageDisplay != null) {
-            mSecurityMessageDisplay.setMessage("");
-        }
-        final boolean wasDisabled = mPasswordEntry.isEnabled();
-        setPasswordEntryEnabled(true);
-        setPasswordEntryInputEnabled(true);
-        // Don't call showSoftInput when PasswordEntry is invisible or in pausing stage.
-        if (!mResumed || !mPasswordEntry.isVisibleToUser()) {
-            return;
-        }
-        if (wasDisabled) {
-            mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
-        }
     }
 
     @Override
@@ -104,29 +75,6 @@
     }
 
     @Override
-    public boolean needsInput() {
-        return true;
-    }
-
-    @Override
-    public void onResume(final int reason) {
-        super.onResume(reason);
-
-        // Wait a bit to focus the field so the focusable flag on the window is already set then.
-        post(new Runnable() {
-            @Override
-            public void run() {
-                if (isShown() && mPasswordEntry.isEnabled()) {
-                    mPasswordEntry.requestFocus();
-                    if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
-                        mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
-                    }
-                }
-            }
-        });
-    }
-
-    @Override
     protected int getPromptReasonStringRes(int reason) {
         switch (reason) {
             case PROMPT_REASON_RESTART:
@@ -146,97 +94,13 @@
         }
     }
 
-    @Override
-    public void onPause() {
-        super.onPause();
-        mImm.hideSoftInputFromWindow(getWindowToken(), 0);
-    }
-
-    @Override
-    public void onStartingToHide() {
-        mImm.hideSoftInputFromWindow(getWindowToken(), 0);
-    }
-
-    private void updateSwitchImeButton() {
-        // If there's more than one IME, enable the IME switcher button
-        final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE;
-        final boolean shouldBeVisible = hasMultipleEnabledIMEsOrSubtypes(mImm, false);
-        if (wasVisible != shouldBeVisible) {
-            mSwitchImeButton.setVisibility(shouldBeVisible ? View.VISIBLE : View.GONE);
-        }
-
-        // TODO: Check if we still need this hack.
-        // If no icon is visible, reset the start margin on the password field so the text is
-        // still centered.
-        if (mSwitchImeButton.getVisibility() != View.VISIBLE) {
-            android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams();
-            if (params instanceof MarginLayoutParams) {
-                final MarginLayoutParams mlp = (MarginLayoutParams) params;
-                mlp.setMarginStart(0);
-                mPasswordEntry.setLayoutParams(params);
-            }
-        }
-    }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mImm = (InputMethodManager) getContext().getSystemService(
-                Context.INPUT_METHOD_SERVICE);
-
         mPasswordEntry = findViewById(getPasswordTextViewId());
-        mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
         mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry);
-        mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
-        mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
-                | InputType.TYPE_TEXT_VARIATION_PASSWORD);
-        mPasswordEntry.setOnEditorActionListener(this);
-        mPasswordEntry.addTextChangedListener(this);
-
-        // Poke the wakelock any time the text is selected or modified
-        mPasswordEntry.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mCallback.userActivity();
-            }
-        });
-
-        // Set selected property on so the view can send accessibility events.
-        mPasswordEntry.setSelected(true);
-
-        mSwitchImeButton = findViewById(R.id.switch_ime_button);
-        mSwitchImeButton.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mCallback.userActivity(); // Leave the screen on a bit longer
-                // Do not show auxiliary subtypes in password lock screen.
-                mImm.showInputMethodPickerFromSystem(false /* showAuxiliarySubtypes */,
-                        getContext().getDisplayId());
-            }
-        });
-
-        View cancelBtn = findViewById(R.id.cancel_button);
-        if (cancelBtn != null) {
-            cancelBtn.setOnClickListener(view -> {
-                mCallback.reset();
-                mCallback.onCancelClicked();
-            });
-        }
-
-        // If there's more than one IME, enable the IME switcher button
-        updateSwitchImeButton();
-
-        // When we the current user is switching, InputMethodManagerService sometimes has not
-        // switched internal state yet here. As a quick workaround, we check the keyboard state
-        // again.
-        // TODO: Remove this workaround by ensuring such a race condition never happens.
-        postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                updateSwitchImeButton();
-            }
-        }, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
     }
 
     @Override
@@ -265,59 +129,6 @@
         mPasswordEntryDisabler.setInputEnabled(enabled);
     }
 
-    /**
-     * Method adapted from com.android.inputmethod.latin.Utils
-     *
-     * @param imm The input method manager
-     * @param shouldIncludeAuxiliarySubtypes
-     * @return true if we have multiple IMEs to choose from
-     */
-    private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
-            final boolean shouldIncludeAuxiliarySubtypes) {
-        final List<InputMethodInfo> enabledImis =
-                imm.getEnabledInputMethodListAsUser(KeyguardUpdateMonitor.getCurrentUser());
-
-        // Number of the filtered IMEs
-        int filteredImisCount = 0;
-
-        for (InputMethodInfo imi : enabledImis) {
-            // We can return true immediately after we find two or more filtered IMEs.
-            if (filteredImisCount > 1) return true;
-            final List<InputMethodSubtype> subtypes =
-                    imm.getEnabledInputMethodSubtypeList(imi, true);
-            // IMEs that have no subtypes should be counted.
-            if (subtypes.isEmpty()) {
-                ++filteredImisCount;
-                continue;
-            }
-
-            int auxCount = 0;
-            for (InputMethodSubtype subtype : subtypes) {
-                if (subtype.isAuxiliary()) {
-                    ++auxCount;
-                }
-            }
-            final int nonAuxCount = subtypes.size() - auxCount;
-
-            // IMEs that have one or more non-auxiliary subtypes should be counted.
-            // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
-            // subtypes should be counted as well.
-            if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
-                ++filteredImisCount;
-                continue;
-            }
-        }
-
-        return filteredImisCount > 1
-        // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
-        // input method subtype (The current IME should be LatinIME.)
-                || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
-    }
-
-    @Override
-    public void showUsabilityHint() {
-    }
-
     @Override
     public int getWrongPasswordStringId() {
         return R.string.kg_wrong_password;
@@ -346,45 +157,8 @@
     }
 
     @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-        if (mCallback != null) {
-            mCallback.userActivity();
-        }
-    }
-
-    @Override
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
-    }
-
-    @Override
-    public void afterTextChanged(Editable s) {
-        // Poor man's user edit detection, assuming empty text is programmatic and everything else
-        // is from the user.
-        if (!TextUtils.isEmpty(s)) {
-            onUserInput();
-        }
-    }
-
-    @Override
-    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
-        // Check if this was the result of hitting the enter key
-        final boolean isSoftImeEvent = event == null
-                && (actionId == EditorInfo.IME_NULL
-                || actionId == EditorInfo.IME_ACTION_DONE
-                || actionId == EditorInfo.IME_ACTION_NEXT);
-        final boolean isKeyboardEnterKey = event != null
-                && KeyEvent.isConfirmKey(event.getKeyCode())
-                && event.getAction() == KeyEvent.ACTION_DOWN;
-        if (isSoftImeEvent || isKeyboardEnterKey) {
-            verifyPasswordAndUnlock();
-            return true;
-        }
-        return false;
-    }
-
-    @Override
     public CharSequence getTitle() {
-        return getContext().getString(
+        return getResources().getString(
                 com.android.internal.R.string.keyguard_accessibility_password_unlock);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
new file mode 100644
index 0000000..d34ea8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.method.TextKeyListener;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import java.util.List;
+
+public class KeyguardPasswordViewController
+        extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
+
+    private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500;  // 500ms
+
+    private final KeyguardSecurityCallback mKeyguardSecurityCallback;
+    private final InputMethodManager mInputMethodManager;
+    private final DelayableExecutor mMainExecutor;
+    private final boolean mShowImeAtScreenOn;
+    private TextView mPasswordEntry;
+    private View mSwitchImeButton;
+
+    private final OnEditorActionListener mOnEditorActionListener = (v, actionId, event) -> {
+        // Check if this was the result of hitting the enter key
+        final boolean isSoftImeEvent = event == null
+                && (actionId == EditorInfo.IME_NULL
+                || actionId == EditorInfo.IME_ACTION_DONE
+                || actionId == EditorInfo.IME_ACTION_NEXT);
+        final boolean isKeyboardEnterKey = event != null
+                && KeyEvent.isConfirmKey(event.getKeyCode())
+                && event.getAction() == KeyEvent.ACTION_DOWN;
+        if (isSoftImeEvent || isKeyboardEnterKey) {
+            verifyPasswordAndUnlock();
+            return true;
+        }
+        return false;
+    };
+
+    private final TextWatcher mTextWatcher = new TextWatcher() {
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            mKeyguardSecurityCallback.userActivity();
+        }
+
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+        }
+
+        @Override
+        public void afterTextChanged(Editable s) {
+            if (!TextUtils.isEmpty(s)) {
+                onUserInput();
+            }
+        }
+    };
+
+    protected KeyguardPasswordViewController(KeyguardPasswordView view,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SecurityMode securityMode,
+            LockPatternUtils lockPatternUtils,
+            KeyguardSecurityCallback keyguardSecurityCallback,
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+            LatencyTracker latencyTracker,
+            InputMethodManager inputMethodManager,
+            @Main DelayableExecutor mainExecutor,
+            @Main Resources resources) {
+        super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
+                messageAreaControllerFactory, latencyTracker);
+        mKeyguardSecurityCallback = keyguardSecurityCallback;
+        mInputMethodManager = inputMethodManager;
+        mMainExecutor = mainExecutor;
+        mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
+        mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
+        mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
+    }
+
+    @Override
+    protected void onViewAttached() {
+        super.onViewAttached();
+        mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
+        mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
+        mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
+                | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+        // Set selected property on so the view can send accessibility events.
+        mPasswordEntry.setSelected(true);
+        mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener);
+        mPasswordEntry.addTextChangedListener(mTextWatcher);
+        // Poke the wakelock any time the text is selected or modified
+        mPasswordEntry.setOnClickListener(v -> mKeyguardSecurityCallback.userActivity());
+
+        mSwitchImeButton.setOnClickListener(v -> {
+            mKeyguardSecurityCallback.userActivity(); // Leave the screen on a bit longer
+            // Do not show auxiliary subtypes in password lock screen.
+            mInputMethodManager.showInputMethodPickerFromSystem(false,
+                    mView.getContext().getDisplayId());
+        });
+
+        View cancelBtn = mView.findViewById(R.id.cancel_button);
+        if (cancelBtn != null) {
+            cancelBtn.setOnClickListener(view -> {
+                mKeyguardSecurityCallback.reset();
+                mKeyguardSecurityCallback.onCancelClicked();
+            });
+        }
+
+        // If there's more than one IME, enable the IME switcher button
+        updateSwitchImeButton();
+
+        // When we the current user is switching, InputMethodManagerService sometimes has not
+        // switched internal state yet here. As a quick workaround, we check the keyboard state
+        // again.
+        // TODO: Remove this workaround by ensuring such a race condition never happens.
+        mMainExecutor.executeDelayed(
+                this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
+    }
+
+    @Override
+    protected void onViewDetached() {
+        super.onViewDetached();
+        mPasswordEntry.setOnEditorActionListener(null);
+    }
+
+    @Override
+    public boolean needsInput() {
+        return true;
+    }
+
+    @Override
+    void resetState() {
+        mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
+        mMessageAreaController.setMessage("");
+        final boolean wasDisabled = mPasswordEntry.isEnabled();
+        mView.setPasswordEntryEnabled(true);
+        mView.setPasswordEntryInputEnabled(true);
+        // Don't call showSoftInput when PasswordEntry is invisible or in pausing stage.
+        if (!mResumed || !mPasswordEntry.isVisibleToUser()) {
+            return;
+        }
+        if (wasDisabled) {
+            mInputMethodManager.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
+        }
+    }
+
+    @Override
+    public void onResume(int reason) {
+        super.onResume(reason);
+        // Wait a bit to focus the field so the focusable flag on the window is already set then.
+        mMainExecutor.execute(() -> {
+            if (mView.isShown() && mPasswordEntry.isEnabled()) {
+                mPasswordEntry.requestFocus();
+                if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
+                    mInputMethodManager.showSoftInput(
+                            mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0);
+    }
+
+    @Override
+    public void onStartingToHide() {
+        mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0);
+    }
+
+    private void updateSwitchImeButton() {
+        // If there's more than one IME, enable the IME switcher button
+        final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE;
+        final boolean shouldBeVisible = hasMultipleEnabledIMEsOrSubtypes(
+                mInputMethodManager, false);
+        if (wasVisible != shouldBeVisible) {
+            mSwitchImeButton.setVisibility(shouldBeVisible ? View.VISIBLE : View.GONE);
+        }
+
+        // TODO: Check if we still need this hack.
+        // If no icon is visible, reset the start margin on the password field so the text is
+        // still centered.
+        if (mSwitchImeButton.getVisibility() != View.VISIBLE) {
+            android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams();
+            if (params instanceof MarginLayoutParams) {
+                final MarginLayoutParams mlp = (MarginLayoutParams) params;
+                mlp.setMarginStart(0);
+                mPasswordEntry.setLayoutParams(params);
+            }
+        }
+    }
+
+    /**
+     * Method adapted from com.android.inputmethod.latin.Utils
+     *
+     * @param imm The input method manager
+     * @param shouldIncludeAuxiliarySubtypes
+     * @return true if we have multiple IMEs to choose from
+     */
+    private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
+            final boolean shouldIncludeAuxiliarySubtypes) {
+        final List<InputMethodInfo> enabledImis =
+                imm.getEnabledInputMethodListAsUser(KeyguardUpdateMonitor.getCurrentUser());
+
+        // Number of the filtered IMEs
+        int filteredImisCount = 0;
+
+        for (InputMethodInfo imi : enabledImis) {
+            // We can return true immediately after we find two or more filtered IMEs.
+            if (filteredImisCount > 1) return true;
+            final List<InputMethodSubtype> subtypes =
+                    imm.getEnabledInputMethodSubtypeList(imi, true);
+            // IMEs that have no subtypes should be counted.
+            if (subtypes.isEmpty()) {
+                ++filteredImisCount;
+                continue;
+            }
+
+            int auxCount = 0;
+            for (InputMethodSubtype subtype : subtypes) {
+                if (subtype.isAuxiliary()) {
+                    ++auxCount;
+                }
+            }
+            final int nonAuxCount = subtypes.size() - auxCount;
+
+            // IMEs that have one or more non-auxiliary subtypes should be counted.
+            // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
+            // subtypes should be counted as well.
+            if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
+                ++filteredImisCount;
+                continue;
+            }
+        }
+
+        return filteredImisCount > 1
+                // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's
+                //enabled input method subtype (The current IME should be LatinIME.)
+                || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index c4a9fcb..bdcf467 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -15,62 +15,39 @@
  */
 package com.android.keyguard;
 
-import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
-import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
-
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.graphics.Rect;
-import android.os.AsyncTask;
-import android.os.CountDownTimer;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
-import android.widget.LinearLayout;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.LatencyTracker;
-import com.android.internal.widget.LockPatternChecker;
-import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternView;
-import com.android.internal.widget.LockscreenCredential;
 import com.android.settingslib.animation.AppearAnimationCreator;
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
-import java.util.List;
-
-public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView,
-        AppearAnimationCreator<LockPatternView.CellState>,
-        EmergencyButton.EmergencyButtonCallback {
+public class KeyguardPatternView extends KeyguardInputView
+        implements AppearAnimationCreator<LockPatternView.CellState> {
 
     private static final String TAG = "SecurityPatternView";
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
 
-    // how long before we clear the wrong pattern
-    private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
 
     // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK
     private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000;
 
-    // how many cells the user has to cross before we poke the wakelock
-    private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
-
     // How much we scale up the duration of the disappear animation when the current user is locked
     public static final float DISAPPEAR_MULTIPLIER_LOCKED = 1.5f;
 
     // Extra padding, in pixels, that should eat touch events.
     private static final int PATTERNS_TOUCH_AREA_EXTENSION = 40;
 
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final AppearAnimationUtils mAppearAnimationUtils;
     private final DisappearAnimationUtils mDisappearAnimationUtils;
     private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
@@ -78,11 +55,7 @@
     private final Rect mTempRect = new Rect();
     private final Rect mLockPatternScreenBounds = new Rect();
 
-    private CountDownTimer mCountdownTimer = null;
-    private LockPatternUtils mLockPatternUtils;
-    private AsyncTask<?, ?, ?> mPendingLockCheck;
     private LockPatternView mLockPatternView;
-    private KeyguardSecurityCallback mCallback;
 
     /**
      * Keeps track of the last time we poked the wake lock during dispatching of the touch event.
@@ -92,26 +65,9 @@
      */
     private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;
 
-    /**
-     * Useful for clearing out the wrong pattern after a delay
-     */
-    private Runnable mCancelPatternRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mLockPatternView.clearPattern();
-        }
-    };
-    @VisibleForTesting
     KeyguardMessageArea mSecurityMessageDisplay;
     private View mEcaView;
     private ViewGroup mContainer;
-    private int mDisappearYTranslation;
-
-    enum FooterMode {
-        Normal,
-        ForgotLockPattern,
-        VerifyUnlocked
-    }
 
     public KeyguardPatternView(Context context) {
         this(context, null);
@@ -119,7 +75,6 @@
 
     public KeyguardPatternView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mAppearAnimationUtils = new AppearAnimationUtils(context,
                 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */,
                 2.0f /* delayScale */, AnimationUtils.loadInterpolator(
@@ -132,50 +87,16 @@
                 (long) (125 * DISAPPEAR_MULTIPLIER_LOCKED), 1.2f /* translationScale */,
                 0.6f /* delayScale */, AnimationUtils.loadInterpolator(
                 mContext, android.R.interpolator.fast_out_linear_in));
-        mDisappearYTranslation = getResources().getDimensionPixelSize(
-                R.dimen.disappear_y_translation);
-    }
-
-    @Override
-    public void setKeyguardCallback(KeyguardSecurityCallback callback) {
-        mCallback = callback;
-    }
-
-    @Override
-    public void setLockPatternUtils(LockPatternUtils utils) {
-        mLockPatternUtils = utils;
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mLockPatternUtils = mLockPatternUtils == null
-                ? new LockPatternUtils(mContext) : mLockPatternUtils;
 
         mLockPatternView = findViewById(R.id.lockPatternView);
-        mLockPatternView.setSaveEnabled(false);
-        mLockPatternView.setOnPatternListener(new UnlockPatternListener());
-        mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
-                KeyguardUpdateMonitor.getCurrentUser()));
-
-        // vibrate mode will be the same for the life of this screen
-        mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
 
         mEcaView = findViewById(R.id.keyguard_selector_fade_container);
         mContainer = findViewById(R.id.container);
-
-        EmergencyButton button = findViewById(R.id.emergency_call_button);
-        if (button != null) {
-            button.setCallback(this);
-        }
-
-        View cancelBtn = findViewById(R.id.cancel_button);
-        if (cancelBtn != null) {
-            cancelBtn.setOnClickListener(view -> {
-                mCallback.reset();
-                mCallback.onCancelClicked();
-            });
-        }
     }
 
     @Override
@@ -185,11 +106,6 @@
     }
 
     @Override
-    public void onEmergencyButtonClickedWhenInCall() {
-        mCallback.reset();
-    }
-
-    @Override
     public boolean onTouchEvent(MotionEvent ev) {
         boolean result = super.onTouchEvent(ev);
         // as long as the user is entering a pattern (i.e sending a touch event that was handled
@@ -217,248 +133,11 @@
     }
 
     @Override
-    public void reset() {
-        // reset lock pattern
-        mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
-                KeyguardUpdateMonitor.getCurrentUser()));
-        mLockPatternView.enableInput();
-        mLockPatternView.setEnabled(true);
-        mLockPatternView.clearPattern();
-
-        if (mSecurityMessageDisplay == null) {
-            return;
-        }
-
-        // if the user is currently locked out, enforce it.
-        long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
-                KeyguardUpdateMonitor.getCurrentUser());
-        if (deadline != 0) {
-            handleAttemptLockout(deadline);
-        } else {
-            displayDefaultSecurityMessage();
-        }
-    }
-
-    private void displayDefaultSecurityMessage() {
-        if (mSecurityMessageDisplay != null) {
-            mSecurityMessageDisplay.setMessage("");
-        }
-    }
-
-    @Override
-    public void showUsabilityHint() {
-    }
-
-    @Override
-    public boolean disallowInterceptTouch(MotionEvent event) {
+    boolean disallowInterceptTouch(MotionEvent event) {
         return !mLockPatternView.isEmpty()
                 || mLockPatternScreenBounds.contains((int) event.getRawX(), (int) event.getRawY());
     }
 
-    /** TODO: hook this up */
-    public void cleanUp() {
-        if (DEBUG) Log.v(TAG, "Cleanup() called on " + this);
-        mLockPatternUtils = null;
-        mLockPatternView.setOnPatternListener(null);
-    }
-
-    private class UnlockPatternListener implements LockPatternView.OnPatternListener {
-
-        @Override
-        public void onPatternStart() {
-            mLockPatternView.removeCallbacks(mCancelPatternRunnable);
-            mSecurityMessageDisplay.setMessage("");
-        }
-
-        @Override
-        public void onPatternCleared() {
-        }
-
-        @Override
-        public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
-            mCallback.userActivity();
-            mCallback.onUserInput();
-        }
-
-        @Override
-        public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
-            mKeyguardUpdateMonitor.setCredentialAttempted();
-            mLockPatternView.disableInput();
-            if (mPendingLockCheck != null) {
-                mPendingLockCheck.cancel(false);
-            }
-
-            final int userId = KeyguardUpdateMonitor.getCurrentUser();
-            if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
-                mLockPatternView.enableInput();
-                onPatternChecked(userId, false, 0, false /* not valid - too short */);
-                return;
-            }
-
-            if (LatencyTracker.isEnabled(mContext)) {
-                LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
-                LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
-            }
-            mPendingLockCheck = LockPatternChecker.checkCredential(
-                    mLockPatternUtils,
-                    LockscreenCredential.createPattern(pattern),
-                    userId,
-                    new LockPatternChecker.OnCheckCallback() {
-
-                        @Override
-                        public void onEarlyMatched() {
-                            if (LatencyTracker.isEnabled(mContext)) {
-                                LatencyTracker.getInstance(mContext).onActionEnd(
-                                        ACTION_CHECK_CREDENTIAL);
-                            }
-                            onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
-                                    true /* isValidPattern */);
-                        }
-
-                        @Override
-                        public void onChecked(boolean matched, int timeoutMs) {
-                            if (LatencyTracker.isEnabled(mContext)) {
-                                LatencyTracker.getInstance(mContext).onActionEnd(
-                                        ACTION_CHECK_CREDENTIAL_UNLOCKED);
-                            }
-                            mLockPatternView.enableInput();
-                            mPendingLockCheck = null;
-                            if (!matched) {
-                                onPatternChecked(userId, false /* matched */, timeoutMs,
-                                        true /* isValidPattern */);
-                            }
-                        }
-
-                        @Override
-                        public void onCancelled() {
-                            // We already got dismissed with the early matched callback, so we
-                            // cancelled the check. However, we still need to note down the latency.
-                            if (LatencyTracker.isEnabled(mContext)) {
-                                LatencyTracker.getInstance(mContext).onActionEnd(
-                                        ACTION_CHECK_CREDENTIAL_UNLOCKED);
-                            }
-                        }
-                    });
-            if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
-                mCallback.userActivity();
-                mCallback.onUserInput();
-            }
-        }
-
-        private void onPatternChecked(int userId, boolean matched, int timeoutMs,
-                boolean isValidPattern) {
-            boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
-            if (matched) {
-                mCallback.reportUnlockAttempt(userId, true, 0);
-                if (dismissKeyguard) {
-                    mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
-                    mCallback.dismiss(true, userId);
-                }
-            } else {
-                mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
-                if (isValidPattern) {
-                    mCallback.reportUnlockAttempt(userId, false, timeoutMs);
-                    if (timeoutMs > 0) {
-                        long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
-                                userId, timeoutMs);
-                        handleAttemptLockout(deadline);
-                    }
-                }
-                if (timeoutMs == 0) {
-                    mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern);
-                    mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
-                }
-            }
-        }
-    }
-
-    private void handleAttemptLockout(long elapsedRealtimeDeadline) {
-        mLockPatternView.clearPattern();
-        mLockPatternView.setEnabled(false);
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-        final long secondsInFuture = (long) Math.ceil(
-                (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
-        mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
-
-            @Override
-            public void onTick(long millisUntilFinished) {
-                final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
-                mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString(
-                                R.plurals.kg_too_many_failed_attempts_countdown,
-                                secondsRemaining, secondsRemaining));
-            }
-
-            @Override
-            public void onFinish() {
-                mLockPatternView.setEnabled(true);
-                displayDefaultSecurityMessage();
-            }
-
-        }.start();
-    }
-
-    @Override
-    public boolean needsInput() {
-        return false;
-    }
-
-    @Override
-    public void onPause() {
-        if (mCountdownTimer != null) {
-            mCountdownTimer.cancel();
-            mCountdownTimer = null;
-        }
-        if (mPendingLockCheck != null) {
-            mPendingLockCheck.cancel(false);
-            mPendingLockCheck = null;
-        }
-        displayDefaultSecurityMessage();
-    }
-
-    @Override
-    public void onResume(int reason) {
-    }
-
-    @Override
-    public KeyguardSecurityCallback getCallback() {
-        return mCallback;
-    }
-
-    @Override
-    public void showPromptReason(int reason) {
-        switch (reason) {
-            case PROMPT_REASON_RESTART:
-                mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_restart_pattern);
-                break;
-            case PROMPT_REASON_TIMEOUT:
-                mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
-                break;
-            case PROMPT_REASON_DEVICE_ADMIN:
-                mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_device_admin);
-                break;
-            case PROMPT_REASON_USER_REQUEST:
-                mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_user_request);
-                break;
-            case PROMPT_REASON_PREPARE_FOR_UPDATE:
-                mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
-                break;
-            case PROMPT_REASON_NONE:
-                break;
-            default:
-                mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
-                break;
-        }
-    }
-
-    @Override
-    public void showMessage(CharSequence message, ColorStateList colorState) {
-        if (colorState != null) {
-            mSecurityMessageDisplay.setNextMessageColor(colorState);
-        }
-        mSecurityMessageDisplay.setMessage(message);
-    }
-
-    @Override
     public void startAppearAnimation() {
         enableClipping(false);
         setAlpha(1f);
@@ -467,12 +146,7 @@
                 0, mAppearAnimationUtils.getInterpolator());
         mAppearAnimationUtils.startAnimation2d(
                 mLockPatternView.getCellStates(),
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        enableClipping(true);
-                    }
-                },
+                () -> enableClipping(true),
                 this);
         if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
             mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
@@ -484,11 +158,9 @@
         }
     }
 
-    @Override
-    public boolean startDisappearAnimation(final Runnable finishRunnable) {
-        float durationMultiplier = mKeyguardUpdateMonitor.needsSlowUnlockTransition()
-                ? DISAPPEAR_MULTIPLIER_LOCKED
-                : 1f;
+    public boolean startDisappearAnimation(boolean needsSlowUnlockTransition,
+            final Runnable finishRunnable) {
+        float durationMultiplier = needsSlowUnlockTransition ? DISAPPEAR_MULTIPLIER_LOCKED : 1f;
         mLockPatternView.clearPattern();
         enableClipping(false);
         setTranslationY(0);
@@ -497,10 +169,8 @@
                 -mDisappearAnimationUtils.getStartTranslation(),
                 mDisappearAnimationUtils.getInterpolator());
 
-        DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor
-                .needsSlowUnlockTransition()
-                        ? mDisappearAnimationUtilsLocked
-                        : mDisappearAnimationUtils;
+        DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
+                        ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils;
         disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
                 () -> {
                     enableClipping(true);
@@ -549,7 +219,7 @@
 
     @Override
     public CharSequence getTitle() {
-        return getContext().getString(
+        return getResources().getString(
                 com.android.internal.R.string.keyguard_accessibility_pattern_unlock);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
new file mode 100644
index 0000000..3db9db7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
+import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
+
+import android.content.res.ColorStateList;
+import android.os.AsyncTask;
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.view.View;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternChecker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockPatternView.Cell;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.keyguard.EmergencyButton.EmergencyButtonCallback;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+
+import java.util.List;
+
+public class KeyguardPatternViewController
+        extends KeyguardInputViewController<KeyguardPatternView> {
+
+    // how many cells the user has to cross before we poke the wakelock
+    private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
+
+    // how long before we clear the wrong pattern
+    private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
+
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final LockPatternUtils mLockPatternUtils;
+    private final LatencyTracker mLatencyTracker;
+    private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
+
+    private KeyguardMessageAreaController mMessageAreaController;
+    private LockPatternView mLockPatternView;
+    private CountDownTimer mCountdownTimer;
+    private AsyncTask<?, ?, ?> mPendingLockCheck;
+
+    private EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() {
+        @Override
+        public void onEmergencyButtonClickedWhenInCall() {
+            getKeyguardSecurityCallback().reset();
+        }
+    };
+
+    /**
+     * Useful for clearing out the wrong pattern after a delay
+     */
+    private Runnable mCancelPatternRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mLockPatternView.clearPattern();
+        }
+    };
+
+    private class UnlockPatternListener implements LockPatternView.OnPatternListener {
+
+        @Override
+        public void onPatternStart() {
+            mLockPatternView.removeCallbacks(mCancelPatternRunnable);
+            mMessageAreaController.setMessage("");
+        }
+
+        @Override
+        public void onPatternCleared() {
+        }
+
+        @Override
+        public void onPatternCellAdded(List<Cell> pattern) {
+            getKeyguardSecurityCallback().userActivity();
+            getKeyguardSecurityCallback().onUserInput();
+        }
+
+        @Override
+        public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
+            mKeyguardUpdateMonitor.setCredentialAttempted();
+            mLockPatternView.disableInput();
+            if (mPendingLockCheck != null) {
+                mPendingLockCheck.cancel(false);
+            }
+
+            final int userId = KeyguardUpdateMonitor.getCurrentUser();
+            if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
+                mLockPatternView.enableInput();
+                onPatternChecked(userId, false, 0, false /* not valid - too short */);
+                return;
+            }
+
+            mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL);
+            mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+            mPendingLockCheck = LockPatternChecker.checkCredential(
+                    mLockPatternUtils,
+                    LockscreenCredential.createPattern(pattern),
+                    userId,
+                    new LockPatternChecker.OnCheckCallback() {
+
+                        @Override
+                        public void onEarlyMatched() {
+                            mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL);
+                            onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
+                                    true /* isValidPattern */);
+                        }
+
+                        @Override
+                        public void onChecked(boolean matched, int timeoutMs) {
+                            mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+                            mLockPatternView.enableInput();
+                            mPendingLockCheck = null;
+                            if (!matched) {
+                                onPatternChecked(userId, false /* matched */, timeoutMs,
+                                        true /* isValidPattern */);
+                            }
+                        }
+
+                        @Override
+                        public void onCancelled() {
+                            // We already got dismissed with the early matched callback, so we
+                            // cancelled the check. However, we still need to note down the latency.
+                            mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+                        }
+                    });
+            if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
+                getKeyguardSecurityCallback().userActivity();
+                getKeyguardSecurityCallback().onUserInput();
+            }
+        }
+
+        private void onPatternChecked(int userId, boolean matched, int timeoutMs,
+                boolean isValidPattern) {
+            boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
+            if (matched) {
+                getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
+                if (dismissKeyguard) {
+                    mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
+                    getKeyguardSecurityCallback().dismiss(true, userId);
+                }
+            } else {
+                mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
+                if (isValidPattern) {
+                    getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs);
+                    if (timeoutMs > 0) {
+                        long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
+                                userId, timeoutMs);
+                        handleAttemptLockout(deadline);
+                    }
+                }
+                if (timeoutMs == 0) {
+                    mMessageAreaController.setMessage(R.string.kg_wrong_pattern);
+                    mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
+                }
+            }
+        }
+    }
+
+    protected KeyguardPatternViewController(KeyguardPatternView view,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SecurityMode securityMode,
+            LockPatternUtils lockPatternUtils,
+            KeyguardSecurityCallback keyguardSecurityCallback,
+            LatencyTracker latencyTracker,
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
+        super(view, securityMode, keyguardSecurityCallback);
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mLockPatternUtils = lockPatternUtils;
+        mLatencyTracker = latencyTracker;
+        mMessageAreaControllerFactory = messageAreaControllerFactory;
+        KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
+        mMessageAreaController = mMessageAreaControllerFactory.create(kma);
+        mLockPatternView = mView.findViewById(R.id.lockPatternView);
+    }
+
+    @Override
+    public void init() {
+        super.init();
+        mMessageAreaController.init();
+    }
+
+    @Override
+    protected void onViewAttached() {
+        mLockPatternView.setOnPatternListener(new UnlockPatternListener());
+        mLockPatternView.setSaveEnabled(false);
+        mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
+                KeyguardUpdateMonitor.getCurrentUser()));
+        // vibrate mode will be the same for the life of this screen
+        mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
+
+        EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
+        if (button != null) {
+            button.setCallback(mEmergencyButtonCallback);
+        }
+
+        View cancelBtn = mView.findViewById(R.id.cancel_button);
+        if (cancelBtn != null) {
+            cancelBtn.setOnClickListener(view -> {
+                getKeyguardSecurityCallback().reset();
+                getKeyguardSecurityCallback().onCancelClicked();
+            });
+        }
+    }
+
+    @Override
+    protected void onViewDetached() {
+        super.onViewDetached();
+        mLockPatternView.setOnPatternListener(null);
+        EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
+        if (button != null) {
+            button.setCallback(null);
+        }
+        View cancelBtn = mView.findViewById(R.id.cancel_button);
+        if (cancelBtn != null) {
+            cancelBtn.setOnClickListener(null);
+        }
+    }
+
+    @Override
+    public void reset() {
+        // reset lock pattern
+        mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
+                KeyguardUpdateMonitor.getCurrentUser()));
+        mLockPatternView.enableInput();
+        mLockPatternView.setEnabled(true);
+        mLockPatternView.clearPattern();
+
+        // if the user is currently locked out, enforce it.
+        long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+                KeyguardUpdateMonitor.getCurrentUser());
+        if (deadline != 0) {
+            handleAttemptLockout(deadline);
+        } else {
+            displayDefaultSecurityMessage();
+        }
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        if (mCountdownTimer != null) {
+            mCountdownTimer.cancel();
+            mCountdownTimer = null;
+        }
+
+        if (mPendingLockCheck != null) {
+            mPendingLockCheck.cancel(false);
+            mPendingLockCheck = null;
+        }
+        displayDefaultSecurityMessage();
+    }
+
+    @Override
+    public boolean needsInput() {
+        return false;
+    }
+
+    @Override
+    public void showPromptReason(int reason) {
+        /// TODO: move all this logic into the MessageAreaController?
+        switch (reason) {
+            case PROMPT_REASON_RESTART:
+                mMessageAreaController.setMessage(R.string.kg_prompt_reason_restart_pattern);
+                break;
+            case PROMPT_REASON_TIMEOUT:
+                mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+                break;
+            case PROMPT_REASON_DEVICE_ADMIN:
+                mMessageAreaController.setMessage(R.string.kg_prompt_reason_device_admin);
+                break;
+            case PROMPT_REASON_USER_REQUEST:
+                mMessageAreaController.setMessage(R.string.kg_prompt_reason_user_request);
+                break;
+            case PROMPT_REASON_PREPARE_FOR_UPDATE:
+                mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+                break;
+            case PROMPT_REASON_NONE:
+                break;
+            default:
+                mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+                break;
+        }
+    }
+
+    @Override
+    public void showMessage(CharSequence message, ColorStateList colorState) {
+        if (colorState != null) {
+            mMessageAreaController.setNextMessageColor(colorState);
+        }
+        mMessageAreaController.setMessage(message);
+    }
+
+    @Override
+    public void startAppearAnimation() {
+        super.startAppearAnimation();
+    }
+
+    @Override
+    public boolean startDisappearAnimation(Runnable finishRunnable) {
+        return mView.startDisappearAnimation(
+                mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
+    }
+
+    private void displayDefaultSecurityMessage() {
+        mMessageAreaController.setMessage("");
+    }
+
+    private void handleAttemptLockout(long elapsedRealtimeDeadline) {
+        mLockPatternView.clearPattern();
+        mLockPatternView.setEnabled(false);
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long secondsInFuture = (long) Math.ceil(
+                (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
+        mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
+
+            @Override
+            public void onTick(long millisUntilFinished) {
+                final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
+                mMessageAreaController.setMessage(mView.getResources().getQuantityString(
+                        R.plurals.kg_too_many_failed_attempts_countdown,
+                        secondsRemaining, secondsRemaining));
+            }
+
+            @Override
+            public void onFinish() {
+                mLockPatternView.setEnabled(true);
+                displayDefaultSecurityMessage();
+            }
+
+        }.start();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index c7f27cf..7fa43116 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -16,11 +16,17 @@
 
 package com.android.keyguard;
 
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
-import android.view.MotionEvent;
 import android.view.View;
 
 import com.android.internal.widget.LockscreenCredential;
@@ -29,22 +35,12 @@
 /**
  * A Pin based Keyguard input view
  */
-public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
-        implements View.OnKeyListener, View.OnTouchListener {
+public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView {
 
     protected PasswordTextView mPasswordEntry;
     private View mOkButton;
     private View mDeleteButton;
-    private View mButton0;
-    private View mButton1;
-    private View mButton2;
-    private View mButton3;
-    private View mButton4;
-    private View mButton5;
-    private View mButton6;
-    private View mButton7;
-    private View mButton8;
-    private View mButton9;
+    private View[] mButtons = new View[10];
 
     public KeyguardPinBasedInputView(Context context) {
         this(context, null);
@@ -62,7 +58,6 @@
 
     @Override
     protected void resetState() {
-        setPasswordEntryEnabled(true);
     }
 
     @Override
@@ -86,10 +81,10 @@
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         if (KeyEvent.isConfirmKey(keyCode)) {
-            performClick(mOkButton);
+            mOkButton.performClick();
             return true;
         } else if (keyCode == KeyEvent.KEYCODE_DEL) {
-            performClick(mDeleteButton);
+            mDeleteButton.performClick();
             return true;
         }
         if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
@@ -125,42 +120,9 @@
         }
     }
 
-    private void performClick(View view) {
-        view.performClick();
-    }
-
     private void performNumberClick(int number) {
-        switch (number) {
-            case 0:
-                performClick(mButton0);
-                break;
-            case 1:
-                performClick(mButton1);
-                break;
-            case 2:
-                performClick(mButton2);
-                break;
-            case 3:
-                performClick(mButton3);
-                break;
-            case 4:
-                performClick(mButton4);
-                break;
-            case 5:
-                performClick(mButton5);
-                break;
-            case 6:
-                performClick(mButton6);
-                break;
-            case 7:
-                performClick(mButton7);
-                break;
-            case 8:
-                performClick(mButton8);
-                break;
-            case 9:
-                performClick(mButton9);
-                break;
+        if (number >= 0 && number <= 9) {
+            mButtons[number].performClick();
         }
     }
 
@@ -177,94 +139,31 @@
     @Override
     protected void onFinishInflate() {
         mPasswordEntry = findViewById(getPasswordTextViewId());
-        mPasswordEntry.setOnKeyListener(this);
 
         // Set selected property on so the view can send accessibility events.
         mPasswordEntry.setSelected(true);
 
-        mPasswordEntry.setUserActivityListener(new PasswordTextView.UserActivityListener() {
-            @Override
-            public void onUserActivity() {
-                onUserInput();
-            }
-        });
-
         mOkButton = findViewById(R.id.key_enter);
-        if (mOkButton != null) {
-            mOkButton.setOnTouchListener(this);
-            mOkButton.setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    if (mPasswordEntry.isEnabled()) {
-                        verifyPasswordAndUnlock();
-                    }
-                }
-            });
-            mOkButton.setOnHoverListener(new LiftToActivateListener(getContext()));
-        }
 
         mDeleteButton = findViewById(R.id.delete_button);
         mDeleteButton.setVisibility(View.VISIBLE);
-        mDeleteButton.setOnTouchListener(this);
-        mDeleteButton.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                // check for time-based lockouts
-                if (mPasswordEntry.isEnabled()) {
-                    mPasswordEntry.deleteLastChar();
-                }
-            }
-        });
-        mDeleteButton.setOnLongClickListener(new View.OnLongClickListener() {
-            @Override
-            public boolean onLongClick(View v) {
-                // check for time-based lockouts
-                if (mPasswordEntry.isEnabled()) {
-                    resetPasswordText(true /* animate */, true /* announce */);
-                }
-                doHapticKeyClick();
-                return true;
-            }
-        });
 
-        mButton0 = findViewById(R.id.key0);
-        mButton1 = findViewById(R.id.key1);
-        mButton2 = findViewById(R.id.key2);
-        mButton3 = findViewById(R.id.key3);
-        mButton4 = findViewById(R.id.key4);
-        mButton5 = findViewById(R.id.key5);
-        mButton6 = findViewById(R.id.key6);
-        mButton7 = findViewById(R.id.key7);
-        mButton8 = findViewById(R.id.key8);
-        mButton9 = findViewById(R.id.key9);
+        mButtons[0] = findViewById(R.id.key0);
+        mButtons[1] = findViewById(R.id.key1);
+        mButtons[2] = findViewById(R.id.key2);
+        mButtons[3] = findViewById(R.id.key3);
+        mButtons[4] = findViewById(R.id.key4);
+        mButtons[5] = findViewById(R.id.key5);
+        mButtons[6] = findViewById(R.id.key6);
+        mButtons[7] = findViewById(R.id.key7);
+        mButtons[8] = findViewById(R.id.key8);
+        mButtons[9] = findViewById(R.id.key9);
 
         mPasswordEntry.requestFocus();
         super.onFinishInflate();
     }
 
     @Override
-    public void onResume(int reason) {
-        super.onResume(reason);
-        mPasswordEntry.requestFocus();
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            doHapticKeyClick();
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onKey(View v, int keyCode, KeyEvent event) {
-        if (event.getAction() == KeyEvent.ACTION_DOWN) {
-            return onKeyDown(keyCode, event);
-        }
-        return false;
-    }
-
-    @Override
     public CharSequence getTitle() {
         return getContext().getString(
                 com.android.internal.R.string.keyguard_accessibility_pin_unlock);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
new file mode 100644
index 0000000..4d0ebff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnKeyListener;
+import android.view.View.OnTouchListener;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+
+public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
+        extends KeyguardAbsKeyInputViewController<T> {
+
+    private final LiftToActivateListener mLiftToActivateListener;
+    protected PasswordTextView mPasswordEntry;
+
+    private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> {
+        if (event.getAction() == KeyEvent.ACTION_DOWN) {
+            return mView.onKeyDown(keyCode, event);
+        }
+        return false;
+    };
+
+    private final OnTouchListener mOnTouchListener = (v, event) -> {
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            mView.doHapticKeyClick();
+        }
+        return false;
+    };
+
+    protected KeyguardPinBasedInputViewController(T view,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SecurityMode securityMode,
+            LockPatternUtils lockPatternUtils,
+            KeyguardSecurityCallback keyguardSecurityCallback,
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+            LatencyTracker latencyTracker,
+            LiftToActivateListener liftToActivateListener) {
+        super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
+                messageAreaControllerFactory, latencyTracker);
+        mLiftToActivateListener = liftToActivateListener;
+        mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
+    }
+
+    @Override
+    protected void onViewAttached() {
+        super.onViewAttached();
+
+        mPasswordEntry.setOnKeyListener(mOnKeyListener);
+        mPasswordEntry.setUserActivityListener(this::onUserInput);
+
+        View deleteButton = mView.findViewById(R.id.delete_button);
+        deleteButton.setOnTouchListener(mOnTouchListener);
+        deleteButton.setOnClickListener(v -> {
+            // check for time-based lockouts
+            if (mPasswordEntry.isEnabled()) {
+                mPasswordEntry.deleteLastChar();
+            }
+        });
+        deleteButton.setOnLongClickListener(v -> {
+            // check for time-based lockouts
+            if (mPasswordEntry.isEnabled()) {
+                mView.resetPasswordText(true /* animate */, true /* announce */);
+            }
+            mView.doHapticKeyClick();
+            return true;
+        });
+
+        View okButton = mView.findViewById(R.id.key_enter);
+        if (okButton != null) {
+            okButton.setOnTouchListener(mOnTouchListener);
+            okButton.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (mPasswordEntry.isEnabled()) {
+                        verifyPasswordAndUnlock();
+                    }
+                }
+            });
+            okButton.setOnHoverListener(mLiftToActivateListener);
+        }
+    }
+
+    @Override
+    public void onResume(int reason) {
+        super.onResume(reason);
+        mPasswordEntry.requestFocus();
+    }
+
+    @Override
+    void resetState() {
+        mView.setPasswordEntryEnabled(true);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
new file mode 100644
index 0000000..6769436
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import android.view.View;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
+
+public class KeyguardPinViewController
+        extends KeyguardPinBasedInputViewController<KeyguardPINView> {
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+    protected KeyguardPinViewController(KeyguardPINView view,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SecurityMode securityMode, LockPatternUtils lockPatternUtils,
+            KeyguardSecurityCallback keyguardSecurityCallback,
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+            LatencyTracker latencyTracker,
+            LiftToActivateListener liftToActivateListener) {
+        super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
+                messageAreaControllerFactory, latencyTracker, liftToActivateListener);
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+    }
+
+    @Override
+    protected void onViewAttached() {
+        super.onViewAttached();
+
+        View cancelBtn = mView.findViewById(R.id.cancel_button);
+        if (cancelBtn != null) {
+            cancelBtn.setOnClickListener(view -> {
+                getKeyguardSecurityCallback().reset();
+                getKeyguardSecurityCallback().onCancelClicked();
+            });
+        }
+    }
+
+    @Override
+    void resetState() {
+        super.resetState();
+        mMessageAreaController.setMessage("");
+    }
+
+    @Override
+    public boolean startDisappearAnimation(Runnable finishRunnable) {
+        return mView.startDisappearAnimation(
+                mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 0517227..4115829 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -21,8 +21,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;
@@ -30,25 +28,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;
@@ -63,42 +50,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;
@@ -107,36 +82,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) {
@@ -188,19 +150,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
@@ -251,52 +216,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;
     }
@@ -318,13 +255,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;
                 }
@@ -372,31 +308,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() {
@@ -441,19 +374,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;
@@ -463,53 +390,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
@@ -546,11 +431,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;
@@ -570,13 +456,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:
@@ -595,7 +481,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:
@@ -614,358 +500,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..64676e5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -16,33 +16,166 @@
 
 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 SecurityCallback mSecurityCallback;
+    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);
+            }
+        }
+    };
 
     @Inject
     KeyguardSecurityContainerController(KeyguardSecurityContainer view,
+            AdminSecondaryLockScreenController.Factory adminSecondaryLockScreenControllerFactory,
             LockPatternUtils lockPatternUtils,
-            KeyguardSecurityViewController.Factory keyguardSecurityViewControllerFactory) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            KeyguardSecurityModel keyguardSecurityModel,
+            MetricsLogger metricsLogger,
+            UiEventLogger uiEventLogger,
+            KeyguardStateController keyguardStateController,
+            KeyguardSecurityViewFlipperController securityViewFlipperController) {
         super(view);
         mLockPatternUtils = lockPatternUtils;
-        view.setLockPatternUtils(mLockPatternUtils);
-        mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
+        mUpdateMonitor = keyguardUpdateMonitor;
+        mSecurityModel = keyguardSecurityModel;
+        mMetricsLogger = metricsLogger;
+        mUiEventLogger = uiEventLogger;
+        mKeyguardStateController = keyguardStateController;
+        mSecurityViewFlipperController = securityViewFlipperController;
+        mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create(
+                mKeyguardSecurityCallback);
+    }
+
+    @Override
+    public void init() {
+        super.init();
+        mSecurityViewFlipperController.init();
     }
 
     @Override
     protected void onViewAttached() {
+        mView.setSwipeListener(mSwipeListener);
     }
 
     @Override
@@ -51,68 +184,270 @@
 
     /** */
     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);
+        mSecurityCallback = 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();
     }
 }
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 ed78c94..832edf7 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -80,7 +80,6 @@
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -280,7 +279,6 @@
     @Inject Lazy<VisualStabilityManager> mVisualStabilityManager;
     @Inject Lazy<NotificationGutsManager> mNotificationGutsManager;
     @Inject Lazy<NotificationMediaManager> mNotificationMediaManager;
-    @Inject Lazy<NotificationBlockingHelperManager> mNotificationBlockingHelperManager;
     @Inject Lazy<NotificationRemoteInputManager> mNotificationRemoteInputManager;
     @Inject Lazy<SmartReplyConstants> mSmartReplyConstants;
     @Inject Lazy<NotificationListener> mNotificationListener;
@@ -473,8 +471,6 @@
                 mNotificationGroupAlertTransferHelper::get);
         mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get);
         mProviders.put(NotificationGutsManager.class, mNotificationGutsManager::get);
-        mProviders.put(NotificationBlockingHelperManager.class,
-                mNotificationBlockingHelperManager::get);
         mProviders.put(NotificationRemoteInputManager.class,
                 mNotificationRemoteInputManager::get);
         mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 494a0f64..7140956 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -48,6 +48,7 @@
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.R;
 import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -65,7 +66,8 @@
     private final Point mDisplaySize = new Point();
     private final int mDisplayId;
     @Surface.Rotation
-    private int mRotation;
+    @VisibleForTesting
+    int mRotation;
     private final Rect mMagnificationFrame = new Rect();
     private final SurfaceControl.Transaction mTransaction;
 
@@ -97,6 +99,7 @@
     private SurfaceView mMirrorSurfaceView;
     private int mMirrorSurfaceMargin;
     private int mBorderDragSize;
+    private int mDragViewSize;
     private int mOuterBorderSize;
     // The boundary of magnification frame.
     private final Rect mMagnificationFrameBoundary = new Rect();
@@ -109,7 +112,7 @@
 
     WindowMagnificationController(Context context, @NonNull Handler handler,
             SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
-            MirrorWindowControl mirrorWindowControl,  SurfaceControl.Transaction transaction,
+            MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction,
             @NonNull WindowMagnifierCallback callback) {
         mContext = context;
         mHandler = handler;
@@ -168,6 +171,8 @@
                 R.dimen.magnification_mirror_surface_margin);
         mBorderDragSize = mResources.getDimensionPixelSize(
                 R.dimen.magnification_border_drag_size);
+        mDragViewSize = mResources.getDimensionPixelSize(
+                R.dimen.magnification_drag_view_size);
         mOuterBorderSize = mResources.getDimensionPixelSize(
                 R.dimen.magnification_outer_border_margin);
     }
@@ -203,13 +208,12 @@
      * @param configDiff a bit mask of the differences between the configurations
      */
     void onConfigurationChanged(int configDiff) {
-        if (!isWindowVisible()) {
-            return;
-        }
         if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) {
             updateDimensions();
-            mWm.removeView(mMirrorView);
-            createMirrorWindow();
+            if (isWindowVisible()) {
+                mWm.removeView(mMirrorView);
+                createMirrorWindow();
+            }
         } else if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
             onRotate();
         }
@@ -217,16 +221,20 @@
 
     /** Handles MirrorWindow position when the device rotation changed. */
     private void onRotate() {
-        Display display = mContext.getDisplay();
+        final Display display = mContext.getDisplay();
+        final int oldRotation = mRotation;
         display.getRealSize(mDisplaySize);
         setMagnificationFrameBoundary();
+        mRotation = display.getRotation();
 
+        if (!isWindowVisible()) {
+            return;
+        }
         // Keep MirrorWindow position on the screen unchanged when device rotates 90°
         // clockwise or anti-clockwise.
-        final int rotationDegree = getDegreeFromRotation(display.getRotation(), mRotation);
+        final int rotationDegree = getDegreeFromRotation(mRotation, oldRotation);
         final Matrix matrix = new Matrix();
         matrix.setRotate(rotationDegree);
-        mRotation = display.getRotation();
         if (rotationDegree == 90) {
             matrix.postTranslate(mDisplaySize.x, 0);
         } else if (rotationDegree == 270) {
@@ -307,6 +315,10 @@
         Region regionInsideDragBorder = new Region(mBorderDragSize, mBorderDragSize,
                 mMirrorView.getWidth() - mBorderDragSize,
                 mMirrorView.getHeight() - mBorderDragSize);
+        Rect dragArea = new Rect(mMirrorView.getWidth() - mDragViewSize - mBorderDragSize,
+                mMirrorView.getHeight() - mDragViewSize - mBorderDragSize,
+                mMirrorView.getWidth(), mMirrorView.getHeight());
+        regionInsideDragBorder.op(dragArea, Region.Op.DIFFERENCE);
         return regionInsideDragBorder;
     }
 
@@ -555,6 +567,7 @@
 
     /**
      * Gets the scale.
+     *
      * @return {@link Float#NaN} if the window is invisible.
      */
     float getScale() {
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index a3339f6..0fd4765 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -16,8 +16,13 @@
 
 package com.android.systemui.appops;
 
+import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED;
+
 import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.location.LocationManager;
 import android.media.AudioManager;
@@ -34,6 +39,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dump.DumpManager;
@@ -54,7 +60,7 @@
  * NotificationPresenter to be displayed to the user.
  */
 @SysUISingleton
-public class AppOpsControllerImpl implements AppOpsController,
+public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController,
         AppOpsManager.OnOpActiveChangedInternalListener,
         AppOpsManager.OnOpNotedListener, Dumpable {
 
@@ -65,6 +71,7 @@
     private static final String TAG = "AppOpsControllerImpl";
     private static final boolean DEBUG = false;
 
+    private final BroadcastDispatcher mDispatcher;
     private final AppOpsManager mAppOps;
     private final AudioManager mAudioManager;
     private final LocationManager mLocationManager;
@@ -79,6 +86,7 @@
     private final SparseArray<Set<Callback>> mCallbacksByCode = new SparseArray<>();
     private final PermissionFlagsCache mFlagsCache;
     private boolean mListening;
+    private boolean mMicMuted;
 
     @GuardedBy("mActiveItems")
     private final List<AppOpItem> mActiveItems = new ArrayList<>();
@@ -105,8 +113,10 @@
             @Background Looper bgLooper,
             DumpManager dumpManager,
             PermissionFlagsCache cache,
-            AudioManager audioManager
+            AudioManager audioManager,
+            BroadcastDispatcher dispatcher
     ) {
+        mDispatcher = dispatcher;
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mFlagsCache = cache;
         mBGHandler = new H(bgLooper);
@@ -115,6 +125,7 @@
             mCallbacksByCode.put(OPS[i], new ArraySet<>());
         }
         mAudioManager = audioManager;
+        mMicMuted = audioManager.isMicrophoneMute();
         mLocationManager = context.getSystemService(LocationManager.class);
         dumpManager.registerDumpable(TAG, this);
     }
@@ -133,6 +144,8 @@
             mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
             mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged(
                     mAudioManager.getActiveRecordingConfigurations()));
+            mDispatcher.registerReceiverWithHandler(this,
+                    new IntentFilter(ACTION_MICROPHONE_MUTE_CHANGED), mBGHandler);
 
         } else {
             mAppOps.stopWatchingActive(this);
@@ -140,6 +153,7 @@
             mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback);
 
             mBGHandler.removeCallbacksAndMessages(null); // null removes all
+            mDispatcher.unregisterReceiver(this);
             synchronized (mActiveItems) {
                 mActiveItems.clear();
                 mRecordingsByUid.clear();
@@ -468,6 +482,9 @@
     }
 
     private boolean isAnyRecordingPausedLocked(int uid) {
+        if (mMicMuted) {
+            return true;
+        }
         List<AudioRecordingConfiguration> configs = mRecordingsByUid.get(uid);
         if (configs == null) return false;
         int configsNum = configs.size();
@@ -522,6 +539,12 @@
         }
     };
 
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        mMicMuted = mAudioManager.isMicrophoneMute();
+        updateRecordingPausedStatus();
+    }
+
     protected class H extends Handler {
         H(Looper looper) {
             super(looper);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 06c190f..ba78485 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -16,17 +16,16 @@
 
 package com.android.systemui.biometrics;
 
-import android.annotation.NonNull;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
 import android.annotation.SuppressLint;
-import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.Handler;
-import android.os.Looper;
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -38,10 +37,16 @@
 import android.view.MotionEvent;
 import android.view.WindowManager;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.BrightnessSynchronizer;
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.settings.SystemSettings;
 
 import java.io.FileWriter;
 import java.io.IOException;
@@ -60,8 +65,8 @@
 
     private final FingerprintManager mFingerprintManager;
     private final WindowManager mWindowManager;
-    private final ContentResolver mContentResolver;
-    private final Handler mHandler;
+    private final SystemSettings mSystemSettings;
+    private final DelayableExecutor mFgExecutor;
     private final WindowManager.LayoutParams mLayoutParams;
     private final UdfpsView mView;
     // Debugfs path to control the high-brightness mode.
@@ -88,7 +93,7 @@
     // interrupt is being tracked and a timeout is used as a last resort to turn off high brightness
     // mode.
     private boolean mIsAodInterruptActive;
-    private final Runnable mAodInterruptTimeoutAction = this::onCancelAodInterrupt;
+    @Nullable private Runnable mCancelAodTimeoutAction;
 
     public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
         @Override
@@ -138,18 +143,27 @@
 
     @Inject
     UdfpsController(@NonNull Context context,
-            @NonNull StatusBarStateController statusBarStateController) {
-        mFingerprintManager = context.getSystemService(FingerprintManager.class);
-        mWindowManager = context.getSystemService(WindowManager.class);
-        mContentResolver = context.getContentResolver();
-        mHandler = new Handler(Looper.getMainLooper());
+            @Main Resources resources,
+            LayoutInflater inflater,
+            @Nullable FingerprintManager fingerprintManager,
+            PowerManager powerManager,
+            WindowManager windowManager,
+            SystemSettings systemSettings,
+            @NonNull StatusBarStateController statusBarStateController,
+            @Main DelayableExecutor fgExecutor) {
+        // The fingerprint manager is queried for UDFPS before this class is constructed, so the
+        // fingerprint manager should never be null.
+        mFingerprintManager = checkNotNull(fingerprintManager);
+        mWindowManager = windowManager;
+        mSystemSettings = systemSettings;
+        mFgExecutor = fgExecutor;
         mLayoutParams = createLayoutParams(context);
 
-        mView = (UdfpsView) LayoutInflater.from(context).inflate(R.layout.udfps_view, null, false);
+        mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false);
 
-        mHbmPath = context.getResources().getString(R.string.udfps_hbm_sysfs_path);
-        mHbmEnableCommand = context.getResources().getString(R.string.udfps_hbm_enable_command);
-        mHbmDisableCommand = context.getResources().getString(R.string.udfps_hbm_disable_command);
+        mHbmPath = resources.getString(R.string.udfps_hbm_sysfs_path);
+        mHbmEnableCommand = resources.getString(R.string.udfps_hbm_enable_command);
+        mHbmDisableCommand = resources.getString(R.string.udfps_hbm_disable_command);
 
         mHbmSupported = !TextUtils.isEmpty(mHbmPath);
         mView.setHbmSupported(mHbmSupported);
@@ -157,11 +171,11 @@
 
         // This range only consists of the minimum and maximum values, which only cover
         // non-high-brightness mode.
-        float[] nitsRange = toFloatArray(context.getResources().obtainTypedArray(
+        float[] nitsRange = toFloatArray(resources.obtainTypedArray(
                 com.android.internal.R.array.config_screenBrightnessNits));
 
         // The last value of this range corresponds to the high-brightness mode.
-        float[] nitsAutoBrightnessValues = toFloatArray(context.getResources().obtainTypedArray(
+        float[] nitsAutoBrightnessValues = toFloatArray(resources.obtainTypedArray(
                 com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
 
         mHbmNits = nitsAutoBrightnessValues[nitsAutoBrightnessValues.length - 1];
@@ -170,12 +184,12 @@
         // This range only consists of the minimum and maximum backlight values, which only apply
         // in non-high-brightness mode.
         float[] normalizedBacklightRange = normalizeBacklightRange(
-                context.getResources().getIntArray(
+                resources.getIntArray(
                         com.android.internal.R.array.config_screenBrightnessBacklight));
 
         mBacklightToNitsSpline = Spline.createSpline(normalizedBacklightRange, nitsRange);
         mNitsToHbmBacklightSpline = Spline.createSpline(hbmNitsRange, normalizedBacklightRange);
-        mDefaultBrightness = obtainDefaultBrightness(context);
+        mDefaultBrightness = obtainDefaultBrightness(powerManager);
 
         // TODO(b/160025856): move to the "dump" method.
         Log.v(TAG, String.format("ctor | mNitsRange: [%f, %f]", nitsRange[0], nitsRange[1]));
@@ -194,7 +208,7 @@
     }
 
     private void showUdfpsOverlay() {
-        mHandler.post(() -> {
+        mFgExecutor.execute(() -> {
             if (!mIsOverlayShowing) {
                 try {
                     Log.v(TAG, "showUdfpsOverlay | adding window");
@@ -211,7 +225,7 @@
     }
 
     private void hideUdfpsOverlay() {
-        mHandler.post(() -> {
+        mFgExecutor.execute(() -> {
             if (mIsOverlayShowing) {
                 Log.v(TAG, "hideUdfpsOverlay | removing window");
                 mView.setOnTouchListener(null);
@@ -228,7 +242,7 @@
     // Returns a value in the range of [0, 255].
     private int computeScrimOpacity() {
         // Backlight setting can be NaN, -1.0f, and [0.0f, 1.0f].
-        float backlightSetting = Settings.System.getFloatForUser(mContentResolver,
+        float backlightSetting = mSystemSettings.getFloatForUser(
                 Settings.System.SCREEN_BRIGHTNESS_FLOAT, mDefaultBrightness,
                 UserHandle.USER_CURRENT);
 
@@ -265,7 +279,8 @@
         // Since the sensor that triggers the AOD interrupt doesn't provide ACTION_UP/ACTION_CANCEL,
         // we need to be careful about not letting the screen accidentally remain in high brightness
         // mode. As a mitigation, queue a call to cancel the fingerprint scan.
-        mHandler.postDelayed(mAodInterruptTimeoutAction, AOD_INTERRUPT_TIMEOUT_MILLIS);
+        mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelAodInterrupt,
+                AOD_INTERRUPT_TIMEOUT_MILLIS);
         // using a hard-coded value for major and minor until it is available from the sensor
         onFingerDown(screenX, screenY, 13.0f, 13.0f);
     }
@@ -280,7 +295,10 @@
         if (!mIsAodInterruptActive) {
             return;
         }
-        mHandler.removeCallbacks(mAodInterruptTimeoutAction);
+        if (mCancelAodTimeoutAction != null) {
+            mCancelAodTimeoutAction.run();
+            mCancelAodTimeoutAction = null;
+        }
         mIsAodInterruptActive = false;
         onFingerUp();
     }
@@ -338,8 +356,7 @@
         return lp;
     }
 
-    private static float obtainDefaultBrightness(Context context) {
-        PowerManager powerManager = context.getSystemService(PowerManager.class);
+    private static float obtainDefaultBrightness(PowerManager powerManager) {
         if (powerManager == null) {
             Log.e(TAG, "PowerManager is unavailable. Can't obtain default brightness.");
             return 0f;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java
index 2d90c86..ea612af 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java
@@ -33,7 +33,7 @@
      * @param e UI event
      */
     public void log(Bubble b, UiEventEnum e) {
-        super.log(e, b.getUser().getIdentifier(), b.getPackageName());
+        logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId());
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 38e12a6..eb43127 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -288,6 +288,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/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 1ef806c..d5820f3 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -432,8 +432,12 @@
         /** Give the Part a chance to clean itself up. */
         default void destroy() {}
 
-        /** Alerts that the screenstate is being changed. */
-        default void onScreenState(int state) {}
+        /**
+         *  Alerts that the screenstate is being changed.
+         *  Note: This may be called from within a call to transitionTo, so local DozeState may not
+         *  be accurate nor match with the new displayState.
+         */
+        default void onScreenState(int displayState) {}
 
         /** Sets the {@link DozeMachine} when this Part is associated with one. */
         default void setDozeMachine(DozeMachine dozeMachine) {}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index b872b1a..5aeb8df 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -73,7 +73,6 @@
      * --ei brightness_bucket 1}
      */
     private int mDebugBrightnessBucket = -1;
-    private DozeMachine.State mState;
 
     @Inject
     public DozeScreenBrightness(Context context, @WrappedService DozeMachine.Service service,
@@ -94,7 +93,6 @@
 
     @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
-        mState = newState;
         switch (newState) {
             case INITIALIZED:
             case DOZE:
@@ -112,10 +110,7 @@
 
     @Override
     public void onScreenState(int state) {
-        if (!mScreenOff
-                && (mState == DozeMachine.State.DOZE_AOD
-                     || mState == DozeMachine.State.DOZE_AOD_DOCKED)
-                && (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND)) {
+        if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) {
             setLightSensorEnabled(true);
         } else {
             setLightSensorEnabled(false);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 781bf8d..f8f4f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -43,7 +43,7 @@
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.android.settingslib.R;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
index c704d32..65d4e23 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
@@ -17,15 +17,12 @@
 package com.android.systemui.navigationbar;
 
 import static android.content.Intent.ACTION_OVERLAY_CHANGED;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.om.IOverlayManager;
-import android.content.om.OverlayInfo;
 import android.content.pm.PackageManager;
 import android.content.res.ApkAssets;
 import android.os.PatternMatcher;
@@ -131,9 +128,6 @@
     public void updateCurrentInteractionMode(boolean notify) {
         mCurrentUserContext = getCurrentUserContext();
         int mode = getCurrentInteractionMode(mCurrentUserContext);
-        if (mode == NAV_BAR_MODE_GESTURAL) {
-            switchToDefaultGestureNavOverlayIfNecessary();
-        }
         mUiBgExecutor.execute(() ->
             Settings.Secure.putString(mCurrentUserContext.getContentResolver(),
                     Secure.NAVIGATION_MODE, String.valueOf(mode)));
@@ -187,35 +181,6 @@
         }
     }
 
-    private void switchToDefaultGestureNavOverlayIfNecessary() {
-        final int userId = mCurrentUserContext.getUserId();
-        try {
-            final IOverlayManager om = IOverlayManager.Stub.asInterface(
-                    ServiceManager.getService(Context.OVERLAY_SERVICE));
-            final OverlayInfo info = om.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, userId);
-            if (info != null && !info.isEnabled()) {
-                // Enable the default gesture nav overlay, and move the back gesture inset scale to
-                // Settings.Secure for left and right sensitivity.
-                final int curInset = mCurrentUserContext.getResources().getDimensionPixelSize(
-                        com.android.internal.R.dimen.config_backGestureInset);
-                om.setEnabledExclusiveInCategory(NAV_BAR_MODE_GESTURAL_OVERLAY, userId);
-                final int defInset = mCurrentUserContext.getResources().getDimensionPixelSize(
-                        com.android.internal.R.dimen.config_backGestureInset);
-
-                final float scale = defInset == 0 ? 1.0f : ((float) curInset) / defInset;
-                Settings.Secure.putFloat(mCurrentUserContext.getContentResolver(),
-                        Secure.BACK_GESTURE_INSET_SCALE_LEFT, scale);
-                Settings.Secure.putFloat(mCurrentUserContext.getContentResolver(),
-                        Secure.BACK_GESTURE_INSET_SCALE_RIGHT, scale);
-                if (DEBUG) {
-                    Log.v(TAG, "Moved back sensitivity for user " + userId + " to scale " + scale);
-                }
-            }
-        } catch (SecurityException | IllegalStateException | RemoteException e) {
-            Log.e(TAG, "Failed to switch to default gesture nav overlay for user " + userId);
-        }
-    }
-
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("NavigationModeController:");
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java
index 4f225e2..5bb1794 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java
@@ -280,10 +280,25 @@
 
         PackageManager pm = context.getPackageManager();
         boolean supportsPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
-        if (!supportsPip) {
+        if (supportsPip) {
+            initController(context, broadcastDispatcher, configController, deviceConfig,
+                    displayController, floatingContentCoordinator, sysUiState, pipBoundsHandler,
+                    pipSurfaceTransactionHelper, pipTaskOrganizer, pipUiEventLogger);
+        } else {
             Log.w(TAG, "Device not support PIP feature");
-            return;
         }
+    }
+
+    private void initController(Context context, BroadcastDispatcher broadcastDispatcher,
+            ConfigurationController configController,
+            DeviceConfigProxy deviceConfig,
+            DisplayController displayController,
+            FloatingContentCoordinator floatingContentCoordinator,
+            SysUiState sysUiState,
+            PipBoundsHandler pipBoundsHandler,
+            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+            PipTaskOrganizer pipTaskOrganizer,
+            PipUiEventLogger pipUiEventLogger) {
 
         // Ensure that we are the primary user's SystemUI.
         final int processUser = UserManager.get(context).getUserHandle();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index db77e08..73c6504 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -80,8 +80,6 @@
         mFinished = false;
         // Enqueue jobs to fetch every system tile and then ever package tile.
         addCurrentAndStockTiles(host);
-
-        addPackageTiles(host);
     }
 
     public boolean isFinished() {
@@ -122,23 +120,86 @@
                 tile.destroy();
                 continue;
             }
-            tile.setListening(this, true);
-            tile.refreshState();
-            tile.setListening(this, false);
             tile.setTileSpec(spec);
             tilesToAdd.add(tile);
         }
 
-        mBgExecutor.execute(() -> {
-            for (QSTile tile : tilesToAdd) {
-                final QSTile.State state = tile.getState().copy();
-                // Ignore the current state and get the generic label instead.
-                state.label = tile.getTileLabel();
-                tile.destroy();
-                addTile(tile.getTileSpec(), null, state, true);
+        new TileCollector(tilesToAdd, host).startListening();
+    }
+
+    private static class TilePair {
+        QSTile mTile;
+        boolean mReady = false;
+    }
+
+    private class TileCollector implements QSTile.Callback {
+
+        private final List<TilePair> mQSTileList = new ArrayList<>();
+        private final QSTileHost mQSTileHost;
+
+        TileCollector(List<QSTile> tilesToAdd, QSTileHost host) {
+            for (QSTile tile: tilesToAdd) {
+                TilePair pair = new TilePair();
+                pair.mTile = tile;
+                mQSTileList.add(pair);
             }
+            mQSTileHost = host;
+            if (tilesToAdd.isEmpty()) {
+                mBgExecutor.execute(this::finished);
+            }
+        }
+
+        private void finished() {
             notifyTilesChanged(false);
-        });
+            addPackageTiles(mQSTileHost);
+        }
+
+        private void startListening() {
+            for (TilePair pair: mQSTileList) {
+                pair.mTile.addCallback(this);
+                pair.mTile.setListening(this, true);
+                // Make sure that at least one refresh state happens
+                pair.mTile.refreshState();
+            }
+        }
+
+        // This is called in the Bg thread
+        @Override
+        public void onStateChanged(State s) {
+            boolean allReady = true;
+            for (TilePair pair: mQSTileList) {
+                if (!pair.mReady && pair.mTile.isTileReady()) {
+                    pair.mTile.removeCallback(this);
+                    pair.mTile.setListening(this, false);
+                    pair.mReady = true;
+                } else if (!pair.mReady) {
+                    allReady = false;
+                }
+            }
+            if (allReady) {
+                for (TilePair pair : mQSTileList) {
+                    QSTile tile = pair.mTile;
+                    final QSTile.State state = tile.getState().copy();
+                    // Ignore the current state and get the generic label instead.
+                    state.label = tile.getTileLabel();
+                    tile.destroy();
+                    addTile(tile.getTileSpec(), null, state, true);
+                }
+                finished();
+            }
+        }
+
+        @Override
+        public void onShowDetail(boolean show) {}
+
+        @Override
+        public void onToggleStateChanged(boolean state) {}
+
+        @Override
+        public void onScanStateChanged(boolean state) {}
+
+        @Override
+        public void onAnnouncementRequested(CharSequence announcement) {}
     }
 
     private void addPackageTiles(final QSTileHost host) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 255513a..dfd7e2c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -90,6 +90,10 @@
     private static final long DEFAULT_STALE_TIMEOUT = 10 * DateUtils.MINUTE_IN_MILLIS;
     protected static final Object ARG_SHOW_TRANSIENT_ENABLING = new Object();
 
+    private static final int READY_STATE_NOT_READY = 0;
+    private static final int READY_STATE_READYING = 1;
+    private static final int READY_STATE_READY = 2;
+
     protected final QSHost mHost;
     protected final Context mContext;
     // @NonFinalForTesting
@@ -101,6 +105,7 @@
     protected final ActivityStarter mActivityStarter;
     private final UiEventLogger mUiEventLogger;
     private final QSLogger mQSLogger;
+    private volatile int mReadyState;
 
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
     private final Object mStaleListener = new Object();
@@ -386,7 +391,11 @@
 
     protected void handleRefreshState(Object arg) {
         handleUpdateState(mTmpState, arg);
-        final boolean changed = mTmpState.copyTo(mState);
+        boolean changed = mTmpState.copyTo(mState);
+        if (mReadyState == READY_STATE_READYING) {
+            mReadyState = READY_STATE_READY;
+            changed = true;
+        }
         if (changed) {
             mQSLogger.logTileUpdated(mTileSpec, mState);
             handleStateChanged();
@@ -459,6 +468,9 @@
                     // should not refresh it anymore.
                     if (mLifecycle.getCurrentState().equals(DESTROYED)) return;
                     mLifecycle.setCurrentState(RESUMED);
+                    if (mReadyState == READY_STATE_NOT_READY) {
+                        mReadyState = READY_STATE_READYING;
+                    }
                     refreshState(); // Ensure we get at least one refresh after listening.
                 });
             }
@@ -531,6 +543,15 @@
      */
     public abstract CharSequence getTileLabel();
 
+    /**
+     * @return {@code true} if the tile has refreshed state at least once after having set its
+     *         lifecycle to {@link Lifecycle.State#RESUMED}.
+     */
+    @Override
+    public boolean isTileReady() {
+        return mReadyState == READY_STATE_READY;
+    }
+
     public static int getColorForState(Context context, int state) {
         switch (state) {
             case Tile.STATE_UNAVAILABLE:
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 2327063..01333f0 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
@@ -23,7 +23,6 @@
 import android.os.Handler;
 import android.view.accessibility.AccessibilityManager;
 
-import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.R;
@@ -68,7 +67,6 @@
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl;
 import com.android.systemui.statusbar.notification.row.ChannelEditorDialogController;
-import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.notification.row.PriorityOnboardingDialogController;
@@ -198,20 +196,6 @@
         return new NotificationPanelLoggerImpl();
     }
 
-    /** Provides an instance of {@link NotificationBlockingHelperManager} */
-    @SysUISingleton
-    @Provides
-    static NotificationBlockingHelperManager provideNotificationBlockingHelperManager(
-            Context context,
-            NotificationGutsManager notificationGutsManager,
-            NotificationEntryManager notificationEntryManager,
-            MetricsLogger metricsLogger,
-            GroupMembershipManager groupMembershipManager) {
-        return new NotificationBlockingHelperManager(
-                context, notificationGutsManager, notificationEntryManager, metricsLogger,
-                groupMembershipManager);
-    }
-
     /** Provides an instance of {@link GroupMembershipManager} */
     @SysUISingleton
     @Provides
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 adda049..811a72d 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
@@ -500,10 +500,6 @@
      * or is in a whitelist).
      */
     public boolean getIsNonblockable() {
-        boolean isNonblockable = Dependency.get(NotificationBlockingHelperManager.class)
-                .isNonblockable(mEntry.getSbn().getPackageName(),
-                        mEntry.getChannel().getId());
-
         // If the SystemNotifAsyncTask hasn't finished running or retrieved a value, we'll try once
         // again, but in-place on the main thread this time. This should rarely ever get called.
         if (mEntry != null && mEntry.mIsSystemNotification == null) {
@@ -514,13 +510,12 @@
             mEntry.mIsSystemNotification = isSystemNotification(mContext, mEntry.getSbn());
         }
 
-        isNonblockable |= mEntry.getChannel().isImportanceLockedByOEM();
-        isNonblockable |= mEntry.getChannel().isImportanceLockedByCriticalDeviceFunction();
+        boolean isNonblockable = mEntry.getChannel().isImportanceLockedByOEM()
+                || mEntry.getChannel().isImportanceLockedByCriticalDeviceFunction();
 
         if (!isNonblockable && mEntry != null && mEntry.mIsSystemNotification != null) {
             if (mEntry.mIsSystemNotification) {
-                if (mEntry.getChannel() != null
-                        && !mEntry.getChannel().isBlockable()) {
+                if (mEntry.getChannel() != null && !mEntry.getChannel().isBlockable()) {
                     isNonblockable = true;
                 }
             }
@@ -1398,26 +1393,12 @@
     }
 
     /**
-     * Dismisses the notification with the option of showing the blocking helper in-place if we have
-     * a negative user sentiment.
+     * Dismisses the notification.
      *
      * @param fromAccessibility whether this dismiss is coming from an accessibility action
-     * @return whether a blocking helper is shown in this row
      */
-    public boolean performDismissWithBlockingHelper(boolean fromAccessibility) {
-        NotificationBlockingHelperManager manager =
-                Dependency.get(NotificationBlockingHelperManager.class);
-        boolean isBlockingHelperShown = manager.perhapsShowBlockingHelper(this, mMenuRow);
-
-        Dependency.get(MetricsLogger.class).count(NotificationCounters.NOTIFICATION_DISMISSED, 1);
-
-        // Continue with dismiss since we don't want the blocking helper to be directly associated
-        // with a certain notification.
-        performDismiss(fromAccessibility);
-        return isBlockingHelperShown;
-    }
-
     public void performDismiss(boolean fromAccessibility) {
+        Dependency.get(MetricsLogger.class).count(NotificationCounters.NOTIFICATION_DISMISSED, 1);
         dismiss(fromAccessibility);
         if (mEntry.isClearable()) {
             if (mOnUserInteractionCallback != null) {
@@ -2983,7 +2964,7 @@
         }
         switch (action) {
             case AccessibilityNodeInfo.ACTION_DISMISS:
-                performDismissWithBlockingHelper(true /* fromAccessibility */);
+                performDismiss(true /* fromAccessibility */);
                 return true;
             case AccessibilityNodeInfo.ACTION_COLLAPSE:
             case AccessibilityNodeInfo.ACTION_EXPAND:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index eeac46a..7071b73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -295,10 +295,6 @@
      */
     private void closeControls(int x, int y, boolean save, boolean force) {
         // First try to dismiss any blocking helper.
-        boolean wasBlockingHelperDismissed =
-                Dependency.get(NotificationBlockingHelperManager.class)
-                        .dismissCurrentBlockingHelper();
-
         if (getWindowToken() == null) {
             if (mClosedListener != null) {
                 mClosedListener.onGutsClosed(this);
@@ -307,10 +303,9 @@
         }
 
         if (mGutsContent == null
-                || !mGutsContent.handleCloseControls(save, force)
-                || wasBlockingHelperDismissed) {
+                || !mGutsContent.handleCloseControls(save, force)) {
             // We only want to do a circular reveal if we're not showing the blocking helper.
-            animateClose(x, y, !wasBlockingHelperDismissed /* shouldDoCircularReveal */);
+            animateClose(x, y, true /* shouldDoCircularReveal */);
 
             setExposed(false, mNeedsFalsingProtection);
             if (mClosedListener != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 7a12464..500de2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -106,7 +106,6 @@
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.FooterView;
 import com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView;
-import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
@@ -449,12 +448,11 @@
     private DismissListener mDismissListener;
     private DismissAllAnimationListener mDismissAllAnimationListener;
     private NotificationRemoteInputManager mRemoteInputManager;
+    private ShadeController mShadeController;
 
     private final DisplayMetrics mDisplayMetrics = Dependency.get(DisplayMetrics.class);
     private final LockscreenGestureLogger mLockscreenGestureLogger =
             Dependency.get(LockscreenGestureLogger.class);
-    private final VisualStabilityManager mVisualStabilityManager =
-            Dependency.get(VisualStabilityManager.class);
     protected boolean mClearAllEnabled;
 
     private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
@@ -553,13 +551,6 @@
                 res.getBoolean(R.bool.config_drawNotificationBackground);
         setOutlineProvider(mOutlineProvider);
 
-        // Blocking helper manager wants to know the expanded state, update as well.
-        NotificationBlockingHelperManager blockingHelperManager =
-                Dependency.get(NotificationBlockingHelperManager.class);
-        addOnExpandedHeightChangedListener((height, unused) -> {
-            blockingHelperManager.setNotificationShadeExpanded(height);
-        });
-
         boolean willDraw = mShouldDrawNotificationBackground || DEBUG;
         setWillNotDraw(!willDraw);
         mBackgroundPaint.setAntiAlias(true);
@@ -580,9 +571,7 @@
         if (mFgsSectionView != null) {
             return;
         }
-
         mFgsSectionView = fgsSectionView;
-
         addView(mFgsSectionView, -1);
     }
 
@@ -603,7 +592,6 @@
 
         inflateEmptyShadeView();
         inflateFooterView();
-        mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation);
     }
 
     /**
@@ -1021,23 +1009,6 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
-    public boolean isInVisibleLocation(NotificationEntry entry) {
-        ExpandableNotificationRow row = entry.getRow();
-        ExpandableViewState childViewState = row.getViewState();
-
-        if (childViewState == null) {
-            return false;
-        }
-        if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
-            return false;
-        }
-        if (row.getVisibility() != View.VISIBLE) {
-            return false;
-        }
-        return true;
-    }
-
-    @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
     private void setMaxLayoutHeight(int maxLayoutHeight) {
         mMaxLayoutHeight = maxLayoutHeight;
         mShelf.setMaxLayoutHeight(maxLayoutHeight);
@@ -5371,9 +5342,8 @@
         }
 
         if (viewsToRemove.isEmpty()) {
-            if (closeShade) {
-                Dependency.get(ShadeController.class).animateCollapsePanels(
-                        CommandQueue.FLAG_EXCLUDE_NONE);
+            if (closeShade && mShadeController != null) {
+                mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
             }
             return;
         }
@@ -5409,11 +5379,11 @@
 
         final Runnable onSlideAwayAnimationComplete = () -> {
             if (closeShade) {
-                Dependency.get(ShadeController.class).addPostCollapseAction(() -> {
+                mShadeController.addPostCollapseAction(() -> {
                     setDismissAllInProgress(false);
                     onAnimationComplete.run();
                 });
-                Dependency.get(ShadeController.class).animateCollapsePanels(
+                mShadeController.animateCollapsePanels(
                         CommandQueue.FLAG_EXCLUDE_NONE);
             } else {
                 setDismissAllInProgress(false);
@@ -5697,6 +5667,10 @@
         mRemoteInputManager = remoteInputManager;
     }
 
+    void setShadeController(ShadeController shadeController) {
+        mShadeController = shadeController;
+    }
+
     /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
@@ -6096,7 +6070,7 @@
 
                 if (!mAmbientState.isDozing() || startingChild != null) {
                     // We have notifications, go to locked shade.
-                    Dependency.get(ShadeController.class).goToLockedShade(startingChild);
+                    mShadeController.goToLockedShade(startingChild);
                     if (startingChild instanceof ExpandableNotificationRow) {
                         ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild;
                         row.onExpandedByGesture(true /* drag down is always an open */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 231ff2c..703c214 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -21,11 +21,11 @@
 
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.SelectedRows;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.canChildBeDismissed;
 import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
@@ -85,6 +85,7 @@
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
@@ -104,6 +105,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -139,7 +141,6 @@
     private final ZenModeController mZenModeController;
     private final MetricsLogger mMetricsLogger;
     private final FalsingManager mFalsingManager;
-    private final NotificationSectionsManager mNotificationSectionsManager;
     private final Resources mResources;
     private final NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder;
     private final ScrimController mScrimController;
@@ -153,6 +154,8 @@
     private final ForegroundServiceSectionController mFgServicesSectionController;
     private final LayoutInflater mLayoutInflater;
     private final NotificationRemoteInputManager mRemoteInputManager;
+    private final VisualStabilityManager mVisualStabilityManager;
+    private final ShadeController mShadeController;
     private final KeyguardMediaController mKeyguardMediaController;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final KeyguardBypassController mKeyguardBypassController;
@@ -389,8 +392,6 @@
                         return;
                     }
 
-                    boolean isBlockingHelperShown = false;
-
                     mView.removeDraggedView(view);
                     mView.updateContinuousShadowDrawing();
 
@@ -400,13 +401,10 @@
                             mHeadsUpManager.addSwipedOutNotification(
                                     row.getEntry().getSbn().getKey());
                         }
-                        isBlockingHelperShown =
-                                row.performDismissWithBlockingHelper(false /* fromAccessibility */);
+                        row.performDismiss(false /* fromAccessibility */);
                     }
 
-                    if (!isBlockingHelperShown) {
-                        mView.addSwipedOutView(view);
-                    }
+                    mView.addSwipedOutView(view);
                     mFalsingManager.onNotificationDismissed();
                     if (mFalsingManager.shouldEnforceBouncer()) {
                         mStatusBar.executeRunnableDismissingKeyguard(
@@ -559,7 +557,6 @@
             NotificationLockscreenUserManager lockscreenUserManager,
             MetricsLogger metricsLogger,
             FalsingManager falsingManager,
-            NotificationSectionsManager notificationSectionsManager,
             @Main Resources resources,
             NotificationSwipeHelper.Builder notificationSwipeHelperBuilder,
             StatusBar statusBar,
@@ -576,7 +573,9 @@
             ForegroundServiceDismissalFeatureController fgFeatureController,
             ForegroundServiceSectionController fgServicesSectionController,
             LayoutInflater layoutInflater,
-            NotificationRemoteInputManager remoteInputManager) {
+            NotificationRemoteInputManager remoteInputManager,
+            VisualStabilityManager visualStabilityManager,
+            ShadeController shadeController) {
         mAllowLongPress = allowLongPress;
         mNotificationGutsManager = notificationGutsManager;
         mHeadsUpManager = headsUpManager;
@@ -592,14 +591,12 @@
         mLockscreenUserManager = lockscreenUserManager;
         mMetricsLogger = metricsLogger;
         mFalsingManager = falsingManager;
-        mNotificationSectionsManager = notificationSectionsManager;
         mResources = resources;
         mNotificationSwipeHelperBuilder = notificationSwipeHelperBuilder;
         mStatusBar = statusBar;
         mScrimController = scrimController;
-        groupManager.registerGroupExpansionChangeListener((changedRow, expanded) -> {
-            mView.onGroupExpandChanged(changedRow, expanded);
-        });
+        groupManager.registerGroupExpansionChangeListener(
+                (changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded));
         legacyGroupManager.registerGroupChangeListener(new OnGroupChangeListener() {
             @Override
             public void onGroupCreatedFromChildren(NotificationGroup group) {
@@ -622,6 +619,8 @@
         mFgServicesSectionController = fgServicesSectionController;
         mLayoutInflater = layoutInflater;
         mRemoteInputManager = remoteInputManager;
+        mVisualStabilityManager = visualStabilityManager;
+        mShadeController = shadeController;
     }
 
     public void attach(NotificationStackScrollLayout view) {
@@ -635,6 +634,7 @@
         mView.setFooterDismissListener(() ->
                 mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES));
         mView.setRemoteInputManager(mRemoteInputManager);
+        mView.setShadeController(mShadeController);
 
         if (mFgFeatureController.isForegroundServiceDismissalEnabled()) {
             mView.initializeForegroundServiceSection(
@@ -681,6 +681,8 @@
         mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
         mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
 
+        mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation);
+
         mTunerService.addTunable(
                 (key, newValue) -> {
                     switch (key) {
@@ -723,6 +725,22 @@
         mSilentHeaderController.setOnClearAllClickListener(v -> clearSilentNotifications());
     }
 
+    private boolean isInVisibleLocation(NotificationEntry entry) {
+        ExpandableNotificationRow row = entry.getRow();
+        ExpandableViewState childViewState = row.getViewState();
+
+        if (childViewState == null) {
+            return false;
+        }
+        if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
+            return false;
+        }
+        if (row.getVisibility() != View.VISIBLE) {
+            return false;
+        }
+        return true;
+    }
+
     public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
         mView.addOnExpandedHeightChangedListener(listener);
     }
@@ -1459,7 +1477,7 @@
 
         @Override
         public boolean isInVisibleLocation(NotificationEntry entry) {
-            return mView.isInVisibleLocation(entry);
+            return NotificationStackScrollLayoutController.this.isInVisibleLocation(entry);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index eb8f065..a6cd350 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -23,7 +23,6 @@
 import android.view.LayoutInflater;
 import android.view.View;
 
-import com.android.keyguard.KeyguardMessageArea;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.QSFooterImpl;
 import com.android.systemui.qs.QSPanel;
@@ -108,11 +107,6 @@
         NotificationStackScrollLayout createNotificationStackScrollLayout();
 
         /**
-         * Creates the KeyguardMessageArea.
-         */
-        KeyguardMessageArea createKeyguardMessageArea();
-
-        /**
          * Creates the QSPanel.
          */
         QSPanel createQSPanel();
diff --git a/packages/SystemUI/src/com/android/systemui/util/ViewController.java b/packages/SystemUI/src/com/android/systemui/util/ViewController.java
index 64f8dbb..c7aa780fc 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/ViewController.java
@@ -23,7 +23,20 @@
  * Utility class that handles view lifecycle events for View Controllers.
  *
  * Implementations should handle setup and teardown related activities inside of
- * {@link #onViewAttached()} and {@link  #onViewDetached()}.
+ * {@link #onViewAttached()} and {@link  #onViewDetached()}. Be sure to call {@link #init()} on
+ * any child controllers that this uses. This can be done in {@link init()} if the controllers
+ * are injected, or right after creation time of the child controller.
+ *
+ * Tip: View "attachment" happens top down - parents are notified that they are attached before
+ * any children. That means that if you call a method on a child controller in
+ * {@link #onViewAttached()}, the child controller may not have had its onViewAttach method
+ * called, so it may not be fully set up.
+ *
+ * As such, make sure that methods on your controller are safe to call _before_ its {@link #init()}
+ * and {@link #onViewAttached()} methods are called. Specifically, if your controller must call
+ * {@link View#findViewById(int)} on its root view to setup member variables, do so in its
+ * constructor. Save {@link #onViewAttached()} for things that can happen post-construction - adding
+ * listeners, dynamically changing content, or other runtime decisions.
  *
  * @param <T> View class that this ViewController is for.
  */
@@ -54,10 +67,12 @@
         }
         mInited = true;
 
-        if (mView.isAttachedToWindow()) {
-            mOnAttachStateListener.onViewAttachedToWindow(mView);
+        if (mView != null) {
+            if (mView.isAttachedToWindow()) {
+                mOnAttachStateListener.onViewAttachedToWindow(mView);
+            }
+            mView.addOnAttachStateChangeListener(mOnAttachStateListener);
         }
-        mView.addOnAttachStateChangeListener(mOnAttachStateListener);
     }
 
     /**
diff --git a/packages/SystemUI/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/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..cdb91ec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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(
+                mView,  mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
+                mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
+                mKeyguardStateController, mKeyguardSecurityViewFlipperController);
+
+        mKeyguardSecurityContainerController.setSecurityCallback(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/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 0fe3817..86b1e61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -41,7 +41,6 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -110,7 +109,6 @@
         // KeyguardUpdateMonitor to be created (injected).
         // TODO(b/1531701009) Clean up NotificationContentView creation to prevent this
         mDependency.injectMockDependency(SmartReplyController.class);
-        mDependency.injectMockDependency(NotificationBlockingHelperManager.class);
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index f1f394e..539b321 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -29,11 +29,14 @@
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
 import android.os.Handler;
 import android.testing.AndroidTestingRunner;
 import android.view.Display;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.view.View;
+import android.view.WindowManager;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -63,15 +66,28 @@
     WindowMagnifierCallback mWindowMagnifierCallback;
     @Mock
     SurfaceControl.Transaction mTransaction;
-    private Context mContext;
+    @Mock
+    private WindowManager mWindowManager;
+    private Resources mResources;
     private WindowMagnificationController mWindowMagnificationController;
     private Instrumentation mInstrumentation;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mContext  = Mockito.spy(getContext());
+        mContext = Mockito.spy(getContext());
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        WindowManager wm = mContext.getSystemService(WindowManager.class);
+        doAnswer(invocation ->
+                wm.getMaximumWindowMetrics()
+        ).when(mWindowManager).getMaximumWindowMetrics();
+        mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+        doAnswer(invocation -> {
+            View view = invocation.getArgument(0);
+            WindowManager.LayoutParams lp = invocation.getArgument(1);
+            view.setLayoutParams(lp);
+            return null;
+        }).when(mWindowManager).addView(any(View.class), any(WindowManager.LayoutParams.class));
         doAnswer(invocation -> {
             FrameCallback callback = invocation.getArgument(0);
             callback.doFrame(0);
@@ -81,6 +97,8 @@
         when(mTransaction.remove(any())).thenReturn(mTransaction);
         when(mTransaction.setGeometry(any(), any(), any(),
                 anyInt())).thenReturn(mTransaction);
+        mResources = Mockito.spy(mContext.getResources());
+        when(mContext.getResources()).thenReturn(mResources);
         mWindowMagnificationController = new WindowMagnificationController(mContext,
                 mHandler, mSfVsyncFrameProvider,
                 mMirrorWindowControl, mTransaction, mWindowMagnifierCallback);
@@ -150,4 +168,63 @@
             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
         });
     }
+
+    @Test
+    public void onOrientationChanged_enabled_updateDisplayRotationAndLayout() {
+        final Display display = Mockito.spy(mContext.getDisplay());
+        when(display.getRotation()).thenReturn(Surface.ROTATION_90);
+        when(mContext.getDisplay()).thenReturn(display);
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+                    Float.NaN);
+            Mockito.reset(mWindowManager);
+        });
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
+        });
+
+        assertEquals(Surface.ROTATION_90, mWindowMagnificationController.mRotation);
+        verify(mWindowManager).updateViewLayout(any(), any());
+    }
+
+    @Test
+    public void onOrientationChanged_disabled_updateDisplayRotation() {
+        final Display display = Mockito.spy(mContext.getDisplay());
+        when(display.getRotation()).thenReturn(Surface.ROTATION_90);
+        when(mContext.getDisplay()).thenReturn(display);
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
+        });
+
+        assertEquals(Surface.ROTATION_90, mWindowMagnificationController.mRotation);
+    }
+
+
+    @Test
+    public void onDensityChanged_enabled_updateDimensionsAndLayout() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+                    Float.NaN);
+            Mockito.reset(mWindowManager);
+        });
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
+        });
+
+        verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
+        verify(mWindowManager).removeView(any());
+        verify(mWindowManager).addView(any(), any());
+    }
+
+    @Test
+    public void onDensityChanged_disabled_updateDimensions() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
+        });
+
+        verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 8f082c1..ade3290 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -47,6 +47,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 
 import org.junit.Before;
@@ -82,6 +83,8 @@
     private PackageManager mPackageManager;
     @Mock(stubOnly = true)
     private AudioManager mAudioManager;
+    @Mock()
+    private BroadcastDispatcher mDispatcher;
     @Mock(stubOnly = true)
     private AudioManager.AudioRecordingCallback mRecordingCallback;
     @Mock(stubOnly = true)
@@ -120,7 +123,8 @@
                 mTestableLooper.getLooper(),
                 mDumpManager,
                 mFlagsCache,
-                mAudioManager
+                mAudioManager,
+                mDispatcher
         );
     }
 
@@ -128,12 +132,14 @@
     public void testOnlyListenForFewOps() {
         mController.setListening(true);
         verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController);
+        verify(mDispatcher, times(1)).registerReceiverWithHandler(eq(mController), any(), any());
     }
 
     @Test
     public void testStopListening() {
         mController.setListening(false);
         verify(mAppOpsManager, times(1)).stopWatchingActive(mController);
+        verify(mDispatcher, times(1)).unregisterReceiver(mController);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
new file mode 100644
index 0000000..87ec72f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -0,0 +1,210 @@
+/*
+ * 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.biometrics;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class UdfpsControllerTest extends SysuiTestCase {
+
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
+
+    // Unit under test
+    private UdfpsController mUdfpsController;
+
+    // Dependencies
+    @Mock
+    private Resources mResources;
+    @Mock
+    private LayoutInflater mLayoutInflater;
+    @Mock
+    private FingerprintManager mFingerprintManager;
+    @Mock
+    private PowerManager mPowerManager;
+    @Mock
+    private WindowManager mWindowManager;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
+    private FakeSettings mSystemSettings;
+    private FakeExecutor mFgExecutor;
+
+    // Stuff for configuring mocks
+    @Mock
+    private UdfpsView mUdfpsView;
+    @Mock
+    private TypedArray mBrightnessValues;
+    @Mock
+    private TypedArray mBrightnessBacklight;
+
+    // Capture listeners so that they can be used to send events
+    @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
+    private IUdfpsOverlayController mOverlayController;
+    @Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
+
+    @Before
+    public void setUp() {
+        setUpResources();
+        when(mLayoutInflater.inflate(R.layout.udfps_view, null, false)).thenReturn(mUdfpsView);
+        mSystemSettings = new FakeSettings();
+        mFgExecutor = new FakeExecutor(new FakeSystemClock());
+        mUdfpsController = new UdfpsController(
+                mContext,
+                mResources,
+                mLayoutInflater,
+                mFingerprintManager,
+                mPowerManager,
+                mWindowManager,
+                mSystemSettings,
+                mStatusBarStateController,
+                mFgExecutor);
+        verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
+        mOverlayController = mOverlayCaptor.getValue();
+    }
+
+    private void setUpResources() {
+        when(mBrightnessValues.length()).thenReturn(2);
+        when(mBrightnessValues.getFloat(0, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(1f);
+        when(mBrightnessValues.getFloat(1, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(2f);
+        when(mResources.obtainTypedArray(com.android.internal.R.array.config_screenBrightnessNits))
+                .thenReturn(mBrightnessValues);
+        when(mBrightnessBacklight.length()).thenReturn(2);
+        when(mBrightnessBacklight.getFloat(0, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(1f);
+        when(mBrightnessBacklight.getFloat(1, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(2f);
+        when(mResources.obtainTypedArray(
+                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
+                .thenReturn(mBrightnessBacklight);
+        when(mResources.getIntArray(com.android.internal.R.array.config_screenBrightnessBacklight))
+                .thenReturn(new int[]{1, 2});
+    }
+
+    @Test
+    public void dozeTimeTick() {
+        mUdfpsController.dozeTimeTick();
+        verify(mUdfpsView).dozeTimeTick();
+    }
+
+    @Test
+    public void showUdfpsOverlay_addsViewToWindow() throws RemoteException {
+        mOverlayController.showUdfpsOverlay();
+        mFgExecutor.runAllReady();
+        verify(mWindowManager).addView(eq(mUdfpsView), any());
+    }
+
+    @Test
+    public void hideUdfpsOverlay_removesViewFromWindow() throws RemoteException {
+        mOverlayController.showUdfpsOverlay();
+        mOverlayController.hideUdfpsOverlay();
+        mFgExecutor.runAllReady();
+        verify(mWindowManager).removeView(eq(mUdfpsView));
+    }
+
+    @Test
+    public void fingerDown() throws RemoteException {
+        // Configure UdfpsView to accept the ACTION_DOWN event
+        when(mUdfpsView.isScrimShowing()).thenReturn(false);
+        when(mUdfpsView.isValidTouch(anyFloat(), anyFloat(), anyFloat())).thenReturn(true);
+
+        // GIVEN that the overlay is showing
+        mOverlayController.showUdfpsOverlay();
+        mFgExecutor.runAllReady();
+        // WHEN ACTION_DOWN is received
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
+        event.recycle();
+        // THEN the event is passed to the FingerprintManager
+        verify(mFingerprintManager).onFingerDown(eq(0), eq(0), eq(0f), eq(0f));
+        // AND the scrim and dot is shown
+        verify(mUdfpsView).showScrimAndDot();
+    }
+
+    @Test
+    public void aodInterrupt() throws RemoteException {
+        // GIVEN that the overlay is showing
+        mOverlayController.showUdfpsOverlay();
+        mFgExecutor.runAllReady();
+        // WHEN fingerprint is requested because of AOD interrupt
+        mUdfpsController.onAodInterrupt(0, 0);
+        // THEN the event is passed to the FingerprintManager
+        verify(mFingerprintManager).onFingerDown(eq(0), eq(0), anyFloat(), anyFloat());
+        // AND the scrim and dot is shown
+        verify(mUdfpsView).showScrimAndDot();
+    }
+
+    @Test
+    public void cancelAodInterrupt() throws RemoteException {
+        // GIVEN AOD interrupt
+        mOverlayController.showUdfpsOverlay();
+        mFgExecutor.runAllReady();
+        mUdfpsController.onAodInterrupt(0, 0);
+        // WHEN it is cancelled
+        mUdfpsController.onCancelAodInterrupt();
+        // THEN the scrim and dot is hidden
+        verify(mUdfpsView).hideScrimAndDot();
+    }
+
+    @Test
+    public void aodInterruptTimeout() throws RemoteException {
+        // GIVEN AOD interrupt
+        mOverlayController.showUdfpsOverlay();
+        mFgExecutor.runAllReady();
+        mUdfpsController.onAodInterrupt(0, 0);
+        // WHEN it times out
+        mFgExecutor.advanceClockToNext();
+        mFgExecutor.runAllReady();
+        // THEN the scrim and dot is hidden
+        verify(mUdfpsView).hideScrimAndDot();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index b85b1c2..be0865d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -115,8 +115,6 @@
 
     @Test
     public void testAod_usesLightSensor() {
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
         mScreen.onScreenState(Display.STATE_DOZE);
         waitForSensorManager();
 
@@ -127,8 +125,6 @@
 
     @Test
     public void testAod_usesDebugValue() throws Exception {
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
         mScreen.onScreenState(Display.STATE_DOZE);
         waitForSensorManager();
 
@@ -181,14 +177,13 @@
     }
 
     @Test
-    public void testDozingAfterPulsing_pausesLightSensor() throws Exception {
+    public void testScreenOffAfterPulsing_pausesLightSensor() throws Exception {
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
         mScreen.transitionTo(DOZE, DOZE_REQUEST_PULSE);
         mScreen.transitionTo(DOZE_REQUEST_PULSE, DOZE_PULSING);
         mScreen.transitionTo(DOZE_PULSING, DOZE_PULSE_DONE);
         mScreen.transitionTo(DOZE_PULSE_DONE, DOZE);
-        mScreen.onScreenState(Display.STATE_DOZE);
         waitForSensorManager();
 
         mSensor.sendSensorEvent(1);
@@ -197,6 +192,18 @@
     }
 
     @Test
+    public void testOnScreenStateSetBeforeTransition_stillRegistersSensor() {
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.onScreenState(Display.STATE_DOZE);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        waitForSensorManager();
+
+        mSensor.sendSensorEvent(1);
+
+        assertEquals(1, mServiceFake.screenBrightness);
+    }
+
+    @Test
     public void testNullSensor() throws Exception {
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
                 Optional.empty() /* sensor */, mDozeHost, null /* handler */,
@@ -206,12 +213,15 @@
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
         mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
         mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
+        mScreen.onScreenState(Display.STATE_DOZE);
+        mScreen.onScreenState(Display.STATE_OFF);
     }
 
     @Test
     public void testNoBrightnessDeliveredAfterFinish() throws Exception {
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        mScreen.onScreenState(Display.STATE_DOZE);
         mScreen.transitionTo(DOZE_AOD, FINISH);
         waitForSensorManager();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 53ef866..2ef7c65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -34,6 +34,7 @@
 import static org.mockito.Mockito.when;
 
 import android.Manifest;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -47,8 +48,11 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.logging.InstanceId;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.qs.DetailAdapter;
+import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -68,6 +72,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -116,11 +121,9 @@
         doAnswer(invocation -> {
                     String spec = (String) invocation.getArguments()[0];
                     if (FACTORY_TILES.contains(spec)) {
-                        QSTile m = mock(QSTile.class);
-                        when(m.isAvailable()).thenReturn(true);
-                        when(m.getTileSpec()).thenReturn(spec);
-                        when(m.getState()).thenReturn(mState);
-                        return m;
+                        FakeQSTile tile = new FakeQSTile(mBgExecutor, mMainExecutor);
+                        tile.setState(mState);
+                        return tile;
                     } else {
                         return null;
                     }
@@ -292,4 +295,132 @@
         verifier.verify(t).setTileSpec("hotspot");
         verifier.verify(t).destroy();
     }
+
+    private static class FakeQSTile implements QSTile {
+
+        private String mSpec = "";
+        private List<Callback> mCallbacks = new ArrayList<>();
+        private boolean mRefreshed;
+        private boolean mListening;
+        private State mState = new State();
+        private final Executor mBgExecutor;
+        private final Executor mMainExecutor;
+
+        FakeQSTile(Executor bgExecutor, Executor mainExecutor) {
+            mBgExecutor = bgExecutor;
+            mMainExecutor = mainExecutor;
+        }
+
+        @Override
+        public String getTileSpec() {
+            return mSpec;
+        }
+
+        @Override
+        public boolean isAvailable() {
+            return true;
+        }
+
+        @Override
+        public void setTileSpec(String tileSpec) {
+            mSpec = tileSpec;
+        }
+
+        public void setState(State state) {
+            mState = state;
+            notifyChangedState(mState);
+        }
+
+        @Override
+        public void refreshState() {
+            mBgExecutor.execute(() -> {
+                mRefreshed = true;
+                notifyChangedState(mState);
+            });
+        }
+
+        private void notifyChangedState(State state) {
+            List<Callback> callbacks = new ArrayList<>(mCallbacks);
+            callbacks.forEach(callback -> callback.onStateChanged(state));
+        }
+
+        @Override
+        public void addCallback(Callback callback) {
+            mCallbacks.add(callback);
+        }
+
+        @Override
+        public void removeCallback(Callback callback) {
+            mCallbacks.remove(callback);
+        }
+
+        @Override
+        public void removeCallbacks() {
+            mCallbacks.clear();
+        }
+
+        @Override
+        public void setListening(Object client, boolean listening) {
+            if (listening) {
+                mMainExecutor.execute(() -> {
+                    mListening = true;
+                    refreshState();
+                });
+            }
+        }
+
+        @Override
+        public CharSequence getTileLabel() {
+            return mSpec;
+        }
+
+        @Override
+        public State getState() {
+            return mState;
+        }
+
+        @Override
+        public boolean isTileReady() {
+            return mListening && mRefreshed;
+        }
+
+        @Override
+        public QSIconView createTileView(Context context) {
+            return null;
+        }
+
+        @Override
+        public void click() {}
+
+        @Override
+        public void secondaryClick() {}
+
+        @Override
+        public void longClick() {}
+
+        @Override
+        public void userSwitch(int currentUser) {}
+
+        @Override
+        public int getMetricsCategory() {
+            return 0;
+        }
+
+        @Override
+        public InstanceId getInstanceId() {
+            return null;
+        }
+
+        @Override
+        public void setDetailListening(boolean show) {}
+
+        @Override
+        public void destroy() {}
+
+
+        @Override
+        public DetailAdapter getDetailAdapter() {
+            return null;
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index f29b46c..8f1d71c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -54,7 +54,6 @@
 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;
 
@@ -71,7 +70,6 @@
     boolean mHeadsUpAnimatingAway = false;
 
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
-    @Mock private NotificationBlockingHelperManager mBlockingHelperManager;
 
     @Before
     public void setUp() throws Exception {
@@ -83,9 +81,6 @@
         mGroupRow = mNotificationTestHelper.createGroup();
         mGroupRow.setHeadsUpAnimatingAwayListener(
                 animatingAway -> mHeadsUpAnimatingAway = animatingAway);
-        mDependency.injectTestDependency(
-                NotificationBlockingHelperManager.class,
-                mBlockingHelperManager);
     }
 
     @Test
@@ -257,30 +252,6 @@
     }
 
     @Test
-    public void testPerformDismissWithBlockingHelper_falseWhenBlockingHelperIsntShown() {
-        when(mBlockingHelperManager.perhapsShowBlockingHelper(
-                eq(mGroupRow), any(NotificationMenuRowPlugin.class))).thenReturn(false);
-
-        assertFalse(
-                mGroupRow.performDismissWithBlockingHelper(false /* fromAccessibility */));
-    }
-
-    @Test
-    public void testPerformDismissWithBlockingHelper_doesntPerformOnGroupSummary() {
-        ExpandableNotificationRow childRow = mGroupRow.getChildrenContainer().getViewAtPosition(0);
-        when(mBlockingHelperManager.perhapsShowBlockingHelper(eq(childRow), any(NotificationMenuRowPlugin.class)))
-                .thenReturn(true);
-
-        assertTrue(
-                childRow.performDismissWithBlockingHelper(false /* fromAccessibility */));
-
-        verify(mBlockingHelperManager, times(1))
-                .perhapsShowBlockingHelper(eq(childRow), any(NotificationMenuRowPlugin.class));
-        verify(mBlockingHelperManager, times(0))
-                .perhapsShowBlockingHelper(eq(mGroupRow), any(NotificationMenuRowPlugin.class));
-    }
-
-    @Test
     public void testIsBlockingHelperShowing_isCorrectlyUpdated() {
         mGroupRow.setBlockingHelperShowing(true);
         assertTrue(mGroupRow.isBlockingHelperShowing());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 0d283ce..bb85cec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -3,7 +3,10 @@
  *
  * 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 *
+ * 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.
@@ -31,7 +34,6 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -56,15 +58,14 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.FooterView;
-import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.KeyguardBypassEnabledProvider;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -88,7 +89,6 @@
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
     @Mock private StatusBar mBar;
     @Mock private SysuiStatusBarStateController mBarState;
-    @Mock private NotificationBlockingHelperManager mBlockingHelperManager;
     @Mock private NotificationGroupManagerLegacy mGroupMembershipManger;
     @Mock private NotificationGroupManagerLegacy mGroupExpansionManager;
     @Mock private ExpandHelper mExpandHelper;
@@ -117,10 +117,6 @@
                 1, UserHandle.USER_CURRENT);
 
         // Inject dependencies before initializing the layout
-        mDependency.injectMockDependency(VisualStabilityManager.class);
-        mDependency.injectTestDependency(
-                NotificationBlockingHelperManager.class,
-                mBlockingHelperManager);
         mDependency.injectTestDependency(SysuiStatusBarStateController.class, mBarState);
         mDependency.injectMockDependency(ShadeController.class);
 
@@ -217,12 +213,20 @@
     @Test
     @UiThreadTest
     public void testSetExpandedHeight_blockingHelperManagerReceivedCallbacks() {
-        mStackScroller.setExpandedHeight(0f);
-        verify(mBlockingHelperManager).setNotificationShadeExpanded(0f);
-        reset(mBlockingHelperManager);
+        final float expectedHeight[] = {0f};
+        final float expectedAppear[] = {0f};
 
-        mStackScroller.setExpandedHeight(100f);
-        verify(mBlockingHelperManager).setNotificationShadeExpanded(100f);
+        mStackScroller.addOnExpandedHeightChangedListener((height, appear) -> {
+            Assert.assertEquals(expectedHeight[0], height, 0);
+            Assert.assertEquals(expectedAppear[0], appear, .1);
+        });
+        expectedHeight[0] = 1f;
+        expectedAppear[0] = 1f;
+        mStackScroller.setExpandedHeight(expectedHeight[0]);
+
+        expectedHeight[0] = 100f;
+        expectedAppear[0] = 0f;
+        mStackScroller.setExpandedHeight(expectedHeight[0]);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
index d834176..01d49c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
@@ -60,6 +60,7 @@
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView;
@@ -68,6 +69,7 @@
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -105,7 +107,6 @@
     @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
     @Mock private MetricsLogger mMetricsLogger;
     @Mock private FalsingManager mFalsingManager;
-    @Mock private NotificationSectionsManager mNotificationSectionsManager;
     @Mock private Resources mResources;
     @Mock(answer = Answers.RETURNS_SELF)
     private NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder;
@@ -126,6 +127,8 @@
     @Mock private LayoutInflater mLayoutInflater;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
     @Mock private RemoteInputController mRemoteInputController;
+    @Mock private VisualStabilityManager mVisualStabilityManager;
+    @Mock private ShadeController mShadeController;
 
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -158,7 +161,6 @@
                 mNotificationLockscreenUserManager,
                 mMetricsLogger,
                 mFalsingManager,
-                mNotificationSectionsManager,
                 mResources,
                 mNotificationSwipeHelperBuilder,
                 mStatusBar,
@@ -175,7 +177,9 @@
                 mFgFeatureController,
                 mFgServicesSectionController,
                 mLayoutInflater,
-                mRemoteInputManager
+                mRemoteInputManager,
+                mVisualStabilityManager,
+                mShadeController
         );
 
         when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
diff --git a/packages/Tethering/apex/Android.bp b/packages/Tethering/apex/Android.bp
index 67097a7..0524374 100644
--- a/packages/Tethering/apex/Android.bp
+++ b/packages/Tethering/apex/Android.bp
@@ -19,6 +19,7 @@
     updatable: true,
     min_sdk_version: "current",
     java_libs: ["framework-tethering"],
+    bpfs: ["offload.o"],
     apps: ["Tethering"],
     manifest: "manifest.json",
     key: "com.android.tethering.key",
diff --git a/packages/Tethering/bpf_progs/Android.bp b/packages/Tethering/bpf_progs/Android.bp
new file mode 100644
index 0000000..d54f861
--- /dev/null
+++ b/packages/Tethering/bpf_progs/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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.
+//
+
+//
+// bpf kernel programs
+//
+bpf {
+    name: "offload.o",
+    srcs: ["offload.c"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    include_dirs: [
+        // TODO: get rid of system/netd.
+        "system/netd/bpf_progs",             // for bpf_net_helpers.h
+        "system/netd/libnetdbpf/include",    // for bpf_shared.h
+        "system/netd/libnetdutils/include",  // for UidConstants.h
+    ],
+}
diff --git a/packages/Tethering/bpf_progs/offload.c b/packages/Tethering/bpf_progs/offload.c
new file mode 100644
index 0000000..cc5af31
--- /dev/null
+++ b/packages/Tethering/bpf_progs/offload.c
@@ -0,0 +1,207 @@
+/*
+ * 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 <linux/if.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/pkt_cls.h>
+#include <linux/tcp.h>
+
+#include "bpf_helpers.h"
+#include "bpf_net_helpers.h"
+#include "netdbpf/bpf_shared.h"
+
+DEFINE_BPF_MAP_GRW(tether_ingress_map, HASH, TetherIngressKey, TetherIngressValue, 64,
+                   AID_NETWORK_STACK)
+
+// Tethering stats, indexed by upstream interface.
+DEFINE_BPF_MAP_GRW(tether_stats_map, HASH, uint32_t, TetherStatsValue, 16, AID_NETWORK_STACK)
+
+// Tethering data limit, indexed by upstream interface.
+// (tethering allowed when stats[iif].rxBytes + stats[iif].txBytes < limit[iif])
+DEFINE_BPF_MAP_GRW(tether_limit_map, HASH, uint32_t, uint64_t, 16, AID_NETWORK_STACK)
+
+static inline __always_inline int do_forward(struct __sk_buff* skb, bool is_ethernet) {
+    int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
+    void* data = (void*)(long)skb->data;
+    const void* data_end = (void*)(long)skb->data_end;
+    struct ethhdr* eth = is_ethernet ? data : NULL;  // used iff is_ethernet
+    struct ipv6hdr* ip6 = is_ethernet ? (void*)(eth + 1) : data;
+
+    // Must be meta-ethernet IPv6 frame
+    if (skb->protocol != htons(ETH_P_IPV6)) return TC_ACT_OK;
+
+    // Must have (ethernet and) ipv6 header
+    if (data + l2_header_size + sizeof(*ip6) > data_end) return TC_ACT_OK;
+
+    // Ethertype - if present - must be IPv6
+    if (is_ethernet && (eth->h_proto != htons(ETH_P_IPV6))) return TC_ACT_OK;
+
+    // IP version must be 6
+    if (ip6->version != 6) return TC_ACT_OK;
+
+    // Cannot decrement during forward if already zero or would be zero,
+    // Let the kernel's stack handle these cases and generate appropriate ICMP errors.
+    if (ip6->hop_limit <= 1) return TC_ACT_OK;
+
+    // Protect against forwarding packets sourced from ::1 or fe80::/64 or other weirdness.
+    __be32 src32 = ip6->saddr.s6_addr32[0];
+    if (src32 != htonl(0x0064ff9b) &&                        // 64:ff9b:/32 incl. XLAT464 WKP
+        (src32 & htonl(0xe0000000)) != htonl(0x20000000))    // 2000::/3 Global Unicast
+        return TC_ACT_OK;
+
+    TetherIngressKey k = {
+            .iif = skb->ifindex,
+            .neigh6 = ip6->daddr,
+    };
+
+    TetherIngressValue* v = bpf_tether_ingress_map_lookup_elem(&k);
+
+    // If we don't find any offload information then simply let the core stack handle it...
+    if (!v) return TC_ACT_OK;
+
+    uint32_t stat_and_limit_k = skb->ifindex;
+
+    TetherStatsValue* stat_v = bpf_tether_stats_map_lookup_elem(&stat_and_limit_k);
+
+    // If we don't have anywhere to put stats, then abort...
+    if (!stat_v) return TC_ACT_OK;
+
+    uint64_t* limit_v = bpf_tether_limit_map_lookup_elem(&stat_and_limit_k);
+
+    // If we don't have a limit, then abort...
+    if (!limit_v) return TC_ACT_OK;
+
+    // Required IPv6 minimum mtu is 1280, below that not clear what we should do, abort...
+    const int pmtu = v->pmtu;
+    if (pmtu < IPV6_MIN_MTU) return TC_ACT_OK;
+
+    // Approximate handling of TCP/IPv6 overhead for incoming LRO/GRO packets: default
+    // outbound path mtu of 1500 is not necessarily correct, but worst case we simply
+    // undercount, which is still better then not accounting for this overhead at all.
+    // Note: this really shouldn't be device/path mtu at all, but rather should be
+    // derived from this particular connection's mss (ie. from gro segment size).
+    // This would require a much newer kernel with newer ebpf accessors.
+    // (This is also blindly assuming 12 bytes of tcp timestamp option in tcp header)
+    uint64_t packets = 1;
+    uint64_t bytes = skb->len;
+    if (bytes > pmtu) {
+        const int tcp_overhead = sizeof(struct ipv6hdr) + sizeof(struct tcphdr) + 12;
+        const int mss = pmtu - tcp_overhead;
+        const uint64_t payload = bytes - tcp_overhead;
+        packets = (payload + mss - 1) / mss;
+        bytes = tcp_overhead * packets + payload;
+    }
+
+    // Are we past the limit?  If so, then abort...
+    // Note: will not overflow since u64 is 936 years even at 5Gbps.
+    // Do not drop here.  Offload is just that, whenever we fail to handle
+    // a packet we let the core stack deal with things.
+    // (The core stack needs to handle limits correctly anyway,
+    // since we don't offload all traffic in both directions)
+    if (stat_v->rxBytes + stat_v->txBytes + bytes > *limit_v) return TC_ACT_OK;
+
+    if (!is_ethernet) {
+        is_ethernet = true;
+        l2_header_size = sizeof(struct ethhdr);
+        // Try to inject an ethernet header, and simply return if we fail
+        if (bpf_skb_change_head(skb, l2_header_size, /*flags*/ 0)) {
+            __sync_fetch_and_add(&stat_v->rxErrors, 1);
+            return TC_ACT_OK;
+        }
+
+        // bpf_skb_change_head() invalidates all pointers - reload them
+        data = (void*)(long)skb->data;
+        data_end = (void*)(long)skb->data_end;
+        eth = data;
+        ip6 = (void*)(eth + 1);
+
+        // I do not believe this can ever happen, but keep the verifier happy...
+        if (data + l2_header_size + sizeof(*ip6) > data_end) {
+            __sync_fetch_and_add(&stat_v->rxErrors, 1);
+            return TC_ACT_SHOT;
+        }
+    };
+
+    // CHECKSUM_COMPLETE is a 16-bit one's complement sum,
+    // thus corrections for it need to be done in 16-byte chunks at even offsets.
+    // IPv6 nexthdr is at offset 6, while hop limit is at offset 7
+    uint8_t old_hl = ip6->hop_limit;
+    --ip6->hop_limit;
+    uint8_t new_hl = ip6->hop_limit;
+
+    // bpf_csum_update() always succeeds if the skb is CHECKSUM_COMPLETE and returns an error
+    // (-ENOTSUPP) if it isn't.
+    bpf_csum_update(skb, 0xFFFF - ntohs(old_hl) + ntohs(new_hl));
+
+    __sync_fetch_and_add(&stat_v->rxPackets, packets);
+    __sync_fetch_and_add(&stat_v->rxBytes, bytes);
+
+    // Overwrite any mac header with the new one
+    *eth = v->macHeader;
+
+    // Redirect to forwarded interface.
+    //
+    // Note that bpf_redirect() cannot fail unless you pass invalid flags.
+    // The redirect actually happens after the ebpf program has already terminated,
+    // and can fail for example for mtu reasons at that point in time, but there's nothing
+    // we can do about it here.
+    return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
+}
+
+SEC("schedcls/ingress/tether_ether")
+int sched_cls_ingress_tether_ether(struct __sk_buff* skb) {
+    return do_forward(skb, true);
+}
+
+// Note: section names must be unique to prevent programs from appending to each other,
+// so instead the bpf loader will strip everything past the final $ symbol when actually
+// pinning the program into the filesystem.
+//
+// bpf_skb_change_head() is only present on 4.14+ and 2 trivial kernel patches are needed:
+//   ANDROID: net: bpf: Allow TC programs to call BPF_FUNC_skb_change_head
+//   ANDROID: net: bpf: permit redirect from ingress L3 to egress L2 devices at near max mtu
+// (the first of those has already been upstreamed)
+//
+// 5.4 kernel support was only added to Android Common Kernel in R,
+// and thus a 5.4 kernel always supports this.
+//
+// Hence, this mandatory (must load successfully) implementation for 5.4+ kernels:
+DEFINE_BPF_PROG_KVER("schedcls/ingress/tether_rawip$5_4", AID_ROOT, AID_ROOT,
+                     sched_cls_ingress_tether_rawip_5_4, KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+    return do_forward(skb, false);
+}
+
+// and this identical optional (may fail to load) implementation for [4.14..5.4) patched kernels:
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/ingress/tether_rawip$4_14", AID_ROOT, AID_ROOT,
+                                    sched_cls_ingress_tether_rawip_4_14, KVER(4, 14, 0),
+                                    KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+    return do_forward(skb, false);
+}
+
+// and define a no-op stub for [4.9,4.14) and unpatched [4.14,5.4) kernels.
+// (if the above real 4.14+ program loaded successfully, then bpfloader will have already pinned
+// it at the same location this one would be pinned at and will thus skip loading this stub)
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/ingress/tether_rawip$stub", AID_ROOT, AID_ROOT,
+                           sched_cls_ingress_tether_rawip_stub, KVER_NONE, KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+    return TC_ACT_OK;
+}
+
+LICENSE("Apache 2.0");
+CRITICAL("netd");
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
index 96ed7b4..06cb33b 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
@@ -31,7 +31,7 @@
     <bool name="config_navBarTapThrough">true</bool>
 
     <!-- Controls the size of the back gesture inset. -->
-    <dimen name="config_backGestureInset">24dp</dimen>
+    <dimen name="config_backGestureInset">30dp</dimen>
 
     <!-- Controls whether the navbar needs a scrim with
      {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 533bbe6..92b8608 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -261,9 +261,13 @@
                 focusedValue != null && focusedValue.isText()
                         ? focusedValue.getTextValue().toString() : null;
 
+        final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
+                new InlineFillUi.InlineFillUiInfo(request, focusedId, filterText,
+                        remoteRenderService, userId, sessionId);
+
         final InlineFillUi inlineFillUi =
                 InlineFillUi.forAugmentedAutofill(
-                        request, inlineSuggestionsData, focusedId, filterText,
+                        inlineFillUiInfo, inlineSuggestionsData,
                         new InlineFillUi.InlineSuggestionUiCallback() {
                             @Override
                             public void autofill(Dataset dataset, int datasetIndex) {
@@ -305,15 +309,24 @@
                             }
 
                             @Override
-                            public void startIntentSender(IntentSender intentSender,
-                                    Intent intent) {
+                            public void authenticate(int requestId, int datasetIndex) {
+                                Slog.e(TAG, "authenticate not implemented for augmented autofill");
+                            }
+
+                            @Override
+                            public void startIntentSender(IntentSender intentSender) {
                                 try {
-                                    client.startIntentSender(intentSender, intent);
+                                    client.startIntentSender(intentSender, new Intent());
                                 } catch (RemoteException e) {
                                     Slog.w(TAG, "RemoteException starting intent sender");
                                 }
                             }
-                        }, onErrorCallback, remoteRenderService, userId, sessionId);
+
+                            @Override
+                            public void onError() {
+                                onErrorCallback.run();
+                            }
+                        });
 
         if (inlineSuggestionsCallback.apply(inlineFillUi)) {
             mCallbacks.logAugmentedAutofillShown(sessionId, clientState);
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 1970b57..4d2e4f6 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -3008,14 +3008,36 @@
             return false;
         }
 
-        InlineFillUi inlineFillUi = InlineFillUi.forAutofill(
-                inlineSuggestionsRequest.get(), response, focusedId, filterText,
-                /*uiCallback*/this, /*onErrorCallback*/ () -> {
-                    synchronized (mLock) {
-                        mInlineSessionController.setInlineFillUiLocked(
-                                InlineFillUi.emptyUi(focusedId));
+        final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
+                new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId,
+                        filterText, remoteRenderService, userId, id);
+        InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response,
+                new InlineFillUi.InlineSuggestionUiCallback() {
+                    @Override
+                    public void autofill(@NonNull Dataset dataset, int datasetIndex) {
+                        fill(response.getRequestId(), datasetIndex, dataset);
                     }
-                }, remoteRenderService, userId, id);
+
+                    @Override
+                    public void authenticate(int requestId, int datasetIndex) {
+                        Session.this.authenticate(response.getRequestId(), datasetIndex,
+                                response.getAuthentication(), response.getClientState(),
+                                /* authenticateInline= */ true);
+                    }
+
+                    @Override
+                    public void startIntentSender(@NonNull IntentSender intentSender) {
+                        Session.this.startIntentSender(intentSender, new Intent());
+                    }
+
+                    @Override
+                    public void onError() {
+                        synchronized (mLock) {
+                            mInlineSessionController.setInlineFillUiLocked(
+                                    InlineFillUi.emptyUi(focusedId));
+                        }
+                    }
+                });
         return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
index 25e9d5c..ff17590 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
@@ -20,7 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.Intent;
+import android.annotation.UserIdInt;
 import android.content.IntentSender;
 import android.service.autofill.Dataset;
 import android.service.autofill.FillResponse;
@@ -32,6 +32,7 @@
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 import android.view.inputmethod.InlineSuggestion;
+import android.view.inputmethod.InlineSuggestionInfo;
 import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InlineSuggestionsResponse;
 
@@ -93,59 +94,72 @@
      */
     @NonNull
     public static InlineFillUi emptyUi(@NonNull AutofillId autofillId) {
-        return new InlineFillUi(autofillId, new SparseArray<>(), null);
+        return new InlineFillUi(autofillId);
+    }
+
+    /**
+     * Encapsulates various arguments used by {@link #forAutofill} and {@link #forAugmentedAutofill}
+     */
+    public static class InlineFillUiInfo {
+
+        public int mUserId;
+        public int mSessionId;
+        public InlineSuggestionsRequest mInlineRequest;
+        public AutofillId mFocusedId;
+        public String mFilterText;
+        public RemoteInlineSuggestionRenderService mRemoteRenderService;
+
+        public InlineFillUiInfo(@NonNull InlineSuggestionsRequest inlineRequest,
+                @NonNull AutofillId focusedId, @NonNull String filterText,
+                @NonNull RemoteInlineSuggestionRenderService remoteRenderService,
+                @UserIdInt int userId, int sessionId) {
+            mUserId = userId;
+            mSessionId = sessionId;
+            mInlineRequest = inlineRequest;
+            mFocusedId = focusedId;
+            mFilterText = filterText;
+            mRemoteRenderService = remoteRenderService;
+        }
     }
 
     /**
      * Returns an inline autofill UI for a field based on an Autofilll response.
      */
     @NonNull
-    public static InlineFillUi forAutofill(@NonNull InlineSuggestionsRequest request,
+    public static InlineFillUi forAutofill(@NonNull InlineFillUiInfo inlineFillUiInfo,
             @NonNull FillResponse response,
-            @NonNull AutofillId focusedViewId, @Nullable String filterText,
-            @NonNull AutoFillUI.AutoFillUiCallback uiCallback,
-            @NonNull Runnable onErrorCallback,
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
-            int userId, int sessionId) {
-
-        if (InlineSuggestionFactory.responseNeedAuthentication(response)) {
+            @NonNull InlineSuggestionUiCallback uiCallback) {
+        if (response.getAuthentication() != null && response.getInlinePresentation() != null) {
             InlineSuggestion inlineAuthentication =
-                    InlineSuggestionFactory.createInlineAuthentication(request, response,
-                            uiCallback, onErrorCallback, remoteRenderService, userId, sessionId);
-            return new InlineFillUi(focusedViewId, inlineAuthentication, filterText);
+                    InlineSuggestionFactory.createInlineAuthentication(inlineFillUiInfo, response,
+                            uiCallback);
+            return new InlineFillUi(inlineFillUiInfo, inlineAuthentication);
         } else if (response.getDatasets() != null) {
             SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions =
-                    InlineSuggestionFactory.createAutofillInlineSuggestions(request,
-                            response.getRequestId(),
-                            response.getDatasets(), focusedViewId, uiCallback, onErrorCallback,
-                            remoteRenderService, userId, sessionId);
-            return new InlineFillUi(focusedViewId, inlineSuggestions, filterText);
+                    InlineSuggestionFactory.createInlineSuggestions(inlineFillUiInfo,
+                            InlineSuggestionInfo.SOURCE_AUTOFILL, response.getDatasets(),
+                            uiCallback);
+            return new InlineFillUi(inlineFillUiInfo, inlineSuggestions);
         }
-        return new InlineFillUi(focusedViewId, new SparseArray<>(), filterText);
+        return new InlineFillUi(inlineFillUiInfo, new SparseArray<>());
     }
 
     /**
      * Returns an inline autofill UI for a field based on an Autofilll response.
      */
     @NonNull
-    public static InlineFillUi forAugmentedAutofill(@NonNull InlineSuggestionsRequest request,
+    public static InlineFillUi forAugmentedAutofill(@NonNull InlineFillUiInfo inlineFillUiInfo,
             @NonNull List<Dataset> datasets,
-            @NonNull AutofillId focusedViewId, @Nullable String filterText,
-            @NonNull InlineSuggestionUiCallback uiCallback,
-            @NonNull Runnable onErrorCallback,
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
-            int userId, int sessionId) {
+            @NonNull InlineSuggestionUiCallback uiCallback) {
         SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions =
-                InlineSuggestionFactory.createAugmentedAutofillInlineSuggestions(request, datasets,
-                        focusedViewId,
-                        uiCallback, onErrorCallback, remoteRenderService, userId, sessionId);
-        return new InlineFillUi(focusedViewId, inlineSuggestions, filterText);
+                InlineSuggestionFactory.createInlineSuggestions(inlineFillUiInfo,
+                        InlineSuggestionInfo.SOURCE_PLATFORM, datasets, uiCallback);
+        return new InlineFillUi(inlineFillUiInfo, inlineSuggestions);
     }
 
-    InlineFillUi(@NonNull AutofillId autofillId,
-            @NonNull SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions,
-            @Nullable String filterText) {
-        mAutofillId = autofillId;
+    private InlineFillUi(@Nullable InlineFillUiInfo inlineFillUiInfo,
+            @NonNull SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions) {
+        mAutofillId = inlineFillUiInfo.mFocusedId;
         int size = inlineSuggestions.size();
         mDatasets = new ArrayList<>(size);
         mInlineSuggestions = new ArrayList<>(size);
@@ -154,16 +168,26 @@
             mDatasets.add(value.first);
             mInlineSuggestions.add(value.second);
         }
-        mFilterText = filterText;
+        mFilterText = inlineFillUiInfo.mFilterText;
     }
 
-    InlineFillUi(@NonNull AutofillId autofillId, InlineSuggestion inlineSuggestion,
-            @Nullable String filterText) {
-        mAutofillId = autofillId;
+    private InlineFillUi(@NonNull InlineFillUiInfo inlineFillUiInfo,
+            @NonNull InlineSuggestion inlineSuggestion) {
+        mAutofillId = inlineFillUiInfo.mFocusedId;
         mDatasets = null;
         mInlineSuggestions = new ArrayList<>();
         mInlineSuggestions.add(inlineSuggestion);
-        mFilterText = filterText;
+        mFilterText = inlineFillUiInfo.mFilterText;
+    }
+
+    /**
+     * Only used for constructing an empty InlineFillUi with {@link #emptyUi}
+     */
+    private InlineFillUi(@NonNull AutofillId focusedId) {
+        mAutofillId = focusedId;
+        mDatasets = new ArrayList<>(0);
+        mInlineSuggestions = new ArrayList<>(0);
+        mFilterText = null;
     }
 
     @NonNull
@@ -295,9 +319,21 @@
         void autofill(@NonNull Dataset dataset, int datasetIndex);
 
         /**
+         * Callback to authenticate a dataset.
+         *
+         * <p>Only implemented by regular autofill for now.</p>
+         */
+        void authenticate(int requestId, int datasetIndex);
+
+        /**
          * Callback to start Intent in client app.
          */
-        void startIntentSender(@NonNull IntentSender intentSender, @NonNull Intent intent);
+        void startIntentSender(@NonNull IntentSender intentSender);
+
+        /**
+         * Callback on errors.
+         */
+        void onError();
     }
 
     /**
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
index 8fcb8aa..b15d07b 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -20,184 +20,98 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.IBinder;
 import android.service.autofill.Dataset;
 import android.service.autofill.FillResponse;
 import android.service.autofill.InlinePresentation;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.inputmethod.InlineSuggestion;
 import android.view.inputmethod.InlineSuggestionInfo;
 import android.view.inputmethod.InlineSuggestionsRequest;
-import android.view.inputmethod.InlineSuggestionsResponse;
 import android.widget.inline.InlinePresentationSpec;
 
 import com.android.internal.view.inline.IInlineContentProvider;
-import com.android.server.autofill.RemoteInlineSuggestionRenderService;
 
 import java.util.List;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
 
 final class InlineSuggestionFactory {
     private static final String TAG = "InlineSuggestionFactory";
 
-    public static boolean responseNeedAuthentication(@NonNull FillResponse response) {
-        return response.getAuthentication() != null && response.getInlinePresentation() != null;
-    }
-
     public static InlineSuggestion createInlineAuthentication(
-            @NonNull InlineSuggestionsRequest request, @NonNull FillResponse response,
-            @NonNull AutoFillUI.AutoFillUiCallback client, @NonNull Runnable onErrorCallback,
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService, int userId,
-            int sessionId) {
-        final BiConsumer<Dataset, Integer> onClickFactory = (dataset, datasetIndex) -> {
-            client.authenticate(response.getRequestId(),
-                    datasetIndex, response.getAuthentication(), response.getClientState(),
-                    /* authenticateInline= */ true);
-        };
-        final Consumer<IntentSender> intentSenderConsumer = (intentSender) ->
-                client.startIntentSender(intentSender, new Intent());
+            @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo, @NonNull FillResponse response,
+            @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) {
         InlinePresentation inlineAuthentication = response.getInlinePresentation();
-        return createInlineAuthSuggestion(
-                mergedInlinePresentation(request, 0, inlineAuthentication),
-                remoteRenderService, userId, sessionId,
-                onClickFactory, onErrorCallback, intentSenderConsumer,
-                request.getHostInputToken(), request.getHostDisplayId());
+        final int requestId = response.getRequestId();
+
+        return createInlineSuggestion(inlineFillUiInfo, InlineSuggestionInfo.SOURCE_AUTOFILL,
+                InlineSuggestionInfo.TYPE_ACTION, () -> uiCallback.authenticate(requestId,
+                        AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED),
+                mergedInlinePresentation(inlineFillUiInfo.mInlineRequest, 0, inlineAuthentication),
+                uiCallback);
     }
 
     /**
-     * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by the
-     * autofill service, potentially filtering the datasets.
+     * Creates an array of {@link InlineSuggestion}s with the {@code datasets} provided by either
+     * regular/augmented autofill services.
      */
     @Nullable
-    public static SparseArray<Pair<Dataset, InlineSuggestion>> createAutofillInlineSuggestions(
-            @NonNull InlineSuggestionsRequest request, int requestId,
+    public static SparseArray<Pair<Dataset, InlineSuggestion>> createInlineSuggestions(
+            @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo,
+            @NonNull @InlineSuggestionInfo.Source String suggestionSource,
             @NonNull List<Dataset> datasets,
-            @NonNull AutofillId autofillId,
-            @NonNull AutoFillUI.AutoFillUiCallback client, @NonNull Runnable onErrorCallback,
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
-            int userId, int sessionId) {
-        if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
-        final Consumer<IntentSender> intentSenderConsumer = (intentSender) ->
-                client.startIntentSender(intentSender, new Intent());
-        final BiConsumer<Dataset, Integer> onClickFactory = (dataset, datasetIndex) -> {
-            client.fill(requestId, datasetIndex, dataset);
-        };
+            @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) {
+        if (sDebug) Slog.d(TAG, "createInlineSuggestions(source=" + suggestionSource + ") called");
 
-        return createInlineSuggestionsInternal(/* isAugmented= */ false, request,
-                datasets, autofillId,
-                onErrorCallback, onClickFactory, intentSenderConsumer, remoteRenderService, userId,
-                sessionId);
-    }
-
-    /**
-     * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by augmented
-     * autofill service.
-     */
-    @Nullable
-    public static SparseArray<Pair<Dataset, InlineSuggestion>>
-            createAugmentedAutofillInlineSuggestions(
-            @NonNull InlineSuggestionsRequest request, @NonNull List<Dataset> datasets,
-            @NonNull AutofillId autofillId,
-            @NonNull InlineFillUi.InlineSuggestionUiCallback inlineSuggestionUiCallback,
-            @NonNull Runnable onErrorCallback,
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
-            int userId, int sessionId) {
-        if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
-        return createInlineSuggestionsInternal(/* isAugmented= */ true, request,
-                datasets, autofillId, onErrorCallback,
-                (dataset, datasetIndex) ->
-                        inlineSuggestionUiCallback.autofill(dataset, datasetIndex),
-                (intentSender) ->
-                        inlineSuggestionUiCallback.startIntentSender(intentSender, new Intent()),
-                remoteRenderService, userId, sessionId);
-    }
-
-    @Nullable
-    private static SparseArray<Pair<Dataset, InlineSuggestion>> createInlineSuggestionsInternal(
-            boolean isAugmented, @NonNull InlineSuggestionsRequest request,
-            @NonNull List<Dataset> datasets, @NonNull AutofillId autofillId,
-            @NonNull Runnable onErrorCallback, @NonNull BiConsumer<Dataset, Integer> onClickFactory,
-            @NonNull Consumer<IntentSender> intentSenderConsumer,
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
-            int userId, int sessionId) {
+        final InlineSuggestionsRequest request = inlineFillUiInfo.mInlineRequest;
         SparseArray<Pair<Dataset, InlineSuggestion>> response = new SparseArray<>(datasets.size());
         for (int datasetIndex = 0; datasetIndex < datasets.size(); datasetIndex++) {
             final Dataset dataset = datasets.get(datasetIndex);
-            final int fieldIndex = dataset.getFieldIds().indexOf(autofillId);
+            final int fieldIndex = dataset.getFieldIds().indexOf(inlineFillUiInfo.mFocusedId);
             if (fieldIndex < 0) {
-                Slog.w(TAG, "AutofillId=" + autofillId + " not found in dataset");
+                Slog.w(TAG, "AutofillId=" + inlineFillUiInfo.mFocusedId + " not found in dataset");
                 continue;
             }
-            final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation(
-                    fieldIndex);
+
+            final InlinePresentation inlinePresentation =
+                    dataset.getFieldInlinePresentation(fieldIndex);
             if (inlinePresentation == null) {
                 Slog.w(TAG, "InlinePresentation not found in dataset");
                 continue;
             }
-            InlineSuggestion inlineSuggestion = createInlineSuggestion(isAugmented, dataset,
-                    datasetIndex,
+
+            final String suggestionType =
+                    dataset.getAuthentication() == null ? InlineSuggestionInfo.TYPE_SUGGESTION
+                            : InlineSuggestionInfo.TYPE_ACTION;
+            final int index = datasetIndex;
+
+            InlineSuggestion inlineSuggestion = createInlineSuggestion(
+                    inlineFillUiInfo, suggestionSource, suggestionType,
+                    () -> uiCallback.autofill(dataset, index),
                     mergedInlinePresentation(request, datasetIndex, inlinePresentation),
-                    onClickFactory, remoteRenderService, userId, sessionId,
-                    onErrorCallback, intentSenderConsumer,
-                    request.getHostInputToken(), request.getHostDisplayId());
+                    uiCallback);
             response.append(datasetIndex, Pair.create(dataset, inlineSuggestion));
         }
+
         return response;
     }
 
-    private static InlineSuggestion createInlineSuggestion(boolean isAugmented,
-            @NonNull Dataset dataset, int datasetIndex,
+    private static InlineSuggestion createInlineSuggestion(
+            @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo,
+            @NonNull @InlineSuggestionInfo.Source String suggestionSource,
+            @NonNull @InlineSuggestionInfo.Type String suggestionType,
+            @NonNull Runnable onClickAction,
             @NonNull InlinePresentation inlinePresentation,
-            @NonNull BiConsumer<Dataset, Integer> onClickFactory,
-            @NonNull RemoteInlineSuggestionRenderService remoteRenderService,
-            int userId, int sessionId,
-            @NonNull Runnable onErrorCallback, @NonNull Consumer<IntentSender> intentSenderConsumer,
-            @Nullable IBinder hostInputToken,
-            int displayId) {
-        final String suggestionSource = isAugmented ? InlineSuggestionInfo.SOURCE_PLATFORM
-                : InlineSuggestionInfo.SOURCE_AUTOFILL;
-        final String suggestionType =
-                dataset.getAuthentication() == null ? InlineSuggestionInfo.TYPE_SUGGESTION
-                        : InlineSuggestionInfo.TYPE_ACTION;
+            @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) {
         final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
                 inlinePresentation.getInlinePresentationSpec(), suggestionSource,
                 inlinePresentation.getAutofillHints(), suggestionType,
                 inlinePresentation.isPinned());
 
-        final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo,
-                createInlineContentProvider(inlinePresentation,
-                        () -> onClickFactory.accept(dataset, datasetIndex), onErrorCallback,
-                        intentSenderConsumer, remoteRenderService, userId, sessionId,
-                        hostInputToken, displayId));
-
-        return inlineSuggestion;
-    }
-
-    private static InlineSuggestion createInlineAuthSuggestion(
-            @NonNull InlinePresentation inlinePresentation,
-            @NonNull RemoteInlineSuggestionRenderService remoteRenderService,
-            int userId, int sessionId,
-            @NonNull BiConsumer<Dataset, Integer> onClickFactory, @NonNull Runnable onErrorCallback,
-            @NonNull Consumer<IntentSender> intentSenderConsumer,
-            @Nullable IBinder hostInputToken, int displayId) {
-        final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
-                inlinePresentation.getInlinePresentationSpec(),
-                InlineSuggestionInfo.SOURCE_AUTOFILL, inlinePresentation.getAutofillHints(),
-                InlineSuggestionInfo.TYPE_ACTION, inlinePresentation.isPinned());
-
         return new InlineSuggestion(inlineSuggestionInfo,
-                createInlineContentProvider(inlinePresentation,
-                        () -> onClickFactory.accept(null,
-                                AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED),
-                        onErrorCallback, intentSenderConsumer, remoteRenderService, userId,
-                        sessionId, hostInputToken, displayId));
+                createInlineContentProvider(inlineFillUiInfo, inlinePresentation,
+                        onClickAction, uiCallback));
     }
 
     /**
@@ -216,25 +130,20 @@
                 inlinePresentation.getInlinePresentationSpec().getMinSize(),
                 inlinePresentation.getInlinePresentationSpec().getMaxSize()).setStyle(
                 specFromHost.getStyle()).build();
+
         return new InlinePresentation(inlinePresentation.getSlice(), mergedInlinePresentation,
                 inlinePresentation.isPinned());
     }
 
     private static IInlineContentProvider createInlineContentProvider(
+            @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo,
             @NonNull InlinePresentation inlinePresentation, @Nullable Runnable onClickAction,
-            @NonNull Runnable onErrorCallback,
-            @NonNull Consumer<IntentSender> intentSenderConsumer,
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
-            int userId, int sessionId,
-            @Nullable IBinder hostInputToken,
-            int displayId) {
-        RemoteInlineSuggestionViewConnector
-                remoteInlineSuggestionViewConnector = new RemoteInlineSuggestionViewConnector(
-                remoteRenderService, userId, sessionId, inlinePresentation, hostInputToken,
-                displayId, onClickAction, onErrorCallback, intentSenderConsumer);
-        InlineContentProviderImpl inlineContentProvider = new InlineContentProviderImpl(
-                remoteInlineSuggestionViewConnector, null);
-        return inlineContentProvider;
+            @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) {
+        RemoteInlineSuggestionViewConnector remoteInlineSuggestionViewConnector =
+                new RemoteInlineSuggestionViewConnector(inlineFillUiInfo, inlinePresentation,
+                        onClickAction, uiCallback);
+
+        return new InlineContentProviderImpl(remoteInlineSuggestionViewConnector, null);
     }
 
     private InlineSuggestionFactory() {
diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
index 7257255..46d435d 100644
--- a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
+++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
@@ -57,24 +57,20 @@
     private final Consumer<IntentSender> mStartIntentSenderFromClientApp;
 
     RemoteInlineSuggestionViewConnector(
-            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
-            int userId, int sessionId,
+            @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo,
             @NonNull InlinePresentation inlinePresentation,
-            @Nullable IBinder hostInputToken,
-            int displayId,
             @NonNull Runnable onAutofillCallback,
-            @NonNull Runnable onErrorCallback,
-            @NonNull Consumer<IntentSender> startIntentSenderFromClientApp) {
-        mRemoteRenderService = remoteRenderService;
+            @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) {
+        mRemoteRenderService = inlineFillUiInfo.mRemoteRenderService;
         mInlinePresentation = inlinePresentation;
-        mHostInputToken = hostInputToken;
-        mDisplayId = displayId;
-        mUserId = userId;
-        mSessionId = sessionId;
+        mHostInputToken = inlineFillUiInfo.mInlineRequest.getHostInputToken();
+        mDisplayId = inlineFillUiInfo.mInlineRequest.getHostDisplayId();
+        mUserId = inlineFillUiInfo.mUserId;
+        mSessionId = inlineFillUiInfo.mSessionId;
 
         mOnAutofillCallback = onAutofillCallback;
-        mOnErrorCallback = onErrorCallback;
-        mStartIntentSenderFromClientApp = startIntentSenderFromClientApp;
+        mOnErrorCallback = uiCallback::onError;
+        mStartIntentSenderFromClientApp = uiCallback::startIntentSender;
     }
 
     /**
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index c9e5d01..6ca99d1 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -83,6 +83,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.DumpUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
@@ -484,6 +485,10 @@
         public void dump(@NonNull FileDescriptor fd,
                 @NonNull PrintWriter fout,
                 @Nullable String[] args) {
+            if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), LOG_TAG, fout)) {
+                return;
+            }
+
             fout.append("Companion Device Associations:").append('\n');
             synchronized (mLock) {
                 forEach(mCachedAssociations, a -> {
diff --git a/services/core/java/android/os/UserManagerInternal.java b/services/core/java/android/os/UserManagerInternal.java
index 61e8128..76b5ba5 100644
--- a/services/core/java/android/os/UserManagerInternal.java
+++ b/services/core/java/android/os/UserManagerInternal.java
@@ -227,6 +227,14 @@
     public abstract @NonNull List<UserInfo> getUsers(boolean excludeDying);
 
     /**
+     * Internal implementation of getUsers does not check permissions.
+     * This improves performance for calls from inside system server which already have permissions
+     * checked.
+     */
+    public abstract @NonNull List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying,
+            boolean excludePreCreated);
+
+    /**
      * Checks if the {@code callingUserId} and {@code targetUserId} are same or in same group
      * and that the {@code callingUserId} is not a profile and {@code targetUserId} is enabled.
      *
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 0d79240..4e405cc 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -418,7 +418,8 @@
             if (BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED.equals(action)) {
                 String newName = intent.getStringExtra(BluetoothAdapter.EXTRA_LOCAL_NAME);
                 if (DBG) {
-                    Slog.d(TAG, "Bluetooth Adapter name changed to " + newName);
+                    Slog.d(TAG, "Bluetooth Adapter name changed to " + newName + " by "
+                            + mContext.getPackageName());
                 }
                 if (newName != null) {
                     storeNameAndAddress(newName, null);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 2522e93..ba2e147 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2145,11 +2145,11 @@
             if (validatePhoneId(phoneId)) {
                 mOutgoingSmsEmergencyNumber[phoneId] = emergencyNumber;
                 for (Record r : mRecords) {
+                    // Send to all listeners regardless of subscription
                     if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS)
-                                    && idMatch(r.subId, subId, phoneId)) {
+                            PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS)) {
                         try {
-                            r.callback.onOutgoingEmergencySms(emergencyNumber);
+                            r.callback.onOutgoingEmergencySms(emergencyNumber, subId);
                         } catch (RemoteException ex) {
                             mRemoveList.add(r.binder);
                         }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 06f44b1..a80111c 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -6247,7 +6247,7 @@
 
             int[] users;
             if (userId == UserHandle.USER_ALL) {
-                // TODO(b/157921703): this call is returning all users, not just live ones - we
+                // TODO(b/162888972): this call is returning all users, not just live ones - we
                 // need to either fix the method called, or rename the variable
                 List<UserInfo> liveUsers = UserManager.get(mContext).getUsers();
 
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 7175489..99dc58e 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -256,8 +256,8 @@
     @GuardedBy("this")
     private final Set<UidRange> mBlockedUidsAsToldToNetd = new ArraySet<>();
 
-    // Handle of the user initiating VPN.
-    private final int mUserHandle;
+    // The user id of initiating VPN.
+    private final int mUserId;
 
     interface RetryScheduler {
         void checkInterruptAndDelay(boolean sleepLonger) throws InterruptedException;
@@ -384,26 +384,26 @@
     }
 
     public Vpn(Looper looper, Context context, INetworkManagementService netService,
-            @UserIdInt int userHandle, @NonNull KeyStore keyStore) {
-        this(looper, context, new Dependencies(), netService, userHandle, keyStore,
+            @UserIdInt int userId, @NonNull KeyStore keyStore) {
+        this(looper, context, new Dependencies(), netService, userId, keyStore,
                 new SystemServices(context), new Ikev2SessionCreator());
     }
 
     @VisibleForTesting
     protected Vpn(Looper looper, Context context, Dependencies deps,
             INetworkManagementService netService,
-            int userHandle, @NonNull KeyStore keyStore, SystemServices systemServices,
+            int userId, @NonNull KeyStore keyStore, SystemServices systemServices,
             Ikev2SessionCreator ikev2SessionCreator) {
         mContext = context;
         mDeps = deps;
         mNetd = netService;
-        mUserHandle = userHandle;
+        mUserId = userId;
         mLooper = looper;
         mSystemServices = systemServices;
         mIkev2SessionCreator = ikev2SessionCreator;
 
         mPackage = VpnConfig.LEGACY_VPN;
-        mOwnerUID = getAppUid(mPackage, mUserHandle);
+        mOwnerUID = getAppUid(mPackage, mUserId);
         mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(mPackage);
 
         try {
@@ -613,7 +613,7 @@
         PackageManager pm = mContext.getPackageManager();
         ApplicationInfo appInfo = null;
         try {
-            appInfo = pm.getApplicationInfoAsUser(packageName, 0 /*flags*/, mUserHandle);
+            appInfo = pm.getApplicationInfoAsUser(packageName, 0 /*flags*/, mUserId);
         } catch (NameNotFoundException unused) {
             Log.w(TAG, "Can't find \"" + packageName + "\" when checking always-on support");
         }
@@ -624,7 +624,7 @@
         final Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
         intent.setPackage(packageName);
         List<ResolveInfo> services =
-                pm.queryIntentServicesAsUser(intent, PackageManager.GET_META_DATA, mUserHandle);
+                pm.queryIntentServicesAsUser(intent, PackageManager.GET_META_DATA, mUserId);
         if (services == null || services.size() == 0) {
             return false;
         }
@@ -769,12 +769,12 @@
         final long token = Binder.clearCallingIdentity();
         try {
             mSystemServices.settingsSecurePutStringForUser(Settings.Secure.ALWAYS_ON_VPN_APP,
-                    getAlwaysOnPackage(), mUserHandle);
+                    getAlwaysOnPackage(), mUserId);
             mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
-                    (mAlwaysOn && mLockdown ? 1 : 0), mUserHandle);
+                    (mAlwaysOn && mLockdown ? 1 : 0), mUserId);
             mSystemServices.settingsSecurePutStringForUser(
                     LOCKDOWN_ALLOWLIST_SETTING_NAME,
-                    String.join(",", mLockdownAllowlist), mUserHandle);
+                    String.join(",", mLockdownAllowlist), mUserId);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -786,11 +786,11 @@
         final long token = Binder.clearCallingIdentity();
         try {
             final String alwaysOnPackage = mSystemServices.settingsSecureGetStringForUser(
-                    Settings.Secure.ALWAYS_ON_VPN_APP, mUserHandle);
+                    Settings.Secure.ALWAYS_ON_VPN_APP, mUserId);
             final boolean alwaysOnLockdown = mSystemServices.settingsSecureGetIntForUser(
-                    Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, 0 /*default*/, mUserHandle) != 0;
+                    Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, 0 /*default*/, mUserId) != 0;
             final String allowlistString = mSystemServices.settingsSecureGetStringForUser(
-                    LOCKDOWN_ALLOWLIST_SETTING_NAME, mUserHandle);
+                    LOCKDOWN_ALLOWLIST_SETTING_NAME, mUserId);
             final List<String> allowedPackages = TextUtils.isEmpty(allowlistString)
                     ? Collections.emptyList() : Arrays.asList(allowlistString.split(","));
             setAlwaysOnPackageInternal(
@@ -850,13 +850,13 @@
             DeviceIdleInternal idleController =
                     LocalServices.getService(DeviceIdleInternal.class);
             idleController.addPowerSaveTempWhitelistApp(Process.myUid(), alwaysOnPackage,
-                    VPN_LAUNCH_IDLE_ALLOWLIST_DURATION_MS, mUserHandle, false, "vpn");
+                    VPN_LAUNCH_IDLE_ALLOWLIST_DURATION_MS, mUserId, false, "vpn");
 
             // Start the VPN service declared in the app's manifest.
             Intent serviceIntent = new Intent(VpnConfig.SERVICE_INTERFACE);
             serviceIntent.setPackage(alwaysOnPackage);
             try {
-                return mContext.startServiceAsUser(serviceIntent, UserHandle.of(mUserHandle)) != null;
+                return mContext.startServiceAsUser(serviceIntent, UserHandle.of(mUserId)) != null;
             } catch (RuntimeException e) {
                 Log.e(TAG, "VpnService " + serviceIntent + " failed to start", e);
                 return false;
@@ -958,7 +958,7 @@
         // We can't just check that packageName matches mPackage, because if the app was uninstalled
         // and reinstalled it will no longer be prepared. Similarly if there is a shared UID, the
         // calling package may not be the same as the prepared package. Check both UID and package.
-        return getAppUid(packageName, mUserHandle) == mOwnerUID && mPackage.equals(packageName);
+        return getAppUid(packageName, mUserId) == mOwnerUID && mPackage.equals(packageName);
     }
 
     /** Prepare the VPN for the given package. Does not perform permission checks. */
@@ -998,7 +998,7 @@
 
             Log.i(TAG, "Switched from " + mPackage + " to " + newPackage);
             mPackage = newPackage;
-            mOwnerUID = getAppUid(newPackage, mUserHandle);
+            mOwnerUID = getAppUid(newPackage, mUserId);
             mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(newPackage);
             try {
                 mNetd.allowProtect(mOwnerUID);
@@ -1019,7 +1019,7 @@
         // Check if the caller is authorized.
         enforceControlPermissionOrInternalCaller();
 
-        final int uid = getAppUid(packageName, mUserHandle);
+        final int uid = getAppUid(packageName, mUserId);
         if (uid == -1 || VpnConfig.LEGACY_VPN.equals(packageName)) {
             // Authorization for nonexistent packages (or fake ones) can't be updated.
             return false;
@@ -1095,14 +1095,14 @@
                 || isVpnServicePreConsented(context, packageName);
     }
 
-    private int getAppUid(final String app, final int userHandle) {
+    private int getAppUid(final String app, final int userId) {
         if (VpnConfig.LEGACY_VPN.equals(app)) {
             return Process.myUid();
         }
         PackageManager pm = mContext.getPackageManager();
         return Binder.withCleanCallingIdentity(() -> {
             try {
-                return pm.getPackageUidAsUser(app, userHandle);
+                return pm.getPackageUidAsUser(app, userId);
             } catch (NameNotFoundException e) {
                 return -1;
             }
@@ -1116,7 +1116,7 @@
         PackageManager pm = mContext.getPackageManager();
         try {
             ApplicationInfo appInfo =
-                    pm.getApplicationInfoAsUser(packageName, 0 /*flags*/, mUserHandle);
+                    pm.getApplicationInfoAsUser(packageName, 0 /*flags*/, mUserId);
             return appInfo.targetSdkVersion >= VERSION_CODES.Q;
         } catch (NameNotFoundException unused) {
             Log.w(TAG, "Can't find \"" + packageName + "\"");
@@ -1241,7 +1241,7 @@
 
         mNetworkCapabilities.setOwnerUid(mOwnerUID);
         mNetworkCapabilities.setAdministratorUids(new int[] {mOwnerUID});
-        mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserHandle,
+        mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserId,
                 mConfig.allowedApplications, mConfig.disallowedApplications));
         long token = Binder.clearCallingIdentity();
         try {
@@ -1315,7 +1315,7 @@
             enforceNotRestrictedUser();
 
             ResolveInfo info = AppGlobals.getPackageManager().resolveService(intent,
-                    null, 0, mUserHandle);
+                    null, 0, mUserId);
             if (info == null) {
                 throw new SecurityException("Cannot find " + config.user);
             }
@@ -1352,7 +1352,7 @@
             Connection connection = new Connection();
             if (!mContext.bindServiceAsUser(intent, connection,
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
-                    new UserHandle(mUserHandle))) {
+                    new UserHandle(mUserId))) {
                 throw new IllegalStateException("Cannot bind " + config.user);
             }
 
@@ -1427,10 +1427,10 @@
     }
 
     // Note: Return type guarantees results are deduped and sorted, which callers require.
-    private SortedSet<Integer> getAppsUids(List<String> packageNames, int userHandle) {
+    private SortedSet<Integer> getAppsUids(List<String> packageNames, int userId) {
         SortedSet<Integer> uids = new TreeSet<>();
         for (String app : packageNames) {
-            int uid = getAppUid(app, userHandle);
+            int uid = getAppUid(app, userId);
             if (uid != -1) uids.add(uid);
         }
         return uids;
@@ -1444,22 +1444,22 @@
      * the UID ranges will match the app list specified there. Otherwise, all UIDs
      * in each user and profile will be included.
      *
-     * @param userHandle The userId to create UID ranges for along with any of its restricted
+     * @param userId The userId to create UID ranges for along with any of its restricted
      *                   profiles.
      * @param allowedApplications (optional) List of applications to allow.
      * @param disallowedApplications (optional) List of applications to deny.
      */
     @VisibleForTesting
-    Set<UidRange> createUserAndRestrictedProfilesRanges(@UserIdInt int userHandle,
+    Set<UidRange> createUserAndRestrictedProfilesRanges(@UserIdInt int userId,
             @Nullable List<String> allowedApplications,
             @Nullable List<String> disallowedApplications) {
         final Set<UidRange> ranges = new ArraySet<>();
 
         // Assign the top-level user to the set of ranges
-        addUserToRanges(ranges, userHandle, allowedApplications, disallowedApplications);
+        addUserToRanges(ranges, userId, allowedApplications, disallowedApplications);
 
         // If the user can have restricted profiles, assign all its restricted profiles too
-        if (canHaveRestrictedProfile(userHandle)) {
+        if (canHaveRestrictedProfile(userId)) {
             final long token = Binder.clearCallingIdentity();
             List<UserInfo> users;
             try {
@@ -1468,7 +1468,7 @@
                 Binder.restoreCallingIdentity(token);
             }
             for (UserInfo user : users) {
-                if (user.isRestricted() && (user.restrictedProfileParentId == userHandle)) {
+                if (user.isRestricted() && (user.restrictedProfileParentId == userId)) {
                     addUserToRanges(ranges, user.id, allowedApplications, disallowedApplications);
                 }
             }
@@ -1485,18 +1485,18 @@
      * in the user will be included.
      *
      * @param ranges {@link Set} of {@link UidRange}s to which to add.
-     * @param userHandle The userId to add to {@param ranges}.
+     * @param userId The userId to add to {@param ranges}.
      * @param allowedApplications (optional) allowlist of applications to include.
      * @param disallowedApplications (optional) denylist of applications to exclude.
      */
     @VisibleForTesting
-    void addUserToRanges(@NonNull Set<UidRange> ranges, @UserIdInt int userHandle,
+    void addUserToRanges(@NonNull Set<UidRange> ranges, @UserIdInt int userId,
             @Nullable List<String> allowedApplications,
             @Nullable List<String> disallowedApplications) {
         if (allowedApplications != null) {
             // Add ranges covering all UIDs for allowedApplications.
             int start = -1, stop = -1;
-            for (int uid : getAppsUids(allowedApplications, userHandle)) {
+            for (int uid : getAppsUids(allowedApplications, userId)) {
                 if (start == -1) {
                     start = uid;
                 } else if (uid != stop + 1) {
@@ -1508,9 +1508,9 @@
             if (start != -1) ranges.add(new UidRange(start, stop));
         } else if (disallowedApplications != null) {
             // Add all ranges for user skipping UIDs for disallowedApplications.
-            final UidRange userRange = UidRange.createForUser(userHandle);
+            final UidRange userRange = UidRange.createForUser(userId);
             int start = userRange.start;
-            for (int uid : getAppsUids(disallowedApplications, userHandle)) {
+            for (int uid : getAppsUids(disallowedApplications, userId)) {
                 if (uid == start) {
                     start++;
                 } else {
@@ -1521,16 +1521,16 @@
             if (start <= userRange.stop) ranges.add(new UidRange(start, userRange.stop));
         } else {
             // Add all UIDs for the user.
-            ranges.add(UidRange.createForUser(userHandle));
+            ranges.add(UidRange.createForUser(userId));
         }
     }
 
     // Returns the subset of the full list of active UID ranges the VPN applies to (mVpnUsers) that
-    // apply to userHandle.
-    static private List<UidRange> uidRangesForUser(int userHandle, Set<UidRange> existingRanges) {
+    // apply to userId.
+    private static List<UidRange> uidRangesForUser(int userId, Set<UidRange> existingRanges) {
         // UidRange#createForUser returns the entire range of UIDs available to a macro-user.
         // This is something like 0-99999 ; {@see UserHandle#PER_USER_RANGE}
-        final UidRange userRange = UidRange.createForUser(userHandle);
+        final UidRange userRange = UidRange.createForUser(userId);
         final List<UidRange> ranges = new ArrayList<>();
         for (UidRange range : existingRanges) {
             if (userRange.containsRange(range)) {
@@ -1545,15 +1545,15 @@
      *
      * <p>Should be called on primary ConnectivityService thread.
      */
-    public void onUserAdded(int userHandle) {
+    public void onUserAdded(int userId) {
         // If the user is restricted tie them to the parent user's VPN
-        UserInfo user = UserManager.get(mContext).getUserInfo(userHandle);
-        if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle) {
+        UserInfo user = UserManager.get(mContext).getUserInfo(userId);
+        if (user.isRestricted() && user.restrictedProfileParentId == mUserId) {
             synchronized(Vpn.this) {
                 final Set<UidRange> existingRanges = mNetworkCapabilities.getUids();
                 if (existingRanges != null) {
                     try {
-                        addUserToRanges(existingRanges, userHandle, mConfig.allowedApplications,
+                        addUserToRanges(existingRanges, userId, mConfig.allowedApplications,
                                 mConfig.disallowedApplications);
                         // ConnectivityService will call {@link #updateCapabilities} and apply
                         // those for VPN network.
@@ -1572,16 +1572,16 @@
      *
      * <p>Should be called on primary ConnectivityService thread.
      */
-    public void onUserRemoved(int userHandle) {
+    public void onUserRemoved(int userId) {
         // clean up if restricted
-        UserInfo user = UserManager.get(mContext).getUserInfo(userHandle);
-        if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle) {
+        UserInfo user = UserManager.get(mContext).getUserInfo(userId);
+        if (user.isRestricted() && user.restrictedProfileParentId == mUserId) {
             synchronized(Vpn.this) {
                 final Set<UidRange> existingRanges = mNetworkCapabilities.getUids();
                 if (existingRanges != null) {
                     try {
                         final List<UidRange> removedRanges =
-                            uidRangesForUser(userHandle, existingRanges);
+                                uidRangesForUser(userId, existingRanges);
                         existingRanges.removeAll(removedRanges);
                         // ConnectivityService will call {@link #updateCapabilities} and
                         // apply those for VPN network.
@@ -1639,7 +1639,7 @@
         final Set<UidRange> rangesToTellNetdToAdd;
         if (enforce) {
             final Set<UidRange> rangesThatShouldBeBlocked =
-                    createUserAndRestrictedProfilesRanges(mUserHandle,
+                    createUserAndRestrictedProfilesRanges(mUserId,
                             /* allowedApplications */ null,
                             /* disallowedApplications */ exemptedPackages);
 
@@ -1909,7 +1909,7 @@
     private void updateAlwaysOnNotification(DetailedState networkState) {
         final boolean visible = (mAlwaysOn && networkState != DetailedState.CONNECTED);
 
-        final UserHandle user = UserHandle.of(mUserHandle);
+        final UserHandle user = UserHandle.of(mUserId);
         final long token = Binder.clearCallingIdentity();
         try {
             final NotificationManager notificationManager = NotificationManager.from(mContext);
@@ -2019,7 +2019,7 @@
     private void enforceNotRestrictedUser() {
         Binder.withCleanCallingIdentity(() -> {
             final UserManager mgr = UserManager.get(mContext);
-            final UserInfo user = mgr.getUserInfo(mUserHandle);
+            final UserInfo user = mgr.getUserInfo(mUserId);
 
             if (user.isRestricted()) {
                 throw new SecurityException("Restricted users cannot configure VPNs");
@@ -2054,9 +2054,9 @@
     public void startLegacyVpnPrivileged(VpnProfile profile, KeyStore keyStore,
             LinkProperties egress) {
         UserManager mgr = UserManager.get(mContext);
-        UserInfo user = mgr.getUserInfo(mUserHandle);
+        UserInfo user = mgr.getUserInfo(mUserId);
         if (user.isRestricted() || mgr.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN,
-                    new UserHandle(mUserHandle))) {
+                    new UserHandle(mUserId))) {
             throw new SecurityException("Restricted users cannot establish VPNs");
         }
 
@@ -2984,14 +2984,14 @@
     }
 
     private void verifyCallingUidAndPackage(String packageName) {
-        if (getAppUid(packageName, mUserHandle) != Binder.getCallingUid()) {
+        if (getAppUid(packageName, mUserId) != Binder.getCallingUid()) {
             throw new SecurityException("Mismatched package and UID");
         }
     }
 
     @VisibleForTesting
     String getProfileNameForPackage(String packageName) {
-        return Credentials.PLATFORM_VPN + mUserHandle + "_" + packageName;
+        return Credentials.PLATFORM_VPN + mUserId + "_" + packageName;
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 8468f23..48efa5c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -745,7 +745,7 @@
             info.mode = params.mode;
             info.installReason = params.installReason;
             info.sizeBytes = params.sizeBytes;
-            info.appPackageName = params.appPackageName;
+            info.appPackageName = mPackageName != null ? mPackageName : params.appPackageName;
             if (includeIcon) {
                 info.appIcon = params.appIcon;
             }
@@ -2357,6 +2357,17 @@
                         "Invalid filename: " + targetName);
             }
 
+            // Yell loudly if installers drop attribute installLocation when apps explicitly set.
+            if (apk.installLocation != PackageInfo.INSTALL_LOCATION_UNSPECIFIED) {
+                final String installerPackageName = getInstallerPackageName();
+                if (installerPackageName != null
+                        && (params.installLocation != apk.installLocation)) {
+                    Slog.wtf(TAG, installerPackageName
+                            + " drops manifest attribute android:installLocation in " + targetName
+                            + " for " + mPackageName);
+                }
+            }
+
             final File targetFile = new File(stageDir, targetName);
             resolveAndStageFileLocked(addedFile, targetFile, apk.splitName);
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e1363f9..3ca12ae 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1850,7 +1850,7 @@
                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                     synchronized (mLock) {
                         removeMessages(WRITE_PACKAGE_LIST);
-                        mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
+                        mPermissionManager.writeStateToPackageSettingsTEMP();
                         mSettings.writePackageListLPr(msg.arg1);
                     }
                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
@@ -3095,7 +3095,10 @@
             t.traceEnd();
 
             t.traceBegin("read user settings");
-            mFirstBoot = !mSettings.readLPw(mInjector.getUserManagerInternal().getUsers(false));
+            mFirstBoot = !mSettings.readLPw(mInjector.getUserManagerInternal().getUsers(
+                    /* excludePartial= */ true,
+                    /* excludeDying= */ false,
+                    /* excludePreCreated= */ false));
             t.traceEnd();
 
             // Clean up orphaned packages for which the code path doesn't exist
@@ -3521,7 +3524,7 @@
                     + ((SystemClock.uptimeMillis()-startTime)/1000f)
                     + " seconds");
 
-            mPermissionManager.readPermissionsStateFromPackageSettingsTEMP();
+            mPermissionManager.readStateFromPackageSettingsTEMP();
             // If the platform SDK has changed since the last time we booted,
             // we need to re-grant app permission to catch any new ones that
             // appear.  This is really a hack, and means that apps can in some
@@ -21835,7 +21838,7 @@
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
 
-        mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
+        mPermissionManager.writeStateToPackageSettingsTEMP();
 
         DumpState dumpState = new DumpState();
         boolean fullPreferred = false;
@@ -23715,7 +23718,7 @@
             mDirtyUsers.remove(userId);
             mUserNeedsBadging.delete(userId);
             mPermissionManager.onUserRemoved(userId);
-            mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
+            mPermissionManager.writeStateToPackageSettingsTEMP();
             mSettings.removeUserLPw(userId);
             mPendingBroadcasts.remove(userId);
             mInstantAppRegistry.onUserRemovedLPw(userId);
@@ -23816,9 +23819,9 @@
 
     boolean readPermissionStateForUser(@UserIdInt int userId) {
         synchronized (mPackages) {
-            mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
+            mPermissionManager.writeStateToPackageSettingsTEMP();
             mSettings.readPermissionStateForUserSyncLPr(userId);
-            mPermissionManager.readPermissionsStateFromPackageSettingsTEMP();
+            mPermissionManager.readStateFromPackageSettingsTEMP();
             return mPmInternal.isPermissionUpgradeNeeded(userId);
         }
     }
@@ -25882,12 +25885,12 @@
 
     /**
      * Temporary method that wraps mSettings.writeLPr() and calls
-     * mPermissionManager.writePermissionsStateToPackageSettingsTEMP() beforehand.
+     * mPermissionManager.writeStateToPackageSettingsTEMP() beforehand.
      *
      * TODO(zhanghai): This should be removed once we finish migration of permission storage.
      */
     private void writeSettingsLPrTEMP() {
-        mPermissionManager.writePermissionsStateToPackageSettingsTEMP();
+        mPermissionManager.writeStateToPackageSettingsTEMP();
         mSettings.writeLPr();
     }
 }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f801702..9654307 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1512,6 +1512,7 @@
                     return;
                 }
                 str = new FileInputStream(userPackagesStateFile);
+                if (DEBUG_MU) Log.i(TAG, "Reading " + userPackagesStateFile);
             }
             final XmlPullParser parser = Xml.newPullParser();
             parser.setInput(str, StandardCharsets.UTF_8.name());
@@ -1956,9 +1957,13 @@
 
             serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS);
 
+            if (DEBUG_MU) Log.i(TAG, "Writing " + userPackagesStateFile);
             for (final PackageSetting pkg : mPackages.values()) {
                 final PackageUserState ustate = pkg.readUserState(userId);
-                if (DEBUG_MU) Log.i(TAG, "  pkg=" + pkg.name + ", state=" + ustate.enabled);
+                if (DEBUG_MU) {
+                    Log.i(TAG, "  pkg=" + pkg.name + ", installed=" + ustate.installed
+                            + ", state=" + ustate.enabled);
+                }
 
                 serializer.startTag(null, TAG_PACKAGE);
                 serializer.attribute(null, ATTR_NAME, pkg.name);
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index b12a8f8..d413213 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -164,17 +164,6 @@
         }
     }
 
-    private void updateStoredSession(@NonNull PackageInstallerSession sessionInfo) {
-        synchronized (mStagedSessions) {
-            PackageInstallerSession storedSession = mStagedSessions.get(sessionInfo.sessionId);
-            // storedSession might be null if a call to abortSession was made before the session
-            // is updated.
-            if (storedSession != null) {
-                mStagedSessions.put(sessionInfo.sessionId, sessionInfo);
-            }
-        }
-    }
-
     private void markBootCompleted() {
         mApexManager.markBootCompleted();
     }
@@ -865,7 +854,6 @@
     }
 
     void commitSession(@NonNull PackageInstallerSession session) {
-        updateStoredSession(session);
         mPreRebootVerificationHandler.startPreRebootVerification(session);
     }
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4aaa8a5..0f00579 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -765,8 +765,6 @@
         return null;
     }
 
-    // TODO(b/157921703): replace by getAliveUsers() or remove (so callers
-    // explicitly call the 3-booleans version)
     public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
         return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */
                 true);
@@ -5176,8 +5174,14 @@
 
         @Override
         public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
-            return UserManagerService.this.getUsersInternal(/*excludePartial= */ true,
-                    excludeDying, /* excludePreCreated= */ true);
+            return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */ true);
+        }
+
+        @Override
+        public @NonNull List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying,
+                boolean excludePreCreated) {
+            return UserManagerService.this.getUsersInternal(excludePartial, excludeDying,
+                    excludePreCreated);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 962638b..865b8a1 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -36,6 +36,7 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.DumpState;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.PackageSettingBase;
@@ -139,6 +140,10 @@
         this.perm = perm;
     }
 
+    public boolean hasGids() {
+        return !ArrayUtils.isEmpty(gids);
+    }
+
     public int[] computeGids(int userId) {
         if (perUser) {
             final int[] userGids = new int[gids.length];
@@ -419,9 +424,9 @@
     }
 
     public void enforceDeclaredUsedAndRuntimeOrDevelopment(AndroidPackage pkg,
-            PermissionsState permsState) {
+            UidPermissionState uidState) {
         int index = pkg.getRequestedPermissions().indexOf(name);
-        if (!permsState.hasRequestedPermission(name) && index == -1) {
+        if (!uidState.hasRequestedPermission(name) && index == -1) {
             throw new SecurityException("Package " + pkg.getPackageName()
                     + " has not requested permission " + name);
         }
diff --git a/services/core/java/com/android/server/pm/permission/DevicePermissionState.java b/services/core/java/com/android/server/pm/permission/DevicePermissionState.java
new file mode 100644
index 0000000..b9456acf
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/DevicePermissionState.java
@@ -0,0 +1,77 @@
+/*
+ * 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.pm.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Permission state for this device.
+ */
+public final class DevicePermissionState {
+    @GuardedBy("mLock")
+    @NonNull
+    private final SparseArray<UserPermissionState> mUserStates = new SparseArray<>();
+
+    @NonNull
+    private final Object mLock;
+
+    public DevicePermissionState(@NonNull Object lock) {
+        mLock = lock;
+    }
+
+    @Nullable
+    public UserPermissionState getUserState(@UserIdInt int userId) {
+        synchronized (mLock) {
+            return mUserStates.get(userId);
+        }
+    }
+
+    @NonNull
+    public UserPermissionState getOrCreateUserState(@UserIdInt int userId) {
+        synchronized (mLock) {
+            UserPermissionState userState = mUserStates.get(userId);
+            if (userState == null) {
+                userState = new UserPermissionState(mLock);
+                mUserStates.put(userId, userState);
+            }
+            return userState;
+        }
+    }
+
+    public void removeUserState(@UserIdInt int userId) {
+        synchronized (mLock) {
+            mUserStates.delete(userId);
+        }
+    }
+
+    public int[] getUserIds() {
+        synchronized (mLock) {
+            final int userStatesSize = mUserStates.size();
+            final int[] userIds = new int[userStatesSize];
+            for (int i = 0; i < userStatesSize; i++) {
+                final int userId = mUserStates.keyAt(i);
+                userIds[i] = userId;
+            }
+            return userIds;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 1cfc5b1..2f9e199 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -53,7 +53,7 @@
 import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS;
 import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE;
+import static com.android.server.pm.permission.UidPermissionState.PERMISSION_OPERATION_FAILURE;
 
 import static java.util.concurrent.TimeUnit.SECONDS;
 
@@ -148,12 +148,9 @@
 import com.android.server.pm.permission.PermissionManagerServiceInternal.DefaultDialerProvider;
 import com.android.server.pm.permission.PermissionManagerServiceInternal.DefaultHomeProvider;
 import com.android.server.pm.permission.PermissionManagerServiceInternal.PermissionCallback;
-import com.android.server.pm.permission.PermissionsState.PermissionState;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.SoftRestrictedPermissionPolicy;
 
-import libcore.util.EmptyArray;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -226,8 +223,8 @@
     /** Internal connection to the user manager */
     private final UserManagerInternal mUserManagerInt;
 
-    /** Maps from App ID to PermissionsState */
-    private final SparseArray<PermissionsState> mAppIdStates = new SparseArray<>();
+    @NonNull
+    private final DevicePermissionState mState;
 
     /** Permission controller: User space permission management */
     private PermissionControllerManager mPermissionControllerManager;
@@ -395,6 +392,7 @@
         mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
         mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
         mSettings = new PermissionSettings(mLock);
+        mState = new DevicePermissionState(mLock);
         mAppOpsManager = context.getSystemService(AppOpsManager.class);
 
         mHandlerThread = new ServiceThread(TAG,
@@ -681,12 +679,12 @@
         if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
             return 0;
         }
-        final PermissionsState permissionsState = getPermissionsState(pkg);
-        if (permissionsState == null) {
-            Slog.e(TAG, "Missing permissions state for " + packageName);
+        final UidPermissionState uidState = getUidState(pkg, userId);
+        if (uidState == null) {
+            Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
             return 0;
         }
-        return permissionsState.getPermissionFlags(permName, userId);
+        return uidState.getPermissionFlags(permName);
     }
 
     @Override
@@ -788,14 +786,13 @@
             throw new IllegalArgumentException("Unknown permission: " + permName);
         }
 
-        final PermissionsState permissionsState = getPermissionsState(pkg);
-        if (permissionsState == null) {
-            Slog.e(TAG, "Missing permissions state for " + packageName);
+        final UidPermissionState uidState = getUidState(pkg, userId);
+        if (uidState == null) {
+            Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
             return;
         }
 
-        final boolean hadState =
-                permissionsState.getRuntimePermissionState(permName, userId) != null;
+        final boolean hadState = uidState.getPermissionState(permName) != null;
         if (!hadState) {
             boolean isRequested = false;
             // Fast path, the current package has requested the permission.
@@ -822,20 +819,18 @@
             }
         }
         final boolean permissionUpdated =
-                permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues);
+                uidState.updatePermissionFlags(bp, flagMask, flagValues);
         if (permissionUpdated && bp.isRuntime()) {
             notifyRuntimePermissionStateChanged(packageName, userId);
         }
         if (permissionUpdated && callback != null) {
             // Install and runtime permissions are stored in different places,
             // so figure out what permission changed and persist the change.
-            if (permissionsState.getInstallPermissionState(permName) != null) {
+            if (!bp.isRuntime()) {
                 int userUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
                 callback.onInstallPermissionUpdatedNotifyListener(userUid);
-            } else if (permissionsState.getRuntimePermissionState(permName, userId) != null
-                    || hadState) {
-                callback.onPermissionUpdatedNotifyListener(new int[]{userId}, false,
-                        pkg.getUid());
+            } else {
+                callback.onPermissionUpdatedNotifyListener(new int[]{userId}, false, pkg.getUid());
             }
         }
     }
@@ -868,13 +863,14 @@
 
         final boolean[] changed = new boolean[1];
         mPackageManagerInt.forEachPackage(pkg -> {
-            final PermissionsState permissionsState = getPermissionsState(pkg);
-            if (permissionsState == null) {
-                Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+            final UidPermissionState uidState = getUidState(pkg, userId);
+            if (uidState == null) {
+                Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+                        + userId);
                 return;
             }
-            changed[0] |= permissionsState.updatePermissionFlagsForAllPermissions(
-                    userId, effectiveFlagMask, effectiveFlagValues);
+            changed[0] |= uidState.updatePermissionFlagsForAllPermissions(
+                    effectiveFlagMask, effectiveFlagValues);
             mOnPermissionChangeListeners.onPermissionsChanged(pkg.getUid());
         });
 
@@ -926,19 +922,20 @@
         }
 
         final int uid = UserHandle.getUid(userId, pkg.getUid());
-        final PermissionsState permissionsState = getPermissionsState(pkg);
-        if (permissionsState == null) {
-            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+        final UidPermissionState uidState = getUidState(pkg, userId);
+        if (uidState == null) {
+            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+                    + userId);
             return PackageManager.PERMISSION_DENIED;
         }
 
-        if (checkSinglePermissionInternal(uid, permissionsState, permissionName)) {
+        if (checkSinglePermissionInternal(uid, uidState, permissionName)) {
             return PackageManager.PERMISSION_GRANTED;
         }
 
         final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
         if (fullerPermissionName != null
-                && checkSinglePermissionInternal(uid, permissionsState, fullerPermissionName)) {
+                && checkSinglePermissionInternal(uid, uidState, fullerPermissionName)) {
             return PackageManager.PERMISSION_GRANTED;
         }
 
@@ -946,8 +943,8 @@
     }
 
     private boolean checkSinglePermissionInternal(int uid,
-            @NonNull PermissionsState permissionsState, @NonNull String permissionName) {
-        if (!permissionsState.hasPermission(permissionName, UserHandle.getUserId(uid))) {
+            @NonNull UidPermissionState uidState, @NonNull String permissionName) {
+        if (!uidState.hasPermission(permissionName)) {
             return false;
         }
 
@@ -1141,9 +1138,9 @@
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            final PermissionsState permissionsState = getPermissionsState(pkg);
-            if (permissionsState == null) {
-                Slog.e(TAG, "Missing permissions state for " + packageName);
+            final UidPermissionState uidState = getUidState(pkg, userId);
+            if (uidState == null) {
+                Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
                 return null;
             }
 
@@ -1164,7 +1161,7 @@
             for (int i = 0; i < permissionCount; i++) {
                 final String permissionName = pkg.getRequestedPermissions().get(i);
                 final int currentFlags =
-                        permissionsState.getPermissionFlags(permissionName, userId);
+                        uidState.getPermissionFlags(permissionName);
                 if ((currentFlags & queryFlags) != 0) {
                     if (whitelistedPermissions == null) {
                         whitelistedPermissions = new ArrayList<>();
@@ -1453,13 +1450,14 @@
             throw new IllegalArgumentException("Unknown package: " + packageName);
         }
 
-        final PermissionsState permissionsState = getPermissionsState(pkg);
-        if (permissionsState == null) {
-            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+        final UidPermissionState uidState = getUidState(pkg, userId);
+        if (uidState == null) {
+            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+                    + userId);
             return;
         }
 
-        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, permissionsState);
+        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, uidState);
 
         // If a permission review is required for legacy apps we represent
         // their permissions as always granted runtime ones since we need
@@ -1472,7 +1470,7 @@
 
         final int uid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
 
-        final int flags = permissionsState.getPermissionFlags(permName, userId);
+        final int flags = uidState.getPermissionFlags(permName);
         if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
             Log.e(TAG, "Cannot grant system fixed permission "
                     + permName + " for package " + packageName);
@@ -1502,8 +1500,9 @@
         if (bp.isDevelopment()) {
             // Development permissions must be handled specially, since they are not
             // normal runtime permissions.  For now they apply to all users.
-            if (permissionsState.grantInstallPermission(bp)
-                    != PERMISSION_OPERATION_FAILURE) {
+            // TODO(zhanghai): We are breaking the behavior above by making all permission state
+            //  per-user. It isn't documented behavior and relatively rarely used anyway.
+            if (uidState.grantPermission(bp) != PERMISSION_OPERATION_FAILURE) {
                 if (callback != null) {
                     callback.onInstallPermissionGranted();
                 }
@@ -1521,13 +1520,13 @@
             return;
         }
 
-        final int result = permissionsState.grantRuntimePermission(bp, userId);
+        final int result = uidState.grantPermission(bp);
         switch (result) {
             case PERMISSION_OPERATION_FAILURE: {
                 return;
             }
 
-            case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
+            case UidPermissionState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
                 if (callback != null) {
                     callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId);
                 }
@@ -1617,13 +1616,14 @@
             throw new IllegalArgumentException("Unknown permission: " + permName);
         }
 
-        final PermissionsState permissionsState = getPermissionsState(pkg);
-        if (permissionsState == null) {
-            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+        final UidPermissionState uidState = getUidState(pkg, userId);
+        if (uidState == null) {
+            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+                    + userId);
             return;
         }
 
-        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, permissionsState);
+        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, uidState);
 
         // If a permission review is required for legacy apps we represent
         // their permissions as always granted runtime ones since we need
@@ -1634,7 +1634,7 @@
             return;
         }
 
-        final int flags = permissionsState.getPermissionFlags(permName, userId);
+        final int flags = uidState.getPermissionFlags(permName);
         // Only the system may revoke SYSTEM_FIXED permissions.
         if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
                 && UserHandle.getCallingAppId() != Process.SYSTEM_UID) {
@@ -1649,8 +1649,9 @@
         if (bp.isDevelopment()) {
             // Development permissions must be handled specially, since they are not
             // normal runtime permissions.  For now they apply to all users.
-            if (permissionsState.revokeInstallPermission(bp)
-                    != PERMISSION_OPERATION_FAILURE) {
+            // TODO(zhanghai): We are breaking the behavior above by making all permission state
+            //  per-user. It isn't documented behavior and relatively rarely used anyway.
+            if (uidState.revokePermission(bp) != PERMISSION_OPERATION_FAILURE) {
                 if (callback != null) {
                     mDefaultPermissionCallback.onInstallPermissionRevoked();
                 }
@@ -1659,12 +1660,11 @@
         }
 
         // Permission is already revoked, no need to do anything.
-        if (!permissionsState.hasRuntimePermission(permName, userId)) {
+        if (!uidState.hasPermission(permName)) {
             return;
         }
 
-        if (permissionsState.revokeRuntimePermission(bp, userId)
-                == PERMISSION_OPERATION_FAILURE) {
+        if (uidState.revokePermission(bp) == PERMISSION_OPERATION_FAILURE) {
             return;
         }
 
@@ -2466,19 +2466,7 @@
 
     private void onUserRemoved(@UserIdInt int userId) {
         synchronized (mLock) {
-            final int appIdStatesSize = mAppIdStates.size();
-            for (int i = 0; i < appIdStatesSize; i++) {
-                PermissionsState permissionsState = mAppIdStates.valueAt(i);
-                for (PermissionState permissionState
-                        : permissionsState.getRuntimePermissionStates(userId)) {
-                    BasePermission bp = mSettings.getPermission(permissionState.getName());
-                    if (bp != null) {
-                        permissionsState.revokeRuntimePermission(bp, userId);
-                        permissionsState.updatePermissionFlags(bp, userId,
-                                PackageManager.MASK_PERMISSION_FLAGS_ALL, 0);
-                    }
-                }
-            }
+            mState.removeUserState(userId);
         }
     }
 
@@ -2489,19 +2477,18 @@
         if (ps == null) {
             return Collections.emptySet();
         }
-        final PermissionsState permissionsState = getPermissionsState(ps);
-        if (permissionsState == null) {
-            Slog.e(TAG, "Missing permissions state for " + packageName);
+        final UidPermissionState uidState = getUidState(ps, userId);
+        if (uidState == null) {
+            Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
             return Collections.emptySet();
         }
         if (!ps.getInstantApp(userId)) {
-            return permissionsState.getPermissions(userId);
+            return uidState.getPermissions();
         } else {
             // Install permission state is shared among all users, but instant app state is
             // per-user, so we can only filter it here unless we make install permission state
             // per-user as well.
-            final Set<String> instantPermissions = new ArraySet<>(permissionsState.getPermissions(
-                    userId));
+            final Set<String> instantPermissions = new ArraySet<>(uidState.getPermissions());
             instantPermissions.removeIf(permissionName -> {
                 BasePermission permission = mSettings.getPermission(permissionName);
                 if (permission == null) {
@@ -2533,12 +2520,12 @@
         if (ps == null) {
             return null;
         }
-        final PermissionsState permissionsState = getPermissionsState(ps);
-        if (permissionsState == null) {
-            Slog.e(TAG, "Missing permissions state for " + packageName);
+        final UidPermissionState uidState = getUidState(ps, userId);
+        if (uidState == null) {
+            Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
             return null;
         }
-        return permissionsState.computeGids(userId);
+        return uidState.computeGids(userId);
     }
 
     /**
@@ -2575,15 +2562,17 @@
         if (ps == null) {
             return;
         }
-        final PermissionsState permissionsState = getOrCreatePermissionsState(ps);
 
         final int[] userIds = getAllUserIds();
 
         boolean runtimePermissionsRevoked = false;
         int[] updatedUserIds = EMPTY_INT_ARRAY;
 
-        for (int userId : userIds) {
-            if (permissionsState.isMissing(userId)) {
+        for (final int userId : userIds) {
+            final UserPermissionState userState = mState.getOrCreateUserState(userId);
+            final UidPermissionState uidState = userState.getOrCreateUidState(ps.getAppId());
+
+            if (uidState.isMissing()) {
                 Collection<String> requestedPermissions;
                 int targetSdkVersion;
                 if (!ps.isSharedUser()) {
@@ -2611,222 +2600,221 @@
                             && permission.isRuntime() && !permission.isRemoved()) {
                         if (permission.isHardOrSoftRestricted()
                                 || permission.isImmutablyRestricted()) {
-                            permissionsState.updatePermissionFlags(permission, userId,
+                            uidState.updatePermissionFlags(permission,
                                     FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
                                     FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT);
                         }
                         if (targetSdkVersion < Build.VERSION_CODES.M) {
-                            permissionsState.updatePermissionFlags(permission, userId,
+                            uidState.updatePermissionFlags(permission,
                                     PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
                                             | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
                                     PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
                                             | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT);
-                            permissionsState.grantRuntimePermission(permission, userId);
+                            uidState.grantPermission(permission);
                         }
                     }
                 }
 
-                permissionsState.setMissing(false, userId);
+                uidState.setMissing(false);
                 updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
             }
-        }
 
-        PermissionsState origPermissions = permissionsState;
+            UidPermissionState origState = uidState;
 
-        boolean changedInstallPermission = false;
+            boolean changedInstallPermission = false;
 
-        if (replace) {
-            ps.setInstallPermissionsFixed(false);
-            if (!ps.isSharedUser()) {
-                origPermissions = new PermissionsState(permissionsState);
-                permissionsState.reset();
-            } else {
-                // We need to know only about runtime permission changes since the
-                // calling code always writes the install permissions state but
-                // the runtime ones are written only if changed. The only cases of
-                // changed runtime permissions here are promotion of an install to
-                // runtime and revocation of a runtime from a shared user.
-                synchronized (mLock) {
-                    updatedUserIds = revokeUnusedSharedUserPermissionsLocked(
-                            ps.getSharedUser().getPackages(), permissionsState, userIds);
-                    if (!ArrayUtils.isEmpty(updatedUserIds)) {
-                        runtimePermissionsRevoked = true;
-                    }
-                }
-            }
-        }
-
-        permissionsState.setGlobalGids(mGlobalGids);
-
-        ArraySet<String> newImplicitPermissions = new ArraySet<>();
-
-        final int N = pkg.getRequestedPermissions().size();
-        for (int i = 0; i < N; i++) {
-            final String permName = pkg.getRequestedPermissions().get(i);
-            final BasePermission bp = mSettings.getPermission(permName);
-            final boolean appSupportsRuntimePermissions =
-                    pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M;
-            String upgradedActivityRecognitionPermission = null;
-
-            if (DEBUG_INSTALL && bp != null) {
-                Log.i(TAG, "Package " + pkg.getPackageName()
-                        + " checking " + permName + ": " + bp);
-            }
-
-            if (bp == null || getSourcePackageSetting(bp) == null) {
-                if (packageOfInterest == null || packageOfInterest.equals(
-                        pkg.getPackageName())) {
-                    if (DEBUG_PERMISSIONS) {
-                        Slog.i(TAG, "Unknown permission " + permName
-                                + " in package " + pkg.getPackageName());
-                    }
-                }
-                continue;
-            }
-
-            // Cache newImplicitPermissions before modifing permissionsState as for the shared
-            // uids the original and new state are the same object
-            if (!origPermissions.hasRequestedPermission(permName)
-                    && (pkg.getImplicitPermissions().contains(permName)
-                            || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) {
-                if (pkg.getImplicitPermissions().contains(permName)) {
-                    // If permName is an implicit permission, try to auto-grant
-                    newImplicitPermissions.add(permName);
-
-                    if (DEBUG_PERMISSIONS) {
-                        Slog.i(TAG, permName + " is newly added for " + pkg.getPackageName());
-                    }
+            if (replace) {
+                userState.setInstallPermissionsFixed(ps.name, false);
+                if (!ps.isSharedUser()) {
+                    origState = new UidPermissionState(uidState);
+                    uidState.reset();
                 } else {
-                    // Special case for Activity Recognition permission. Even if AR permission
-                    // is not an implicit permission we want to add it to the list (try to
-                    // auto-grant it) if the app was installed on a device before AR permission
-                    // was split, regardless of if the app now requests the new AR permission
-                    // or has updated its target SDK and AR is no longer implicit to it.
-                    // This is a compatibility workaround for apps when AR permission was
-                    // split in Q.
-                    final List<SplitPermissionInfoParcelable> permissionList =
-                            getSplitPermissions();
-                    int numSplitPerms = permissionList.size();
-                    for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
-                        SplitPermissionInfoParcelable sp = permissionList.get(splitPermNum);
-                        String splitPermName = sp.getSplitPermission();
-                        if (sp.getNewPermissions().contains(permName)
-                                && origPermissions.hasInstallPermission(splitPermName)) {
-                            upgradedActivityRecognitionPermission = splitPermName;
-                            newImplicitPermissions.add(permName);
+                    // We need to know only about runtime permission changes since the
+                    // calling code always writes the install permissions state but
+                    // the runtime ones are written only if changed. The only cases of
+                    // changed runtime permissions here are promotion of an install to
+                    // runtime and revocation of a runtime from a shared user.
+                    synchronized (mLock) {
+                        if (revokeUnusedSharedUserPermissionsLocked(
+                                ps.getSharedUser().getPackages(), uidState)) {
+                            updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+                            runtimePermissionsRevoked = true;
+                        }
+                    }
+                }
+            }
 
-                            if (DEBUG_PERMISSIONS) {
-                                Slog.i(TAG, permName + " is newly added for "
-                                        + pkg.getPackageName());
+            uidState.setGlobalGids(mGlobalGids);
+
+            ArraySet<String> newImplicitPermissions = new ArraySet<>();
+            final String friendlyName = pkg.getPackageName() + "(" + pkg.getUid() + ")";
+
+            final int N = pkg.getRequestedPermissions().size();
+            for (int i = 0; i < N; i++) {
+                final String permName = pkg.getRequestedPermissions().get(i);
+                final BasePermission bp = mSettings.getPermission(permName);
+                final boolean appSupportsRuntimePermissions =
+                        pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M;
+                String upgradedActivityRecognitionPermission = null;
+
+                if (DEBUG_INSTALL && bp != null) {
+                    Log.i(TAG, "Package " + friendlyName
+                            + " checking " + permName + ": " + bp);
+                }
+
+                if (bp == null || getSourcePackageSetting(bp) == null) {
+                    if (packageOfInterest == null || packageOfInterest.equals(
+                            pkg.getPackageName())) {
+                        if (DEBUG_PERMISSIONS) {
+                            Slog.i(TAG, "Unknown permission " + permName
+                                    + " in package " + friendlyName);
+                        }
+                    }
+                    continue;
+                }
+
+                // Cache newImplicitPermissions before modifing permissionsState as for the shared
+                // uids the original and new state are the same object
+                if (!origState.hasRequestedPermission(permName)
+                        && (pkg.getImplicitPermissions().contains(permName)
+                                || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) {
+                    if (pkg.getImplicitPermissions().contains(permName)) {
+                        // If permName is an implicit permission, try to auto-grant
+                        newImplicitPermissions.add(permName);
+
+                        if (DEBUG_PERMISSIONS) {
+                            Slog.i(TAG, permName + " is newly added for " + friendlyName);
+                        }
+                    } else {
+                        // Special case for Activity Recognition permission. Even if AR permission
+                        // is not an implicit permission we want to add it to the list (try to
+                        // auto-grant it) if the app was installed on a device before AR permission
+                        // was split, regardless of if the app now requests the new AR permission
+                        // or has updated its target SDK and AR is no longer implicit to it.
+                        // This is a compatibility workaround for apps when AR permission was
+                        // split in Q.
+                        final List<SplitPermissionInfoParcelable> permissionList =
+                                getSplitPermissions();
+                        int numSplitPerms = permissionList.size();
+                        for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
+                            SplitPermissionInfoParcelable sp = permissionList.get(splitPermNum);
+                            String splitPermName = sp.getSplitPermission();
+                            if (sp.getNewPermissions().contains(permName)
+                                    && origState.hasInstallPermission(splitPermName)) {
+                                upgradedActivityRecognitionPermission = splitPermName;
+                                newImplicitPermissions.add(permName);
+
+                                if (DEBUG_PERMISSIONS) {
+                                    Slog.i(TAG, permName + " is newly added for "
+                                            + friendlyName);
+                                }
+                                break;
                             }
-                            break;
                         }
                     }
                 }
-            }
 
-            // TODO(b/140256621): The package instant app method has been removed
-            //  as part of work in b/135203078, so this has been commented out in the meantime
-            // Limit ephemeral apps to ephemeral allowed permissions.
-//            if (/*pkg.isInstantApp()*/ false && !bp.isInstant()) {
-//                if (DEBUG_PERMISSIONS) {
-//                    Log.i(TAG, "Denying non-ephemeral permission " + bp.getName()
-//                            + " for package " + pkg.getPackageName());
-//                }
-//                continue;
-//            }
+                // TODO(b/140256621): The package instant app method has been removed
+                //  as part of work in b/135203078, so this has been commented out in the meantime
+                // Limit ephemeral apps to ephemeral allowed permissions.
+    //            if (/*pkg.isInstantApp()*/ false && !bp.isInstant()) {
+    //                if (DEBUG_PERMISSIONS) {
+    //                    Log.i(TAG, "Denying non-ephemeral permission " + bp.getName()
+    //                            + " for package " + pkg.getPackageName());
+    //                }
+    //                continue;
+    //            }
 
-            if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
-                if (DEBUG_PERMISSIONS) {
-                    Log.i(TAG, "Denying runtime-only permission " + bp.getName()
-                            + " for package " + pkg.getPackageName());
+                if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
+                    if (DEBUG_PERMISSIONS) {
+                        Log.i(TAG, "Denying runtime-only permission " + bp.getName()
+                                + " for package " + friendlyName);
+                    }
+                    continue;
                 }
-                continue;
-            }
 
-            final String perm = bp.getName();
-            boolean allowedSig = false;
-            int grant = GRANT_DENIED;
+                final String perm = bp.getName();
+                boolean allowedSig = false;
+                int grant = GRANT_DENIED;
 
-            // Keep track of app op permissions.
-            if (bp.isAppOp()) {
-                mSettings.addAppOpPackage(perm, pkg.getPackageName());
-            }
-
-            if (bp.isNormal()) {
-                // For all apps normal permissions are install time ones.
-                grant = GRANT_INSTALL;
-            } else if (bp.isRuntime()) {
-                if (origPermissions.hasInstallPermission(bp.getName())
-                        || upgradedActivityRecognitionPermission != null) {
-                    // Before Q we represented some runtime permissions as install permissions,
-                    // in Q we cannot do this anymore. Hence upgrade them all.
-                    grant = GRANT_UPGRADE;
-                } else {
-                    // For modern apps keep runtime permissions unchanged.
-                    grant = GRANT_RUNTIME;
+                // Keep track of app op permissions.
+                if (bp.isAppOp()) {
+                    mSettings.addAppOpPackage(perm, pkg.getPackageName());
                 }
-            } else if (bp.isSignature()) {
-                // For all apps signature permissions are install time ones.
-                allowedSig = shouldGrantSignaturePermission(perm, pkg, ps, bp, origPermissions);
-                if (allowedSig) {
+
+                if (bp.isNormal()) {
+                    // For all apps normal permissions are install time ones.
                     grant = GRANT_INSTALL;
+                } else if (bp.isRuntime()) {
+                    if (origState.hasInstallPermission(bp.getName())
+                            || upgradedActivityRecognitionPermission != null) {
+                        // Before Q we represented some runtime permissions as install permissions,
+                        // in Q we cannot do this anymore. Hence upgrade them all.
+                        grant = GRANT_UPGRADE;
+                    } else {
+                        // For modern apps keep runtime permissions unchanged.
+                        grant = GRANT_RUNTIME;
+                    }
+                } else if (bp.isSignature()) {
+                    // For all apps signature permissions are install time ones.
+                    allowedSig = shouldGrantSignaturePermission(perm, pkg, ps, bp, origState);
+                    if (allowedSig) {
+                        grant = GRANT_INSTALL;
+                    }
                 }
-            }
 
-            if (grant != GRANT_DENIED) {
-                if (!ps.isSystem() && ps.areInstallPermissionsFixed() && !bp.isRuntime()) {
-                    // If this is an existing, non-system package, then
-                    // we can't add any new permissions to it. Runtime
-                    // permissions can be added any time - they ad dynamic.
-                    if (!allowedSig && !origPermissions.hasInstallPermission(perm)) {
-                        // Except...  if this is a permission that was added
-                        // to the platform (note: need to only do this when
-                        // updating the platform).
-                        if (!isNewPlatformPermissionForPackage(perm, pkg)) {
-                            grant = GRANT_DENIED;
+                if (grant != GRANT_DENIED) {
+                    if (!ps.isSystem() && userState.areInstallPermissionsFixed(ps.name)
+                            && !bp.isRuntime()) {
+                        // If this is an existing, non-system package, then
+                        // we can't add any new permissions to it. Runtime
+                        // permissions can be added any time - they ad dynamic.
+                        if (!allowedSig && !origState.hasInstallPermission(perm)) {
+                            // Except...  if this is a permission that was added
+                            // to the platform (note: need to only do this when
+                            // updating the platform).
+                            if (!isNewPlatformPermissionForPackage(perm, pkg)) {
+                                grant = GRANT_DENIED;
+                            }
                         }
                     }
                 }
-            }
 
-            if (DEBUG_PERMISSIONS) {
-                Slog.i(TAG, "Considering granting permission " + perm + " to package "
-                        + pkg.getPackageName());
-            }
+                if (DEBUG_PERMISSIONS) {
+                    Slog.i(TAG, "Considering granting permission " + perm + " to package "
+                            + pkg.getPackageName());
+                }
 
-            synchronized (mLock) {
-                if (grant != GRANT_DENIED) {
-                    switch (grant) {
-                        case GRANT_INSTALL: {
-                            // Revoke this as runtime permission to handle the case of
-                            // a runtime permission being downgraded to an install one.
-                            // Also in permission review mode we keep dangerous permissions
-                            // for legacy apps
-                            for (int userId : userIds) {
-                                if (origPermissions.getRuntimePermissionState(
-                                        perm, userId) != null) {
+                synchronized (mLock) {
+                    if (grant != GRANT_DENIED) {
+                        switch (grant) {
+                            case GRANT_INSTALL: {
+                                // Revoke this as runtime permission to handle the case of
+                                // a runtime permission being downgraded to an install one.
+                                // Also in permission review mode we keep dangerous permissions
+                                // for legacy apps
+                                final PermissionState origPermissionState =
+                                        origState.getPermissionState(perm);
+                                if (origPermissionState != null
+                                        && origPermissionState.isRuntime()) {
                                     // Revoke the runtime permission and clear the flags.
-                                    origPermissions.revokeRuntimePermission(bp, userId);
-                                    origPermissions.updatePermissionFlags(bp, userId,
+                                    origState.revokePermission(bp);
+                                    origState.updatePermissionFlags(bp,
                                             PackageManager.MASK_PERMISSION_FLAGS_ALL, 0);
                                     // If we revoked a permission permission, we have to write.
                                     updatedUserIds = ArrayUtils.appendInt(
                                             updatedUserIds, userId);
                                 }
-                            }
-                            // Grant an install permission.
-                            if (permissionsState.grantInstallPermission(bp) !=
-                                    PERMISSION_OPERATION_FAILURE) {
-                                changedInstallPermission = true;
-                            }
-                        } break;
+                                // Grant an install permission.
+                                if (uidState.grantPermission(bp) != PERMISSION_OPERATION_FAILURE) {
+                                    changedInstallPermission = true;
+                                }
+                            } break;
 
-                        case GRANT_RUNTIME: {
-                            boolean hardRestricted = bp.isHardRestricted();
-                            boolean softRestricted = bp.isSoftRestricted();
+                            case GRANT_RUNTIME: {
+                                boolean hardRestricted = bp.isHardRestricted();
+                                boolean softRestricted = bp.isSoftRestricted();
 
-                            for (int userId : userIds) {
                                 // If permission policy is not ready we don't deal with restricted
                                 // permissions as the policy may whitelist some permissions. Once
                                 // the policy is initialized we would re-evaluate permissions.
@@ -2834,25 +2822,24 @@
                                         mPermissionPolicyInternal != null
                                                 && mPermissionPolicyInternal.isInitialized(userId);
 
-                                PermissionState permState = origPermissions
-                                        .getRuntimePermissionState(perm, userId);
-                                int flags = permState != null ? permState.getFlags() : 0;
+                                PermissionState origPermState = origState.getPermissionState(perm);
+                                int flags = origPermState != null ? origPermState.getFlags() : 0;
 
                                 boolean wasChanged = false;
 
                                 boolean restrictionExempt =
-                                        (origPermissions.getPermissionFlags(bp.name, userId)
+                                        (origState.getPermissionFlags(bp.name)
                                                 & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
-                                boolean restrictionApplied = (origPermissions.getPermissionFlags(
-                                        bp.name, userId) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
+                                boolean restrictionApplied = (origState.getPermissionFlags(
+                                        bp.name) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
 
                                 if (appSupportsRuntimePermissions) {
                                     // If hard restricted we don't allow holding it
                                     if (permissionPolicyInitialized && hardRestricted) {
                                         if (!restrictionExempt) {
-                                            if (permState != null && permState.isGranted()
-                                                    && permissionsState.revokeRuntimePermission(
-                                                    bp, userId) != PERMISSION_OPERATION_FAILURE) {
+                                            if (origPermState != null && origPermState.isGranted()
+                                                    && uidState.revokePermission(
+                                                    bp) != PERMISSION_OPERATION_FAILURE) {
                                                 wasChanged = true;
                                             }
                                             if (!restrictionApplied) {
@@ -2882,15 +2869,15 @@
                                     // Hard restricted permissions cannot be held.
                                     } else if (!permissionPolicyInitialized
                                             || (!hardRestricted || restrictionExempt)) {
-                                        if (permState != null && permState.isGranted()) {
-                                            if (permissionsState.grantRuntimePermission(bp, userId)
+                                        if (origPermState != null && origPermState.isGranted()) {
+                                            if (uidState.grantPermission(bp)
                                                     == PERMISSION_OPERATION_FAILURE) {
                                                 wasChanged = true;
                                             }
                                         }
                                     }
                                 } else {
-                                    if (permState == null) {
+                                    if (origPermState == null) {
                                         // New permission
                                         if (PLATFORM_PACKAGE_NAME.equals(
                                                 bp.getSourcePackageName())) {
@@ -2902,8 +2889,8 @@
                                         }
                                     }
 
-                                    if (!permissionsState.hasRuntimePermission(bp.name, userId)
-                                            && permissionsState.grantRuntimePermission(bp, userId)
+                                    if (!uidState.hasPermission(bp.name)
+                                            && uidState.grantPermission(bp)
                                                     != PERMISSION_OPERATION_FAILURE) {
                                         wasChanged = true;
                                     }
@@ -2936,36 +2923,32 @@
                                     updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
                                 }
 
-                                permissionsState.updatePermissionFlags(bp, userId,
-                                        MASK_PERMISSION_FLAGS_ALL, flags);
-                            }
-                        } break;
+                                uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL,
+                                        flags);
+                            } break;
 
-                        case GRANT_UPGRADE: {
-                            // Upgrade from Pre-Q to Q permission model. Make all permissions
-                            // runtime
-                            PermissionState permState = origPermissions
-                                    .getInstallPermissionState(perm);
-                            int flags = (permState != null) ? permState.getFlags() : 0;
+                            case GRANT_UPGRADE: {
+                                // Upgrade from Pre-Q to Q permission model. Make all permissions
+                                // runtime
+                                PermissionState origPermState = origState.getPermissionState(perm);
+                                int flags = (origPermState != null) ? origPermState.getFlags() : 0;
 
-                            BasePermission bpToRevoke =
-                                    upgradedActivityRecognitionPermission == null
-                                    ? bp : mSettings.getPermissionLocked(
-                                            upgradedActivityRecognitionPermission);
-                            // Remove install permission
-                            if (origPermissions.revokeInstallPermission(bpToRevoke)
-                                    != PERMISSION_OPERATION_FAILURE) {
-                                origPermissions.updatePermissionFlags(bpToRevoke,
-                                        UserHandle.USER_ALL,
-                                        (MASK_PERMISSION_FLAGS_ALL
-                                                & ~FLAG_PERMISSION_APPLY_RESTRICTION), 0);
-                                changedInstallPermission = true;
-                            }
+                                BasePermission bpToRevoke =
+                                        upgradedActivityRecognitionPermission == null
+                                        ? bp : mSettings.getPermissionLocked(
+                                                upgradedActivityRecognitionPermission);
+                                // Remove install permission
+                                if (origState.revokePermission(bpToRevoke)
+                                        != PERMISSION_OPERATION_FAILURE) {
+                                    origState.updatePermissionFlags(bpToRevoke,
+                                            (MASK_PERMISSION_FLAGS_ALL
+                                                    & ~FLAG_PERMISSION_APPLY_RESTRICTION), 0);
+                                    changedInstallPermission = true;
+                                }
 
-                            boolean hardRestricted = bp.isHardRestricted();
-                            boolean softRestricted = bp.isSoftRestricted();
+                                boolean hardRestricted = bp.isHardRestricted();
+                                boolean softRestricted = bp.isSoftRestricted();
 
-                            for (int userId : userIds) {
                                 // If permission policy is not ready we don't deal with restricted
                                 // permissions as the policy may whitelist some permissions. Once
                                 // the policy is initialized we would re-evaluate permissions.
@@ -2976,18 +2959,18 @@
                                 boolean wasChanged = false;
 
                                 boolean restrictionExempt =
-                                        (origPermissions.getPermissionFlags(bp.name, userId)
+                                        (origState.getPermissionFlags(bp.name)
                                                 & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
-                                boolean restrictionApplied = (origPermissions.getPermissionFlags(
-                                        bp.name, userId) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
+                                boolean restrictionApplied = (origState.getPermissionFlags(
+                                        bp.name) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
 
                                 if (appSupportsRuntimePermissions) {
                                     // If hard restricted we don't allow holding it
                                     if (permissionPolicyInitialized && hardRestricted) {
                                         if (!restrictionExempt) {
-                                            if (permState != null && permState.isGranted()
-                                                    && permissionsState.revokeRuntimePermission(
-                                                    bp, userId) != PERMISSION_OPERATION_FAILURE) {
+                                            if (origPermState != null && origPermState.isGranted()
+                                                    && uidState.revokePermission(
+                                                    bp) != PERMISSION_OPERATION_FAILURE) {
                                                 wasChanged = true;
                                             }
                                             if (!restrictionApplied) {
@@ -3017,15 +3000,15 @@
                                     // Hard restricted permissions cannot be held.
                                     } else if (!permissionPolicyInitialized ||
                                             (!hardRestricted || restrictionExempt)) {
-                                        if (permissionsState.grantRuntimePermission(bp, userId) !=
-                                                PERMISSION_OPERATION_FAILURE) {
+                                        if (uidState.grantPermission(bp)
+                                                != PERMISSION_OPERATION_FAILURE) {
                                              wasChanged = true;
                                         }
                                     }
                                 } else {
-                                    if (!permissionsState.hasRuntimePermission(bp.name, userId)
-                                            && permissionsState.grantRuntimePermission(bp,
-                                                    userId) != PERMISSION_OPERATION_FAILURE) {
+                                    if (!uidState.hasPermission(bp.name)
+                                            && uidState.grantPermission(bp)
+                                                    != PERMISSION_OPERATION_FAILURE) {
                                         flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
                                         wasChanged = true;
                                     }
@@ -3058,71 +3041,74 @@
                                     updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
                                 }
 
-                                permissionsState.updatePermissionFlags(bp, userId,
+                                uidState.updatePermissionFlags(bp,
                                         MASK_PERMISSION_FLAGS_ALL, flags);
-                            }
-                        } break;
+                            } break;
 
-                        default: {
-                            if (packageOfInterest == null
-                                    || packageOfInterest.equals(pkg.getPackageName())) {
-                                if (DEBUG_PERMISSIONS) {
-                                    Slog.i(TAG, "Not granting permission " + perm
-                                            + " to package " + pkg.getPackageName()
-                                            + " because it was previously installed without");
+                            default: {
+                                if (packageOfInterest == null
+                                        || packageOfInterest.equals(pkg.getPackageName())) {
+                                    if (DEBUG_PERMISSIONS) {
+                                        Slog.i(TAG, "Not granting permission " + perm
+                                                + " to package " + friendlyName
+                                                + " because it was previously installed without");
+                                    }
                                 }
-                            }
-                        } break;
-                    }
-                } else {
-                    if (permissionsState.revokeInstallPermission(bp) !=
-                            PERMISSION_OPERATION_FAILURE) {
-                        // Also drop the permission flags.
-                        permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
-                                MASK_PERMISSION_FLAGS_ALL, 0);
-                        changedInstallPermission = true;
-                        if (DEBUG_PERMISSIONS) {
-                            Slog.i(TAG, "Un-granting permission " + perm
-                                    + " from package " + pkg.getPackageName()
-                                    + " (protectionLevel=" + bp.getProtectionLevel()
-                                    + " flags=0x"
-                                    + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, ps))
-                                    + ")");
+                            } break;
                         }
-                    } else if (bp.isAppOp()) {
-                        // Don't print warning for app op permissions, since it is fine for them
-                        // not to be granted, there is a UI for the user to decide.
-                        if (DEBUG_PERMISSIONS
-                                && (packageOfInterest == null
-                                        || packageOfInterest.equals(pkg.getPackageName()))) {
-                            Slog.i(TAG, "Not granting permission " + perm
-                                    + " to package " + pkg.getPackageName()
-                                    + " (protectionLevel=" + bp.getProtectionLevel()
-                                    + " flags=0x"
-                                    + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, ps))
-                                    + ")");
+                    } else {
+                        if (uidState.revokePermission(bp) != PERMISSION_OPERATION_FAILURE) {
+                            // Also drop the permission flags.
+                            uidState.updatePermissionFlags(bp,
+                                    MASK_PERMISSION_FLAGS_ALL, 0);
+                            changedInstallPermission = true;
+                            if (DEBUG_PERMISSIONS) {
+                                Slog.i(TAG, "Un-granting permission " + perm
+                                        + " from package " + friendlyName
+                                        + " (protectionLevel=" + bp.getProtectionLevel()
+                                        + " flags=0x"
+                                        + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg,
+                                                ps))
+                                        + ")");
+                            }
+                        } else if (bp.isAppOp()) {
+                            // Don't print warning for app op permissions, since it is fine for them
+                            // not to be granted, there is a UI for the user to decide.
+                            if (DEBUG_PERMISSIONS
+                                    && (packageOfInterest == null
+                                            || packageOfInterest.equals(pkg.getPackageName()))) {
+                                Slog.i(TAG, "Not granting permission " + perm
+                                        + " to package " + friendlyName
+                                        + " (protectionLevel=" + bp.getProtectionLevel()
+                                        + " flags=0x"
+                                        + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg,
+                                                ps))
+                                        + ")");
+                            }
                         }
                     }
                 }
             }
+
+            if ((changedInstallPermission || replace)
+                    && !userState.areInstallPermissionsFixed(ps.name)
+                    && !ps.isSystem() || ps.getPkgState().isUpdatedSystemApp()) {
+                // This is the first that we have heard about this package, so the
+                // permissions we have now selected are fixed until explicitly
+                // changed.
+                userState.setInstallPermissionsFixed(ps.name, true);
+            }
+
+            synchronized (mLock) {
+                updatedUserIds = revokePermissionsNoLongerImplicitLocked(uidState, pkg,
+                        userId, updatedUserIds);
+                updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origState,
+                        uidState, pkg, newImplicitPermissions, userId, updatedUserIds);
+            }
         }
 
-        if ((changedInstallPermission || replace) && !ps.areInstallPermissionsFixed() &&
-                !ps.isSystem() || ps.getPkgState().isUpdatedSystemApp()) {
-            // This is the first that we have heard about this package, so the
-            // permissions we have now selected are fixed until explicitly
-            // changed.
-            ps.setInstallPermissionsFixed(true);
-        }
-
-        synchronized (mLock) {
-            updatedUserIds = revokePermissionsNoLongerImplicitLocked(permissionsState, pkg,
-                    userIds, updatedUserIds);
-            updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origPermissions,
-                    permissionsState, pkg, newImplicitPermissions, userIds, updatedUserIds);
-            updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, userIds,
-                    updatedUserIds);
-        }
+        updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, userIds,
+                updatedUserIds);
 
         // TODO: Kill UIDs whose GIDs or runtime permissions changed. This might be more important
         //  for shared users.
@@ -3159,40 +3145,38 @@
      *
      * @return The updated value of the {@code updatedUserIds} parameter
      */
-    private @NonNull int[] revokePermissionsNoLongerImplicitLocked(@NonNull PermissionsState ps,
-            @NonNull AndroidPackage pkg, @NonNull int[] userIds, @NonNull int[] updatedUserIds) {
+    private @NonNull int[] revokePermissionsNoLongerImplicitLocked(@NonNull UidPermissionState ps,
+            @NonNull AndroidPackage pkg, int userId, @NonNull int[] updatedUserIds) {
         String pkgName = pkg.getPackageName();
         boolean supportsRuntimePermissions = pkg.getTargetSdkVersion()
                 >= Build.VERSION_CODES.M;
 
-        for (int userId : userIds) {
-            for (String permission : ps.getPermissions(userId)) {
-                if (!pkg.getImplicitPermissions().contains(permission)) {
-                    if (!ps.hasInstallPermission(permission)) {
-                        int flags = ps.getRuntimePermissionState(permission, userId).getFlags();
+        for (String permission : ps.getPermissions()) {
+            if (!pkg.getImplicitPermissions().contains(permission)) {
+                if (!ps.hasInstallPermission(permission)) {
+                    int flags = ps.getPermissionFlags(permission);
 
-                        if ((flags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) {
-                            BasePermission bp = mSettings.getPermissionLocked(permission);
+                    if ((flags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) {
+                        BasePermission bp = mSettings.getPermissionLocked(permission);
 
-                            int flagsToRemove = FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
+                        int flagsToRemove = FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
 
-                            if ((flags & BLOCKING_PERMISSION_FLAGS) == 0
-                                    && supportsRuntimePermissions) {
-                                int revokeResult = ps.revokeRuntimePermission(bp, userId);
-                                if (revokeResult != PERMISSION_OPERATION_FAILURE) {
-                                    if (DEBUG_PERMISSIONS) {
-                                        Slog.i(TAG, "Revoking runtime permission "
-                                                + permission + " for " + pkgName
-                                                + " as it is now requested");
-                                    }
+                        if ((flags & BLOCKING_PERMISSION_FLAGS) == 0
+                                && supportsRuntimePermissions) {
+                            int revokeResult = ps.revokePermission(bp);
+                            if (revokeResult != PERMISSION_OPERATION_FAILURE) {
+                                if (DEBUG_PERMISSIONS) {
+                                    Slog.i(TAG, "Revoking runtime permission "
+                                            + permission + " for " + pkgName
+                                            + " as it is now requested");
                                 }
-
-                                flagsToRemove |= USER_PERMISSION_FLAGS;
                             }
 
-                            ps.updatePermissionFlags(bp, userId, flagsToRemove, 0);
-                            updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+                            flagsToRemove |= USER_PERMISSION_FLAGS;
                         }
+
+                        ps.updatePermissionFlags(bp, flagsToRemove, 0);
+                        updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
                     }
                 }
             }
@@ -3213,12 +3197,10 @@
      * @param newPerm The permission to inherit to
      * @param ps The permission state of the package
      * @param pkg The package requesting the permissions
-     * @param userId The user the permission belongs to
      */
     private void inheritPermissionStateToNewImplicitPermissionLocked(
             @NonNull ArraySet<String> sourcePerms, @NonNull String newPerm,
-            @NonNull PermissionsState ps, @NonNull AndroidPackage pkg,
-            @UserIdInt int userId) {
+            @NonNull UidPermissionState ps, @NonNull AndroidPackage pkg) {
         String pkgName = pkg.getPackageName();
         boolean isGranted = false;
         int flags = 0;
@@ -3226,17 +3208,16 @@
         int numSourcePerm = sourcePerms.size();
         for (int i = 0; i < numSourcePerm; i++) {
             String sourcePerm = sourcePerms.valueAt(i);
-            if ((ps.hasRuntimePermission(sourcePerm, userId))
-                    || ps.hasInstallPermission(sourcePerm)) {
+            if (ps.hasPermission(sourcePerm)) {
                 if (!isGranted) {
                     flags = 0;
                 }
 
                 isGranted = true;
-                flags |= ps.getPermissionFlags(sourcePerm, userId);
+                flags |= ps.getPermissionFlags(sourcePerm);
             } else {
                 if (!isGranted) {
-                    flags |= ps.getPermissionFlags(sourcePerm, userId);
+                    flags |= ps.getPermissionFlags(sourcePerm);
                 }
             }
         }
@@ -3247,11 +3228,11 @@
                         + " for " + pkgName);
             }
 
-            ps.grantRuntimePermission(mSettings.getPermissionLocked(newPerm), userId);
+            ps.grantPermission(mSettings.getPermissionLocked(newPerm));
         }
 
         // Add permission flags
-        ps.updatePermissionFlags(mSettings.getPermission(newPerm), userId, flags, flags);
+        ps.updatePermissionFlags(mSettings.getPermission(newPerm), flags, flags);
     }
 
     /**
@@ -3283,15 +3264,15 @@
      * @param origPs The permission state of the package before the split
      * @param ps The new permission state
      * @param pkg The package the permission belongs to
-     * @param userIds All user IDs in the system, must be passed in because this method is locked
+     * @param userId The user ID
      * @param updatedUserIds List of users for which the permission state has already been changed
      *
      * @return  List of users for which the permission state has been changed
      */
     private @NonNull int[] setInitialGrantForNewImplicitPermissionsLocked(
-            @NonNull PermissionsState origPs, @NonNull PermissionsState ps,
+            @NonNull UidPermissionState origPs, @NonNull UidPermissionState ps,
             @NonNull AndroidPackage pkg, @NonNull ArraySet<String> newImplicitPermissions,
-            @NonNull int[] userIds, @NonNull int[] updatedUserIds) {
+            @UserIdInt int userId, @NonNull int[] updatedUserIds) {
         String pkgName = pkg.getPackageName();
         ArrayMap<String, ArraySet<String>> newToSplitPerms = new ArrayMap<>();
 
@@ -3325,35 +3306,33 @@
                 if (!ps.hasInstallPermission(newPerm)) {
                     BasePermission bp = mSettings.getPermissionLocked(newPerm);
 
-                    for (int userId : userIds) {
-                        if (!newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)) {
-                            ps.updatePermissionFlags(bp, userId,
-                                    FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
-                                    FLAG_PERMISSION_REVOKE_WHEN_REQUESTED);
-                        }
-                        updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+                    if (!newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)) {
+                        ps.updatePermissionFlags(bp,
+                                FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
+                                FLAG_PERMISSION_REVOKE_WHEN_REQUESTED);
+                    }
+                    updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
 
-                        boolean inheritsFromInstallPerm = false;
-                        for (int sourcePermNum = 0; sourcePermNum < sourcePerms.size();
-                                sourcePermNum++) {
-                            if (ps.hasInstallPermission(sourcePerms.valueAt(sourcePermNum))) {
-                                inheritsFromInstallPerm = true;
-                                break;
-                            }
+                    boolean inheritsFromInstallPerm = false;
+                    for (int sourcePermNum = 0; sourcePermNum < sourcePerms.size();
+                            sourcePermNum++) {
+                        if (ps.hasInstallPermission(sourcePerms.valueAt(sourcePermNum))) {
+                            inheritsFromInstallPerm = true;
+                            break;
                         }
+                    }
 
-                        if (!origPs.hasRequestedPermission(sourcePerms)
-                                && !inheritsFromInstallPerm) {
-                            // Both permissions are new so nothing to inherit.
-                            if (DEBUG_PERMISSIONS) {
-                                Slog.i(TAG, newPerm + " does not inherit from " + sourcePerms
-                                        + " for " + pkgName + " as split permission is also new");
-                            }
-                        } else {
-                            // Inherit from new install or existing runtime permissions
-                            inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms,
-                                    newPerm, ps, pkg, userId);
+                    if (!origPs.hasRequestedPermission(sourcePerms)
+                            && !inheritsFromInstallPerm) {
+                        // Both permissions are new so nothing to inherit.
+                        if (DEBUG_PERMISSIONS) {
+                            Slog.i(TAG, newPerm + " does not inherit from " + sourcePerms
+                                    + " for " + pkgName + " as split permission is also new");
                         }
+                    } else {
+                        // Inherit from new install or existing runtime permissions
+                        inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms,
+                                newPerm, ps, pkg);
                     }
                 }
             }
@@ -3484,7 +3463,7 @@
     }
 
     private boolean shouldGrantSignaturePermission(String perm, AndroidPackage pkg,
-            PackageSetting pkgSetting, BasePermission bp, PermissionsState origPermissions) {
+            PackageSetting pkgSetting, BasePermission bp, UidPermissionState origPermissions) {
         boolean oemPermission = bp.isOEM();
         boolean vendorPrivilegedPermission = bp.isVendorPrivileged();
         boolean privilegedPermission = bp.isPrivileged() || bp.isVendorPrivileged();
@@ -3760,12 +3739,13 @@
         }
 
         // Legacy apps have the permission and get user consent on launch.
-        final PermissionsState permissionsState = getPermissionsState(pkg);
-        if (permissionsState == null) {
-            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+        final UidPermissionState uidState = getUidState(pkg, userId);
+        if (uidState == null) {
+            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+                    + userId);
             return false;
         }
-        return permissionsState.isPermissionReviewRequired(userId);
+        return uidState.isPermissionReviewRequired();
     }
 
     private boolean isPackageRequestingPermission(AndroidPackage pkg, String permission) {
@@ -3789,9 +3769,10 @@
 
     private void grantRequestedRuntimePermissionsForUser(AndroidPackage pkg, int userId,
             String[] grantedPermissions, int callingUid, PermissionCallback callback) {
-        final PermissionsState permissionsState = getPermissionsState(pkg);
-        if (permissionsState == null) {
-            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+        final UidPermissionState uidState = getUidState(pkg, userId);
+        if (uidState == null) {
+            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+                    + userId);
             return;
         }
 
@@ -3816,7 +3797,7 @@
                     && (supportsRuntimePermissions || !bp.isRuntimeOnly())
                     && (grantedPermissions == null
                            || ArrayUtils.contains(grantedPermissions, permission))) {
-                final int flags = permissionsState.getPermissionFlags(permission, userId);
+                final int flags = uidState.getPermissionFlags(permission);
                 if (supportsRuntimePermissions) {
                     // Installer cannot change immutable permissions.
                     if ((flags & immutableFlags) == 0) {
@@ -3838,18 +3819,19 @@
     private void setWhitelistedRestrictedPermissionsForUsers(@NonNull AndroidPackage pkg,
             @UserIdInt int[] userIds, @Nullable List<String> permissions, int callingUid,
             @PermissionWhitelistFlags int whitelistFlags, PermissionCallback callback) {
-        final PermissionsState permissionsState = getPermissionsState(pkg);
-        if (permissionsState == null) {
-            Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
-            return;
-        }
-
         SparseArray<ArraySet<String>> oldGrantedRestrictedPermissions = new SparseArray<>();
         boolean updatePermissions = false;
         final int permissionCount = pkg.getRequestedPermissions().size();
 
         for (int i = 0; i < userIds.length; i++) {
             int userId = userIds[i];
+            final UidPermissionState uidState = getUidState(pkg, userId);
+            if (uidState == null) {
+                Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+                        + userId);
+                continue;
+            }
+
             for (int j = 0; j < permissionCount; j++) {
                 final String permissionName = pkg.getRequestedPermissions().get(j);
 
@@ -3859,14 +3841,14 @@
                     continue;
                 }
 
-                if (permissionsState.hasPermission(permissionName, userId)) {
+                if (uidState.hasPermission(permissionName)) {
                     if (oldGrantedRestrictedPermissions.get(userId) == null) {
                         oldGrantedRestrictedPermissions.put(userId, new ArraySet<>());
                     }
                     oldGrantedRestrictedPermissions.get(userId).add(permissionName);
                 }
 
-                final int oldFlags = permissionsState.getPermissionFlags(permissionName, userId);
+                final int oldFlags = uidState.getPermissionFlags(permissionName);
 
                 int newFlags = oldFlags;
                 int mask = 0;
@@ -3921,8 +3903,7 @@
                 // as whitelisting trumps policy i.e. policy cannot grant a non
                 // grantable permission.
                 if ((oldFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
-                    final boolean isGranted = permissionsState.hasPermission(permissionName,
-                            userId);
+                    final boolean isGranted = uidState.hasPermission(permissionName);
                     if (!isWhitelisted && isGranted) {
                         mask |= PackageManager.FLAG_PERMISSION_POLICY_FIXED;
                         newFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED;
@@ -3958,12 +3939,13 @@
                 for (int j = 0; j < oldGrantedCount; j++) {
                     final String permission = oldPermsForUser.valueAt(j);
                     // Sometimes we create a new permission state instance during update.
-                    final PermissionsState newPermissionsState = getPermissionsState(pkg);
-                    if (permissionsState == null) {
-                        Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName());
+                    final UidPermissionState newUidState = getUidState(pkg, userId);
+                    if (newUidState == null) {
+                        Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()
+                                + " and user " + userId);
                         continue;
                     }
-                    if (!newPermissionsState.hasPermission(permission, userId)) {
+                    if (!newUidState.hasPermission(permission)) {
                         callback.onPermissionRevoked(pkg.getUid(), userId, null);
                         break;
                     }
@@ -4012,9 +3994,10 @@
                 continue;
             }
 
-            PermissionsState permissionsState = getPermissionsState(deletedPs.pkg);
-            if (permissionsState == null) {
-                Slog.e(TAG, "Missing permissions state for " + deletedPs.pkg.getPackageName());
+            UidPermissionState uidState = getUidState(deletedPs.pkg, userId);
+            if (uidState == null) {
+                Slog.e(TAG, "Missing permissions state for " + deletedPs.pkg.getPackageName()
+                        + " and user " + userId);
                 continue;
             }
 
@@ -4036,25 +4019,15 @@
                 }
             }
 
-            // Try to revoke as an install permission which is for all users.
             // The package is gone - no need to keep flags for applying policy.
-            permissionsState.updatePermissionFlags(bp, userId,
-                    PackageManager.MASK_PERMISSION_FLAGS_ALL, 0);
-
-            if (permissionsState.revokeInstallPermission(bp)
-                    == PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
-                affectedUserId = UserHandle.USER_ALL;
-            }
+            uidState.updatePermissionFlags(bp, PackageManager.MASK_PERMISSION_FLAGS_ALL, 0);
 
             // Try to revoke as a runtime permission which is per user.
-            if (permissionsState.revokeRuntimePermission(bp, userId)
-                    == PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
-                if (affectedUserId == UserHandle.USER_NULL) {
-                    affectedUserId = userId;
-                } else if (affectedUserId != userId) {
-                    // Multiple users affected.
-                    affectedUserId = UserHandle.USER_ALL;
-                }
+            // TODO(zhanghai): This doesn't make sense. revokePermission() doesn't fail, and why are
+            //  we only killing the uid when gids changed, instead of any permission change?
+            if (uidState.revokePermission(bp)
+                    == UidPermissionState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
+                affectedUserId = userId;
             }
         }
 
@@ -4062,12 +4035,12 @@
     }
 
     @GuardedBy("mLock")
-    private int[] revokeUnusedSharedUserPermissionsLocked(
-            List<AndroidPackage> pkgList, PermissionsState permissionsState, int[] allUserIds) {
+    private boolean revokeUnusedSharedUserPermissionsLocked(
+            List<AndroidPackage> pkgList, UidPermissionState uidState) {
         // Collect all used permissions in the UID
         final ArraySet<String> usedPermissions = new ArraySet<>();
         if (pkgList == null || pkgList.size() == 0) {
-            return EmptyArray.INT;
+            return false;
         }
         for (AndroidPackage pkg : pkgList) {
             if (pkg.getRequestedPermissions().isEmpty()) {
@@ -4083,44 +4056,27 @@
             }
         }
 
-        // Prune install permissions
-        List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates();
-        final int installPermCount = installPermStates.size();
-        for (int i = installPermCount - 1; i >= 0;  i--) {
-            PermissionState permissionState = installPermStates.get(i);
+        boolean runtimePermissionChanged = false;
+
+        // Prune permissions
+        final List<com.android.server.pm.permission.PermissionState> permissionStates =
+                uidState.getPermissionStates();
+        final int permissionStatesSize = permissionStates.size();
+        for (int i = permissionStatesSize - 1; i >= 0; i--) {
+            PermissionState permissionState = permissionStates.get(i);
             if (!usedPermissions.contains(permissionState.getName())) {
                 BasePermission bp = mSettings.getPermissionLocked(permissionState.getName());
                 if (bp != null) {
-                    permissionsState.revokeInstallPermission(bp);
-                    permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
-                            MASK_PERMISSION_FLAGS_ALL, 0);
-                }
-            }
-        }
-
-        int[] runtimePermissionChangedUserIds = EmptyArray.INT;
-
-        // Prune runtime permissions
-        for (int userId : allUserIds) {
-            List<PermissionState> runtimePermStates = permissionsState
-                    .getRuntimePermissionStates(userId);
-            final int runtimePermCount = runtimePermStates.size();
-            for (int i = runtimePermCount - 1; i >= 0; i--) {
-                PermissionState permissionState = runtimePermStates.get(i);
-                if (!usedPermissions.contains(permissionState.getName())) {
-                    BasePermission bp = mSettings.getPermissionLocked(permissionState.getName());
-                    if (bp != null) {
-                        permissionsState.revokeRuntimePermission(bp, userId);
-                        permissionsState.updatePermissionFlags(bp, userId,
-                                MASK_PERMISSION_FLAGS_ALL, 0);
-                        runtimePermissionChangedUserIds = ArrayUtils.appendInt(
-                                runtimePermissionChangedUserIds, userId);
+                    uidState.revokePermission(bp);
+                    uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, 0);
+                    if (permissionState.isRuntime()) {
+                        runtimePermissionChanged = true;
                     }
                 }
             }
         }
 
-        return runtimePermissionChangedUserIds;
+        return runtimePermissionChanged;
     }
 
     /**
@@ -4368,15 +4324,19 @@
                         }
                     } else {
                         mPackageManagerInt.forEachPackage(p -> {
-                            final PermissionsState permissionsState = getPermissionsState(p);
-                            if (permissionsState == null) {
-                                Slog.e(TAG, "Missing permissions state for " + p.getPackageName());
-                                return;
-                            }
-                            if (permissionsState.getInstallPermissionState(bp.getName()) != null) {
-                                permissionsState.revokeInstallPermission(bp);
-                                permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
-                                        MASK_PERMISSION_FLAGS_ALL, 0);
+                            final int[] userIds = mUserManagerInt.getUserIds();
+                            for (final int userId : userIds) {
+                                final UidPermissionState uidState = getUidState(p, userId);
+                                if (uidState == null) {
+                                    Slog.e(TAG, "Missing permissions state for "
+                                            + p.getPackageName() + " and user " + userId);
+                                    return;
+                                }
+                                if (uidState.getPermissionState(bp.getName()) != null) {
+                                    uidState.revokePermission(bp);
+                                    uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL,
+                                            0);
+                                }
                             }
                         });
                     }
@@ -4784,62 +4744,124 @@
     }
 
     @Nullable
-    private PermissionsState getPermissionsState(@NonNull PackageSetting ps) {
-        return getPermissionsState(ps.getAppId());
+    private UidPermissionState getUidState(@NonNull PackageSetting ps,
+            @UserIdInt int userId) {
+        return getUidState(ps.getAppId(), userId);
     }
 
     @Nullable
-    private PermissionsState getPermissionsState(@NonNull AndroidPackage pkg) {
-        return getPermissionsState(pkg.getUid());
+    private UidPermissionState getUidState(@NonNull AndroidPackage pkg,
+            @UserIdInt int userId) {
+        return getUidState(pkg.getUid(), userId);
     }
 
     @Nullable
-    private PermissionsState getPermissionsState(int appId) {
+    private UidPermissionState getUidState(int appId, @UserIdInt int userId) {
         synchronized (mLock) {
-            return mAppIdStates.get(appId);
-        }
-    }
-
-    @Nullable
-    private PermissionsState getOrCreatePermissionsState(@NonNull PackageSetting ps) {
-        return getOrCreatePermissionsState(ps.getAppId());
-    }
-
-    @Nullable
-    private PermissionsState getOrCreatePermissionsState(int appId) {
-        synchronized (mLock) {
-            PermissionsState state = mAppIdStates.get(appId);
-            if (state == null) {
-                state = new PermissionsState();
-                mAppIdStates.put(appId, state);
+            final UserPermissionState userState = mState.getUserState(userId);
+            if (userState == null) {
+                return null;
             }
-            return state;
+            return userState.getUidState(appId);
         }
     }
 
-    private void removePermissionsState(int appId) {
+    private void removeAppState(int appId) {
         synchronized (mLock) {
-            mAppIdStates.remove(appId);
+            final int[] userIds = mState.getUserIds();
+            for (final int userId : userIds) {
+                final UserPermissionState userState = mState.getUserState(userId);
+                userState.removeUidState(appId);
+            }
         }
     }
 
-    private void readPermissionsStateFromPackageSettings() {
+    private void readStateFromPackageSettings() {
+        final int[] userIds = getAllUserIds();
         mPackageManagerInt.forEachPackageSetting(ps -> {
+            final int appId = ps.getAppId();
+            final PermissionsState permissionsState = ps.getPermissionsState();
+
             synchronized (mLock) {
-                mAppIdStates.put(ps.getAppId(), new PermissionsState(ps.getPermissionsState()));
+                for (final int userId : userIds) {
+                    final UserPermissionState userState = mState.getOrCreateUserState(userId);
+
+                    userState.setInstallPermissionsFixed(ps.name, ps.areInstallPermissionsFixed());
+                    final UidPermissionState uidState = userState.getOrCreateUidState(appId);
+                    uidState.reset();
+                    uidState.setGlobalGids(permissionsState.getGlobalGids());
+                    uidState.setMissing(permissionsState.isMissing(userId));
+                    readStateFromPermissionStates(uidState,
+                            permissionsState.getInstallPermissionStates(), false);
+                    readStateFromPermissionStates(uidState,
+                            permissionsState.getRuntimePermissionStates(userId), true);
+                }
             }
         });
     }
 
-    private void writePermissionsStateToPackageSettings() {
+    private void readStateFromPermissionStates(@NonNull UidPermissionState uidState,
+            @NonNull List<PermissionsState.PermissionState> permissionStates, boolean isRuntime) {
+        final int permissionStatesSize = permissionStates.size();
+        for (int i = 0; i < permissionStatesSize; i++) {
+            final PermissionsState.PermissionState permissionState = permissionStates.get(i);
+            final BasePermission permission = permissionState.getPermission();
+            uidState.putPermissionState(permission, isRuntime, permissionState.isGranted(),
+                    permissionState.getFlags());
+        }
+    }
+
+    private void writeStateToPackageSettings() {
+        final int[] userIds = mState.getUserIds();
         mPackageManagerInt.forEachPackageSetting(ps -> {
+            ps.setInstallPermissionsFixed(false);
+            final PermissionsState permissionsState = ps.getPermissionsState();
+            permissionsState.reset();
+            final int appId = ps.getAppId();
+
             synchronized (mLock) {
-                final PermissionsState permissionsState = mAppIdStates.get(ps.getAppId());
-                if (permissionsState == null) {
-                    Slog.e(TAG, "Missing permissions state for " + ps.name);
-                    return;
+                for (final int userId : userIds) {
+                    final UserPermissionState userState = mState.getUserState(userId);
+                    if (userState == null) {
+                        Slog.e(TAG, "Missing user state for " + userId);
+                        continue;
+                    }
+
+                    if (userState.areInstallPermissionsFixed(ps.name)) {
+                        ps.setInstallPermissionsFixed(true);
+                    }
+
+                    final UidPermissionState uidState = userState.getUidState(appId);
+                    if (uidState == null) {
+                        Slog.e(TAG, "Missing permission state for " + ps.name + " and user "
+                                + userId);
+                        continue;
+                    }
+
+                    permissionsState.setGlobalGids(uidState.getGlobalGids());
+                    permissionsState.setMissing(uidState.isMissing(), userId);
+                    final List<PermissionState> permissionStates = uidState.getPermissionStates();
+                    final int permissionStatesSize = permissionStates.size();
+                    for (int i = 0; i < permissionStatesSize; i++) {
+                        final PermissionState permissionState = permissionStates.get(i);
+
+                        final BasePermission permission = permissionState.getPermission();
+                        if (permissionState.isGranted()) {
+                            if (permissionState.isRuntime()) {
+                                permissionsState.grantRuntimePermission(permission, userId);
+                            } else {
+                                permissionsState.grantInstallPermission(permission);
+                            }
+                        }
+                        final int flags = permissionState.getFlags();
+                        if (flags != 0) {
+                            final int flagsUserId = permissionState.isRuntime() ? userId
+                                    : UserHandle.USER_ALL;
+                            permissionsState.updatePermissionFlags(permission, flagsUserId, flags,
+                                    flags);
+                        }
+                    }
                 }
-                ps.getPermissionsState().copyFrom(permissionsState);
             }
         });
     }
@@ -4876,12 +4898,12 @@
             PermissionManagerService.this.removeAllPermissions(pkg, chatty);
         }
         @Override
-        public void readPermissionsStateFromPackageSettingsTEMP() {
-            PermissionManagerService.this.readPermissionsStateFromPackageSettings();
+        public void readStateFromPackageSettingsTEMP() {
+            PermissionManagerService.this.readStateFromPackageSettings();
         }
         @Override
-        public void writePermissionsStateToPackageSettingsTEMP() {
-            PermissionManagerService.this.writePermissionsStateToPackageSettings();
+        public void writeStateToPackageSettingsTEMP() {
+            PermissionManagerService.this.writeStateToPackageSettings();
         }
         @Override
         public void onUserRemoved(@UserIdInt int userId) {
@@ -4889,7 +4911,7 @@
         }
         @Override
         public void removePermissionsStateTEMP(int appId) {
-            PermissionManagerService.this.removePermissionsState(appId);
+            PermissionManagerService.this.removeAppState(appId);
         }
         @Override
         @UserIdInt
@@ -5222,10 +5244,10 @@
 
         @Override
         public void onNewUserCreated(int userId) {
-            mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId);
             // NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG
             PermissionManagerService.this.updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL,
                     true, mDefaultPermissionCallback);
+            mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index ca207d8..6d9bd2a 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -266,21 +266,21 @@
     public abstract void removeAllPermissions(@NonNull AndroidPackage pkg, boolean chatty);
 
     /**
-     * Read {@code PermissionsState} from package settings.
+     * Read permission state from package settings.
      *
      * TODO(zhanghai): This is a temporary method because we should not expose
      * {@code PackageSetting} which is a implementation detail that permission should not know.
      * Instead, it should retrieve the legacy state via a defined API.
      */
-    public abstract void readPermissionsStateFromPackageSettingsTEMP();
+    public abstract void readStateFromPackageSettingsTEMP();
 
     /**
-     * Write {@code PermissionsState} from to settings.
+     * Write permission state to package settings.
      *
      * TODO(zhanghai): This is a temporary method and should be removed once we migrated persistence
      * for permission.
      */
-    public abstract void writePermissionsStateToPackageSettingsTEMP();
+    public abstract void writeStateToPackageSettingsTEMP();
 
     /**
      * Notify that a user has been removed and its permission state should be removed as well.
diff --git a/services/core/java/com/android/server/pm/permission/PermissionState.java b/services/core/java/com/android/server/pm/permission/PermissionState.java
new file mode 100644
index 0000000..2ed9a50
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/PermissionState.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.server.pm.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * State for a single permission.
+ */
+public final class PermissionState {
+
+    @NonNull
+    private final BasePermission mPermission;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private boolean mRuntime;
+
+    @GuardedBy("mLock")
+    private boolean mGranted;
+
+    @GuardedBy("mLock")
+    private int mFlags;
+
+    public PermissionState(@NonNull BasePermission permission, boolean isRuntime) {
+        mPermission = permission;
+        mRuntime = isRuntime;
+    }
+
+    public PermissionState(@NonNull PermissionState other) {
+        this(other.mPermission, other.mRuntime);
+
+        mGranted = other.mGranted;
+        mFlags = other.mFlags;
+    }
+
+    @NonNull
+    public BasePermission getPermission() {
+        return mPermission;
+    }
+
+    @NonNull
+    public String getName() {
+        return mPermission.getName();
+    }
+
+    @Nullable
+    public int[] computeGids(@UserIdInt int userId) {
+        return mPermission.computeGids(userId);
+    }
+
+    public boolean isRuntime() {
+        synchronized (mLock) {
+            return mRuntime;
+        }
+    }
+
+    public boolean isGranted() {
+        synchronized (mLock) {
+            return mGranted;
+        }
+    }
+
+    public boolean grant() {
+        synchronized (mLock) {
+            if (mGranted) {
+                return false;
+            }
+            mGranted = true;
+            UidPermissionState.invalidateCache();
+            return true;
+        }
+    }
+
+    public boolean revoke() {
+        synchronized (mLock) {
+            if (!mGranted) {
+                return false;
+            }
+            mGranted = false;
+            UidPermissionState.invalidateCache();
+            return true;
+        }
+    }
+
+    public int getFlags() {
+        synchronized (mLock) {
+            return mFlags;
+        }
+    }
+
+    public boolean updateFlags(int flagMask, int flagValues) {
+        synchronized (mLock) {
+            final int newFlags = flagValues & flagMask;
+
+            // Okay to do before the modification because we hold the lock.
+            UidPermissionState.invalidateCache();
+
+            final int oldFlags = mFlags;
+            mFlags = (mFlags & ~flagMask) | newFlags;
+            return mFlags != oldFlags;
+        }
+    }
+
+    public boolean isDefault() {
+        synchronized (mLock) {
+            return !mGranted && mFlags == 0;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionsState.java b/services/core/java/com/android/server/pm/permission/PermissionsState.java
index bad59cb..4fb2d5f 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionsState.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionsState.java
@@ -86,6 +86,10 @@
         copyFrom(prototype);
     }
 
+    public int[] getGlobalGids() {
+        return mGlobalGids;
+    }
+
     /**
      * Sets the global gids, applicable to all users.
      *
@@ -825,7 +829,7 @@
 
                 PermissionState userState = mUserStates.get(userId);
                 if (userState == null) {
-                    userState = new PermissionState(mPerm.getName());
+                    userState = new PermissionState(mPerm);
                     mUserStates.put(userId, userState);
                 }
 
@@ -908,7 +912,7 @@
                     }
                     return userState.mFlags != oldFlags;
                 } else if (newFlags != 0) {
-                    userState = new PermissionState(mPerm.getName());
+                    userState = new PermissionState(mPerm);
                     userState.mFlags = newFlags;
                     mUserStates.put(userId, userState);
                     return true;
@@ -929,16 +933,16 @@
     }
 
     public static final class PermissionState {
-        private final String mName;
+        private final BasePermission mPermission;
         private boolean mGranted;
         private int mFlags;
 
-        public PermissionState(String name) {
-            mName = name;
+        public PermissionState(BasePermission permission) {
+            mPermission = permission;
         }
 
         public PermissionState(PermissionState other) {
-            mName = other.mName;
+            mPermission = other.mPermission;
             mGranted = other.mGranted;
             mFlags = other.mFlags;
         }
@@ -947,8 +951,12 @@
             return !mGranted && mFlags == 0;
         }
 
+        public BasePermission getPermission() {
+            return mPermission;
+        }
+
         public String getName() {
-            return mName;
+            return mPermission.getName();
         }
 
         public boolean isGranted() {
diff --git a/services/core/java/com/android/server/pm/permission/UidPermissionState.java b/services/core/java/com/android/server/pm/permission/UidPermissionState.java
new file mode 100644
index 0000000..4c047ff
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/UidPermissionState.java
@@ -0,0 +1,574 @@
+/*
+ * Copyright (C) 2015 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.pm.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Permission state for a UID.
+ * <p>
+ * This class is also responsible for keeping track of the Linux GIDs per
+ * user for a package or a shared user. The GIDs are computed as a set of
+ * the GIDs for all granted permissions' GIDs on a per user basis.
+ */
+public final class UidPermissionState {
+    /** The permission operation failed. */
+    public static final int PERMISSION_OPERATION_FAILURE = -1;
+
+    /** The permission operation succeeded and no gids changed. */
+    public static final int PERMISSION_OPERATION_SUCCESS = 0;
+
+    /** The permission operation succeeded and gids changed. */
+    public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 1;
+
+    private static final int[] NO_GIDS = {};
+
+    @NonNull
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private ArrayMap<String, PermissionState> mPermissions;
+
+    @NonNull
+    private int[] mGlobalGids = NO_GIDS;
+
+    private boolean mMissing;
+
+    private boolean mPermissionReviewRequired;
+
+    public UidPermissionState() {
+        /* do nothing */
+    }
+
+    public UidPermissionState(@NonNull UidPermissionState prototype) {
+        copyFrom(prototype);
+    }
+
+    /**
+     * Gets the global gids, applicable to all users.
+     */
+    @NonNull
+    public int[] getGlobalGids() {
+        return mGlobalGids;
+    }
+
+    /**
+     * Sets the global gids, applicable to all users.
+     *
+     * @param globalGids The global gids.
+     */
+    public void setGlobalGids(@NonNull int[] globalGids) {
+        if (!ArrayUtils.isEmpty(globalGids)) {
+            mGlobalGids = Arrays.copyOf(globalGids, globalGids.length);
+        }
+    }
+
+    static void invalidateCache() {
+        PackageManager.invalidatePackageInfoCache();
+    }
+
+    /**
+     * Initialized this instance from another one.
+     *
+     * @param other The other instance.
+     */
+    public void copyFrom(@NonNull UidPermissionState other) {
+        if (other == this) {
+            return;
+        }
+
+        synchronized (mLock) {
+            if (mPermissions != null) {
+                if (other.mPermissions == null) {
+                    mPermissions = null;
+                } else {
+                    mPermissions.clear();
+                }
+            }
+            if (other.mPermissions != null) {
+                if (mPermissions == null) {
+                    mPermissions = new ArrayMap<>();
+                }
+                final int permissionCount = other.mPermissions.size();
+                for (int i = 0; i < permissionCount; i++) {
+                    String name = other.mPermissions.keyAt(i);
+                    PermissionState permissionState = other.mPermissions.valueAt(i);
+                    mPermissions.put(name, new PermissionState(permissionState));
+                }
+            }
+        }
+
+        mGlobalGids = NO_GIDS;
+        if (other.mGlobalGids != NO_GIDS) {
+            mGlobalGids = other.mGlobalGids.clone();
+        }
+
+        mMissing = other.mMissing;
+
+        mPermissionReviewRequired = other.mPermissionReviewRequired;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final UidPermissionState other = (UidPermissionState) obj;
+
+        synchronized (mLock) {
+            if (mPermissions == null) {
+                if (other.mPermissions != null) {
+                    return false;
+                }
+            } else if (!mPermissions.equals(other.mPermissions)) {
+                return false;
+            }
+        }
+
+        if (mMissing != other.mMissing) {
+            return false;
+        }
+
+        if (mPermissionReviewRequired != other.mPermissionReviewRequired) {
+            return false;
+        }
+        return Arrays.equals(mGlobalGids, other.mGlobalGids);
+    }
+
+    /**
+     * Check whether the permissions state is missing for a user. This can happen if permission
+     * state is rolled back and we'll need to generate a reasonable default state to keep the app
+     * usable.
+     */
+    public boolean isMissing() {
+        return mMissing;
+    }
+
+    /**
+     * Set whether the permissions state is missing for a user. This can happen if permission state
+     * is rolled back and we'll need to generate a reasonable default state to keep the app usable.
+     */
+    public void setMissing(boolean missing) {
+        mMissing = missing;
+    }
+
+    public boolean isPermissionReviewRequired() {
+        return mPermissionReviewRequired;
+    }
+
+    /**
+     * Gets whether the state has a given permission.
+     *
+     * @param name The permission name.
+     * @return Whether the state has the permission.
+     */
+    public boolean hasPermission(@NonNull String name) {
+        synchronized (mLock) {
+            if (mPermissions == null) {
+                return false;
+            }
+            PermissionState permissionState = mPermissions.get(name);
+            return permissionState != null && permissionState.isGranted();
+        }
+    }
+
+    /**
+     * Gets whether the state has a given install permission.
+     *
+     * @param name The permission name.
+     * @return Whether the state has the install permission.
+     */
+    public boolean hasInstallPermission(@NonNull String name) {
+        synchronized (mLock) {
+            if (mPermissions == null) {
+                return false;
+            }
+            PermissionState permissionState = mPermissions.get(name);
+            return permissionState != null && permissionState.isGranted()
+                    && !permissionState.isRuntime();
+        }
+    }
+
+    /**
+     * Returns whether the state has any known request for the given permission name,
+     * whether or not it has been granted.
+     *
+     * @deprecated Not all requested permissions may be here.
+     */
+    @Deprecated
+    public boolean hasRequestedPermission(@NonNull ArraySet<String> names) {
+        synchronized (mLock) {
+            if (mPermissions == null) {
+                return false;
+            }
+            for (int i = names.size() - 1; i >= 0; i--) {
+                if (mPermissions.get(names.valueAt(i)) != null) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns whether the state has any known request for the given permission name,
+     * whether or not it has been granted.
+     *
+     * @deprecated Not all requested permissions may be here.
+     */
+    @Deprecated
+    public boolean hasRequestedPermission(@NonNull String name) {
+        return mPermissions != null && (mPermissions.get(name) != null);
+    }
+
+    /**
+     * Gets all permissions for a given device user id regardless if they
+     * are install time or runtime permissions.
+     *
+     * @return The permissions or an empty set.
+     */
+    @NonNull
+    public Set<String> getPermissions() {
+        synchronized (mLock) {
+            if (mPermissions == null) {
+                return Collections.emptySet();
+            }
+
+            Set<String> permissions = new ArraySet<>(mPermissions.size());
+
+            final int permissionCount = mPermissions.size();
+            for (int i = 0; i < permissionCount; i++) {
+                String permission = mPermissions.keyAt(i);
+
+                if (hasPermission(permission)) {
+                    permissions.add(permission);
+                }
+            }
+
+            return permissions;
+        }
+    }
+
+    /**
+     * Gets the flags for a permission.
+     *
+     * @param name The permission name.
+     * @return The permission state or null if no such.
+     */
+    public int getPermissionFlags(@NonNull String name) {
+        PermissionState permState = getPermissionState(name);
+        if (permState != null) {
+            return permState.getFlags();
+        }
+        return 0;
+    }
+
+    /**
+     * Update the flags associated with a given permission.
+     * @param permission The permission whose flags to update.
+     * @param flagMask Mask for which flags to change.
+     * @param flagValues New values for the mask flags.
+     * @return Whether the permission flags changed.
+     */
+    public boolean updatePermissionFlags(@NonNull BasePermission permission, int flagMask,
+            int flagValues) {
+        if (flagMask == 0) {
+            return false;
+        }
+
+        PermissionState permissionState = ensurePermissionState(permission);
+
+        final int oldFlags = permissionState.getFlags();
+
+        synchronized (mLock) {
+            final boolean updated = permissionState.updateFlags(flagMask, flagValues);
+            if (updated) {
+                final int newFlags = permissionState.getFlags();
+                if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0
+                        && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+                    mPermissionReviewRequired = true;
+                } else if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0
+                        && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
+                    if (mPermissionReviewRequired && !hasPermissionRequiringReview()) {
+                        mPermissionReviewRequired = false;
+                    }
+                }
+            }
+            return updated;
+        }
+    }
+
+    private boolean hasPermissionRequiringReview() {
+        synchronized (mLock) {
+            final int permissionCount = mPermissions.size();
+            for (int i = 0; i < permissionCount; i++) {
+                final PermissionState permission = mPermissions.valueAt(i);
+                if ((permission.getFlags() & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public boolean updatePermissionFlagsForAllPermissions(int flagMask, int flagValues) {
+        synchronized (mLock) {
+            if (mPermissions == null) {
+                return false;
+            }
+            boolean changed = false;
+            final int permissionCount = mPermissions.size();
+            for (int i = 0; i < permissionCount; i++) {
+                PermissionState permissionState = mPermissions.valueAt(i);
+                changed |= permissionState.updateFlags(flagMask, flagValues);
+            }
+            return changed;
+        }
+    }
+
+    /**
+     * Compute the Linux gids for a given device user from the permissions
+     * granted to this user. Note that these are computed to avoid additional
+     * state as they are rarely accessed.
+     *
+     * @param userId The device user id.
+     * @return The gids for the device user.
+     */
+    @NonNull
+    public int[] computeGids(@UserIdInt int userId) {
+        int[] gids = mGlobalGids;
+
+        synchronized (mLock) {
+            if (mPermissions != null) {
+                final int permissionCount = mPermissions.size();
+                for (int i = 0; i < permissionCount; i++) {
+                    PermissionState permissionState = mPermissions.valueAt(i);
+                    if (!permissionState.isGranted()) {
+                        continue;
+                    }
+                    final int[] permGids = permissionState.computeGids(userId);
+                    if (permGids != NO_GIDS) {
+                        gids = appendInts(gids, permGids);
+                    }
+                }
+            }
+        }
+
+        return gids;
+    }
+
+    /**
+     * Compute the Linux gids for all device users from the permissions
+     * granted to these users.
+     *
+     * @return The gids for all device users.
+     */
+    @NonNull
+    public int[] computeGids(@NonNull int[] userIds) {
+        int[] gids = mGlobalGids;
+
+        for (int userId : userIds) {
+            final int[] userGids = computeGids(userId);
+            gids = appendInts(gids, userGids);
+        }
+
+        return gids;
+    }
+
+    /**
+     * Resets the internal state of this object.
+     */
+    public void reset() {
+        mGlobalGids = NO_GIDS;
+
+        synchronized (mLock) {
+            mPermissions = null;
+            invalidateCache();
+        }
+
+        mMissing = false;
+        mPermissionReviewRequired = false;
+    }
+
+    /**
+     * Gets the state for a permission or null if no such.
+     *
+     * @param name The permission name.
+     * @return The permission state.
+     */
+    @Nullable
+    public PermissionState getPermissionState(@NonNull String name) {
+        synchronized (mLock) {
+            if (mPermissions == null) {
+                return null;
+            }
+            return mPermissions.get(name);
+        }
+    }
+
+    /**
+     * Gets all permission states.
+     *
+     * @return The permission states or an empty set.
+     */
+    @NonNull
+    public List<PermissionState> getPermissionStates() {
+        synchronized (mLock) {
+            if (mPermissions == null) {
+                return Collections.emptyList();
+            }
+            return new ArrayList<>(mPermissions.values());
+        }
+    }
+
+    /**
+     * Put a permission state.
+     */
+    public void putPermissionState(@NonNull BasePermission permission, boolean isRuntime,
+            boolean isGranted, int flags) {
+        synchronized (mLock) {
+            ensureNoPermissionState(permission.name);
+            PermissionState permissionState = ensurePermissionState(permission, isRuntime);
+            if (isGranted) {
+                permissionState.grant();
+            }
+            permissionState.updateFlags(flags, flags);
+            if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+                mPermissionReviewRequired = true;
+            }
+        }
+    }
+
+    /**
+     * Grant a permission.
+     *
+     * @param permission The permission to grant.
+     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+     *     #PERMISSION_OPERATION_FAILURE}.
+     */
+    public int grantPermission(@NonNull BasePermission permission) {
+        if (hasPermission(permission.getName())) {
+            return PERMISSION_OPERATION_SUCCESS;
+        }
+
+        PermissionState permissionState = ensurePermissionState(permission);
+
+        if (!permissionState.grant()) {
+            return PERMISSION_OPERATION_FAILURE;
+        }
+
+        return permission.hasGids() ? PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED
+                : PERMISSION_OPERATION_SUCCESS;
+    }
+
+    /**
+     * Revoke a permission.
+     *
+     * @param permission The permission to revoke.
+     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+     *     #PERMISSION_OPERATION_FAILURE}.
+     */
+    public int revokePermission(@NonNull BasePermission permission) {
+        final String permissionName = permission.getName();
+        if (!hasPermission(permissionName)) {
+            return PERMISSION_OPERATION_SUCCESS;
+        }
+
+        PermissionState permissionState;
+        synchronized (mLock) {
+            permissionState = mPermissions.get(permissionName);
+        }
+
+        if (!permissionState.revoke()) {
+            return PERMISSION_OPERATION_FAILURE;
+        }
+
+        if (permissionState.isDefault()) {
+            ensureNoPermissionState(permissionName);
+        }
+
+        return permission.hasGids() ? PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED
+                : PERMISSION_OPERATION_SUCCESS;
+    }
+
+    // TODO: fix this to use arraycopy and append all ints in one go
+    private static int[] appendInts(int[] current, int[] added) {
+        if (current != null && added != null) {
+            for (int guid : added) {
+                current = ArrayUtils.appendInt(current, guid);
+            }
+        }
+        return current;
+    }
+
+    @NonNull
+    private PermissionState ensurePermissionState(@NonNull BasePermission permission) {
+        return ensurePermissionState(permission, permission.isRuntime());
+    }
+
+    @NonNull
+    private PermissionState ensurePermissionState(@NonNull BasePermission permission,
+            boolean isRuntime) {
+        final String permissionName = permission.getName();
+        synchronized (mLock) {
+            if (mPermissions == null) {
+                mPermissions = new ArrayMap<>();
+            }
+            PermissionState permissionState = mPermissions.get(permissionName);
+            if (permissionState == null) {
+                permissionState = new PermissionState(permission, isRuntime);
+                mPermissions.put(permissionName, permissionState);
+            }
+            return permissionState;
+        }
+    }
+
+    private void ensureNoPermissionState(@NonNull String name) {
+        synchronized (mLock) {
+            if (mPermissions == null) {
+                return;
+            }
+            mPermissions.remove(name);
+            if (mPermissions.isEmpty()) {
+                mPermissions = null;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/UserPermissionState.java b/services/core/java/com/android/server/pm/permission/UserPermissionState.java
new file mode 100644
index 0000000..7f55cb1
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/UserPermissionState.java
@@ -0,0 +1,103 @@
+/*
+ * 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.pm.permission;
+
+import android.annotation.AppIdInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Permission state for a user.
+ */
+public final class UserPermissionState {
+    /**
+     * Whether the install permissions have been granted to a package, so that no install
+     * permissions should be added to it unless the package is upgraded.
+     */
+    @GuardedBy("mLock")
+    @NonNull
+    private final ArraySet<String> mInstallPermissionsFixed = new ArraySet<>();
+
+    /**
+     * Maps from app ID to {@link UidPermissionState}.
+     */
+    @GuardedBy("mLock")
+    @NonNull
+    private final SparseArray<UidPermissionState> mUidStates = new SparseArray<>();
+
+    @NonNull
+    private final Object mLock;
+
+    public UserPermissionState(@NonNull Object lock) {
+        mLock = lock;
+    }
+
+    public boolean areInstallPermissionsFixed(@NonNull String packageName) {
+        synchronized (mLock) {
+            return mInstallPermissionsFixed.contains(packageName);
+        }
+    }
+
+    public void setInstallPermissionsFixed(@NonNull String packageName, boolean fixed) {
+        synchronized (mLock) {
+            if (fixed) {
+                mInstallPermissionsFixed.add(packageName);
+            } else {
+                mInstallPermissionsFixed.remove(packageName);
+            }
+        }
+    }
+
+    @Nullable
+    public UidPermissionState getUidState(@AppIdInt int appId) {
+        checkAppId(appId);
+        synchronized (mLock) {
+            return mUidStates.get(appId);
+        }
+    }
+
+    @NonNull
+    public UidPermissionState getOrCreateUidState(@AppIdInt int appId) {
+        checkAppId(appId);
+        synchronized (mLock) {
+            UidPermissionState uidState = mUidStates.get(appId);
+            if (uidState == null) {
+                uidState = new UidPermissionState();
+                mUidStates.put(appId, uidState);
+            }
+            return uidState;
+        }
+    }
+
+    public void removeUidState(@AppIdInt int appId) {
+        checkAppId(appId);
+        synchronized (mLock) {
+            mUidStates.delete(appId);
+        }
+    }
+
+    private void checkAppId(@AppIdInt int appId) {
+        if (UserHandle.getUserId(appId) != 0) {
+            throw new IllegalArgumentException(appId + " is not an app ID");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java
index 8568d5f..4a90bbc 100644
--- a/services/core/java/com/android/server/wm/BarController.java
+++ b/services/core/java/com/android/server/wm/BarController.java
@@ -16,88 +16,19 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.BarControllerProto.STATE;
-import static com.android.server.wm.BarControllerProto.TRANSIENT_STATE;
-
 import android.annotation.NonNull;
-import android.app.StatusBarManager;
 import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.WindowManager;
-
-import com.android.server.LocalServices;
-import com.android.server.UiThread;
-import com.android.server.statusbar.StatusBarManagerInternal;
-
-import java.io.PrintWriter;
 
 /**
  * Controls state/behavior specific to a system bar window.
  */
 public class BarController {
-    private static final boolean DEBUG = false;
-
-    private static final int TRANSIENT_BAR_NONE = 0;
-    private static final int TRANSIENT_BAR_SHOW_REQUESTED = 1;
-    private static final int TRANSIENT_BAR_SHOWING = 2;
-    private static final int TRANSIENT_BAR_HIDING = 3;
-
-    private static final int TRANSLUCENT_ANIMATION_DELAY_MS = 1000;
-
-    private static final int MSG_NAV_BAR_VISIBILITY_CHANGED = 1;
-
-    protected final String mTag;
-    protected final int mDisplayId;
-    private final int mTransientFlag;
-    private final int mUnhideFlag;
-    private final int mTranslucentFlag;
-    private final int mTransparentFlag;
-    private final int mStatusBarManagerId;
-    private final int mTranslucentWmFlag;
     private final int mWindowType;
-    protected final Handler mHandler;
-    private final Object mServiceAquireLock = new Object();
-    private StatusBarManagerInternal mStatusBarInternal;
 
-    protected WindowState mWin;
-    private @StatusBarManager.WindowVisibleState int mState =
-            StatusBarManager.WINDOW_STATE_SHOWING;
-    private int mTransientBarState;
-    private boolean mPendingShow;
-    private long mLastTranslucent;
-    private boolean mShowTransparent;
-    private boolean mSetUnHideFlagWhenNextTransparent;
-    private boolean mNoAnimationOnNextShow;
     private final Rect mContentFrame = new Rect();
 
-    private OnBarVisibilityChangedListener mVisibilityChangeListener;
-
-    BarController(String tag, int displayId, int transientFlag, int unhideFlag, int translucentFlag,
-            int statusBarManagerId, int windowType, int translucentWmFlag, int transparentFlag) {
-        mTag = "BarController." + tag;
-        mDisplayId = displayId;
-        mTransientFlag = transientFlag;
-        mUnhideFlag = unhideFlag;
-        mTranslucentFlag = translucentFlag;
-        mStatusBarManagerId = statusBarManagerId;
+    BarController(int windowType) {
         mWindowType = windowType;
-        mTranslucentWmFlag = translucentWmFlag;
-        mTransparentFlag = transparentFlag;
-        mHandler = new BarHandler();
-    }
-
-    void setWindow(WindowState win) {
-        if (ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL) {
-            // BarController gets replaced with InsetsPolicy in the full insets mode.
-            return;
-        }
-        mWin = win;
     }
 
     /**
@@ -109,67 +40,6 @@
         mContentFrame.set(frame);
     }
 
-    void setShowTransparent(boolean transparent) {
-        if (transparent != mShowTransparent) {
-            mShowTransparent = transparent;
-            mSetUnHideFlagWhenNextTransparent = transparent;
-            mNoAnimationOnNextShow = true;
-        }
-    }
-
-    void showTransient() {
-        if (mWin != null) {
-            setTransientBarState(TRANSIENT_BAR_SHOW_REQUESTED);
-        }
-    }
-
-    boolean isTransientShowing() {
-        return mTransientBarState == TRANSIENT_BAR_SHOWING;
-    }
-
-    boolean isTransientShowRequested() {
-        return mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED;
-    }
-
-    boolean wasRecentlyTranslucent() {
-        return (SystemClock.uptimeMillis() - mLastTranslucent) < TRANSLUCENT_ANIMATION_DELAY_MS;
-    }
-
-    void adjustSystemUiVisibilityLw(int oldVis, int vis) {
-        if (mWin != null && mTransientBarState == TRANSIENT_BAR_SHOWING
-                && (vis & mTransientFlag) == 0) {
-            // sysui requests hide
-            setTransientBarState(TRANSIENT_BAR_HIDING);
-            setBarShowingLw(false);
-        } else if (mWin != null && (oldVis & mUnhideFlag) != 0 && (vis & mUnhideFlag) == 0) {
-            // sysui ready to unhide
-            setBarShowingLw(true);
-        }
-    }
-
-    int applyTranslucentFlagLw(WindowState win, int vis, int oldVis) {
-        if (mWin != null) {
-            if (win != null) {
-                int fl = PolicyControl.getWindowFlags(win, null);
-                if ((fl & mTranslucentWmFlag) != 0) {
-                    vis |= mTranslucentFlag;
-                } else {
-                    vis &= ~mTranslucentFlag;
-                }
-                if ((fl & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
-                        && isTransparentAllowed(win)) {
-                    vis |= mTransparentFlag;
-                } else {
-                    vis &= ~mTransparentFlag;
-                }
-            } else {
-                vis = (vis & ~mTranslucentFlag) | (oldVis & mTranslucentFlag);
-                vis = (vis & ~mTransparentFlag) | (oldVis & mTransparentFlag);
-            }
-        }
-        return vis;
-    }
-
     private Rect getContentFrame(@NonNull WindowState win) {
         final Rect rotatedContentFrame = win.mToken.getFixedRotationBarContentFrame(mWindowType);
         return rotatedContentFrame != null ? rotatedContentFrame : mContentFrame;
@@ -188,212 +58,4 @@
         }
         return win.letterboxNotIntersectsOrFullyContains(getContentFrame(win));
     }
-
-    boolean setBarShowingLw(final boolean show) {
-        if (mWin == null) return false;
-        if (show && mTransientBarState == TRANSIENT_BAR_HIDING) {
-            mPendingShow = true;
-            return false;
-        }
-        final boolean wasVis = mWin.isVisibleLw();
-        final boolean wasAnim = mWin.isAnimatingLw();
-        final boolean skipAnim = skipAnimation();
-        final boolean change = show ? mWin.showLw(!mNoAnimationOnNextShow && !skipAnim)
-                : mWin.hideLw(!mNoAnimationOnNextShow && !skipAnim);
-        mNoAnimationOnNextShow = false;
-        final int state = computeStateLw(wasVis, wasAnim, mWin, change);
-        final boolean stateChanged = updateStateLw(state);
-
-        if (change && (mVisibilityChangeListener != null)) {
-            mHandler.obtainMessage(MSG_NAV_BAR_VISIBILITY_CHANGED, show ? 1 : 0, 0).sendToTarget();
-        }
-
-        return change || stateChanged;
-    }
-
-    void setOnBarVisibilityChangedListener(OnBarVisibilityChangedListener listener,
-            boolean invokeWithState) {
-        mVisibilityChangeListener = listener;
-        if (invokeWithState) {
-            // Optionally report the initial window state for initialization purposes
-            mHandler.obtainMessage(MSG_NAV_BAR_VISIBILITY_CHANGED,
-                    (mState == StatusBarManager.WINDOW_STATE_SHOWING) ? 1 : 0, 0).sendToTarget();
-        }
-    }
-
-    protected boolean skipAnimation() {
-        return !mWin.isDrawn();
-    }
-
-    private @StatusBarManager.WindowVisibleState int computeStateLw(
-            boolean wasVis, boolean wasAnim, WindowState win, boolean change) {
-        if (win.isDrawn()) {
-            final boolean vis = win.isVisibleLw();
-            final boolean anim = win.isAnimatingLw();
-            if (mState == StatusBarManager.WINDOW_STATE_HIDING && !change && !vis) {
-                return StatusBarManager.WINDOW_STATE_HIDDEN;
-            } else if (mState == StatusBarManager.WINDOW_STATE_HIDDEN && vis) {
-                return StatusBarManager.WINDOW_STATE_SHOWING;
-            } else if (change) {
-                if (wasVis && vis && !wasAnim && anim) {
-                    return StatusBarManager.WINDOW_STATE_HIDING;
-                } else {
-                    return StatusBarManager.WINDOW_STATE_SHOWING;
-                }
-            }
-        }
-        return mState;
-    }
-
-    private boolean updateStateLw(@StatusBarManager.WindowVisibleState final int state) {
-        if (mWin != null && state != mState) {
-            mState = state;
-            if (DEBUG) Slog.d(mTag, "mState: " + StatusBarManager.windowStateToString(state));
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    StatusBarManagerInternal statusbar = getStatusBarInternal();
-                    if (statusbar != null) {
-                        statusbar.setWindowState(mDisplayId, mStatusBarManagerId, state);
-                    }
-                }
-            });
-            return true;
-        }
-        return false;
-    }
-
-    boolean checkHiddenLw() {
-        if (mWin != null && mWin.isDrawn()) {
-            if (!mWin.isVisibleLw() && !mWin.isAnimatingLw()) {
-                updateStateLw(StatusBarManager.WINDOW_STATE_HIDDEN);
-            }
-            if (mTransientBarState == TRANSIENT_BAR_HIDING && !mWin.isVisibleLw()) {
-                // Finished animating out, clean up and reset style
-                setTransientBarState(TRANSIENT_BAR_NONE);
-                if (mPendingShow) {
-                    setBarShowingLw(true);
-                    mPendingShow = false;
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    boolean checkShowTransientBarLw() {
-        if (mTransientBarState == TRANSIENT_BAR_SHOWING) {
-            if (DEBUG) Slog.d(mTag, "Not showing transient bar, already shown");
-            return false;
-        } else if (mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED) {
-            if (DEBUG) Slog.d(mTag, "Not showing transient bar, already requested");
-            return false;
-        } else if (mWin == null) {
-            if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar doesn't exist");
-            return false;
-        } else if (mWin.isDisplayed()) {
-            if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar already visible");
-            return false;
-        } else {
-            return true;
-        }
-    }
-
-    int updateVisibilityLw(boolean transientAllowed, int oldVis, int vis) {
-        if (mWin == null) return vis;
-        if (isTransientShowing() || isTransientShowRequested()) { // transient bar requested
-            if (transientAllowed) {
-                vis |= mTransientFlag;
-                if ((oldVis & mTransientFlag) == 0) {
-                    vis |= mUnhideFlag;  // tell sysui we're ready to unhide
-                }
-                setTransientBarState(TRANSIENT_BAR_SHOWING);  // request accepted
-            } else {
-                setTransientBarState(TRANSIENT_BAR_NONE);  // request denied
-            }
-        }
-        if (mShowTransparent) {
-            vis |= mTransparentFlag;
-            if (mSetUnHideFlagWhenNextTransparent) {
-                vis |= mUnhideFlag;
-                mSetUnHideFlagWhenNextTransparent = false;
-            }
-        }
-        if (mTransientBarState != TRANSIENT_BAR_NONE) {
-            vis |= mTransientFlag;  // ignore clear requests until transition completes
-            vis &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE;  // never show transient bars in low profile
-        }
-        if ((vis & mTranslucentFlag) != 0 || (oldVis & mTranslucentFlag) != 0
-                || ((vis | oldVis) & mTransparentFlag) != 0) {
-            mLastTranslucent = SystemClock.uptimeMillis();
-        }
-        return vis;
-    }
-
-    private void setTransientBarState(int state) {
-        if (mWin != null && state != mTransientBarState) {
-            if (mTransientBarState == TRANSIENT_BAR_SHOWING || state == TRANSIENT_BAR_SHOWING) {
-                mLastTranslucent = SystemClock.uptimeMillis();
-            }
-            mTransientBarState = state;
-            if (DEBUG) Slog.d(mTag, "mTransientBarState: " + transientBarStateToString(state));
-        }
-    }
-
-    protected StatusBarManagerInternal getStatusBarInternal() {
-        synchronized (mServiceAquireLock) {
-            if (mStatusBarInternal == null) {
-                mStatusBarInternal = LocalServices.getService(StatusBarManagerInternal.class);
-            }
-            return mStatusBarInternal;
-        }
-    }
-
-    private static String transientBarStateToString(int state) {
-        if (state == TRANSIENT_BAR_HIDING) return "TRANSIENT_BAR_HIDING";
-        if (state == TRANSIENT_BAR_SHOWING) return "TRANSIENT_BAR_SHOWING";
-        if (state == TRANSIENT_BAR_SHOW_REQUESTED) return "TRANSIENT_BAR_SHOW_REQUESTED";
-        if (state == TRANSIENT_BAR_NONE) return "TRANSIENT_BAR_NONE";
-        throw new IllegalArgumentException("Unknown state " + state);
-    }
-
-    void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        final long token = proto.start(fieldId);
-        proto.write(STATE, mState);
-        proto.write(TRANSIENT_STATE, mTransientBarState);
-        proto.end(token);
-    }
-
-    void dump(PrintWriter pw, String prefix) {
-        if (mWin != null) {
-            pw.print(prefix); pw.println(mTag);
-            pw.print(prefix); pw.print("  "); pw.print("mState"); pw.print('=');
-            pw.println(StatusBarManager.windowStateToString(mState));
-            pw.print(prefix); pw.print("  "); pw.print("mTransientBar"); pw.print('=');
-            pw.println(transientBarStateToString(mTransientBarState));
-            pw.print(prefix); pw.print("  mContentFrame="); pw.println(mContentFrame);
-        }
-    }
-
-    private class BarHandler extends Handler {
-        BarHandler() {
-            super(UiThread.getHandler().getLooper());
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_NAV_BAR_VISIBILITY_CHANGED:
-                    final boolean visible = msg.arg1 != 0;
-                    if (mVisibilityChangeListener != null) {
-                        mVisibilityChangeListener.onBarVisibilityChanged(visible);
-                    }
-                    break;
-            }
-        }
-    }
-
-    interface OnBarVisibilityChangedListener {
-        void onBarVisibilityChanged(boolean visible);
-    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2ca849d..0044d74 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -192,7 +192,6 @@
 import android.view.SurfaceControl.Transaction;
 import android.view.SurfaceSession;
 import android.view.View;
-import android.view.ViewRootImpl;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
@@ -3744,7 +3743,6 @@
 
     void statusBarVisibilityChanged(int visibility) {
         mLastStatusBarVisibility = visibility;
-        visibility = getDisplayPolicy().adjustSystemUiVisibilityLw(visibility);
         updateStatusBarVisibilityLocked(visibility);
     }
 
@@ -3769,32 +3767,16 @@
 
     void updateSystemUiVisibility(int visibility, int globalDiff) {
         forAllWindows(w -> {
-            try {
-                final int curValue = w.mSystemUiVisibility;
-                final int diff = (curValue ^ visibility) & globalDiff;
-                final int newValue = (curValue & ~diff) | (visibility & diff);
-                if (newValue != curValue) {
-                    w.mSeq++;
-                    w.mSystemUiVisibility = newValue;
-                }
-                if ((newValue != curValue || w.mAttrs.hasSystemUiListeners)
-                        && ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL) {
-                    w.mClient.dispatchSystemUiVisibilityChanged(w.mSeq,
-                            visibility, newValue, diff);
-                }
-            } catch (RemoteException e) {
-                // so sorry
+            final int curValue = w.mSystemUiVisibility;
+            final int diff = (curValue ^ visibility) & globalDiff;
+            final int newValue = (curValue & ~diff) | (visibility & diff);
+            if (newValue != curValue) {
+                w.mSeq++;
+                w.mSystemUiVisibility = newValue;
             }
         }, true /* traverseTopToBottom */);
     }
 
-    void reevaluateStatusBarVisibility() {
-        int visibility = getDisplayPolicy().adjustSystemUiVisibilityLw(mLastStatusBarVisibility);
-        if (updateStatusBarVisibilityLocked(visibility)) {
-            mWmService.mWindowPlacerLocked.requestTraversal();
-        }
-    }
-
     void onWindowFreezeTimeout() {
         Slog.w(TAG_WM, "Window freeze timeout expired.");
         mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 779f6b2..572c9b3 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -57,8 +57,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
@@ -66,13 +64,11 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
@@ -134,14 +130,13 @@
 import android.hardware.input.InputManager;
 import android.hardware.power.Boost;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.util.ArraySet;
-import android.util.IntArray;
-import android.util.Pair;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -199,7 +194,6 @@
  */
 public class DisplayPolicy {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayPolicy" : TAG_WM;
-    private static final boolean DEBUG = false;
 
     private static final boolean ALTERNATE_CAR_MODE_NAV_SIZE = false;
 
@@ -221,18 +215,6 @@
     /** Use the transit animation in style resource (see {@link #selectAnimation}). */
     static final int ANIMATION_STYLEABLE = 0;
 
-    /**
-     * These are the system UI flags that, when changing, can cause the layout
-     * of the screen to change.
-     */
-    private static final int SYSTEM_UI_CHANGING_LAYOUT =
-            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
-                    | View.SYSTEM_UI_FLAG_FULLSCREEN
-                    | View.STATUS_BAR_TRANSLUCENT
-                    | View.NAVIGATION_BAR_TRANSLUCENT
-                    | View.STATUS_BAR_TRANSPARENT
-                    | View.NAVIGATION_BAR_TRANSPARENT;
-
     private static final int[] SHOW_TYPES_FOR_SWIPE = {ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR};
     private static final int[] SHOW_TYPES_FOR_PANIC = {ITYPE_NAVIGATION_BAR};
 
@@ -327,21 +309,10 @@
 
     private boolean mLastImmersiveMode;
 
-    private final StatusBarController mStatusBarController;
-
+    private StatusBarManagerInternal mStatusBarInternal;
+    private final BarController mStatusBarController;
     private final BarController mNavigationBarController;
 
-    private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener =
-            new BarController.OnBarVisibilityChangedListener() {
-                @Override
-                public void onBarVisibilityChanged(boolean visible) {
-                    if (mAccessibilityManager == null) {
-                        return;
-                    }
-                    mAccessibilityManager.notifyAccessibilityButtonVisibilityChanged(visible);
-                }
-            };
-
     // The windows we were told about in focusChanged.
     private WindowState mFocusedWindow;
     private WindowState mLastFocusedWindow;
@@ -353,15 +324,8 @@
     private boolean mLastNavVisible;
     private boolean mLastNavTranslucent;
     private boolean mLastNavAllowedHidden;
-    private boolean mLastNotificationShadeForcesShowingNavigation;
 
-    int mLastSystemUiFlags;
-    // Bits that we are in the process of clearing, so we want to prevent
-    // them from being set by applications until everything has been updated
-    // to have them clear.
-    private int mResettingSystemUiFlags = 0;
-    // Bits that we are currently always keeping cleared.
-    private int mForceClearedSystemUiFlags = 0;
+    private int mLastDisableFlags;
     private int mLastAppearance;
     private int mLastFullscreenAppearance;
     private int mLastDockedAppearance;
@@ -430,6 +394,8 @@
 
     private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
 
+    private final WindowManagerInternal.AppTransitionListener mAppTransitionListener;
+
     private class PolicyHandler extends Handler {
 
         PolicyHandler(Looper looper) {
@@ -472,16 +438,9 @@
         mLock = service.getWindowManagerLock();
 
         final int displayId = displayContent.getDisplayId();
-        mStatusBarController = new StatusBarController(displayId);
-        mNavigationBarController = new BarController("NavigationBar",
-                displayId,
-                View.NAVIGATION_BAR_TRANSIENT,
-                View.NAVIGATION_BAR_UNHIDE,
-                View.NAVIGATION_BAR_TRANSLUCENT,
-                StatusBarManager.WINDOW_NAVIGATION_BAR,
-                TYPE_NAVIGATION_BAR,
-                FLAG_TRANSLUCENT_NAVIGATION,
-                View.NAVIGATION_BAR_TRANSPARENT);
+
+        mStatusBarController = new BarController(TYPE_STATUS_BAR);
+        mNavigationBarController = new BarController(TYPE_NAVIGATION_BAR);
 
         final Resources r = mContext.getResources();
         mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer);
@@ -612,8 +571,58 @@
                     }
                 });
         displayContent.registerPointerEventListener(mSystemGestures);
-        displayContent.mAppTransition.registerListenerLocked(
-                mStatusBarController.getAppTransitionListener());
+        mAppTransitionListener = new WindowManagerInternal.AppTransitionListener() {
+
+            private Runnable mAppTransitionPending = () -> {
+                StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+                if (statusBar != null) {
+                    statusBar.appTransitionPending(displayId);
+                }
+            };
+
+            private Runnable mAppTransitionCancelled = () -> {
+                StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+                if (statusBar != null) {
+                    statusBar.appTransitionCancelled(displayId);
+                }
+            };
+
+            private Runnable mAppTransitionFinished = () -> {
+                StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+                if (statusBar != null) {
+                    statusBar.appTransitionFinished(displayId);
+                }
+            };
+
+            @Override
+            public void onAppTransitionPendingLocked() {
+                mHandler.post(mAppTransitionPending);
+            }
+
+            @Override
+            public int onAppTransitionStartingLocked(int transit, long duration,
+                    long statusBarAnimationStartTime, long statusBarAnimationDuration) {
+                mHandler.post(() -> {
+                    StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+                    if (statusBar != null) {
+                        statusBar.appTransitionStarting(mContext.getDisplayId(),
+                                statusBarAnimationStartTime, statusBarAnimationDuration);
+                    }
+                });
+                return 0;
+            }
+
+            @Override
+            public void onAppTransitionCancelledLocked(int transit) {
+                mHandler.post(mAppTransitionCancelled);
+            }
+
+            @Override
+            public void onAppTransitionFinishedLocked(IBinder token) {
+                mHandler.post(mAppTransitionFinished);
+            }
+        };
+        displayContent.mAppTransition.registerListenerLocked(mAppTransitionListener);
         mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext, looper,
                 mService.mVrModeEnabled);
 
@@ -1066,7 +1075,6 @@
                 break;
             case TYPE_STATUS_BAR:
                 mStatusBar = win;
-                mStatusBarController.setWindow(win);
                 final TriConsumer<DisplayFrames, WindowState, Rect> frameProvider =
                         (displayFrames, windowState, rect) -> {
                             rect.bottom = rect.top + getStatusBarHeight(displayFrames);
@@ -1077,9 +1085,6 @@
                 break;
             case TYPE_NAVIGATION_BAR:
                 mNavigationBar = win;
-                mNavigationBarController.setWindow(win);
-                mNavigationBarController.setOnBarVisibilityChangedListener(
-                        mNavBarVisibilityListener, true);
                 mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, win,
                         (displayFrames, windowState, inOutFrame) -> {
 
@@ -1135,12 +1140,10 @@
                         switch (insetsType) {
                             case ITYPE_STATUS_BAR:
                                 mStatusBarAlt = win;
-                                mStatusBarController.setWindow(mStatusBarAlt);
                                 mStatusBarAltPosition = getAltBarPosition(attrs);
                                 break;
                             case ITYPE_NAVIGATION_BAR:
                                 mNavigationBarAlt = win;
-                                mNavigationBarController.setWindow(mNavigationBarAlt);
                                 mNavigationBarAltPosition = getAltBarPosition(attrs);
                                 break;
                         }
@@ -1210,12 +1213,10 @@
         if (mStatusBar == win || mStatusBarAlt == win) {
             mStatusBar = null;
             mStatusBarAlt = null;
-            mStatusBarController.setWindow(null);
             mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, null, null);
         } else if (mNavigationBar == win || mNavigationBarAlt == win) {
             mNavigationBar = null;
             mNavigationBarAlt = null;
-            mNavigationBarController.setWindow(null);
             mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, null, null);
         } else if (mNotificationShade == win) {
             mNotificationShade = null;
@@ -1235,7 +1236,7 @@
     }
 
     @VisibleForTesting
-    StatusBarController getStatusBarController() {
+    BarController getStatusBarController() {
         return mStatusBarController;
     }
 
@@ -1368,25 +1369,6 @@
     }
 
     /**
-     * Called when a new system UI visibility is being reported, allowing
-     * the policy to adjust what is actually reported.
-     * @param visibility The raw visibility reported by the status bar.
-     * @return The new desired visibility.
-     */
-    public int adjustSystemUiVisibilityLw(int visibility) {
-        mStatusBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility);
-        mNavigationBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility);
-
-        // Reset any bits in mForceClearingStatusBarVisibility that
-        // are now clear.
-        mResettingSystemUiFlags &= visibility;
-        // Clear any bits in the new visibility that are currently being
-        // force cleared, before reporting it.
-        return visibility & ~mResettingSystemUiFlags
-                & ~mForceClearedSystemUiFlags;
-    }
-
-    /**
      * @return true if the system bars are forced to stay visible
      */
     public boolean areSystemBarsForcedShownLw(WindowState windowState) {
@@ -1481,16 +1463,6 @@
         return mForceShowSystemBars;
     }
 
-    private final Runnable mClearHideNavigationFlag = new Runnable() {
-        @Override
-        public void run() {
-            synchronized (mLock) {
-                mForceClearedSystemUiFlags &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
-                mDisplayContent.reevaluateStatusBarVisibility();
-            }
-        }
-    };
-
     /**
      * Input handler used while nav bar is hidden.  Captures any touch on the screen,
      * to determine when the nav bar should be shown and prevent applications from
@@ -1515,32 +1487,6 @@
                                 return;
                             }
                             showSystemBars();
-                            // Any user activity always causes us to show the
-                            // navigation controls, if they had been hidden.
-                            // We also clear the low profile and only content
-                            // flags so that tapping on the screen will atomically
-                            // restore all currently hidden screen decorations.
-                            int newVal = mResettingSystemUiFlags
-                                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
-                                    | View.SYSTEM_UI_FLAG_LOW_PROFILE
-                                    | View.SYSTEM_UI_FLAG_FULLSCREEN;
-                            if (mResettingSystemUiFlags != newVal) {
-                                mResettingSystemUiFlags = newVal;
-                                changed = true;
-                            }
-                            // We don't allow the system's nav bar to be hidden
-                            // again for 1 second, to prevent applications from
-                            // spamming us and keeping it from being shown.
-                            newVal = mForceClearedSystemUiFlags
-                                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
-                            if (mForceClearedSystemUiFlags != newVal) {
-                                mForceClearedSystemUiFlags = newVal;
-                                changed = true;
-                                mHandler.postDelayed(mClearHideNavigationFlag, 1000);
-                            }
-                            if (changed) {
-                                mDisplayContent.reevaluateStatusBarVisibility();
-                            }
                         }
                     }
                 }
@@ -1594,12 +1540,12 @@
                     contentFrame -> layoutNavigationBar(displayFrames,
                             mDisplayContent.getConfiguration().uiMode, mLastNavVisible,
                             mLastNavTranslucent, mLastNavAllowedHidden,
-                            mLastNotificationShadeForcesShowingNavigation, contentFrame));
+                            contentFrame));
         }
         if (mStatusBar != null) {
             simulateLayoutDecorWindow(mStatusBar, displayFrames, insetsState,
                     simulatedWindowFrames, barContentFrames,
-                    contentFrame -> layoutStatusBar(displayFrames, mLastSystemUiFlags,
+                    contentFrame -> layoutStatusBar(displayFrames, mLastAppearance,
                             contentFrame));
         }
         layoutScreenDecorWindows(displayFrames, simulatedWindowFrames);
@@ -1621,25 +1567,17 @@
 
         // For purposes of putting out fake window up to steal focus, we will
         // drive nav being hidden only by whether it is requested.
-        final int sysui = mLastSystemUiFlags;
+        final int appearance = mLastAppearance;
         final int behavior = mLastBehavior;
         final InsetsSourceProvider provider =
                 mDisplayContent.getInsetsStateController().peekSourceProvider(ITYPE_NAVIGATION_BAR);
         boolean navVisible = provider != null ? provider.isClientVisible()
                 : InsetsState.getDefaultVisibility(ITYPE_NAVIGATION_BAR);
-        boolean navTranslucent = (sysui
-                & (View.NAVIGATION_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSPARENT)) != 0;
-        boolean immersive = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0
-                || (behavior & BEHAVIOR_SHOW_BARS_BY_SWIPE) != 0;
-        boolean immersiveSticky = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0
-                || (behavior & BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) != 0;
+        boolean navTranslucent = (appearance & APPEARANCE_OPAQUE_NAVIGATION_BARS) == 0;
+        boolean immersive = (behavior & BEHAVIOR_SHOW_BARS_BY_SWIPE) != 0;
+        boolean immersiveSticky = (behavior & BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) != 0;
         boolean navAllowedHidden = immersive || immersiveSticky;
         navTranslucent &= !immersiveSticky;  // transient trumps translucent
-        boolean isKeyguardShowing = isKeyguardShowing() && !isKeyguardOccluded();
-        boolean notificationShadeForcesShowingNavigation =
-                !isKeyguardShowing && mNotificationShade != null
-                && (mNotificationShade.getAttrs().privateFlags
-                & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
 
         updateHideNavInputEventReceiver();
 
@@ -1647,21 +1585,15 @@
         // be hidden (because of the screen aspect ratio), then take that into account.
         navVisible |= !canHideNavigationBar();
 
-        boolean updateSysUiVisibility = layoutNavigationBar(displayFrames, uiMode, navVisible,
-                navTranslucent, navAllowedHidden, notificationShadeForcesShowingNavigation,
-                null /* simulatedContentFrame */);
+        layoutNavigationBar(displayFrames, uiMode, navVisible,
+                navTranslucent, navAllowedHidden, null /* simulatedContentFrame */);
         if (DEBUG_LAYOUT) Slog.i(TAG, "mDock rect:" + displayFrames.mDock);
-        updateSysUiVisibility |= layoutStatusBar(displayFrames, sysui,
-                null /* simulatedContentFrame */);
-        if (updateSysUiVisibility) {
-            updateSystemUiVisibilityLw();
-        }
+        layoutStatusBar(displayFrames, appearance, null /* simulatedContentFrame */);
         layoutScreenDecorWindows(displayFrames, null /* simulatedFrames */);
         postAdjustDisplayFrames(displayFrames);
         mLastNavVisible = navVisible;
         mLastNavTranslucent = navTranslucent;
         mLastNavAllowedHidden = navAllowedHidden;
-        mLastNotificationShadeForcesShowingNavigation = notificationShadeForcesShowingNavigation;
     }
 
     void updateHideNavInputEventReceiver() {
@@ -1825,11 +1757,11 @@
         displayFrames.mContent.set(dockFrame);
     }
 
-    private boolean layoutStatusBar(DisplayFrames displayFrames, int sysui,
+    private void layoutStatusBar(DisplayFrames displayFrames, int appearance,
             Rect simulatedContentFrame) {
         // decide where the status bar goes ahead of time
         if (mStatusBar == null) {
-            return false;
+            return;
         }
         // apply any status bar insets
         getRotatedWindowBounds(displayFrames, mStatusBar, sTmpStatusFrame);
@@ -1860,10 +1792,9 @@
             mStatusBarController.setContentFrame(sTmpRect);
         }
 
-        boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0
-                || mDisplayContent.getInsetsPolicy().isTransient(ITYPE_STATUS_BAR);
-        boolean statusBarTranslucent = (sysui
-                & (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
+        boolean statusBarTransient =
+                mDisplayContent.getInsetsPolicy().isTransient(ITYPE_STATUS_BAR);
+        boolean statusBarTranslucent = (appearance & APPEARANCE_OPAQUE_STATUS_BARS) == 0;
 
         // If the status bar is hidden, we don't want to cause windows behind it to scroll.
         if (mStatusBar.isVisibleLw() && !statusBarTransient) {
@@ -1879,8 +1810,7 @@
                     "dock=%s content=%s cur=%s", dockFrame.toString(),
                     displayFrames.mContent.toString(), displayFrames.mCurrent.toString()));
 
-            if (!statusBarTranslucent && !mStatusBarController.wasRecentlyTranslucent()
-                    && !mStatusBar.isAnimatingLw()) {
+            if (!statusBarTranslucent && !mStatusBar.isAnimatingLw()) {
 
                 // If the opaque status bar is currently requested to be visible, and not in the
                 // process of animating on or off, then we can tell the app that it is covered by
@@ -1888,18 +1818,17 @@
                 displayFrames.mSystem.top = displayFrames.mStable.top;
             }
         }
-        return mStatusBarController.checkHiddenLw();
     }
 
-    private boolean layoutNavigationBar(DisplayFrames displayFrames, int uiMode, boolean navVisible,
-            boolean navTranslucent, boolean navAllowedHidden,
-            boolean statusBarForcesShowingNavigation, Rect simulatedContentFrame) {
+    private void layoutNavigationBar(DisplayFrames displayFrames, int uiMode, boolean navVisible,
+            boolean navTranslucent, boolean navAllowedHidden, Rect simulatedContentFrame) {
         if (mNavigationBar == null) {
-            return false;
+            return;
         }
 
         final Rect navigationFrame = sTmpNavFrame;
-        boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
+        boolean navBarTransient =
+                mDisplayContent.getInsetsPolicy().isTransient(ITYPE_NAVIGATION_BAR);
         // Force the navigation bar to its appropriate place and size. We need to do this directly,
         // instead of relying on it to bubble up from the nav bar, because this needs to change
         // atomically with screen rotations.
@@ -1924,18 +1853,11 @@
                             - getNavigationBarHeight(rotation, uiMode);
             navigationFrame.top = topNavBar;
             displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top;
-            if (transientNavBarShowing) {
-                mNavigationBarController.setBarShowingLw(true);
-            } else if (navVisible) {
-                mNavigationBarController.setBarShowingLw(true);
+            if (navVisible && !navBarTransient) {
                 dockFrame.bottom = displayFrames.mRestricted.bottom = top;
-            } else {
-                // We currently want to hide the navigation UI - unless we expanded the status bar.
-                mNavigationBarController.setBarShowingLw(statusBarForcesShowingNavigation);
             }
             if (navVisible && !navTranslucent && !navAllowedHidden
-                    && !mNavigationBar.isAnimatingLw()
-                    && !mNavigationBarController.wasRecentlyTranslucent()) {
+                    && !mNavigationBar.isAnimatingLw()) {
                 // If the opaque nav bar is currently requested to be visible and not in the process
                 // of animating on or off, then we can tell the app that it is covered by it.
                 displayFrames.mSystem.bottom = top;
@@ -1946,18 +1868,11 @@
                     - getNavigationBarWidth(rotation, uiMode);
             navigationFrame.left = left;
             displayFrames.mStable.right = displayFrames.mStableFullscreen.right = left;
-            if (transientNavBarShowing) {
-                mNavigationBarController.setBarShowingLw(true);
-            } else if (navVisible) {
-                mNavigationBarController.setBarShowingLw(true);
+            if (navVisible && !navBarTransient) {
                 dockFrame.right = displayFrames.mRestricted.right = left;
-            } else {
-                // We currently want to hide the navigation UI - unless we expanded the status bar.
-                mNavigationBarController.setBarShowingLw(statusBarForcesShowingNavigation);
             }
             if (navVisible && !navTranslucent && !navAllowedHidden
-                    && !mNavigationBar.isAnimatingLw()
-                    && !mNavigationBarController.wasRecentlyTranslucent()) {
+                    && !mNavigationBar.isAnimatingLw()) {
                 // If the nav bar is currently requested to be visible, and not in the process of
                 // animating on or off, then we can tell the app that it is covered by it.
                 displayFrames.mSystem.right = left;
@@ -1968,18 +1883,11 @@
                     + getNavigationBarWidth(rotation, uiMode);
             navigationFrame.right = right;
             displayFrames.mStable.left = displayFrames.mStableFullscreen.left = right;
-            if (transientNavBarShowing) {
-                mNavigationBarController.setBarShowingLw(true);
-            } else if (navVisible) {
-                mNavigationBarController.setBarShowingLw(true);
+            if (navVisible && !navBarTransient) {
                 dockFrame.left = displayFrames.mRestricted.left = right;
-            } else {
-                // We currently want to hide the navigation UI - unless we expanded the status bar.
-                mNavigationBarController.setBarShowingLw(statusBarForcesShowingNavigation);
             }
             if (navVisible && !navTranslucent && !navAllowedHidden
-                    && !mNavigationBar.isAnimatingLw()
-                    && !mNavigationBarController.wasRecentlyTranslucent()) {
+                    && !mNavigationBar.isAnimatingLw()) {
                 // If the nav bar is currently requested to be visible, and not in the process of
                 // animating on or off, then we can tell the app that it is covered by it.
                 displayFrames.mSystem.left = right;
@@ -2008,7 +1916,6 @@
         }
 
         if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + navigationFrame);
-        return mNavigationBarController.checkHiddenLw();
     }
 
     private boolean canReceiveInput(WindowState win) {
@@ -2059,9 +1966,6 @@
         dcf.setEmpty();
         windowFrames.setParentFrameWasClippedByDisplayCutout(false);
 
-        final boolean hasNavBar = hasNavigationBar() && mNavigationBar != null
-                && mNavigationBar.isVisibleLw();
-
         final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
 
         final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
@@ -2407,53 +2311,27 @@
                     + " top=" + mTopFullscreenOpaqueWindowState);
             final boolean forceShowStatusBar = (getStatusBar().getAttrs().privateFlags
                     & PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR) != 0;
-            final boolean notificationShadeForcesShowingNavigation =
-                    mNotificationShade != null
-                            && (mNotificationShade.getAttrs().privateFlags
-                            & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
 
             boolean topAppHidesStatusBar = topAppHidesStatusBar();
             if (mForceStatusBar || forceShowStatusBar) {
                 if (DEBUG_LAYOUT) Slog.v(TAG, "Showing status bar: forced");
-                if (mStatusBarController.setBarShowingLw(true)) {
-                    changes |= FINISH_LAYOUT_REDO_LAYOUT;
-                }
                 // Maintain fullscreen layout until incoming animation is complete.
                 topIsFullscreen = mTopIsFullscreen && mStatusBar.isAnimatingLw();
-                // Transient status bar is not allowed if notification shade is expecting the
-                // navigation keys from the user.
-                if (notificationShadeForcesShowingNavigation
-                        && mStatusBarController.isTransientShowing()) {
-                    mStatusBarController.updateVisibilityLw(false /*transientAllowed*/,
-                            mLastSystemUiFlags, mLastSystemUiFlags);
-                }
             } else if (mTopFullscreenOpaqueWindowState != null) {
                 topIsFullscreen = topAppHidesStatusBar;
                 // The subtle difference between the window for mTopFullscreenOpaqueWindowState
                 // and mTopIsFullscreen is that mTopIsFullscreen is set only if the window
                 // has the FLAG_FULLSCREEN set.  Not sure if there is another way that to be the
                 // case though.
-                if (mStatusBarController.isTransientShowing()) {
-                    if (mStatusBarController.setBarShowingLw(true)) {
-                        changes |= FINISH_LAYOUT_REDO_LAYOUT;
-                    }
-                } else if (topIsFullscreen && !mDisplayContent.getDefaultTaskDisplayArea()
+                if (!topIsFullscreen || mDisplayContent.getDefaultTaskDisplayArea()
                         .isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
-                    if (DEBUG_LAYOUT) Slog.v(TAG, "** HIDING status bar");
-                    if (mStatusBarController.setBarShowingLw(false)) {
-                        changes |= FINISH_LAYOUT_REDO_LAYOUT;
-                    } else {
-                        if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar already hiding");
-                    }
-                } else {
-                    if (DEBUG_LAYOUT) Slog.v(TAG, "** SHOWING status bar: top is not fullscreen");
-                    if (mStatusBarController.setBarShowingLw(true)) {
-                        changes |= FINISH_LAYOUT_REDO_LAYOUT;
-                    }
                     topAppHidesStatusBar = false;
                 }
             }
-            mStatusBarController.setTopAppHidesStatusBar(topAppHidesStatusBar);
+            StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+            if (statusBar != null) {
+                statusBar.setTopAppHidesStatusBar(topAppHidesStatusBar);
+            }
         }
 
         if (mTopIsFullscreen != topIsFullscreen) {
@@ -2464,7 +2342,7 @@
             mTopIsFullscreen = topIsFullscreen;
         }
 
-        if ((updateSystemUiVisibilityLw() & SYSTEM_UI_CHANGING_LAYOUT) != 0) {
+        if (updateSystemUiVisibilityLw()) {
             // If the navigation bar has been hidden or shown, we need to do another
             // layout pass to update that window.
             changes |= FINISH_LAYOUT_REDO_LAYOUT;
@@ -2497,7 +2375,6 @@
             Slog.d(TAG, "attr: " + attrs + " request: " + request);
         }
         return (fl & LayoutParams.FLAG_FULLSCREEN) != 0
-                || (sysui & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0
                 || (request != null && !request.isVisible());
     }
 
@@ -2910,7 +2787,7 @@
         if (mDisplayContent.isDefaultDisplay) {
             mService.mPolicy.onDefaultDisplayFocusChangedLw(newFocus);
         }
-        if ((updateSystemUiVisibilityLw() & SYSTEM_UI_CHANGING_LAYOUT) != 0) {
+        if (updateSystemUiVisibilityLw()) {
             // If the navigation bar has been hidden or shown, we need to do another
             // layout pass to update that window.
             return FINISH_LAYOUT_REDO_LAYOUT;
@@ -2977,17 +2854,20 @@
     }
 
     void resetSystemUiVisibilityLw() {
-        mLastSystemUiFlags = 0;
+        mLastDisableFlags = 0;
         updateSystemUiVisibilityLw();
     }
 
-    int updateSystemUiVisibilityLw() {
+    /**
+     * @return {@code true} if the update may affect the layout.
+     */
+    boolean updateSystemUiVisibilityLw() {
         // If there is no window focused, there will be nobody to handle the events
         // anyway, so just hang on in whatever state we're in until things settle down.
         WindowState winCandidate = mFocusedWindow != null ? mFocusedWindow
                 : mTopFullscreenOpaqueWindowState;
         if (winCandidate == null) {
-            return 0;
+            return false;
         }
 
         // The immersive mode confirmation should never affect the system bar visibility, otherwise
@@ -3003,7 +2883,7 @@
                     : lastFocusCanReceiveKeys ? mLastFocusedWindow
                             : mTopFullscreenOpaqueWindowState;
             if (winCandidate == null) {
-                return 0;
+                return false;
             }
         }
         final WindowState win = winCandidate;
@@ -3011,17 +2891,9 @@
 
         mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
 
-        int tmpVisibility = PolicyControl.getSystemUiVisibility(win, null)
-                & ~mResettingSystemUiFlags
-                & ~mForceClearedSystemUiFlags;
-        if (mForcingShowNavBar && win.getSurfaceLayer() < mForcingShowNavBarLayer) {
-            tmpVisibility
-                    &= ~PolicyControl.adjustClearableFlags(win, View.SYSTEM_UI_CLEARABLE_FLAGS);
-        }
-
-        final int fullscreenAppearance = updateLightStatusBarAppearanceLw(0 /* vis */,
+        final int fullscreenAppearance = updateLightStatusBarLw(0 /* vis */,
                 mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
-        final int dockedAppearance = updateLightStatusBarAppearanceLw(0 /* vis */,
+        final int dockedAppearance = updateLightStatusBarLw(0 /* vis */,
                 mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
         final boolean inSplitScreen =
                 mService.mRoot.getDefaultTaskDisplayArea().isSplitScreenModeActivated();
@@ -3034,32 +2906,24 @@
         mService.getStackBounds(inSplitScreen ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                         : WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_UNDEFINED, mNonDockedStackBounds);
-        final Pair<Integer, WindowState> result =
-                updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility);
-        final int visibility = result.first;
-        final WindowState navColorWin = result.second;
+        final int disableFlags = win.getSystemUiVisibility() & StatusBarManager.DISABLE_MASK;
+        final int opaqueAppearance = updateSystemBarsLw(win, disableFlags);
+        final WindowState navColorWin = chooseNavigationColorWindowLw(
+                mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState,
+                mDisplayContent.mInputMethodWindow, mNavigationBarPosition);
         final boolean isNavbarColorManagedByIme =
                 navColorWin != null && navColorWin == mDisplayContent.mInputMethodWindow;
-        final int opaqueAppearance = InsetsFlags.getAppearance(visibility)
-                & (APPEARANCE_OPAQUE_STATUS_BARS | APPEARANCE_OPAQUE_NAVIGATION_BARS);
-        final int appearance = updateLightNavigationBarAppearanceLw(
+        final int appearance = updateLightNavigationBarLw(
                 win.mAttrs.insetsFlags.appearance, mTopFullscreenOpaqueWindowState,
                 mTopFullscreenOpaqueOrDimmingWindowState,
                 mDisplayContent.mInputMethodWindow, navColorWin) | opaqueAppearance;
-        final int diff = visibility ^ mLastSystemUiFlags;
-        final InsetsPolicy insetsPolicy = getInsetsPolicy();
-        final boolean isFullscreen = (visibility & (View.SYSTEM_UI_FLAG_FULLSCREEN
-                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)) != 0
-                || (PolicyControl.getWindowFlags(win, win.mAttrs) & FLAG_FULLSCREEN) != 0
-                || (getStatusBar() != null && insetsPolicy.isHidden(ITYPE_STATUS_BAR))
-                || (getNavigationBar() != null && insetsPolicy.isHidden(
-                        ITYPE_NAVIGATION_BAR));
+        final InsetsState requestedInsets = win.getRequestedInsetsState();
         final int behavior = win.mAttrs.insetsFlags.behavior;
-        final boolean isImmersive = (visibility & (View.SYSTEM_UI_FLAG_IMMERSIVE
-                        | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)) != 0
-                || behavior == BEHAVIOR_SHOW_BARS_BY_SWIPE
+        final boolean isImmersive = behavior == BEHAVIOR_SHOW_BARS_BY_SWIPE
                 || behavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
-        if (diff == 0
+        final boolean isFullscreen = !requestedInsets.getSourceOrDefaultVisibility(ITYPE_STATUS_BAR)
+                || !requestedInsets.getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR);
+        if (mLastDisableFlags == disableFlags
                 && mLastAppearance == appearance
                 && mLastFullscreenAppearance == fullscreenAppearance
                 && mLastDockedAppearance == dockedAppearance
@@ -3068,14 +2932,10 @@
                 && mLastFocusIsImmersive == isImmersive
                 && mLastNonDockedStackBounds.equals(mNonDockedStackBounds)
                 && mLastDockedStackBounds.equals(mDockedStackBounds)) {
-            return 0;
+            return false;
         }
 
-        // Obtains which types should show transient and which types should abort transient.
-        // If there is no transient state change, this pair will contain two empty arrays.
-        final Pair<int[], int[]> transientState = getTransientState(visibility, mLastSystemUiFlags);
-
-        mLastSystemUiFlags = visibility;
+        mLastDisableFlags = disableFlags;
         mLastAppearance = appearance;
         mLastFullscreenAppearance = fullscreenAppearance;
         mLastDockedAppearance = dockedAppearance;
@@ -3097,50 +2957,17 @@
             StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
             if (statusBar != null) {
                 final int displayId = getDisplayId();
-                statusBar.setDisableFlags(displayId, visibility & StatusBarManager.DISABLE_MASK,
-                        cause);
-                if (transientState.first.length > 0) {
-                    statusBar.showTransient(displayId, transientState.first);
-                }
-                if (transientState.second.length > 0) {
-                    statusBar.abortTransient(displayId, transientState.second);
-                }
+                statusBar.setDisableFlags(displayId, disableFlags, cause);
                 statusBar.onSystemBarAppearanceChanged(displayId, appearance,
                         appearanceRegions, isNavbarColorManagedByIme);
                 statusBar.topAppWindowChanged(displayId, isFullscreen, isImmersive);
 
-                // TODO(b/118118435): Remove this after removing system UI visibilities.
-                synchronized (mLock) {
-                    mDisplayContent.statusBarVisibilityChanged(
-                            visibility & ~(View.STATUS_BAR_UNHIDE | View.NAVIGATION_BAR_UNHIDE));
-                }
             }
         });
-        return diff;
+        return true;
     }
 
-    private static Pair<int[], int[]> getTransientState(int vis, int oldVis) {
-        final IntArray typesToShow = new IntArray(0);
-        final IntArray typesToAbort = new IntArray(0);
-        updateTransientState(vis, oldVis, View.STATUS_BAR_TRANSIENT, ITYPE_STATUS_BAR, typesToShow,
-                typesToAbort);
-        updateTransientState(vis, oldVis, View.NAVIGATION_BAR_TRANSIENT,
-                ITYPE_NAVIGATION_BAR, typesToShow, typesToAbort);
-        return Pair.create(typesToShow.toArray(), typesToAbort.toArray());
-    }
-
-    private static void updateTransientState(int vis, int oldVis, int transientFlag,
-            @InternalInsetsType int type, IntArray typesToShow, IntArray typesToAbort) {
-        final boolean wasTransient = (oldVis & transientFlag) != 0;
-        final boolean isTransient = (vis & transientFlag) != 0;
-        if (!wasTransient && isTransient) {
-            typesToShow.add(type);
-        } else if (wasTransient && !isTransient) {
-            typesToAbort.add(type);
-        }
-    }
-
-    private int updateLightStatusBarAppearanceLw(@Appearance int appearance, WindowState opaque,
+    private int updateLightStatusBarLw(@Appearance int appearance, WindowState opaque,
             WindowState opaqueOrDimming) {
         final boolean onKeyguard = isKeyguardShowing() && !isKeyguardOccluded();
         final WindowState statusColorWin = onKeyguard ? mNotificationShade : opaqueOrDimming;
@@ -3207,24 +3034,7 @@
     }
 
     @VisibleForTesting
-    static int updateLightNavigationBarLw(int vis, WindowState opaque, WindowState opaqueOrDimming,
-            WindowState imeWindow, WindowState navColorWin) {
-
-        if (navColorWin != null) {
-            if (navColorWin == imeWindow || navColorWin == opaque) {
-                // Respect the light flag.
-                vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
-                vis |= PolicyControl.getSystemUiVisibility(navColorWin, null)
-                        & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
-            } else if (navColorWin == opaqueOrDimming && navColorWin.isDimming()) {
-                // Clear the light flag for dimming window.
-                vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
-            }
-        }
-        return vis;
-    }
-
-    private int updateLightNavigationBarAppearanceLw(int appearance, WindowState opaque,
+    int updateLightNavigationBarLw(int appearance, WindowState opaque,
             WindowState opaqueOrDimming, WindowState imeWindow, WindowState navColorWin) {
 
         if (navColorWin != null) {
@@ -3244,7 +3054,7 @@
         return appearance;
     }
 
-    private Pair<Integer, WindowState> updateSystemBarsLw(WindowState win, int oldVis, int vis) {
+    private int updateSystemBarsLw(WindowState win, int disableFlags) {
         final boolean dockedStackVisible = mDisplayContent.getDefaultTaskDisplayArea()
                 .isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         final boolean freeformStackVisible = mDisplayContent.getDefaultTaskDisplayArea()
@@ -3257,115 +3067,46 @@
         mForceShowSystemBars = dockedStackVisible || win.inFreeformWindowingMode() || resizing;
         final boolean forceOpaqueStatusBar = mForceShowSystemBars && !isKeyguardShowing();
 
-        // apply translucent bar vis flags
-        WindowState fullscreenTransWin = isKeyguardShowing() && !isKeyguardOccluded()
-                ? mNotificationShade
-                : mTopFullscreenOpaqueWindowState;
-        vis = mStatusBarController.applyTranslucentFlagLw(fullscreenTransWin, vis, oldVis);
-        vis = mNavigationBarController.applyTranslucentFlagLw(fullscreenTransWin, vis, oldVis);
-        int dockedVis = mStatusBarController.applyTranslucentFlagLw(
-                mTopDockedOpaqueWindowState, 0, 0);
-        dockedVis = mNavigationBarController.applyTranslucentFlagLw(
-                mTopDockedOpaqueWindowState, dockedVis, 0);
-
         final boolean fullscreenDrawsStatusBarBackground =
-                drawsStatusBarBackground(vis, mTopFullscreenOpaqueWindowState);
+                drawsStatusBarBackground(mTopFullscreenOpaqueWindowState);
         final boolean dockedDrawsStatusBarBackground =
-                drawsStatusBarBackground(dockedVis, mTopDockedOpaqueWindowState);
+                drawsStatusBarBackground(mTopDockedOpaqueWindowState);
         final boolean fullscreenDrawsNavBarBackground =
-                drawsNavigationBarBackground(vis, mTopFullscreenOpaqueWindowState);
+                drawsNavigationBarBackground(mTopFullscreenOpaqueWindowState);
         final boolean dockedDrawsNavigationBarBackground =
-                drawsNavigationBarBackground(dockedVis, mTopDockedOpaqueWindowState);
+                drawsNavigationBarBackground(mTopDockedOpaqueWindowState);
 
-        // prevent status bar interaction from clearing certain flags
-        int type = win.getAttrs().type;
-        boolean notificationShadeHasFocus = type == TYPE_NOTIFICATION_SHADE;
-        if (notificationShadeHasFocus && !isKeyguardShowing()) {
-            int flags = View.SYSTEM_UI_FLAG_FULLSCREEN
-                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
-                    | View.SYSTEM_UI_FLAG_IMMERSIVE
-                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
-                    | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
-            if (isKeyguardOccluded()) {
-                flags |= View.STATUS_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSLUCENT;
-            }
-            vis = (vis & ~flags) | (oldVis & flags);
-        }
+        int appearance = APPEARANCE_OPAQUE_NAVIGATION_BARS | APPEARANCE_OPAQUE_STATUS_BARS;
 
         if (fullscreenDrawsStatusBarBackground && dockedDrawsStatusBarBackground) {
-            vis |= View.STATUS_BAR_TRANSPARENT;
-            vis &= ~View.STATUS_BAR_TRANSLUCENT;
-        } else if (forceOpaqueStatusBar) {
-            vis &= ~(View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT);
+            appearance &= ~APPEARANCE_OPAQUE_STATUS_BARS;
         }
 
-        vis = configureNavBarOpacity(vis, dockedStackVisible, freeformStackVisible, resizing,
-                fullscreenDrawsNavBarBackground, dockedDrawsNavigationBarBackground);
+        appearance = configureNavBarOpacity(appearance, dockedStackVisible,
+                freeformStackVisible, resizing, fullscreenDrawsNavBarBackground,
+                dockedDrawsNavigationBarBackground);
 
-        // update status bar
-        boolean immersiveSticky =
-                (vis & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
-        final boolean hideStatusBarWM =
-                mTopFullscreenOpaqueWindowState != null
-                        && (PolicyControl.getWindowFlags(mTopFullscreenOpaqueWindowState, null)
-                        & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0;
-        final boolean hideStatusBarSysui =
-                (vis & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
-        final boolean hideNavBarSysui = (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
-                // We shouldn't rely on the system UI visibilities anymore because the window can
-                // use the new API (e.g., WindowInsetsController.hide) to hide navigation bar.
-                // TODO(b/149813814): clean up the system UI flag usages in this function.
-                || !win.getRequestedInsetsState().getSourceOrDefaultVisibility(
+        final InsetsState requestedInsetsState = win.getRequestedInsetsState();
+        final boolean requestHideNavBar = !requestedInsetsState.getSourceOrDefaultVisibility(
                         ITYPE_NAVIGATION_BAR);
 
-        final boolean transientStatusBarAllowed = getStatusBar() != null
-                && (notificationShadeHasFocus || (!mForceShowSystemBars
-                && (hideStatusBarWM || (hideStatusBarSysui && immersiveSticky))));
-
-        final boolean transientNavBarAllowed = mNavigationBar != null
-                && !mForceShowSystemBars && hideNavBarSysui && immersiveSticky;
-
         final long now = SystemClock.uptimeMillis();
         final boolean pendingPanic = mPendingPanicGestureUptime != 0
                 && now - mPendingPanicGestureUptime <= PANIC_GESTURE_EXPIRATION;
         final DisplayPolicy defaultDisplayPolicy =
                 mService.getDefaultDisplayContentLocked().getDisplayPolicy();
-        if (pendingPanic && hideNavBarSysui && win != mNotificationShade
+        if (pendingPanic && requestHideNavBar && win != mNotificationShade
                 && getInsetsPolicy().isHidden(ITYPE_NAVIGATION_BAR)
                 // TODO (b/111955725): Show keyguard presentation on all external displays
                 && defaultDisplayPolicy.isKeyguardDrawComplete()) {
             // The user performed the panic gesture recently, we're about to hide the bars,
             // we're no longer on the Keyguard and the screen is ready. We can now request the bars.
             mPendingPanicGestureUptime = 0;
-            if (!isNavBarEmpty(vis)) {
+            if (!isNavBarEmpty(disableFlags)) {
                 mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_PANIC);
             }
         }
 
-        final boolean denyTransientStatus = mStatusBarController.isTransientShowRequested()
-                && !transientStatusBarAllowed && hideStatusBarSysui;
-        final boolean denyTransientNav = mNavigationBarController.isTransientShowRequested()
-                && !transientNavBarAllowed;
-        if (denyTransientStatus || denyTransientNav || mForceShowSystemBars) {
-            // clear the clearable flags instead
-            clearClearableFlagsLw();
-            vis &= ~View.SYSTEM_UI_CLEARABLE_FLAGS;
-        }
-
-        final boolean immersive = (vis & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;
-        immersiveSticky = (vis & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
-        final boolean navAllowedHidden = immersive || immersiveSticky;
-
-        if (hideNavBarSysui && !navAllowedHidden
-                && mService.mPolicy.getWindowLayerLw(win)
-                        > mService.mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_CONSUMER)) {
-            // We can't hide the navbar from this window otherwise the input consumer would not get
-            // the input events.
-            vis = (vis & ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
-        }
-
-        vis = mStatusBarController.updateVisibilityLw(transientStatusBarAllowed, oldVis, vis);
-
         // update navigation bar
         boolean oldImmersiveMode = mLastImmersiveMode;
         boolean newImmersiveMode = isImmersiveMode(win);
@@ -3374,23 +3115,13 @@
             final String pkg = win.getOwningPackage();
             mImmersiveModeConfirmation.immersiveModeChangedLw(pkg, newImmersiveMode,
                     mService.mPolicy.isUserSetupComplete(),
-                    isNavBarEmpty(win.getSystemUiVisibility()));
+                    isNavBarEmpty(disableFlags));
         }
 
-        vis = mNavigationBarController.updateVisibilityLw(transientNavBarAllowed, oldVis, vis);
-
-        final WindowState navColorWin = chooseNavigationColorWindowLw(
-                mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState,
-                mDisplayContent.mInputMethodWindow, mNavigationBarPosition);
-        vis = updateLightNavigationBarLw(vis, mTopFullscreenOpaqueWindowState,
-                mTopFullscreenOpaqueOrDimmingWindowState,
-                mDisplayContent.mInputMethodWindow, navColorWin);
-
-        return Pair.create(vis, navColorWin);
+        return appearance;
     }
 
-    private boolean drawsBarBackground(int vis, WindowState win, BarController controller,
-            int translucentFlag) {
+    private boolean drawsBarBackground(WindowState win, BarController controller) {
         if (!controller.isTransparentAllowed(win)) {
             return false;
         }
@@ -3403,73 +3134,59 @@
         final boolean forceDrawsSystemBars =
                 (win.getAttrs().privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
 
-        return forceDrawsSystemBars || drawsSystemBars && (vis & translucentFlag) == 0;
+        return forceDrawsSystemBars || drawsSystemBars;
     }
 
-    private boolean drawsStatusBarBackground(int vis, WindowState win) {
-        return drawsBarBackground(vis, win, mStatusBarController, FLAG_TRANSLUCENT_STATUS);
+    private boolean drawsStatusBarBackground(WindowState win) {
+        return drawsBarBackground(win, mStatusBarController);
     }
 
-    private boolean drawsNavigationBarBackground(int vis, WindowState win) {
-        return drawsBarBackground(vis, win, mNavigationBarController, FLAG_TRANSLUCENT_NAVIGATION);
+    private boolean drawsNavigationBarBackground(WindowState win) {
+        return drawsBarBackground(win, mNavigationBarController);
     }
 
     /**
      * @return the current visibility flags with the nav-bar opacity related flags toggled based
      *         on the nav bar opacity rules chosen by {@link #mNavBarOpacityMode}.
      */
-    private int configureNavBarOpacity(int visibility, boolean dockedStackVisible,
+    private int configureNavBarOpacity(int appearance, boolean dockedStackVisible,
             boolean freeformStackVisible, boolean isDockedDividerResizing,
             boolean fullscreenDrawsBackground, boolean dockedDrawsNavigationBarBackground) {
         if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) {
             if (fullscreenDrawsBackground && dockedDrawsNavigationBarBackground) {
-                visibility = setNavBarTransparentFlag(visibility);
+                appearance = clearNavBarOpaqueFlag(appearance);
             } else if (dockedStackVisible) {
-                visibility = setNavBarOpaqueFlag(visibility);
+                appearance = setNavBarOpaqueFlag(appearance);
             }
         } else if (mNavBarOpacityMode == NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED) {
             if (dockedStackVisible || freeformStackVisible || isDockedDividerResizing) {
                 if (mIsFreeformWindowOverlappingWithNavBar) {
-                    visibility = setNavBarTranslucentFlag(visibility);
+                    appearance = clearNavBarOpaqueFlag(appearance);
                 } else {
-                    visibility = setNavBarOpaqueFlag(visibility);
+                    appearance = setNavBarOpaqueFlag(appearance);
                 }
             } else if (fullscreenDrawsBackground) {
-                visibility = setNavBarTransparentFlag(visibility);
+                appearance = clearNavBarOpaqueFlag(appearance);
             }
         } else if (mNavBarOpacityMode == NAV_BAR_TRANSLUCENT_WHEN_FREEFORM_OPAQUE_OTHERWISE) {
             if (isDockedDividerResizing) {
-                visibility = setNavBarOpaqueFlag(visibility);
+                appearance = setNavBarOpaqueFlag(appearance);
             } else if (freeformStackVisible) {
-                visibility = setNavBarTranslucentFlag(visibility);
+                appearance = clearNavBarOpaqueFlag(appearance);
             } else {
-                visibility = setNavBarOpaqueFlag(visibility);
+                appearance = setNavBarOpaqueFlag(appearance);
             }
         }
 
-        return visibility;
+        return appearance;
     }
 
-    private int setNavBarOpaqueFlag(int visibility) {
-        return visibility & ~(View.NAVIGATION_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSPARENT);
+    private int setNavBarOpaqueFlag(int appearance) {
+        return appearance | APPEARANCE_OPAQUE_NAVIGATION_BARS;
     }
 
-    private int setNavBarTranslucentFlag(int visibility) {
-        visibility &= ~View.NAVIGATION_BAR_TRANSPARENT;
-        return visibility | View.NAVIGATION_BAR_TRANSLUCENT;
-    }
-
-    private int setNavBarTransparentFlag(int visibility) {
-        visibility &= ~View.NAVIGATION_BAR_TRANSLUCENT;
-        return visibility | View.NAVIGATION_BAR_TRANSPARENT;
-    }
-
-    private void clearClearableFlagsLw() {
-        int newVal = mResettingSystemUiFlags | View.SYSTEM_UI_CLEARABLE_FLAGS;
-        if (newVal != mResettingSystemUiFlags) {
-            mResettingSystemUiFlags = newVal;
-            mDisplayContent.reevaluateStatusBarVisibility();
-        }
+    private int clearNavBarOpaqueFlag(int appearance) {
+        return appearance & ~APPEARANCE_OPAQUE_NAVIGATION_BARS;
     }
 
     private boolean isImmersiveMode(WindowState win) {
@@ -3520,7 +3237,7 @@
         // taken over the whole screen.
         boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(isScreenOn,
                 SystemClock.elapsedRealtime(), isImmersiveMode(mSystemUiControllingWindow),
-                isNavBarEmpty(mLastSystemUiFlags));
+                isNavBarEmpty(mLastDisableFlags));
         if (panic) {
             mHandler.post(mHiddenNavPanic);
         }
@@ -3579,14 +3296,9 @@
         pw.print(prefix); pw.print("mKeyguardDrawComplete="); pw.print(mKeyguardDrawComplete);
         pw.print(" mWindowManagerDrawComplete="); pw.println(mWindowManagerDrawComplete);
         pw.print(prefix); pw.print("mHdmiPlugged="); pw.println(mHdmiPlugged);
-        if (mLastSystemUiFlags != 0 || mResettingSystemUiFlags != 0
-                || mForceClearedSystemUiFlags != 0) {
-            pw.print(prefix); pw.print("mLastSystemUiFlags=0x");
-            pw.print(Integer.toHexString(mLastSystemUiFlags));
-            pw.print(" mResettingSystemUiFlags=0x");
-            pw.print(Integer.toHexString(mResettingSystemUiFlags));
-            pw.print(" mForceClearedSystemUiFlags=0x");
-            pw.println(Integer.toHexString(mForceClearedSystemUiFlags));
+        if (mLastDisableFlags != 0) {
+            pw.print(prefix); pw.print("mLastDisableFlags=0x");
+            pw.print(Integer.toHexString(mLastDisableFlags));
         }
         pw.print(prefix); pw.print("mShowingDream="); pw.print(mShowingDream);
         pw.print(" mDreamingLockscreen="); pw.print(mDreamingLockscreen);
@@ -3635,8 +3347,6 @@
         pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars");
         pw.print(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars());
         pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
-        mStatusBarController.dump(pw, prefix);
-        mNavigationBarController.dump(pw, prefix);
 
         pw.print(prefix); pw.println("Looper state:");
         mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + "  ");
diff --git a/services/core/java/com/android/server/wm/StatusBarController.java b/services/core/java/com/android/server/wm/StatusBarController.java
deleted file mode 100644
index 3564e0b..0000000
--- a/services/core/java/com/android/server/wm/StatusBarController.java
+++ /dev/null
@@ -1,109 +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.server.wm;
-
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
-
-import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
-
-import android.app.StatusBarManager;
-import android.os.IBinder;
-import android.view.View;
-
-import com.android.server.statusbar.StatusBarManagerInternal;
-
-/**
- * Implements status bar specific behavior.
- */
-public class StatusBarController extends BarController {
-
-    private final AppTransitionListener mAppTransitionListener = new AppTransitionListener() {
-
-        private Runnable mAppTransitionPending = () -> {
-            StatusBarManagerInternal statusBar = getStatusBarInternal();
-            if (statusBar != null) {
-                statusBar.appTransitionPending(mDisplayId);
-            }
-        };
-
-        private Runnable mAppTransitionCancelled = () -> {
-            StatusBarManagerInternal statusBar = getStatusBarInternal();
-            if (statusBar != null) {
-                statusBar.appTransitionCancelled(mDisplayId);
-            }
-        };
-
-        private Runnable mAppTransitionFinished = () -> {
-            StatusBarManagerInternal statusBar = getStatusBarInternal();
-            if (statusBar != null) {
-                statusBar.appTransitionFinished(mDisplayId);
-            }
-        };
-
-        @Override
-        public void onAppTransitionPendingLocked() {
-            mHandler.post(mAppTransitionPending);
-        }
-
-        @Override
-        public int onAppTransitionStartingLocked(int transit, long duration,
-                long statusBarAnimationStartTime, long statusBarAnimationDuration) {
-            mHandler.post(() -> {
-                StatusBarManagerInternal statusBar = getStatusBarInternal();
-                if (statusBar != null) {
-                    statusBar.appTransitionStarting(mDisplayId,
-                            statusBarAnimationStartTime, statusBarAnimationDuration);
-                }
-            });
-            return 0;
-        }
-
-        @Override
-        public void onAppTransitionCancelledLocked(int transit) {
-            mHandler.post(mAppTransitionCancelled);
-        }
-
-        @Override
-        public void onAppTransitionFinishedLocked(IBinder token) {
-            mHandler.post(mAppTransitionFinished);
-        }
-    };
-
-    StatusBarController(int displayId) {
-        super("StatusBar",
-                displayId,
-                View.STATUS_BAR_TRANSIENT,
-                View.STATUS_BAR_UNHIDE,
-                View.STATUS_BAR_TRANSLUCENT,
-                StatusBarManager.WINDOW_STATUS_BAR,
-                TYPE_STATUS_BAR,
-                FLAG_TRANSLUCENT_STATUS,
-                View.STATUS_BAR_TRANSPARENT);
-    }
-
-    void setTopAppHidesStatusBar(boolean hidesStatusBar) {
-        StatusBarManagerInternal statusBar = getStatusBarInternal();
-        if (statusBar != null) {
-            statusBar.setTopAppHidesStatusBar(hidesStatusBar);
-        }
-    }
-
-    AppTransitionListener getAppTransitionListener() {
-        return mAppTransitionListener;
-    }
-}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3a2b670..8c2619d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3069,6 +3069,9 @@
         if (!checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard")) {
             throw new SecurityException("Requires CONTROL_KEYGUARD permission");
         }
+        if (mAtmInternal.isDreaming()) {
+            mAtmService.mStackSupervisor.wakeUp("dismissKeyguard");
+        }
         synchronized (mGlobalLock) {
             mPolicy.dismissKeyguardLw(callback, message);
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f8d457e..183a149 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2081,9 +2081,7 @@
     ActiveAdmin getActiveAdminUncheckedLocked(ComponentName who, int userHandle, boolean parent) {
         ensureLocked();
         if (parent) {
-            Preconditions.checkCallAuthorization(isManagedProfile(userHandle), String.format(
-                    "You can not call APIs on the parent profile outside a managed profile, "
-                            + "userId = %d", userHandle));
+            enforceManagedProfile(userHandle, "call APIs on the parent profile");
         }
         ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
         if (admin != null && parent) {
@@ -2259,7 +2257,8 @@
             @Nullable String permission) throws SecurityException {
         ensureLocked();
         if (parent) {
-            Preconditions.checkCallingUser(isManagedProfile(getCallerIdentity(who).getUserId()));
+            enforceManagedProfile(mInjector.userHandleGetCallingUserId(),
+                    "call APIs on the parent profile");
         }
         ActiveAdmin admin = getActiveAdminOrCheckPermissionForCallerLocked(
                 who, reqPolicy, permission);
@@ -2853,7 +2852,7 @@
         if (!mHasFeature) {
             return;
         }
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+        enforceManageUsers();
 
         synchronized (getLockObject()) {
             final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
@@ -4043,13 +4042,10 @@
         if (!mHasFeature) {
             return true;
         }
-        Objects.requireNonNull(admin, "ComponentName is null");
-
-        final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-        Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
-
-        return !isSeparateProfileChallengeEnabled(caller.getUserId());
+        final int userId = mInjector.userHandleGetCallingUserId();
+        enforceProfileOrDeviceOwner(admin);
+        enforceManagedProfile(userId, "query unified challenge status");
+        return !isSeparateProfileChallengeEnabled(userId);
     }
 
     @Override
@@ -4061,9 +4057,7 @@
 
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
-        Preconditions.checkCallAuthorization(isManagedProfile(userHandle), String.format(
-                "can not call APIs refering to the parent profile outside a managed profile, "
-                        + "userId = %d", userHandle));
+        enforceManagedProfile(userHandle, "call APIs refering to the parent profile");
 
         synchronized (getLockObject()) {
             final int targetUser = getProfileParentId(userHandle);
@@ -4085,9 +4079,7 @@
 
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
-        Preconditions.checkCallAuthorization(!isManagedProfile(userHandle), String.format(
-                "You can not check password sufficiency for a managed profile, userId = %d",
-                userHandle));
+        enforceNotManagedProfile(userHandle, "check password sufficiency");
         enforceUserUnlocked(userHandle);
 
         synchronized (getLockObject()) {
@@ -4643,9 +4635,8 @@
         if (!mHasFeature && !hasCallingPermission(permission.LOCK_DEVICE)) {
             return;
         }
-        final CallerIdentity caller = getCallerIdentity();
 
-        final int callingUserId = caller.getUserId();
+        final int callingUserId = mInjector.userHandleGetCallingUserId();
         ComponentName adminComponent = null;
         synchronized (getLockObject()) {
             // Make sure the caller has any active admin with the right policy or
@@ -4662,13 +4653,16 @@
                     // For Profile Owners only, callers with only permission not allowed.
                     if ((flags & DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY) != 0) {
                         // Evict key
-                        Preconditions.checkCallingUser(isManagedProfile(callingUserId));
-                        Preconditions.checkArgument(!parent,
-                                "Cannot set FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY for the parent");
+                        enforceManagedProfile(
+                                callingUserId, "set FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY");
                         if (!isProfileOwner(adminComponent, callingUserId)) {
                             throw new SecurityException("Only profile owner admins can set "
                                     + "FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY");
                         }
+                        if (parent) {
+                            throw new IllegalArgumentException(
+                                    "Cannot set FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY for the parent");
+                        }
                         if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
                             throw new UnsupportedOperationException(
                                     "FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY only applies to FBE devices");
@@ -4713,19 +4707,32 @@
 
     @Override
     public void enforceCanManageCaCerts(ComponentName who, String callerPackage) {
-        final CallerIdentity caller = getCallerIdentity(who, callerPackage);
-        Preconditions.checkCallAuthorization(canManageCaCerts(caller));
+        if (who == null) {
+            if (!isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(),
+                    DELEGATION_CERT_INSTALL)) {
+                mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
+            }
+        } else {
+            enforceProfileOrDeviceOwner(who);
+        }
     }
 
-    private boolean canManageCaCerts(CallerIdentity caller) {
-        return isDeviceOwner(caller) || isProfileOwner(caller) || isCallerDelegate(caller,
-                DELEGATION_CERT_INSTALL) || hasCallingOrSelfPermission(MANAGE_CA_CERTIFICATES);
+    private void enforceProfileOrDeviceOwner(ComponentName who) {
+        synchronized (getLockObject()) {
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+        }
+    }
+
+    private void enforceNetworkStackOrProfileOrDeviceOwner(ComponentName who) {
+        if (hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK)) {
+            return;
+        }
+        enforceProfileOrDeviceOwner(who);
     }
 
     @Override
     public boolean approveCaCert(String alias, int userId, boolean approval) {
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+        enforceManageUsers();
         synchronized (getLockObject()) {
             Set<String> certs = getUserData(userId).mAcceptedCaCertificates;
             boolean changed = (approval ? certs.add(alias) : certs.remove(alias));
@@ -4740,8 +4747,7 @@
 
     @Override
     public boolean isCaCertApproved(String alias, int userId) {
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+        enforceManageUsers();
         synchronized (getLockObject()) {
             return getUserData(userId).mAcceptedCaCertificates.contains(alias);
         }
@@ -4766,20 +4772,21 @@
     }
 
     @Override
-    public boolean installCaCert(ComponentName admin, String callerPackage, byte[] certBuffer) {
+    public boolean installCaCert(ComponentName admin, String callerPackage, byte[] certBuffer)
+            throws RemoteException {
         if (!mHasFeature) {
             return false;
         }
-        final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
-        Preconditions.checkCallAuthorization(canManageCaCerts(caller));
+        enforceCanManageCaCerts(admin, callerPackage);
 
+        final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
         final String alias = mInjector.binderWithCleanCallingIdentity(() -> {
-            String installedAlias = mCertificateMonitor.installCaCert(
-                    caller.getUserHandle(), certBuffer);
+            String installedAlias = mCertificateMonitor.installCaCert(userHandle, certBuffer);
+            final boolean isDelegate = (admin == null);
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.INSTALL_CA_CERT)
-                    .setAdmin(caller.getPackageName())
-                    .setBoolean(/* isDelegate */ admin == null)
+                    .setAdmin(callerPackage)
+                    .setBoolean(isDelegate)
                     .write();
             return installedAlias;
         });
@@ -4790,8 +4797,8 @@
         }
 
         synchronized (getLockObject()) {
-            getUserData(caller.getUserId()).mOwnerInstalledCaCerts.add(alias);
-            saveSettingsLocked(caller.getUserId());
+            getUserData(userHandle.getIdentifier()).mOwnerInstalledCaCerts.add(alias);
+            saveSettingsLocked(userHandle.getIdentifier());
         }
         return true;
     }
@@ -4801,22 +4808,22 @@
         if (!mHasFeature) {
             return;
         }
-        final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
-        Preconditions.checkCallAuthorization(canManageCaCerts(caller));
+        enforceCanManageCaCerts(admin, callerPackage);
 
+        final int userId = mInjector.userHandleGetCallingUserId();
         mInjector.binderWithCleanCallingIdentity(() -> {
-            mCertificateMonitor.uninstallCaCerts(caller.getUserHandle(), aliases);
+            mCertificateMonitor.uninstallCaCerts(UserHandle.of(userId), aliases);
+            final boolean isDelegate = (admin == null);
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.UNINSTALL_CA_CERTS)
-                    .setAdmin(caller.getPackageName())
-                    .setBoolean(/* isDelegate */ admin == null)
+                    .setAdmin(callerPackage)
+                    .setBoolean(isDelegate)
                     .write();
         });
 
         synchronized (getLockObject()) {
-            if (getUserData(caller.getUserId()).mOwnerInstalledCaCerts.removeAll(
-                    Arrays.asList(aliases))) {
-                saveSettingsLocked(caller.getUserId());
+            if (getUserData(userId).mOwnerInstalledCaCerts.removeAll(Arrays.asList(aliases))) {
+                saveSettingsLocked(userId);
             }
         }
     }
@@ -5606,10 +5613,8 @@
     public boolean setAlwaysOnVpnPackage(ComponentName who, String vpnPackage, boolean lockdown,
             List<String> lockdownWhitelist)
             throws SecurityException {
-        Objects.requireNonNull(who, "ComponentName is null");
-
+        enforceProfileOrDeviceOwner(who);
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
 
         final int userId = caller.getUserId();
         mInjector.binderWithCleanCallingIdentity(() -> {
@@ -5635,7 +5640,7 @@
             }
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.SET_ALWAYS_ON_VPN_PACKAGE)
-                    .setAdmin(caller.getComponentName())
+                    .setAdmin(who)
                     .setStrings(vpnPackage)
                     .setBoolean(lockdown)
                     .setInt(lockdownWhitelist != null ? lockdownWhitelist.size() : 0)
@@ -5655,14 +5660,11 @@
 
     @Override
     public String getAlwaysOnVpnPackage(ComponentName admin) throws SecurityException {
-        Objects.requireNonNull(admin, "ComponentName is null");
+        enforceProfileOrDeviceOwner(admin);
 
-        final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-
+        final int userId = mInjector.userHandleGetCallingUserId();
         return mInjector.binderWithCleanCallingIdentity(
-                () -> mInjector.getConnectivityManager().getAlwaysOnVpnPackageForUser(
-                        caller.getUserId()));
+                () -> mInjector.getConnectivityManager().getAlwaysOnVpnPackageForUser(userId));
     }
 
     @Override
@@ -5676,14 +5678,11 @@
 
     @Override
     public boolean isAlwaysOnVpnLockdownEnabled(ComponentName admin) throws SecurityException {
-        Objects.requireNonNull(admin, "ComponentName is null");
+        enforceNetworkStackOrProfileOrDeviceOwner(admin);
 
-        final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
-                || hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK));
-
+        final int userId = mInjector.userHandleGetCallingUserId();
         return mInjector.binderWithCleanCallingIdentity(
-                () -> mInjector.getConnectivityManager().isVpnLockdownEnabled(caller.getUserId()));
+                () -> mInjector.getConnectivityManager().isVpnLockdownEnabled(userId));
     }
 
     @Override
@@ -5698,14 +5697,11 @@
     @Override
     public List<String> getAlwaysOnVpnLockdownWhitelist(ComponentName admin)
             throws SecurityException {
-        Objects.requireNonNull(admin, "ComponentName is null");
+        enforceProfileOrDeviceOwner(admin);
 
-        final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-
+        final int userId = mInjector.userHandleGetCallingUserId();
         return mInjector.binderWithCleanCallingIdentity(
-                () -> mInjector.getConnectivityManager().getVpnLockdownWhitelist(
-                        caller.getUserId()));
+                () -> mInjector.getConnectivityManager().getVpnLockdownWhitelist(userId));
     }
 
     private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason, boolean wipeEuicc) {
@@ -5994,13 +5990,11 @@
         if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
+        enforceSystemCaller("report password change");
 
-        final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isSystemUid(caller));
         // Managed Profile password can only be changed when it has a separate challenge.
         if (!isSeparateProfileChallengeEnabled(userId)) {
-            Preconditions.checkCallAuthorization(!isManagedProfile(userId), String.format("You can "
-                    + "not set the active password for a managed profile, userId = %d", userId));
+            enforceNotManagedProfile(userId, "set the active password");
         }
 
         DevicePolicyData policy = getUserData(userId);
@@ -6053,9 +6047,8 @@
         Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(BIND_DEVICE_ADMIN));
         if (!isSeparateProfileChallengeEnabled(userHandle)) {
-            Preconditions.checkCallAuthorization(!isManagedProfile(userHandle), String.format(
-                    "You can not report failed password attempt if separate profile challenge is "
-                            + "not in place for a managed profile, userId = %d", userHandle));
+            enforceNotManagedProfile(userHandle,
+                    "report failed password attempt if separate profile challenge is not in place");
         }
 
         boolean wipeData = false;
@@ -7284,7 +7277,7 @@
             return null;
         }
         if (!callingUserOnly) {
-            Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+            enforceManageUsers();
         }
         synchronized (getLockObject()) {
             if (!mOwners.hasDeviceOwner()) {
@@ -7303,8 +7296,7 @@
         if (!mHasFeature) {
             return UserHandle.USER_NULL;
         }
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+        enforceManageUsers();
         synchronized (getLockObject()) {
             return mOwners.hasDeviceOwner() ? mOwners.getDeviceOwnerUserId() : UserHandle.USER_NULL;
         }
@@ -7319,8 +7311,7 @@
         if (!mHasFeature) {
             return null;
         }
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+        enforceManageUsers();
         synchronized (getLockObject()) {
             if (!mOwners.hasDeviceOwner()) {
                 return null;
@@ -7545,10 +7536,8 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
 
-        final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallingUser(!isManagedProfile(caller.getUserId()));
-
-        final int userId = caller.getUserId();
+        final int userId = mInjector.userHandleGetCallingUserId();
+        enforceNotManagedProfile(userId, "clear profile owner");
         enforceUserUnlocked(userId);
         synchronized (getLockObject()) {
             // Check if this is the profile owner who is calling
@@ -7665,10 +7654,9 @@
         if (!mHasFeature) {
             return DevicePolicyManager.STATE_USER_UNMANAGED;
         }
-        final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(canManageUsers(caller));
-
-        return getUserProvisioningState(caller.getUserId());
+        enforceManageUsers();
+        int userHandle = mInjector.userHandleGetCallingUserId();
+        return getUserProvisioningState(userHandle);
     }
 
     private int getUserProvisioningState(int userHandle) {
@@ -7706,8 +7694,8 @@
                 }
                 transitionCheckNeeded = false;
             } else {
-                Preconditions.checkCallAuthorization(
-                        hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                // For all other cases, caller must have MANAGE_PROFILE_AND_DEVICE_OWNERS.
+                enforceCanManageProfileAndDeviceOwners();
             }
 
             final DevicePolicyData policyData = getUserData(userHandle);
@@ -7762,13 +7750,11 @@
             return;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-
-        final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isProfileOwner(caller));
-        Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
-
         synchronized (getLockObject()) {
-            final int userId = caller.getUserId();
+            // Check if this is the profile owner who is calling
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            final int userId = UserHandle.getCallingUserId();
+            enforceManagedProfile(userId, "enable the profile");
             // Check if the profile is already enabled.
             UserInfo managedProfile = getUserInfo(userId);
             if (managedProfile.isEnabled()) {
@@ -7794,15 +7780,14 @@
     @Override
     public void setProfileName(ComponentName who, String profileName) {
         Objects.requireNonNull(who, "ComponentName is null");
+        enforceProfileOrDeviceOwner(who);
 
-        final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-
+        final int userId = UserHandle.getCallingUserId();
         mInjector.binderWithCleanCallingIdentity(() -> {
-            mUserManager.setUserName(caller.getUserId(), profileName);
+            mUserManager.setUserName(userId, profileName);
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.SET_PROFILE_NAME)
-                    .setAdmin(caller.getComponentName())
+                    .setAdmin(who)
                     .write();
         });
     }
@@ -7911,8 +7896,7 @@
         if (!mHasFeature) {
             return null;
         }
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+        enforceManageUsers();
         ComponentName profileOwner = getProfileOwner(userHandle);
         if (profileOwner == null) {
             return null;
@@ -8084,8 +8068,7 @@
             }
             return;
         }
-        Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+        enforceCanManageProfileAndDeviceOwners();
 
         if ((mIsWatch || hasUserSetupCompleted(userHandle))) {
             if (!isCallerWithSystemUid()) {
@@ -8121,8 +8104,7 @@
             @UserIdInt int userId,
             boolean hasIncompatibleAccountsOrNonAdb) {
         if (!isAdb()) {
-            Preconditions.checkCallAuthorization(
-                    hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+            enforceCanManageProfileAndDeviceOwners();
         }
 
         final int code = checkDeviceOwnerProvisioningPreConditionLocked(
@@ -8176,9 +8158,11 @@
         }
     }
 
-    private boolean canManageUsers(CallerIdentity caller) {
-        return isSystemUid(caller) || isRootUid(caller)
-                || hasCallingOrSelfPermission(permission.MANAGE_USERS);
+    private void enforceManageUsers() {
+        final int callingUid = mInjector.binderGetCallingUid();
+        if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) {
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
+        }
     }
 
     private boolean hasCallingPermission(String permission) {
@@ -8208,6 +8192,20 @@
                 || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS);
     }
 
+    private void enforceManagedProfile(int userId, String message) {
+        if (!isManagedProfile(userId)) {
+            throw new SecurityException(String.format(
+                    "You can not %s outside a managed profile, userId = %d", message, userId));
+        }
+    }
+
+    private void enforceNotManagedProfile(int userId, String message) {
+        if (isManagedProfile(userId)) {
+            throw new SecurityException(String.format(
+                    "You can not %s for a managed profile, userId = %d", message, userId));
+        }
+    }
+
     private void enforceDeviceOwnerOrManageUsers() {
         synchronized (getLockObject()) {
             if (getActiveAdminWithPolicyForUidLocked(null, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER,
@@ -8215,7 +8213,7 @@
                 return;
             }
         }
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+        enforceManageUsers();
     }
 
     private void enforceProfileOwnerOrSystemUser() {
@@ -8816,7 +8814,6 @@
             return null;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
 
@@ -8831,8 +8828,7 @@
         if (!mHasFeature) {
             return null;
         }
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+        enforceManageUsers();
         synchronized (getLockObject()) {
             List<String> result = null;
             // If we have multiple profiles we return the intersection of the
@@ -8917,7 +8913,6 @@
             return false;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
 
@@ -8959,7 +8954,6 @@
             return null;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
 
@@ -8971,13 +8965,13 @@
 
     @Override
     public List getPermittedInputMethodsForCurrentUser() {
-        final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(canManageUsers(caller));
+        enforceManageUsers();
 
+        final int callingUserId = mInjector.userHandleGetCallingUserId();
         synchronized (getLockObject()) {
             List<String> result = null;
             // Only device or profile owners can have permitted lists set.
-            DevicePolicyData policy = getUserDataUnchecked(caller.getUserId());
+            DevicePolicyData policy = getUserDataUnchecked(callingUserId);
             for (int i = 0; i < policy.mAdminList.size(); i++) {
                 ActiveAdmin admin = policy.mAdminList.get(i);
                 List<String> fromAdmin = admin.permittedInputMethods;
@@ -8992,8 +8986,8 @@
 
             // If we have a permitted list add all system input methods.
             if (result != null) {
-                List<InputMethodInfo> imes = InputMethodManagerInternal
-                        .get().getInputMethodListAsUser(caller.getUserId());
+                List<InputMethodInfo> imes =
+                        InputMethodManagerInternal.get().getInputMethodListAsUser(callingUserId);
                 if (imes != null) {
                     for (InputMethodInfo ime : imes) {
                         ServiceInfo serviceInfo = ime.getServiceInfo();
@@ -9437,12 +9431,11 @@
     @Override
     public boolean isEphemeralUser(ComponentName who) {
         Objects.requireNonNull(who, "ComponentName is null");
+        enforceProfileOrDeviceOwner(who);
 
-        final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-
+        final int callingUserId = mInjector.userHandleGetCallingUserId();
         return mInjector.binderWithCleanCallingIdentity(
-                () -> mInjector.getUserManager().isUserEphemeral(caller.getUserId()));
+                () -> mInjector.getUserManager().isUserEphemeral(callingUserId));
     }
 
     @Override
@@ -10170,7 +10163,6 @@
             return;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
 
@@ -10194,7 +10186,6 @@
             return false;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
 
@@ -10217,23 +10208,12 @@
 
     @Override
     public void setSecondaryLockscreenEnabled(ComponentName who, boolean enabled) {
-        Objects.requireNonNull(who, "ComponentName is null");
-
-        // Check can set secondary lockscreen enabled
-        final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-        Preconditions.checkCallAuthorization(!isManagedProfile(caller.getUserId()),
-                String.format("User %d is not allowed to call setSecondaryLockscreenEnabled",
-                        caller.getUserId()));
-        // Allow testOnly admins to bypass supervision config requirement.
-        Preconditions.checkCallAuthorization(isAdminTestOnlyLocked(who, caller.getUserId())
-                        || isDefaultSupervisor(caller), String.format("Admin %s is not the "
-                + "default supervision component", caller.getComponentName()));
-
+        enforceCanSetSecondaryLockscreenEnabled(who);
         synchronized (getLockObject()) {
-            DevicePolicyData policy = getUserData(caller.getUserId());
+            final int userId = mInjector.userHandleGetCallingUserId();
+            DevicePolicyData policy = getUserData(userId);
             policy.mSecondaryLockscreenEnabled = enabled;
-            saveSettingsLocked(caller.getUserId());
+            saveSettingsLocked(userId);
         }
     }
 
@@ -10244,14 +10224,31 @@
         }
     }
 
-    private boolean isDefaultSupervisor(CallerIdentity caller) {
+    private void enforceCanSetSecondaryLockscreenEnabled(ComponentName who) {
+        enforceProfileOrDeviceOwner(who);
+        final int userId = mInjector.userHandleGetCallingUserId();
+        if (isManagedProfile(userId)) {
+            throw new SecurityException(
+                    "User " + userId + " is not allowed to call setSecondaryLockscreenEnabled");
+        }
+        synchronized (getLockObject()) {
+            if (isAdminTestOnlyLocked(who, userId)) {
+                // Allow testOnly admins to bypass supervision config requirement.
+                return;
+            }
+        }
+        // Only the default supervision app can use this API.
         final String supervisor = mContext.getResources().getString(
                 com.android.internal.R.string.config_defaultSupervisionProfileOwnerComponent);
         if (supervisor == null) {
-            return false;
+            throw new SecurityException("Unable to set secondary lockscreen setting, no "
+                    + "default supervision component defined");
         }
         final ComponentName supervisorComponent = ComponentName.unflattenFromString(supervisor);
-        return caller.getComponentName().equals(supervisorComponent);
+        if (!who.equals(supervisorComponent)) {
+            throw new SecurityException(
+                    "Admin " + who + " is not the default supervision component");
+        }
     }
 
     @Override
@@ -11591,9 +11588,7 @@
     @Override
     public SystemUpdateInfo getPendingSystemUpdate(ComponentName admin) {
         Objects.requireNonNull(admin, "ComponentName is null");
-
-        final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        enforceProfileOrDeviceOwner(admin);
 
         return mOwners.getSystemUpdateInfo();
     }
@@ -11784,11 +11779,8 @@
 
     @Override
     public int checkProvisioningPreCondition(String action, String packageName) {
-        Objects.requireNonNull(packageName, "packageName is null");
-
-        Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
-
+        Objects.requireNonNull(packageName);
+        enforceCanManageProfileAndDeviceOwners();
         return checkProvisioningPreConditionSkipPermission(action, packageName);
     }
 
@@ -12049,12 +12041,8 @@
 
     @Override
     public boolean isManagedProfile(ComponentName admin) {
-        Objects.requireNonNull(admin, "ComponentName is null");
-
-        final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-
-        return isManagedProfile(caller.getUserId());
+        enforceProfileOrDeviceOwner(admin);
+        return isManagedProfile(mInjector.userHandleGetCallingUserId());
     }
 
     @Override
@@ -12180,10 +12168,8 @@
             return;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
-
+        enforceManagedProfile(caller.getUserId(), "set organization color");
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
             admin.organizationColor = color;
@@ -12191,7 +12177,7 @@
         }
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_ORGANIZATION_COLOR)
-                .setAdmin(caller.getComponentName())
+                .setAdmin(who)
                 .write();
     }
 
@@ -12204,10 +12190,9 @@
 
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userId));
-        Preconditions.checkCallAuthorization(canManageUsers(caller));
-        Preconditions.checkCallAuthorization(isManagedProfile(userId), String.format("You can not "
-                + "set organization color outside a managed profile, userId = %d", userId));
 
+        enforceManageUsers();
+        enforceManagedProfile(userId, "set organization color");
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerAdminLocked(userId);
             admin.organizationColor = color;
@@ -12221,10 +12206,8 @@
             return ActiveAdmin.DEF_ORGANIZATION_COLOR;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
-
+        enforceManagedProfile(caller.getUserId(), "get organization color");
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
             return admin.organizationColor;
@@ -12240,9 +12223,8 @@
 
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
-        Preconditions.checkCallAuthorization(isManagedProfile(userHandle), String.format("You can "
-                + "not get organization color outside a managed profile, userId = %d", userHandle));
 
+        enforceManagedProfile(userHandle, "get organization color");
         synchronized (getLockObject()) {
             ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userHandle);
             return (profileOwner != null)
@@ -12275,10 +12257,8 @@
             return null;
         }
         Objects.requireNonNull(who, "ComponentName is null");
-
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
-
+        enforceManagedProfile(caller.getUserId(), "get organization name");
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
             return admin.organizationName;
@@ -12306,10 +12286,8 @@
 
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
-        Preconditions.checkCallAuthorization(isManagedProfile(userHandle), String.format(
-                "You can not get organization name outside a managed profile, userId = %d",
-                userHandle));
 
+        enforceManagedProfile(userHandle, "get organization name");
         synchronized (getLockObject()) {
             ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userHandle);
             return (profileOwner != null)
@@ -12744,6 +12722,16 @@
         return mSecurityLogMonitor.forceLogs();
     }
 
+    private void enforceCanManageDeviceAdmin() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS,
+                null);
+    }
+
+    private void enforceCanManageProfileAndDeviceOwners() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null);
+    }
+
     private void enforceCallerSystemUserHandle() {
         final int callingUid = mInjector.binderGetCallingUid();
         final int userId = UserHandle.getUserId(callingUid);
@@ -12754,11 +12742,9 @@
 
     @Override
     public boolean isUninstallInQueue(final String packageName) {
-        final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
-
-        Pair<String, Integer> packageUserPair = new Pair<>(packageName, caller.getUserId());
+        enforceCanManageDeviceAdmin();
+        final int userId = mInjector.userHandleGetCallingUserId();
+        Pair<String, Integer> packageUserPair = new Pair<>(packageName, userId);
         synchronized (getLockObject()) {
             return mPackagesToRemove.contains(packageUserPair);
         }
@@ -12766,13 +12752,11 @@
 
     @Override
     public void uninstallPackageWithActiveAdmins(final String packageName) {
+        enforceCanManageDeviceAdmin();
         Preconditions.checkArgument(!TextUtils.isEmpty(packageName));
 
-        final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
+        final int userId = mInjector.userHandleGetCallingUserId();
 
-        final int userId = caller.getUserId();
         enforceUserUnlocked(userId);
 
         final ComponentName profileOwner = getProfileOwner(userId);
@@ -12821,9 +12805,7 @@
 
     @Override
     public boolean isDeviceProvisioned() {
-        final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(canManageUsers(caller));
-
+        enforceManageUsers();
         synchronized (getLockObject()) {
             return getUserDataUnchecked(UserHandle.USER_SYSTEM).mUserSetupComplete;
         }
@@ -12907,8 +12889,7 @@
 
     @Override
     public void setDeviceProvisioningConfigApplied() {
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+        enforceManageUsers();
         synchronized (getLockObject()) {
             DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
             policy.mDeviceProvisioningConfigApplied = true;
@@ -12918,8 +12899,7 @@
 
     @Override
     public boolean isDeviceProvisioningConfigApplied() {
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+        enforceManageUsers();
         synchronized (getLockObject()) {
             final DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
             return policy.mDeviceProvisioningConfigApplied;
@@ -12935,10 +12915,8 @@
      */
     @Override
     public void forceUpdateUserSetupComplete() {
-        Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+        enforceCanManageProfileAndDeviceOwners();
         enforceCallerSystemUserHandle();
-
         // no effect if it's called from user build
         if (!mInjector.isBuildDebuggable()) {
             return;
@@ -12959,28 +12937,25 @@
         if (!mHasFeature) {
             return;
         }
-        Objects.requireNonNull(admin, "ComponentName is null");
-
-        final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-
-        toggleBackupServiceActive(caller.getUserId(), enabled);
+        Objects.requireNonNull(admin);
+        enforceProfileOrDeviceOwner(admin);
+        int userId = mInjector.userHandleGetCallingUserId();
+        toggleBackupServiceActive(userId, enabled);
     }
 
     @Override
     public boolean isBackupServiceEnabled(ComponentName admin) {
+        Objects.requireNonNull(admin);
         if (!mHasFeature) {
             return true;
         }
-        Objects.requireNonNull(admin, "ComponentName is null");
 
-        final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-
+        enforceProfileOrDeviceOwner(admin);
         synchronized (getLockObject()) {
             try {
                 IBackupManager ibm = mInjector.getIBackupManager();
-                return ibm != null && ibm.isBackupServiceActive(caller.getUserId());
+                return ibm != null && ibm.isBackupServiceActive(
+                    mInjector.userHandleGetCallingUserId());
             } catch (RemoteException e) {
                 throw new IllegalStateException("Failed requesting backup service state.", e);
             }
@@ -13292,9 +13267,9 @@
             return false;
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
-                || isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)
-                || hasCallingOrSelfPermission(permission.MANAGE_USERS));
+        Preconditions.checkCallAuthorization(
+                isDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)
+                        || hasCallingOrSelfPermission(permission.MANAGE_USERS));
 
         synchronized (getLockObject()) {
             return isNetworkLoggingEnabledInternalLocked();
@@ -13562,14 +13537,13 @@
         Objects.requireNonNull(admin, "ComponentName is null");
         Objects.requireNonNull(packageName, "packageName is null");
         Objects.requireNonNull(callback, "callback is null");
-
-        final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        enforceProfileOrDeviceOwner(admin);
+        final int userId = UserHandle.getCallingUserId();
 
         long ident = mInjector.binderClearCallingIdentity();
         try {
             ActivityManager.getService().clearApplicationUserData(packageName, false, callback,
-                    caller.getUserId());
+                    userId);
         } catch(RemoteException re) {
             // Same process, should not happen.
         } catch (SecurityException se) {
@@ -13622,9 +13596,7 @@
     @Override
     public List<String> getDisallowedSystemApps(ComponentName admin, int userId,
             String provisioningAction) throws RemoteException {
-        Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
-
+        enforceCanManageProfileAndDeviceOwners();
         return new ArrayList<>(
                 mOverlayPackagesProvider.getNonRequiredApps(admin, userId, provisioningAction));
     }
@@ -13635,23 +13607,31 @@
         if (!mHasFeature) {
             return;
         }
-        Objects.requireNonNull(admin, "ComponentName is null");
+
+        Objects.requireNonNull(admin, "Admin cannot be null.");
         Objects.requireNonNull(target, "Target cannot be null.");
-        Preconditions.checkArgument(!admin.equals(target),
-                "Provided administrator and target are the same object.");
-        Preconditions.checkArgument(!admin.getPackageName().equals(target.getPackageName()),
-                "Provided administrator and target have the same package name.");
 
-        final CallerIdentity caller = getCallerIdentity(admin);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        enforceProfileOrDeviceOwner(admin);
 
-        final int callingUserId = caller.getUserId();
+        if (admin.equals(target)) {
+            throw new IllegalArgumentException("Provided administrator and target are "
+                    + "the same object.");
+        }
+
+        if (admin.getPackageName().equals(target.getPackageName())) {
+            throw new IllegalArgumentException("Provided administrator and target have "
+                    + "the same package name.");
+        }
+
+        final int callingUserId = mInjector.userHandleGetCallingUserId();
         final DevicePolicyData policy = getUserData(callingUserId);
         final DeviceAdminInfo incomingDeviceInfo = findAdmin(target, callingUserId,
                 /* throwForMissingPermission= */ true);
         checkActiveAdminPrecondition(target, incomingDeviceInfo, policy);
-        Preconditions.checkArgument(incomingDeviceInfo.supportsTransferOwnership(),
-                "Provided target does not support ownership transfer.");
+        if (!incomingDeviceInfo.supportsTransferOwnership()) {
+            throw new IllegalArgumentException("Provided target does not support "
+                    + "ownership transfer.");
+        }
 
         final long id = mInjector.binderClearCallingIdentity();
         String ownerType = null;
@@ -13674,7 +13654,7 @@
                 if (bundle == null) {
                     bundle = new PersistableBundle();
                 }
-                if (isProfileOwner(caller)) {
+                if (isProfileOwner(admin, callingUserId)) {
                     ownerType = ADMIN_TYPE_PROFILE_OWNER;
                     prepareTransfer(admin, target, bundle, callingUserId,
                             ADMIN_TYPE_PROFILE_OWNER);
@@ -13685,7 +13665,7 @@
                     if (isUserAffiliatedWithDeviceLocked(callingUserId)) {
                         notifyAffiliatedProfileTransferOwnershipComplete(callingUserId);
                     }
-                } else if (isDeviceOwner(caller)) {
+                } else if (isDeviceOwner(admin, callingUserId)) {
                     ownerType = ADMIN_TYPE_DEVICE_OWNER;
                     prepareTransfer(admin, target, bundle, callingUserId,
                             ADMIN_TYPE_DEVICE_OWNER);
@@ -14355,8 +14335,7 @@
         if (!mHasFeature) {
             return false;
         }
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+        enforceManageUsers();
         long id = mInjector.binderClearCallingIdentity();
         try {
             return isManagedKioskInternal();
@@ -14381,8 +14360,7 @@
         if (!mHasFeature) {
             return false;
         }
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
-
+        enforceManageUsers();
         return mInjector.binderWithCleanCallingIdentity(() -> isUnattendedManagedKioskUnchecked());
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 0789d68..120182f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -1081,6 +1081,49 @@
         }
     }
 
+    @Test
+    public void nonWakeupAlarmsDeferred() throws Exception {
+        final int numAlarms = 10;
+        final PendingIntent[] pis = new PendingIntent[numAlarms];
+        for (int i = 0; i < numAlarms; i++) {
+            pis[i] = getNewMockPendingIntent();
+            setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 5, pis[i]);
+        }
+        doReturn(true).when(mService).checkAllowNonWakeupDelayLocked(anyLong());
+        // Advance time past all expirations.
+        mNowElapsedTest += numAlarms + 5;
+        mTestTimer.expire();
+        assertEquals(numAlarms, mService.mPendingNonWakeupAlarms.size());
+
+        // These alarms should be sent on interactive state change to true
+        mService.interactiveStateChangedLocked(false);
+        mService.interactiveStateChangedLocked(true);
+
+        for (int i = 0; i < numAlarms; i++) {
+            verify(pis[i]).send(eq(mMockContext), eq(0), any(Intent.class), any(),
+                    any(Handler.class), isNull(), any());
+        }
+    }
+
+    @Test
+    public void alarmCountOnPendingNonWakeupAlarmsRemoved() throws Exception {
+        final int numAlarms = 10;
+        final PendingIntent[] pis = new PendingIntent[numAlarms];
+        for (int i = 0; i < numAlarms; i++) {
+            pis[i] = getNewMockPendingIntent();
+            setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 5, pis[i]);
+        }
+        doReturn(true).when(mService).checkAllowNonWakeupDelayLocked(anyLong());
+        // Advance time past all expirations.
+        mNowElapsedTest += numAlarms + 5;
+        mTestTimer.expire();
+        assertEquals(numAlarms, mService.mPendingNonWakeupAlarms.size());
+        for (int i = 0; i < numAlarms; i++) {
+            mService.removeLocked(pis[i], null);
+            assertEquals(numAlarms - i - 1, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0));
+        }
+    }
+
     @After
     public void tearDown() {
         if (mMockingSession != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 1bf9c2a..6d40034 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -46,6 +46,7 @@
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.ServiceInfo;
 import android.net.Uri;
 import android.os.SystemClock;
 import android.provider.MediaStore;
@@ -685,6 +686,8 @@
     }
 
     private static JobStatus createJobStatus(JobInfo job) {
-        return JobStatus.createFromJobInfo(job, 0, null, -1, "JobStatusTest");
+        JobStatus jobStatus = JobStatus.createFromJobInfo(job, 0, null, -1, "JobStatusTest");
+        jobStatus.serviceInfo = mock(ServiceInfo.class);
+        return jobStatus;
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 1cf133a..18bd6b1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -61,6 +61,7 @@
 import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.ServiceInfo;
 import android.os.BatteryManager;
 import android.os.BatteryManagerInternal;
 import android.os.Handler;
@@ -299,6 +300,7 @@
             JobInfo jobInfo) {
         JobStatus js = JobStatus.createFromJobInfo(
                 jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
+        js.serviceInfo = mock(ServiceInfo.class);
         // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
         js.setStandbyBucket(FREQUENT_INDEX);
         // Make sure Doze and background-not-restricted don't affect tests.
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 631b4d4..4ce6411 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4407,7 +4407,7 @@
 
         // Caller is Profile Owner, but no supervision app is configured.
         setAsProfileOwner(admin1);
-        assertExpectException(SecurityException.class, "is not the default supervision component",
+        assertExpectException(SecurityException.class, "no default supervision component defined",
                 () -> dpm.setSecondaryLockscreenEnabled(admin1, true));
         assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of(CALLER_USER_HANDLE)));
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index 83df406..c0d5f7b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -19,11 +19,18 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.annotation.Nullable;
-import android.app.Instrumentation;
-import android.hardware.hdmi.HdmiDeviceInfo;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
 import android.os.Looper;
+import android.os.PowerManager;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 
@@ -31,10 +38,11 @@
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
 
@@ -44,7 +52,7 @@
 @RunWith(JUnit4.class)
 public class ArcInitiationActionFromAvrTest {
 
-    private HdmiDeviceInfo mDeviceInfoForTests;
+    private Context mContextSpy;
     private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
     private HdmiCecController mHdmiCecController;
     private HdmiControlService mHdmiControlService;
@@ -52,59 +60,46 @@
     private ArcInitiationActionFromAvr mAction;
 
     private TestLooper mTestLooper = new TestLooper();
-    private boolean mSendCecCommandSuccess;
-    private boolean mShouldDispatchARCInitiated;
-    private boolean mArcInitSent;
-    private boolean mRequestActiveSourceSent;
-    private Instrumentation mInstrumentation;
     private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
 
-    @Before
-    public void setUp() {
-        mDeviceInfoForTests = new HdmiDeviceInfo(1000, 1);
+    @Mock private IPowerManager mIPowerManagerMock;
+    @Mock private IThermalService mIThermalServiceMock;
+    @Mock private AudioManager mAudioManager;
 
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
+        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
+        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(mIPowerManagerMock.isInteractive()).thenReturn(true);
 
         mHdmiControlService =
-                new HdmiControlService(mInstrumentation.getTargetContext()) {
-                    @Override
-                    void sendCecCommand(
-                            HdmiCecMessage command, @Nullable SendMessageCallback callback) {
-                        switch (command.getOpcode()) {
-                            case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE:
-                                if (callback != null) {
-                                    callback.onSendCompleted(
-                                            mSendCecCommandSuccess
-                                                    ? SendMessageResult.SUCCESS
-                                                    : SendMessageResult.NACK);
-                                }
-                                mRequestActiveSourceSent = true;
-                                break;
-                            case Constants.MESSAGE_INITIATE_ARC:
-                                if (callback != null) {
-                                    callback.onSendCompleted(
-                                            mSendCecCommandSuccess
-                                                    ? SendMessageResult.SUCCESS
-                                                    : SendMessageResult.NACK);
-                                }
-                                mArcInitSent = true;
-                                if (mShouldDispatchARCInitiated) {
-                                    mHdmiCecLocalDeviceAudioSystem.dispatchMessage(
-                                            HdmiCecMessageBuilder.buildReportArcInitiated(
-                                                    Constants.ADDR_TV,
-                                                    Constants.ADDR_AUDIO_SYSTEM));
-                                }
-                                break;
-                            default:
-                        }
-                    }
-
+                new HdmiControlService(mContextSpy) {
                     @Override
                     boolean isPowerStandby() {
                         return false;
                     }
 
                     @Override
+                    void wakeUp() {
+                    }
+
+                    @Override
+                    PowerManager getPowerManager() {
+                        return powerManager;
+                    }
+
+                    @Override
+                    AudioManager getAudioManager() {
+                        return mAudioManager;
+                    }
+
+                    @Override
                     boolean isAddressAllocated() {
                         return true;
                     }
@@ -115,23 +110,11 @@
                     }
                 };
 
-        mHdmiCecLocalDeviceAudioSystem =
-                new HdmiCecLocalDeviceAudioSystem(mHdmiControlService) {
-                    @Override
-                    HdmiDeviceInfo getDeviceInfo() {
-                        return mDeviceInfoForTests;
-                    }
-
-                    @Override
-                    void setArcStatus(boolean enabled) {
-                        // do nothing
-                    }
-
-                    @Override
-                    protected boolean isSystemAudioActivated() {
-                        return true;
-                    }
-                };
+        mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService) {
+            @Override
+            protected void setPreferredAddress(int addr) {
+            }
+        };
 
         mHdmiCecLocalDeviceAudioSystem.init();
         Looper looper = mTestLooper.getLooper();
@@ -150,18 +133,88 @@
         mTestLooper.dispatchAll();
     }
 
-    @Ignore("b/120845532")
     @Test
-    public void arcInitiation_requestActiveSource() {
-        mSendCecCommandSuccess = true;
-        mShouldDispatchARCInitiated = true;
-        mRequestActiveSourceSent = false;
-        mArcInitSent = false;
+    public void arcInitiation_initiated() {
+        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+                Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
 
+        assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc);
+
+        mHdmiControlService.sendCecCommand(
+                HdmiCecMessageBuilder.buildReportArcInitiated(
+                        Constants.ADDR_TV,
+                        Constants.ADDR_AUDIO_SYSTEM));
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
+    }
+
+    @Test
+    public void arcInitiation_sendFailed() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_INITIATE_ARC, SendMessageResult.NACK);
+        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+                Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc);
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+    }
+
+    @Test
+    public void arcInitiation_terminated() {
         mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
         mTestLooper.dispatchAll();
 
-        assertThat(mArcInitSent).isTrue();
-        assertThat(mRequestActiveSourceSent).isTrue();
+        HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+                Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc);
+
+        mHdmiControlService.handleCecCommand(HdmiCecMessageBuilder.buildReportArcTerminated(
+                Constants.ADDR_TV,
+                Constants.ADDR_AUDIO_SYSTEM));
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+    }
+
+    @Test
+    public void arcInitiation_abort() {
+        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+                Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc);
+
+        mHdmiControlService.handleCecCommand(
+                HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                        Constants.ADDR_TV,
+                        Constants.ADDR_AUDIO_SYSTEM, Constants.MESSAGE_INITIATE_ARC,
+                        Constants.ABORT_REFUSED));
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+    }
+
+    //Fail
+    @Test
+    public void arcInitiation_timeout() {
+        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+                Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc);
+
+        mTestLooper.moveTimeForward(1001);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index dc326ee..f986a70 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -15,13 +15,22 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
 import static com.google.common.truth.Truth.assertThat;
 
-import android.annotation.Nullable;
-import android.app.Instrumentation;
-import android.hardware.hdmi.HdmiDeviceInfo;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
 import android.os.Looper;
+import android.os.PowerManager;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 
@@ -29,10 +38,13 @@
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
 
 /** Tests for {@link ArcTerminationActionFromAvr} */
 @SmallTest
@@ -40,45 +52,47 @@
 @RunWith(JUnit4.class)
 public class ArcTerminationActionFromAvrTest {
 
-    private HdmiDeviceInfo mDeviceInfoForTests;
+    private Context mContextSpy;
     private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
     private ArcTerminationActionFromAvr mAction;
 
+    private HdmiCecController mHdmiCecController;
+    private HdmiControlService mHdmiControlService;
+    private FakeNativeWrapper mNativeWrapper;
+
     private TestLooper mTestLooper = new TestLooper();
-    private boolean mSendCecCommandSuccess;
-    private boolean mShouldDispatchReportArcTerminated;
-    private Instrumentation mInstrumentation;
-    @Nullable private Boolean mArcEnabled = null;
+    private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+
+    @Mock private IPowerManager mIPowerManagerMock;
+    @Mock private IThermalService mIThermalServiceMock;
+    @Mock private AudioManager mAudioManager;
 
     @Before
-    public void setUp() {
-        mDeviceInfoForTests = new HdmiDeviceInfo(1000, 1);
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
 
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
-        HdmiControlService hdmiControlService =
-                new HdmiControlService(mInstrumentation.getTargetContext()) {
+        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
+        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
+        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+
+        mHdmiControlService =
+                new HdmiControlService(mContextSpy) {
                     @Override
-                    void sendCecCommand(
-                            HdmiCecMessage command, @Nullable SendMessageCallback callback) {
-                        switch (command.getOpcode()) {
-                            case Constants.MESSAGE_TERMINATE_ARC:
-                                if (callback != null) {
-                                    callback.onSendCompleted(
-                                            mSendCecCommandSuccess
-                                                    ? SendMessageResult.SUCCESS
-                                                    : SendMessageResult.NACK);
-                                }
-                                if (mShouldDispatchReportArcTerminated) {
-                                    mHdmiCecLocalDeviceAudioSystem.dispatchMessage(
-                                            HdmiCecMessageBuilder.buildReportArcTerminated(
-                                                    Constants.ADDR_TV,
-                                                    mHdmiCecLocalDeviceAudioSystem.mAddress));
-                                }
-                                break;
-                            default:
-                                throw new IllegalArgumentException("Unexpected message");
-                        }
+                    void wakeUp() {
+                    }
+
+                    @Override
+                    PowerManager getPowerManager() {
+                        return powerManager;
+                    }
+
+                    @Override
+                    AudioManager getAudioManager() {
+                        return mAudioManager;
                     }
 
                     @Override
@@ -97,55 +111,71 @@
                     }
                 };
 
-        mHdmiCecLocalDeviceAudioSystem =
-                new HdmiCecLocalDeviceAudioSystem(hdmiControlService) {
-                    @Override
-                    HdmiDeviceInfo getDeviceInfo() {
-                        return mDeviceInfoForTests;
-                    }
-
-                    @Override
-                    void setArcStatus(boolean enabled) {
-                        mArcEnabled = enabled;
-                    }
-                };
-        mHdmiCecLocalDeviceAudioSystem.init();
         Looper looper = mTestLooper.getLooper();
-        hdmiControlService.setIoLooper(looper);
+        mHdmiControlService.setIoLooper(looper);
+        mNativeWrapper = new FakeNativeWrapper();
+        mHdmiCecController =
+                HdmiCecController.createWithNativeWrapper(this.mHdmiControlService, mNativeWrapper);
+        mHdmiControlService.setCecController(mHdmiCecController);
+        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+        mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+        mHdmiControlService.initPortInfo();
 
+        mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService) {
+            @Override
+            protected void setPreferredAddress(int addr) {
+            }
+        };
+        mHdmiCecLocalDeviceAudioSystem.init();
         mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem);
+
+        mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
+        mTestLooper.dispatchAll();
     }
 
     @Test
-    @Ignore("b/120845532")
-    public void testSendMessage_notSuccess() {
-        mSendCecCommandSuccess = false;
-        mShouldDispatchReportArcTerminated = false;
+    public void testSendMessage_sendFailed() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_TERMINATE_ARC,
+                SendMessageResult.NACK);
         mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
-
         mTestLooper.dispatchAll();
-        assertThat(mArcEnabled).isNull();
+        HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+                Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
     }
 
     @Test
-    public void testReportArcTerminated_notReceived() {
-        mSendCecCommandSuccess = true;
-        mShouldDispatchReportArcTerminated = false;
+    public void testReportArcTerminated_timeout() {
         mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
-
-        mTestLooper.moveTimeForward(1000);
         mTestLooper.dispatchAll();
-        assertThat(mArcEnabled).isNull();
+        HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+                Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
     }
 
     @Test
     public void testReportArcTerminated_received() {
-        mSendCecCommandSuccess = true;
-        mShouldDispatchReportArcTerminated = true;
         mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
-
-        mTestLooper.moveTimeForward(1000);
         mTestLooper.dispatchAll();
-        assertThat(mArcEnabled).isFalse();
+        HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+                Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
+
+        HdmiCecMessage arcTerminatedResponse = HdmiCecMessageBuilder.buildReportArcTerminated(
+                Constants.ADDR_TV, Constants.ADDR_AUDIO_SYSTEM);
+
+        mHdmiControlService.handleCecCommand(arcTerminatedResponse);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index 7538468..01f0a3d 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -24,6 +24,7 @@
 import com.google.common.collect.Iterables;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 
 /** Fake {@link NativeWrapper} useful for testing. */
@@ -48,6 +49,7 @@
             };
 
     private final List<HdmiCecMessage> mResultMessages = new ArrayList<>();
+    private final HashMap<Integer, Integer> mMessageSendResult = new HashMap<>();
     private int mMyPhysicalAddress = 0;
     private HdmiPortInfo[] mHdmiPortInfo = null;
 
@@ -65,9 +67,10 @@
         if (body.length == 0) {
             return mPollAddressResponse[dstAddress];
         } else {
-            mResultMessages.add(HdmiCecMessageBuilder.of(srcAddress, dstAddress, body));
+            HdmiCecMessage message = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body);
+            mResultMessages.add(message);
+            return mMessageSendResult.getOrDefault(message.getOpcode(), SendMessageResult.SUCCESS);
         }
-        return SendMessageResult.SUCCESS;
     }
 
     @Override
@@ -132,6 +135,10 @@
         mPollAddressResponse[logicalAddress] = response;
     }
 
+    public void setMessageSendResult(int opcode, int result) {
+        mMessageSendResult.put(opcode, result);
+    }
+
     @VisibleForTesting
     protected void setPhysicalAddress(int physicalAddress) {
         mMyPhysicalAddress = physicalAddress;
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 94e4041..9511181 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -124,7 +124,7 @@
         spyOn(mNavBarWindow);
 
         // Disabling this call for most tests since it can override the systemUiFlags when called.
-        doReturn(0).when(mDisplayPolicy).updateSystemUiVisibilityLw();
+        doReturn(false).when(mDisplayPolicy).updateSystemUiVisibilityLw();
 
         updateDisplayFrames();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index b77d21c..a55423a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -19,9 +19,9 @@
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.Surface.ROTATION_0;
-import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_TOUCH;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -78,8 +78,7 @@
         attrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         attrs.format = PixelFormat.OPAQUE;
-        attrs.systemUiVisibility = attrs.subtreeSystemUiVisibility = win.mSystemUiVisibility =
-                hasLightNavBar ? SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0;
+        attrs.insetsFlags.appearance = hasLightNavBar ? APPEARANCE_LIGHT_NAVIGATION_BARS : 0;
         return win;
     }
 
@@ -103,8 +102,7 @@
         attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_LAYOUT_IN_SCREEN
                 | (drawNavBar ? FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS : 0);
         attrs.format = PixelFormat.TRANSPARENT;
-        attrs.systemUiVisibility = attrs.subtreeSystemUiVisibility = win.mSystemUiVisibility =
-                hasLightNavBar ? SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0;
+        attrs.insetsFlags.appearance = hasLightNavBar ? APPEARANCE_LIGHT_NAVIGATION_BARS : 0;
         win.mHasSurface = visible;
         return win;
     }
@@ -163,6 +161,7 @@
 
     @Test
     public void testUpdateLightNavigationBarLw() {
+        DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
         final WindowState opaqueDarkNavBar = createOpaqueFullscreen(false);
         final WindowState opaqueLightNavBar = createOpaqueFullscreen(true);
 
@@ -171,50 +170,50 @@
         final WindowState imeDrawDarkNavBar = createInputMethodWindow(true, true, false);
         final WindowState imeDrawLightNavBar = createInputMethodWindow(true, true, true);
 
-        assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
-                DisplayPolicy.updateLightNavigationBarLw(
-                        SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, null, null,
+        assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS,
+                displayPolicy.updateLightNavigationBarLw(
+                        APPEARANCE_LIGHT_NAVIGATION_BARS, null, null,
                         null, null));
 
-        // Opaque top fullscreen window overrides SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR flag.
-        assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
+        // Opaque top fullscreen window overrides APPEARANCE_LIGHT_NAVIGATION_BARS flag.
+        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
                 0, opaqueDarkNavBar, opaqueDarkNavBar, null, opaqueDarkNavBar));
-        assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
-                SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueDarkNavBar, opaqueDarkNavBar, null,
+        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
+                APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueDarkNavBar, opaqueDarkNavBar, null,
                 opaqueDarkNavBar));
-        assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
-                DisplayPolicy.updateLightNavigationBarLw(0, opaqueLightNavBar,
+        assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS,
+                displayPolicy.updateLightNavigationBarLw(0, opaqueLightNavBar,
                         opaqueLightNavBar, null, opaqueLightNavBar));
-        assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
-                DisplayPolicy.updateLightNavigationBarLw(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+        assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS,
+                displayPolicy.updateLightNavigationBarLw(APPEARANCE_LIGHT_NAVIGATION_BARS,
                         opaqueLightNavBar, opaqueLightNavBar, null, opaqueLightNavBar));
 
-        // Dimming window clears SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.
-        assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
+        // Dimming window clears APPEARANCE_LIGHT_NAVIGATION_BARS.
+        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
                 0, opaqueDarkNavBar, dimming, null, dimming));
-        assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
+        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
                 0, opaqueLightNavBar, dimming, null, dimming));
-        assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
-                SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueDarkNavBar, dimming, null, dimming));
-        assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
-                SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueLightNavBar, dimming, null, dimming));
-        assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
-                SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueLightNavBar, dimming, imeDrawLightNavBar,
+        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
+                APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueDarkNavBar, dimming, null, dimming));
+        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
+                APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar, dimming, null, dimming));
+        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
+                APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar, dimming, imeDrawLightNavBar,
                 dimming));
 
-        // IME window clears SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
-        assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
-                SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, null, null, imeDrawDarkNavBar,
+        // IME window clears APPEARANCE_LIGHT_NAVIGATION_BARS
+        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
+                APPEARANCE_LIGHT_NAVIGATION_BARS, null, null, imeDrawDarkNavBar,
                 imeDrawDarkNavBar));
 
-        // Even if the top fullscreen has SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, IME window wins.
-        assertEquals(0, DisplayPolicy.updateLightNavigationBarLw(
-                SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueLightNavBar, opaqueLightNavBar,
+        // Even if the top fullscreen has APPEARANCE_LIGHT_NAVIGATION_BARS, IME window wins.
+        assertEquals(0, displayPolicy.updateLightNavigationBarLw(
+                APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar, opaqueLightNavBar,
                 imeDrawDarkNavBar, imeDrawDarkNavBar));
 
-        // IME window should be able to use SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.
-        assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
-                DisplayPolicy.updateLightNavigationBarLw(0, opaqueDarkNavBar,
+        // IME window should be able to use APPEARANCE_LIGHT_NAVIGATION_BARS.
+        assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS,
+                displayPolicy.updateLightNavigationBarLw(0, opaqueDarkNavBar,
                         opaqueDarkNavBar, imeDrawLightNavBar, imeDrawLightNavBar));
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
index af8cb02..94ffcda 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
@@ -45,7 +45,6 @@
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 import android.view.Gravity;
-import android.view.View;
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.R;
@@ -99,11 +98,9 @@
 
         mStatusBarWindow.mAttrs.gravity = Gravity.TOP;
         addWindow(mStatusBarWindow);
-        mDisplayPolicy.mLastSystemUiFlags |= View.STATUS_BAR_TRANSPARENT;
 
         mNavBarWindow.mAttrs.gravity = Gravity.BOTTOM;
         addWindow(mNavBarWindow);
-        mDisplayPolicy.mLastSystemUiFlags |= View.NAVIGATION_BAR_TRANSPARENT;
 
         // Update source frame and visibility of insets providers.
         mDisplayContent.getInsetsStateController().onPostLayout();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index edcf0d4..f154073 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -566,7 +566,7 @@
         assertEquals(new Rect(mActivity.getBounds().left, 0, dh - mActivity.getBounds().right, 0),
                 mActivity.getLetterboxInsets());
 
-        final StatusBarController statusBarController =
+        final BarController statusBarController =
                 mActivity.mDisplayContent.getDisplayPolicy().getStatusBarController();
         // The activity doesn't fill the display, so the letterbox of the rotated activity is
         // overlapped with the rotated content frame of status bar. Hence the status bar shouldn't
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 2510385..5b7cf5a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -27,6 +27,8 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
@@ -164,4 +166,14 @@
 
         verify(mWm.mAtmService).setFocusedTask(tappedTask.mTaskId);
     }
+
+    @Test
+    public void testDismissKeyguardCanWakeUp() {
+        doReturn(true).when(mWm).checkCallingPermission(anyString(), anyString());
+        spyOn(mWm.mAtmInternal);
+        doReturn(true).when(mWm.mAtmInternal).isDreaming();
+        doNothing().when(mWm.mAtmService.mStackSupervisor).wakeUp(anyString());
+        mWm.dismissKeyguard(null, "test-dismiss-keyguard");
+        verify(mWm.mAtmService.mStackSupervisor).wakeUp(anyString());
+    }
 }
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index d4308c4..cfc7ac1 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -157,7 +157,8 @@
 
         List<String> enabledCarrierPackages = new ArrayList<>();
         int carrierAppsHandledSdk =
-                Settings.Secure.getInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0);
+                Settings.Secure.getIntForUser(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED,
+                        0, contentResolver.getUserId());
         if (DEBUG) {
             Log.i(TAG, "Last execution SDK: " + carrierAppsHandledSdk);
         }
@@ -309,8 +310,8 @@
 
             // Mark the execution so we do not disable apps again on this SDK version.
             if (!hasRunEver || !hasRunForSdk) {
-                Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED,
-                        Build.VERSION.SDK_INT);
+                Settings.Secure.putIntForUser(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED,
+                        Build.VERSION.SDK_INT, contentResolver.getUserId());
             }
 
             if (!enabledCarrierPackages.isEmpty()) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4de1abf..829c746 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -32,6 +32,7 @@
 import android.service.carrier.CarrierService;
 import android.telecom.TelecomManager;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsSsData;
 
 import com.android.internal.telephony.ICarrierConfigLoader;
 import com.android.telephony.Rlog;
@@ -66,6 +67,18 @@
     public static final String EXTRA_SUBSCRIPTION_INDEX =
             SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX;
 
+    /**
+     * Service class flag if not specify a service class.
+     * Reference: 3GPP TS 27.007 Section 7.4 Facility lock +CLCK
+     */
+    public static final int SERVICE_CLASS_NONE = ImsSsData.SERVICE_CLASS_NONE;
+
+    /**
+     * Service class flag for voice telephony.
+     * Reference: 3GPP TS 27.007 Section 7.4 Facility lock +CLCK
+     */
+    public static final int SERVICE_CLASS_VOICE = ImsSsData.SERVICE_CLASS_VOICE;
+
     private final Context mContext;
 
     /**
@@ -212,6 +225,18 @@
             "call_barring_supports_deactivate_all_bool";
 
     /**
+     * Specifies the service class for call barring service. Default value is
+     * {@link #SERVICE_CLASS_VOICE}.
+     * The value set as below:
+     * <ul>
+     * <li>0: {@link #SERVICE_CLASS_NONE}</li>
+     * <li>1: {@link #SERVICE_CLASS_VOICE}</li>
+     * </ul>
+     */
+    public static final String KEY_CALL_BARRING_DEFAULT_SERVICE_CLASS_INT =
+            "call_barring_default_service_class_int";
+
+    /**
      * Flag indicating whether the Phone app should ignore EVENT_SIM_NETWORK_LOCKED
      * events from the Sim.
      * If true, this will prevent the IccNetworkDepersonalizationPanel from being shown, and
@@ -3889,10 +3914,23 @@
      * Indicating whether DUN APN should be disabled when the device is roaming. In that case,
      * the default APN (i.e. internet) will be used for tethering.
      *
+     * This config is only available when using Preset APN(not user edited) as Preferred APN.
+     *
      * @hide
      */
-    public static final String KEY_DISABLE_DUN_APN_WHILE_ROAMING =
-            "disable_dun_apn_while_roaming";
+    public static final String KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL =
+            "disable_dun_apn_while_roaming_with_preset_apn_bool";
+
+    /**
+     * Where there is no preferred APN, specifies the carrier's default preferred APN.
+     * Specifies the {@link android.provider.Telephony.Carriers.APN} of the default preferred apn.
+     *
+     * This config is only available with Preset APN(not user edited).
+     *
+     * @hide
+     */
+    public static final String KEY_DEFAULT_PREFERRED_APN_NAME_STRING =
+            "default_preferred_apn_name_string";
 
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
@@ -3968,6 +4006,7 @@
         sDefaults.putBoolean(KEY_CALL_BARRING_VISIBILITY_BOOL, false);
         sDefaults.putBoolean(KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL, true);
         sDefaults.putBoolean(KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL, true);
+        sDefaults.putInt(KEY_CALL_BARRING_DEFAULT_SERVICE_CLASS_INT, SERVICE_CLASS_VOICE);
         sDefaults.putBoolean(KEY_CALL_FORWARDING_VISIBILITY_BOOL, true);
         sDefaults.putBoolean(KEY_CALL_FORWARDING_WHEN_UNREACHABLE_SUPPORTED_BOOL, true);
         sDefaults.putBoolean(KEY_CALL_FORWARDING_WHEN_UNANSWERED_SUPPORTED_BOOL, true);
@@ -4432,7 +4471,8 @@
                 "ims:2", "cbs:2", "ia:2", "emergency:2", "mcx:3", "xcap:3"
         });
         sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]);
-        sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING, false);
+        sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
+        sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 969016b..559a774 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -306,6 +306,8 @@
     private static boolean sServiceHandleCacheEnabled = true;
 
     @GuardedBy("sCacheLock")
+    private static ITelephony sITelephony;
+    @GuardedBy("sCacheLock")
     private static IPhoneSubInfo sIPhoneSubInfo;
     @GuardedBy("sCacheLock")
     private static ISub sISub;
@@ -4180,7 +4182,7 @@
         }
     }
 
-   /**
+    /**
      * @param keyAvailability bitmask that defines the availabilty of keys for a type.
      * @param keyType the key type which is being checked. (WLAN, EPDG)
      * @return true if the digit at position keyType is 1, else false.
@@ -5499,13 +5501,39 @@
         }
     }
 
-   /**
-    * @hide
-    */
+    /**
+     * @hide
+     */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     private ITelephony getITelephony() {
-        return ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
-                .getTelephonyServiceManager().getTelephonyServiceRegisterer().get());
+        // Keeps cache disabled until test fixes are checked into AOSP.
+        if (!sServiceHandleCacheEnabled) {
+            return ITelephony.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getTelephonyServiceRegisterer()
+                            .get());
+        }
+
+        if (sITelephony == null) {
+            ITelephony temp = ITelephony.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getTelephonyServiceRegisterer()
+                            .get());
+            synchronized (sCacheLock) {
+                if (sITelephony == null && temp != null) {
+                    try {
+                        sITelephony = temp;
+                        sITelephony.asBinder().linkToDeath(sServiceDeath, 0);
+                    } catch (Exception e) {
+                        // something has gone horribly wrong
+                        sITelephony = null;
+                    }
+                }
+            }
+        }
+        return sITelephony;
     }
 
     private IOns getIOns() {
@@ -13401,6 +13429,10 @@
     */
     private static void resetServiceCache() {
         synchronized (sCacheLock) {
+            if (sITelephony != null) {
+                sITelephony.asBinder().unlinkToDeath(sServiceDeath, 0);
+                sITelephony = null;
+            }
             if (sISub != null) {
                 sISub.asBinder().unlinkToDeath(sServiceDeath, 0);
                 sISub = null;
@@ -13417,9 +13449,9 @@
         }
     }
 
-   /**
-    * @hide
-    */
+    /**
+     * @hide
+     */
     static IPhoneSubInfo getSubscriberInfoService() {
         // Keeps cache disabled until test fixes are checked into AOSP.
         if (!sServiceHandleCacheEnabled) {
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 2b3072e..da7311c 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -137,18 +137,30 @@
         }
 
         @Override
-        public IImsMmTelFeature createMmTelFeature(int slotId, IImsFeatureStatusCallback c) {
-            return createMmTelFeatureInternal(slotId, c);
+        public IImsMmTelFeature createMmTelFeature(int slotId) {
+            return createMmTelFeatureInternal(slotId);
         }
 
         @Override
-        public IImsRcsFeature createRcsFeature(int slotId, IImsFeatureStatusCallback c) {
-            return createRcsFeatureInternal(slotId, c);
+        public IImsRcsFeature createRcsFeature(int slotId) {
+            return createRcsFeatureInternal(slotId);
         }
 
         @Override
-        public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) {
-            ImsService.this.removeImsFeature(slotId, featureType, c);
+        public void addFeatureStatusCallback(int slotId, int featureType,
+                IImsFeatureStatusCallback c) {
+            ImsService.this.addImsFeatureStatusCallback(slotId, featureType, c);
+        }
+
+        @Override
+        public void removeFeatureStatusCallback(int slotId, int featureType,
+                IImsFeatureStatusCallback c) {
+            ImsService.this.removeImsFeatureStatusCallback(slotId, featureType, c);
+        }
+
+        @Override
+        public void removeImsFeature(int slotId, int featureType) {
+            ImsService.this.removeImsFeature(slotId, featureType);
         }
 
         @Override
@@ -204,11 +216,10 @@
         return mFeaturesBySlot.get(slotId);
     }
 
-    private IImsMmTelFeature createMmTelFeatureInternal(int slotId,
-            IImsFeatureStatusCallback c) {
+    private IImsMmTelFeature createMmTelFeatureInternal(int slotId) {
         MmTelFeature f = createMmTelFeature(slotId);
         if (f != null) {
-            setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL, c);
+            setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL);
             return f.getBinder();
         } else {
             Log.e(LOG_TAG, "createMmTelFeatureInternal: null feature returned.");
@@ -216,11 +227,10 @@
         }
     }
 
-    private IImsRcsFeature createRcsFeatureInternal(int slotId,
-            IImsFeatureStatusCallback c) {
+    private IImsRcsFeature createRcsFeatureInternal(int slotId) {
         RcsFeature f = createRcsFeature(slotId);
         if (f != null) {
-            setupFeature(f, slotId, ImsFeature.FEATURE_RCS, c);
+            setupFeature(f, slotId, ImsFeature.FEATURE_RCS);
             return f.getBinder();
         } else {
             Log.e(LOG_TAG, "createRcsFeatureInternal: null feature returned.");
@@ -228,13 +238,45 @@
         }
     }
 
-    private void setupFeature(ImsFeature f, int slotId, int featureType,
-            IImsFeatureStatusCallback c) {
+    private void setupFeature(ImsFeature f, int slotId, int featureType) {
         f.initialize(this, slotId);
-        f.addImsFeatureStatusCallback(c);
         addImsFeature(slotId, featureType, f);
     }
 
+    private void addImsFeatureStatusCallback(int slotId, int featureType,
+            IImsFeatureStatusCallback c) {
+        synchronized (mFeaturesBySlot) {
+            // get ImsFeature associated with the slot/feature
+            SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
+            if (features == null) {
+                Log.w(LOG_TAG, "Can not add ImsFeatureStatusCallback - no features on slot "
+                        + slotId);
+                return;
+            }
+            ImsFeature f = features.get(featureType);
+            if (f != null) {
+                f.addImsFeatureStatusCallback(c);
+            }
+        }
+    }
+
+    private void removeImsFeatureStatusCallback(int slotId, int featureType,
+            IImsFeatureStatusCallback c) {
+        synchronized (mFeaturesBySlot) {
+            // get ImsFeature associated with the slot/feature
+            SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
+            if (features == null) {
+                Log.w(LOG_TAG, "Can not remove ImsFeatureStatusCallback - no features on slot "
+                        + slotId);
+                return;
+            }
+            ImsFeature f = features.get(featureType);
+            if (f != null) {
+                f.removeImsFeatureStatusCallback(c);
+            }
+        }
+    }
+
     private void addImsFeature(int slotId, int featureType, ImsFeature f) {
         synchronized (mFeaturesBySlot) {
             // Get SparseArray for Features, by querying slot Id
@@ -248,8 +290,7 @@
         }
     }
 
-    private void removeImsFeature(int slotId, int featureType,
-            IImsFeatureStatusCallback c) {
+    private void removeImsFeature(int slotId, int featureType) {
         synchronized (mFeaturesBySlot) {
             // get ImsFeature associated with the slot/feature
             SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
@@ -264,7 +305,6 @@
                         + featureType + " exists on slot " + slotId);
                 return;
             }
-            f.removeImsFeatureStatusCallback(c);
             f.onFeatureRemoved();
             features.remove(featureType);
         }
diff --git a/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl b/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl
index c7da681..c956cbc 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl
@@ -31,12 +31,14 @@
  */
 interface IImsServiceController {
     void setListener(IImsServiceControllerListener l);
-    IImsMmTelFeature createMmTelFeature(int slotId, in IImsFeatureStatusCallback c);
-    IImsRcsFeature createRcsFeature(int slotId, in IImsFeatureStatusCallback c);
+    IImsMmTelFeature createMmTelFeature(int slotId);
+    IImsRcsFeature createRcsFeature(int slotId);
     ImsFeatureConfiguration querySupportedImsFeatures();
+    void addFeatureStatusCallback(int slotId, int featureType, in IImsFeatureStatusCallback c);
+    void removeFeatureStatusCallback(int slotId, int featureType, in IImsFeatureStatusCallback c);
     // Synchronous call to ensure the ImsService is ready before continuing with feature creation.
     void notifyImsServiceReadyForFeatureCreation();
-    void removeImsFeature(int slotId, int featureType, in IImsFeatureStatusCallback c);
+    void removeImsFeature(int slotId, int featureType);
     IImsConfig getConfig(int slotId);
     IImsRegistration getRegistration(int slotId);
     oneway void enableIms(int slotId);
diff --git a/telephony/java/android/telephony/ims/compat/ImsService.java b/telephony/java/android/telephony/ims/compat/ImsService.java
index eafbb14..41d1d72 100644
--- a/telephony/java/android/telephony/ims/compat/ImsService.java
+++ b/telephony/java/android/telephony/ims/compat/ImsService.java
@@ -21,7 +21,6 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Intent;
 import android.os.IBinder;
-import android.os.RemoteException;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ims.compat.feature.ImsFeature;
 import android.telephony.ims.compat.feature.MMTelFeature;
@@ -91,25 +90,35 @@
     protected final IBinder mImsServiceController = new IImsServiceController.Stub() {
 
         @Override
-        public IImsMMTelFeature createEmergencyMMTelFeature(int slotId,
+        public IImsMMTelFeature createEmergencyMMTelFeature(int slotId) {
+            return createEmergencyMMTelFeatureInternal(slotId);
+        }
+
+        @Override
+        public IImsMMTelFeature createMMTelFeature(int slotId) {
+            return createMMTelFeatureInternal(slotId);
+        }
+
+        @Override
+        public IImsRcsFeature createRcsFeature(int slotId) {
+            return createRcsFeatureInternal(slotId);
+        }
+
+        @Override
+        public void removeImsFeature(int slotId, int featureType) {
+            ImsService.this.removeImsFeature(slotId, featureType);
+        }
+
+        @Override
+        public void addFeatureStatusCallback(int slotId, int featureType,
                 IImsFeatureStatusCallback c) {
-            return createEmergencyMMTelFeatureInternal(slotId, c);
+            addImsFeatureStatusCallback(slotId, featureType, c);
         }
 
         @Override
-        public IImsMMTelFeature createMMTelFeature(int slotId, IImsFeatureStatusCallback c) {
-            return createMMTelFeatureInternal(slotId, c);
-        }
-
-        @Override
-        public IImsRcsFeature createRcsFeature(int slotId, IImsFeatureStatusCallback c) {
-            return createRcsFeatureInternal(slotId, c);
-        }
-
-        @Override
-        public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
-                throws RemoteException {
-            ImsService.this.removeImsFeature(slotId, featureType, c);
+        public void removeFeatureStatusCallback(int slotId, int featureType,
+                IImsFeatureStatusCallback c) {
+            removeImsFeatureStatusCallback(slotId, featureType, c);
         }
     };
 
@@ -137,46 +146,40 @@
         return mFeaturesBySlot.get(slotId);
     }
 
-    private IImsMMTelFeature createEmergencyMMTelFeatureInternal(int slotId,
-            IImsFeatureStatusCallback c) {
+    private IImsMMTelFeature createEmergencyMMTelFeatureInternal(int slotId) {
         MMTelFeature f = onCreateEmergencyMMTelImsFeature(slotId);
         if (f != null) {
-            setupFeature(f, slotId, ImsFeature.EMERGENCY_MMTEL, c);
+            setupFeature(f, slotId, ImsFeature.EMERGENCY_MMTEL);
             return f.getBinder();
         } else {
             return null;
         }
     }
 
-    private IImsMMTelFeature createMMTelFeatureInternal(int slotId,
-            IImsFeatureStatusCallback c) {
+    private IImsMMTelFeature createMMTelFeatureInternal(int slotId) {
         MMTelFeature f = onCreateMMTelImsFeature(slotId);
         if (f != null) {
-            setupFeature(f, slotId, ImsFeature.MMTEL, c);
+            setupFeature(f, slotId, ImsFeature.MMTEL);
             return f.getBinder();
         } else {
             return null;
         }
     }
 
-    private IImsRcsFeature createRcsFeatureInternal(int slotId,
-            IImsFeatureStatusCallback c) {
+    private IImsRcsFeature createRcsFeatureInternal(int slotId) {
         RcsFeature f = onCreateRcsFeature(slotId);
         if (f != null) {
-            setupFeature(f, slotId, ImsFeature.RCS, c);
+            setupFeature(f, slotId, ImsFeature.RCS);
             return f.getBinder();
         } else {
             return null;
         }
     }
 
-    private void setupFeature(ImsFeature f, int slotId, int featureType,
-            IImsFeatureStatusCallback c) {
+    private void setupFeature(ImsFeature f, int slotId, int featureType) {
         f.setContext(this);
         f.setSlotId(slotId);
-        f.addImsFeatureStatusCallback(c);
         addImsFeature(slotId, featureType, f);
-        // TODO: Remove once new onFeatureReady AIDL is merged in.
         f.onFeatureReady();
     }
 
@@ -193,12 +196,45 @@
         }
     }
 
-    private void removeImsFeature(int slotId, int featureType,
+    private void addImsFeatureStatusCallback(int slotId, int featureType,
             IImsFeatureStatusCallback c) {
         synchronized (mFeaturesBySlot) {
             // get ImsFeature associated with the slot/feature
             SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
             if (features == null) {
+                Log.w(LOG_TAG, "Can not add ImsFeatureStatusCallback. No ImsFeatures exist on"
+                        + " slot " + slotId);
+                return;
+            }
+            ImsFeature f = features.get(featureType);
+            if (f != null) {
+                f.addImsFeatureStatusCallback(c);
+            }
+        }
+    }
+
+    private void removeImsFeatureStatusCallback(int slotId, int featureType,
+            IImsFeatureStatusCallback c) {
+        synchronized (mFeaturesBySlot) {
+            // get ImsFeature associated with the slot/feature
+            SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
+            if (features == null) {
+                Log.w(LOG_TAG, "Can not remove ImsFeatureStatusCallback. No ImsFeatures exist on"
+                        + " slot " + slotId);
+                return;
+            }
+            ImsFeature f = features.get(featureType);
+            if (f != null) {
+                f.removeImsFeatureStatusCallback(c);
+            }
+        }
+    }
+
+    private void removeImsFeature(int slotId, int featureType) {
+        synchronized (mFeaturesBySlot) {
+            // get ImsFeature associated with the slot/feature
+            SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
+            if (features == null) {
                 Log.w(LOG_TAG, "Can not remove ImsFeature. No ImsFeatures exist on slot "
                         + slotId);
                 return;
@@ -209,7 +245,6 @@
                         + featureType + " exists on slot " + slotId);
                 return;
             }
-            f.removeImsFeatureStatusCallback(c);
             f.onFeatureRemoved();
             features.remove(featureType);
         }
diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
index 857089f..e9528f4 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -25,8 +25,10 @@
  * {@hide}
  */
 interface IImsServiceController {
-    IImsMMTelFeature createEmergencyMMTelFeature(int slotId, in IImsFeatureStatusCallback c);
-    IImsMMTelFeature createMMTelFeature(int slotId, in IImsFeatureStatusCallback c);
-    IImsRcsFeature createRcsFeature(int slotId, in IImsFeatureStatusCallback c);
-    void removeImsFeature(int slotId, int featureType, in IImsFeatureStatusCallback c);
+    IImsMMTelFeature createEmergencyMMTelFeature(int slotId);
+    IImsMMTelFeature createMMTelFeature(int slotId);
+    IImsRcsFeature createRcsFeature(int slotId);
+    void removeImsFeature(int slotId, int featureType);
+    void addFeatureStatusCallback(int slotId, int featureType, in IImsFeatureStatusCallback c);
+    void removeFeatureStatusCallback(int slotId, int featureType, in IImsFeatureStatusCallback c);
 }
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index a53ea16..d430db5 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -29,7 +29,8 @@
         "flickerlib",
         "truth-prebuilt",
         "launcher-helper-lib",
-        "launcher-aosp-tapl"
+        "launcher-aosp-tapl",
+        "platform-test-annotations",
     ],
 }
 
@@ -50,6 +51,7 @@
         "truth-prebuilt",
         "app-helpers-core",
         "launcher-helper-lib",
-        "launcher-aosp-tapl"
+        "launcher-aosp-tapl",
+        "platform-test-annotations",
     ],
 }
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index b64811b..c1ba21a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.dsl.flicker
@@ -39,6 +40,7 @@
  * Test IME window closing back to app window transitions.
  * To run this test: `atest FlickerTests:CloseImeWindowToAppTest`
  */
+@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index 0940c19..2c00722 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.NonRotationTestBase
@@ -39,6 +40,7 @@
  * Test IME window closing back to app window transitions.
  * To run this test: `atest FlickerTests:CloseImeWindowToAppTest`
  */
+@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index c2e87db..4697adc 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.NonRotationTestBase
@@ -40,6 +41,7 @@
  * Test IME window closing to home transitions.
  * To run this test: `atest FlickerTests:CloseImeWindowToHomeTest`
  */
+@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index 11ccb69..2caa8f3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.NonRotationTestBase
@@ -39,6 +40,7 @@
  * Test IME window opening transitions.
  * To run this test: `atest FlickerTests:OpenImeWindowTest`
  */
+@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 1759072..2c9c8ba 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.launch
 
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.dsl.flicker
@@ -38,6 +39,7 @@
  * Test cold launch app from launcher.
  * To run this test: `atest FlickerTests:OpenAppColdTest`
  */
+@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt
index 87c8633..7447bda 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.splitscreen
 
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.NonRotationTestBase
@@ -43,6 +44,7 @@
  * Test open app to split screen.
  * To run this test: `atest FlickerTests:SplitScreenToLauncherTest`
  */
+@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tools/aapt2/DominatorTree_test.cpp b/tools/aapt2/DominatorTree_test.cpp
index fe4f951..3e49034 100644
--- a/tools/aapt2/DominatorTree_test.cpp
+++ b/tools/aapt2/DominatorTree_test.cpp
@@ -173,4 +173,30 @@
   EXPECT_EQ(expected, printer.ToString(&tree));
 }
 
+TEST(DominatorTreeTest, NonZeroDensitiesMatch) {
+  const ConfigDescription sw600_config = test::ParseConfigOrDie("sw600dp");
+  const ConfigDescription sw600_hdpi_config = test::ParseConfigOrDie("sw600dp-hdpi");
+  const ConfigDescription sw800_hdpi_config = test::ParseConfigOrDie("sw800dp-hdpi");
+  const ConfigDescription sw800_xxhdpi_config = test::ParseConfigOrDie("sw800dp-xxhdpi");
+
+  std::vector<std::unique_ptr<ResourceConfigValue>> configs;
+  configs.push_back(util::make_unique<ResourceConfigValue>(ConfigDescription::DefaultConfig(), ""));
+  configs.push_back(util::make_unique<ResourceConfigValue>(sw600_config, ""));
+  configs.push_back(util::make_unique<ResourceConfigValue>(sw600_hdpi_config, ""));
+  configs.push_back(util::make_unique<ResourceConfigValue>(sw800_hdpi_config, ""));
+  configs.push_back(util::make_unique<ResourceConfigValue>(sw800_xxhdpi_config, ""));
+
+  DominatorTree tree(configs);
+  PrettyPrinter printer;
+
+  std::string expected =
+      "<default>\n"
+      "  sw600dp-v13\n"
+      "    sw600dp-hdpi-v13\n"
+      "      sw800dp-hdpi-v13\n"
+      "      sw800dp-xxhdpi-v13\n";
+  EXPECT_EQ(expected, printer.ToString(&tree));
+}
+
+
 }  // namespace aapt
diff --git a/wifi/api/system-current.txt b/wifi/api/system-current.txt
index c3e573c..ed5ed82 100644
--- a/wifi/api/system-current.txt
+++ b/wifi/api/system-current.txt
@@ -292,6 +292,7 @@
     method public int getBandwidth();
     method @Nullable public android.net.MacAddress getBssid();
     method public int getFrequency();
+    method public int getWifiStandard();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int CHANNEL_WIDTH_160MHZ = 6; // 0x6
     field public static final int CHANNEL_WIDTH_20MHZ = 2; // 0x2
diff --git a/wifi/java/android/net/wifi/SoftApInfo.java b/wifi/java/android/net/wifi/SoftApInfo.java
index 40981f7..cf61f81 100644
--- a/wifi/java/android/net/wifi/SoftApInfo.java
+++ b/wifi/java/android/net/wifi/SoftApInfo.java
@@ -86,8 +86,6 @@
      */
     public static final int CHANNEL_WIDTH_160MHZ = 6;
 
-
-
     /** The frequency which AP resides on.  */
     private int mFrequency = 0;
 
@@ -99,6 +97,11 @@
     private MacAddress mBssid;
 
     /**
+     * The operational mode of the AP.
+     */
+    private @WifiAnnotations.WifiStandard int mWifiStandard = ScanResult.WIFI_STANDARD_UNKNOWN;
+
+    /**
      * Get the frequency which AP resides on.
      */
     public int getFrequency() {
@@ -163,6 +166,27 @@
     }
 
     /**
+     * Set the operational mode of the AP.
+     *
+     * @param wifiStandard values from {@link ScanResult}'s {@code WIFI_STANDARD_}
+     * @hide
+     */
+    public void setWifiStandard(@WifiAnnotations.WifiStandard int wifiStandard) {
+        mWifiStandard = wifiStandard;
+    }
+
+    /**
+     * Get the operational mode of the AP.
+     * @return valid values from {@link ScanResult}'s {@code WIFI_STANDARD_}
+     */
+    public @WifiAnnotations.WifiStandard int getWifiStandard() {
+        if (!SdkLevelUtil.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return mWifiStandard;
+    }
+
+    /**
      * @hide
      */
     public SoftApInfo(@Nullable SoftApInfo source) {
@@ -170,6 +194,7 @@
             mFrequency = source.mFrequency;
             mBandwidth = source.mBandwidth;
             mBssid = source.mBssid;
+            mWifiStandard = source.mWifiStandard;
         }
     }
 
@@ -191,6 +216,7 @@
         dest.writeInt(mFrequency);
         dest.writeInt(mBandwidth);
         dest.writeParcelable(mBssid, flags);
+        dest.writeInt(mWifiStandard);
     }
 
     @NonNull
@@ -201,6 +227,7 @@
             info.mFrequency = in.readInt();
             info.mBandwidth = in.readInt();
             info.mBssid = in.readParcelable(MacAddress.class.getClassLoader());
+            info.mWifiStandard = in.readInt();
             return info;
         }
 
@@ -215,8 +242,9 @@
         StringBuilder sbuf = new StringBuilder();
         sbuf.append("SoftApInfo{");
         sbuf.append("bandwidth= ").append(mBandwidth);
-        sbuf.append(",frequency= ").append(mFrequency);
+        sbuf.append(", frequency= ").append(mFrequency);
         if (mBssid != null) sbuf.append(",bssid=").append(mBssid.toString());
+        sbuf.append(", wifiStandard= ").append(mWifiStandard);
         sbuf.append("}");
         return sbuf.toString();
     }
@@ -228,11 +256,12 @@
         SoftApInfo softApInfo = (SoftApInfo) o;
         return mFrequency == softApInfo.mFrequency
                 && mBandwidth == softApInfo.mBandwidth
-                && Objects.equals(mBssid, softApInfo.mBssid);
+                && Objects.equals(mBssid, softApInfo.mBssid)
+                && mWifiStandard == softApInfo.mWifiStandard;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mFrequency, mBandwidth, mBssid);
+        return Objects.hash(mFrequency, mBandwidth, mBssid, mWifiStandard);
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/SoftApInfoTest.java b/wifi/tests/src/android/net/wifi/SoftApInfoTest.java
index 458a954..2821c35 100644
--- a/wifi/tests/src/android/net/wifi/SoftApInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApInfoTest.java
@@ -17,6 +17,7 @@
 package android.net.wifi;
 
 import android.net.MacAddress;
+import android.net.wifi.util.SdkLevelUtil;
 import android.os.Parcel;
 
 import static org.junit.Assert.assertEquals;
@@ -40,6 +41,7 @@
         info.setFrequency(2412);
         info.setBandwidth(SoftApInfo.CHANNEL_WIDTH_20MHZ);
         info.setBssid(MacAddress.fromString("aa:bb:cc:dd:ee:ff"));
+        info.setWifiStandard(ScanResult.WIFI_STANDARD_LEGACY);
 
 
         SoftApInfo copiedInfo = new SoftApInfo(info);
@@ -57,6 +59,7 @@
         info.setFrequency(2412);
         info.setBandwidth(SoftApInfo.CHANNEL_WIDTH_20MHZ);
         info.setBssid(MacAddress.fromString("aa:bb:cc:dd:ee:ff"));
+        info.setWifiStandard(ScanResult.WIFI_STANDARD_LEGACY);
 
         Parcel parcelW = Parcel.obtain();
         info.writeToParcel(parcelW, 0);
@@ -72,4 +75,19 @@
         assertEquals(info.hashCode(), fromParcel.hashCode());
     }
 
+
+    /**
+     * Verifies the initial value same as expected.
+     */
+    @Test
+    public void testInitialValue() throws Exception {
+        SoftApInfo info = new SoftApInfo();
+        assertEquals(info.getFrequency(), 0);
+        assertEquals(info.getBandwidth(), SoftApInfo.CHANNEL_WIDTH_INVALID);
+        if (SdkLevelUtil.isAtLeastS()) {
+            assertEquals(info.getBssid(), null);
+            assertEquals(info.getWifiStandard(), ScanResult.WIFI_STANDARD_UNKNOWN);
+        }
+    }
+
 }