Merge "Preserve invocation order in RemoteInputConnection"
diff --git a/Android.bp b/Android.bp
index 0ac41c0..f1f7a8e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -468,8 +468,6 @@
     sdk_version: "module_current",
     min_sdk_version: "30",
     srcs: [
-        ":modules-utils-preconditions-srcs",
-        "core/java/android/os/HandlerExecutor.java",
         "core/java/com/android/internal/util/AsyncChannel.java",
         "core/java/com/android/internal/util/AsyncService.java",
         "core/java/com/android/internal/util/Protocol.java",
diff --git a/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java b/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java
index 2700fff..e3691a7 100644
--- a/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java
+++ b/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java
@@ -64,9 +64,13 @@
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             os.reset();
-            final FastDataOutput out = new FastDataOutput(os, BUFFER_SIZE);
-            doWrite(out);
-            out.flush();
+            final FastDataOutput out = FastDataOutput.obtain(os);
+            try {
+                doWrite(out);
+                out.flush();
+            } finally {
+                out.release();
+            }
         }
     }
 
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index b47c9cf..3ceb8873 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -35,8 +35,14 @@
   }
 
   public final class PendingIntent implements android.os.Parcelable {
+    method public boolean addCancelListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.PendingIntent.CancelListener);
     method @RequiresPermission(android.Manifest.permission.GET_INTENT_SENDER_INTENT) public boolean intentFilterEquals(@Nullable android.app.PendingIntent);
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_INTENT_SENDER_INTENT) public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int);
+    method public void removeCancelListener(@NonNull android.app.PendingIntent.CancelListener);
+  }
+
+  public static interface PendingIntent.CancelListener {
+    method public void onCancelled(@NonNull android.app.PendingIntent);
   }
 
   public class StatusBarManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 11b26a9..171138e 100755
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1985,6 +1985,7 @@
   public final class BluetoothDevice implements android.os.Parcelable {
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean canBondWithoutDialog();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean cancelBondProcess();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean createBond(int);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean createBondOutOfBand(int, @Nullable android.bluetooth.OobData, @Nullable android.bluetooth.OobData);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean fetchUuidsWithSdp(int);
     method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public byte[] getMetadata(int);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 183e916..96fa9c4 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -313,11 +313,17 @@
   }
 
   public final class PendingIntent implements android.os.Parcelable {
+    method public boolean addCancelListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.PendingIntent.CancelListener);
     method @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public boolean intentFilterEquals(@Nullable android.app.PendingIntent);
     method @NonNull @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int);
+    method public void removeCancelListener(@NonNull android.app.PendingIntent.CancelListener);
     field @Deprecated public static final int FLAG_MUTABLE_UNAUDITED = 33554432; // 0x2000000
   }
 
+  public static interface PendingIntent.CancelListener {
+    method public void onCancelled(@NonNull android.app.PendingIntent);
+  }
+
   public final class PictureInPictureParams implements android.os.Parcelable {
     method public java.util.List<android.app.RemoteAction> getActions();
     method public float getAspectRatio();
diff --git a/core/java/android/annotation/SuppressLint.java b/core/java/android/annotation/SuppressLint.java
new file mode 100644
index 0000000..2d3456b
--- /dev/null
+++ b/core/java/android/annotation/SuppressLint.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Indicates that Lint should ignore the specified warnings for the annotated element. */
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+@Retention(RetentionPolicy.CLASS)
+public @interface SuppressLint {
+    /**
+     * The set of warnings (identified by the lint issue id) that should be
+     * ignored by lint. It is not an error to specify an unrecognized name.
+     */
+    String[] value();
+}
diff --git a/core/java/android/annotation/TargetApi.java b/core/java/android/annotation/TargetApi.java
new file mode 100644
index 0000000..975318e
--- /dev/null
+++ b/core/java/android/annotation/TargetApi.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2012 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Indicates that Lint should treat this type as targeting a given API level, no matter what the
+    project target is. */
+@Target({TYPE, METHOD, CONSTRUCTOR, FIELD})
+@Retention(RetentionPolicy.CLASS)
+public @interface TargetApi {
+    /**
+     * This sets the target api level for the type..
+     */
+    int value();
+}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 6b738ff..47e4810 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -250,7 +250,14 @@
             in String[] resolvedTypes, int flags, in Bundle options, int userId);
     void cancelIntentSender(in IIntentSender sender);
     ActivityManager.PendingIntentInfo getInfoForIntentSender(in IIntentSender sender);
-    void registerIntentSenderCancelListener(in IIntentSender sender, in IResultReceiver receiver);
+    /**
+      This method used to be called registerIntentSenderCancelListener(), was void, and
+      would call `receiver` if the PI has already been canceled.
+      Now it returns false if the PI is cancelled, without calling `receiver`.
+      The method was renamed to catch calls to the original method.
+     */
+    boolean registerIntentSenderCancelListenerEx(in IIntentSender sender,
+        in IResultReceiver receiver);
     void unregisterIntentSenderCancelListener(in IIntentSender sender, in IResultReceiver receiver);
     void enterSafeMode();
     void noteWakeupAlarm(in IIntentSender sender, in WorkSource workSource, int sourceUid,
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 0136a35..0863500 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -53,8 +53,10 @@
 import android.os.UserHandle;
 import android.util.AndroidException;
 import android.util.ArraySet;
+import android.util.Pair;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
 
 import java.lang.annotation.Retention;
@@ -62,6 +64,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * A description of an Intent and target action to perform with it.  Instances
@@ -127,7 +130,27 @@
     private final IIntentSender mTarget;
     private IResultReceiver mCancelReceiver;
     private IBinder mWhitelistToken;
-    private ArraySet<CancelListener> mCancelListeners;
+
+    /**
+     * To protect {@link #mCancelListeners}. We could stop lazy-initialization and synchronize
+     * on {@link #mCancelListeners} directly, and that wouldn't increase allocations
+     * (an empty ArraySet won't causew extra allocations), but
+     * because an empty ArraySet is slightly larger than an Object, and because
+     * {@link #addCancelListener} is rarely used, having a separate lock object would probably
+     * be a net win.
+     */
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private ArraySet<Pair<Executor, CancelListener>> mCancelListeners;
+
+    /**
+     * Whether the PI is canceld or not. Note this is essentially a "cache" that's updated
+     * only when the client uses {@link #addCancelListener}. Even if this is fase, that
+     * still doesn't know the PI is *not* cancled, but if it's true, this PI is definitely canceled.
+     */
+    @GuardedBy("mLock")
+    private boolean mCanceled;
 
     // cached pending intent information
     private @Nullable PendingIntentInfo mCachedInfo;
@@ -1048,19 +1071,38 @@
     }
 
     /**
-     * Register a listener to when this pendingIntent is cancelled. There are no guarantees on which
-     * thread a listener will be called and it's up to the caller to synchronize. This may
-     * trigger a synchronous binder call so should therefore usually be called on a background
-     * thread.
+     * @hide
+     * @deprecated use {@link #addCancelListener(Executor, CancelListener)} instead.
+     */
+    @Deprecated
+    public void registerCancelListener(@NonNull CancelListener cancelListener) {
+        if (!addCancelListener(Runnable::run, cancelListener)) {
+            // Call the callback right away synchronously, if the PI has been canceled already.
+            cancelListener.onCancelled(this);
+        }
+    }
+
+    /**
+     * Register a listener to when this pendingIntent is cancelled.
+     *
+     * @return true if the listener has been set successfully. false if the {@link PendingIntent}
+     * has already been canceled.
      *
      * @hide
      */
-    public void registerCancelListener(CancelListener cancelListener) {
-        synchronized (this) {
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
+    public boolean addCancelListener(@NonNull Executor executor,
+            @NonNull CancelListener cancelListener) {
+        synchronized (mLock) {
+            if (mCanceled) {
+                return false;
+            }
+
             if (mCancelReceiver == null) {
                 mCancelReceiver = new IResultReceiver.Stub() {
                     @Override
-                    public void send(int resultCode, Bundle resultData) throws RemoteException {
+                    public void send(int resultCode, Bundle resultData) {
                         notifyCancelListeners();
                     }
                 };
@@ -1069,42 +1111,69 @@
                 mCancelListeners = new ArraySet<>();
             }
             boolean wasEmpty = mCancelListeners.isEmpty();
-            mCancelListeners.add(cancelListener);
+            mCancelListeners.add(Pair.create(executor, cancelListener));
             if (wasEmpty) {
+                boolean success;
                 try {
-                    ActivityManager.getService().registerIntentSenderCancelListener(mTarget,
-                            mCancelReceiver);
+                    success = ActivityManager.getService().registerIntentSenderCancelListenerEx(
+                            mTarget, mCancelReceiver);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
+                if (!success) {
+                    mCanceled = true;
+                }
+                return success;
+            } else {
+                return !mCanceled;
             }
         }
     }
 
     private void notifyCancelListeners() {
-        ArraySet<CancelListener> cancelListeners;
-        synchronized (this) {
+        ArraySet<Pair<Executor, CancelListener>> cancelListeners;
+        synchronized (mLock) {
+            if (mCancelListeners == null || mCancelListeners.size() == 0) {
+                return;
+            }
+            mCanceled = true;
             cancelListeners = new ArraySet<>(mCancelListeners);
+            mCancelListeners.clear();
         }
         int size = cancelListeners.size();
         for (int i = 0; i < size; i++) {
-            cancelListeners.valueAt(i).onCancelled(this);
+            final Pair<Executor, CancelListener> pair = cancelListeners.valueAt(i);
+            pair.first.execute(() -> pair.second.onCancelled(this));
         }
     }
 
     /**
+     * @hide
+     * @deprecated use {@link #removeCancelListener(CancelListener)} instead.
+     */
+    @Deprecated
+    public void unregisterCancelListener(CancelListener cancelListener) {
+        removeCancelListener(cancelListener);
+    }
+
+    /**
      * Un-register a listener to when this pendingIntent is cancelled.
      *
      * @hide
      */
-    public void unregisterCancelListener(CancelListener cancelListener) {
-        synchronized (this) {
-            if (mCancelListeners == null) {
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
+    public void removeCancelListener(@NonNull CancelListener cancelListener) {
+        synchronized (mLock) {
+            if (mCancelListeners.size() == 0) {
                 return;
             }
-            boolean wasEmpty = mCancelListeners.isEmpty();
-            mCancelListeners.remove(cancelListener);
-            if (mCancelListeners.isEmpty() && !wasEmpty) {
+            for (int i = mCancelListeners.size() - 1; i >= 0; i--) {
+                if (mCancelListeners.valueAt(i).second == cancelListener) {
+                    mCancelListeners.removeAt(i);
+                }
+            }
+            if (mCancelListeners.isEmpty()) {
                 try {
                     ActivityManager.getService().unregisterIntentSenderCancelListener(mTarget,
                             mCancelReceiver);
@@ -1401,13 +1470,15 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
     public interface CancelListener {
         /**
          * Called when a Pending Intent is cancelled.
          *
          * @param intent The intent that was cancelled.
          */
-        void onCancelled(PendingIntent intent);
+        void onCancelled(@NonNull PendingIntent intent);
     }
 
     private PendingIntentInfo getCachedInfo() {
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index eee981d..97b4a53 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1448,7 +1448,7 @@
      * @throws IllegalArgumentException if an invalid transport was specified
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     @RequiresLegacyBluetoothAdminPermission
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
diff --git a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java
new file mode 100644
index 0000000..603b06d
--- /dev/null
+++ b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 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.hardware.biometrics;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Common constants for biometric overlays.
+ * @hide
+ */
+public interface BiometricOverlayConstants {
+    /** Unknown usage. */
+    int REASON_UNKNOWN = 0;
+    /** User is about to enroll. */
+    int REASON_ENROLL_FIND_SENSOR = 1;
+    /** User is enrolling. */
+    int REASON_ENROLL_ENROLLING = 2;
+    /** Usage from BiometricPrompt. */
+    int REASON_AUTH_BP = 3;
+    /** Usage from Keyguard. */
+    int REASON_AUTH_KEYGUARD = 4;
+    /** Non-specific usage (from FingerprintManager). */
+    int REASON_AUTH_OTHER = 5;
+
+    @IntDef({REASON_UNKNOWN,
+            REASON_ENROLL_FIND_SENSOR,
+            REASON_ENROLL_ENROLLING,
+            REASON_AUTH_BP,
+            REASON_AUTH_KEYGUARD,
+            REASON_AUTH_OTHER})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ShowReason {}
+}
diff --git a/core/java/android/hardware/biometrics/SensorLocationInternal.aidl b/core/java/android/hardware/biometrics/SensorLocationInternal.aidl
new file mode 100644
index 0000000..0981904
--- /dev/null
+++ b/core/java/android/hardware/biometrics/SensorLocationInternal.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 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.hardware.biometrics;
+
+// @hide
+parcelable SensorLocationInternal;
diff --git a/core/java/android/hardware/biometrics/SensorLocationInternal.java b/core/java/android/hardware/biometrics/SensorLocationInternal.java
new file mode 100644
index 0000000..fb25a2f
--- /dev/null
+++ b/core/java/android/hardware/biometrics/SensorLocationInternal.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 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.hardware.biometrics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The location of a sensor relative to a physical display.
+ *
+ * Note that the location may change depending on other attributes of the device, such as
+ * fold status, which are not yet included in this class.
+ * @hide
+ */
+public class SensorLocationInternal implements Parcelable {
+
+    /** Default value to use when the sensor's location is unknown or undefined. */
+    public static final SensorLocationInternal DEFAULT = new SensorLocationInternal("", 0, 0, 0);
+
+    /**
+     * The stable display id.
+     */
+    @NonNull
+    public final String displayId;
+
+    /**
+     * The location of the center of the sensor if applicable. For example, sensors of type
+     * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the
+     * distance in pixels, measured from the left edge of the screen.
+     */
+    public final int sensorLocationX;
+
+    /**
+     * The location of the center of the sensor if applicable. For example, sensors of type
+     * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the
+     * distance in pixels, measured from the top edge of the screen.
+     *
+     */
+    public final int sensorLocationY;
+
+    /**
+     * The radius of the sensor if applicable. For example, sensors of type
+     * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the radius
+     * of the sensor, in pixels.
+     */
+    public final int sensorRadius;
+
+    public SensorLocationInternal(@Nullable String displayId,
+            int sensorLocationX, int sensorLocationY, int sensorRadius) {
+        this.displayId = displayId != null ? displayId : "";
+        this.sensorLocationX = sensorLocationX;
+        this.sensorLocationY = sensorLocationY;
+        this.sensorRadius = sensorRadius;
+    }
+
+    protected SensorLocationInternal(Parcel in) {
+        displayId = in.readString16NoHelper();
+        sensorLocationX = in.readInt();
+        sensorLocationY = in.readInt();
+        sensorRadius = in.readInt();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(displayId);
+        dest.writeInt(sensorLocationX);
+        dest.writeInt(sensorLocationY);
+        dest.writeInt(sensorRadius);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<SensorLocationInternal> CREATOR =
+            new Creator<SensorLocationInternal>() {
+        @Override
+        public SensorLocationInternal createFromParcel(Parcel in) {
+            return new SensorLocationInternal(in);
+        }
+
+        @Override
+        public SensorLocationInternal[] newArray(int size) {
+            return new SensorLocationInternal[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        return "[id: " + displayId
+                + ", x: " + sensorLocationX
+                + ", y: " + sensorLocationY
+                + ", r: " + sensorRadius + "]";
+    }
+}
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
index 45c6b29..4bf9a74 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
@@ -21,7 +21,9 @@
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.hardware.biometrics.ComponentInfoInternal;
+import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.os.Parcel;
@@ -38,34 +40,14 @@
      */
     public final @FingerprintSensorProperties.SensorType int sensorType;
 
-    /**
-     * The location of the center of the sensor if applicable. For example, sensors of type
-     * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the
-     * distance in pixels, measured from the left edge of the screen.
-     */
-    public final int sensorLocationX;
-
-    /**
-     * The location of the center of the sensor if applicable. For example, sensors of type
-     * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the
-     * distance in pixels, measured from the top edge of the screen.
-     *
-     */
-    public final int sensorLocationY;
-
-    /**
-     * The radius of the sensor if applicable. For example, sensors of type
-     * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the radius
-     * of the sensor, in pixels.
-     */
-    public final int sensorRadius;
+    private final List<SensorLocationInternal> mSensorLocations;
 
     public FingerprintSensorPropertiesInternal(int sensorId,
             @SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
             @NonNull List<ComponentInfoInternal> componentInfo,
             @FingerprintSensorProperties.SensorType int sensorType,
-            boolean resetLockoutRequiresHardwareAuthToken, int sensorLocationX, int sensorLocationY,
-            int sensorRadius) {
+            boolean resetLockoutRequiresHardwareAuthToken,
+            @NonNull List<SensorLocationInternal> sensorLocations) {
         // IBiometricsFingerprint@2.1 handles lockout in the framework, so the challenge is not
         // required as it can only be generated/attested/verified by TEE components.
         // IFingerprint@1.0 handles lockout below the HAL, but does not require a challenge. See
@@ -73,9 +55,7 @@
         super(sensorId, strength, maxEnrollmentsPerUser, componentInfo,
             resetLockoutRequiresHardwareAuthToken, false /* resetLockoutRequiresChallenge */);
         this.sensorType = sensorType;
-        this.sensorLocationX = sensorLocationX;
-        this.sensorLocationY = sensorLocationY;
-        this.sensorRadius = sensorRadius;
+        this.mSensorLocations = List.copyOf(sensorLocations);
     }
 
     /**
@@ -88,16 +68,15 @@
             boolean resetLockoutRequiresHardwareAuthToken) {
         // TODO(b/179175438): Value should be provided from the HAL
         this(sensorId, strength, maxEnrollmentsPerUser, componentInfo, sensorType,
-                resetLockoutRequiresHardwareAuthToken, 540 /* sensorLocationX */,
-                1636 /* sensorLocationY */, 130 /* sensorRadius */);
+                resetLockoutRequiresHardwareAuthToken, List.of(new SensorLocationInternal(
+                        "" /* displayId */,  540 /* sensorLocationX */, 1636 /* sensorLocationY */,
+                        130 /* sensorRadius */)));
     }
 
     protected FingerprintSensorPropertiesInternal(Parcel in) {
         super(in);
         sensorType = in.readInt();
-        sensorLocationX = in.readInt();
-        sensorLocationY = in.readInt();
-        sensorRadius = in.readInt();
+        mSensorLocations = in.createTypedArrayList(SensorLocationInternal.CREATOR);
     }
 
     public static final Creator<FingerprintSensorPropertiesInternal> CREATOR =
@@ -122,9 +101,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeInt(sensorType);
-        dest.writeInt(sensorLocationX);
-        dest.writeInt(sensorLocationY);
-        dest.writeInt(sensorRadius);
+        dest.writeTypedList(mSensorLocations);
     }
 
     public boolean isAnyUdfpsType() {
@@ -150,6 +127,44 @@
         }
     }
 
+    /**
+     * Get the default location.
+     *
+     * Use this method when the sensor's relationship to the displays on the device do not
+     * matter.
+     * @return
+     */
+    @NonNull
+    public SensorLocationInternal getLocation() {
+        final SensorLocationInternal location = getLocation("" /* displayId */);
+        return location != null ? location : SensorLocationInternal.DEFAULT;
+    }
+
+    /**
+     * Get the location of a sensor relative to a physical display layout.
+     *
+     * @param displayId stable display id
+     * @return location or null if none is specified
+     */
+    @Nullable
+    public SensorLocationInternal getLocation(String displayId) {
+        for (SensorLocationInternal location : mSensorLocations) {
+            if (location.displayId.equals(displayId)) {
+                return location;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets all locations relative to all supported display layouts.
+     * @return supported locations
+     */
+    @NonNull
+    public List<SensorLocationInternal> getAllLocations() {
+        return mSensorLocations;
+    }
+
     @Override
     public String toString() {
         return "ID: " + sensorId + ", Strength: " + sensorStrength + ", Type: " + sensorType;
diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
index f18360ff..938431d 100644
--- a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
+++ b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
@@ -22,13 +22,6 @@
  * @hide
  */
 oneway interface IUdfpsOverlayController {
-    const int REASON_UNKNOWN = 0;
-    const int REASON_ENROLL_FIND_SENSOR = 1;
-    const int REASON_ENROLL_ENROLLING = 2;
-    const int REASON_AUTH_BP = 3; // BiometricPrompt
-    const int REASON_AUTH_FPM_KEYGUARD = 4; // FingerprintManager usage from Keyguard
-    const int REASON_AUTH_FPM_OTHER = 5; // Other FingerprintManager usage
-
     // Shows the overlay.
     void showUdfpsOverlay(int sensorId, int reason, IUdfpsOverlayControllerCallback callback);
 
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 68917a8..08f75df 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -47,7 +47,6 @@
 import android.util.BackupUtils;
 import android.util.Log;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.net.module.util.NetworkIdentityUtils;
 
@@ -151,24 +150,6 @@
         }
     }
 
-    private static boolean sForceAllNetworkTypes = false;
-
-    /**
-     * Results in matching against all mobile network types.
-     *
-     * <p>See {@link #matchesMobile} and {@link matchesMobileWildcard}.
-     */
-    @VisibleForTesting
-    public static void forceAllNetworkTypes() {
-        sForceAllNetworkTypes = true;
-    }
-
-    /** Resets the affect of {@link #forceAllNetworkTypes}. */
-    @VisibleForTesting
-    public static void resetForceAllNetworkTypes() {
-        sForceAllNetworkTypes = false;
-    }
-
     /**
      * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
      * the given IMSI.
@@ -611,7 +592,7 @@
             // Only metered mobile network would be matched regardless of metered filter.
             // This is used to exclude non-metered APNs, e.g. IMS. See ag/908650.
             // TODO: Respect metered filter and remove mMetered condition.
-            return (sForceAllNetworkTypes || (ident.mType == TYPE_MOBILE && ident.mMetered))
+            return (ident.mType == TYPE_MOBILE && ident.mMetered)
                     && !ArrayUtils.isEmpty(mMatchSubscriberIds)
                     && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId)
                     && matchesCollapsedRatType(ident);
@@ -726,7 +707,7 @@
         if (ident.mType == TYPE_WIMAX) {
             return true;
         } else {
-            return (sForceAllNetworkTypes || (ident.mType == TYPE_MOBILE && ident.mMetered))
+            return (ident.mType == TYPE_MOBILE && ident.mMetered)
                     && matchesCollapsedRatType(ident);
         }
     }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 719dc53..7c4de82 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -8578,7 +8578,7 @@
          * inactive so can be dropped.
          */
         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-        public boolean reset(long uptimeUs, long realtimeUs) {
+        public boolean reset(long uptimeUs, long realtimeUs, int resetReason) {
             boolean active = false;
 
             mOnBatteryBackgroundTimeBase.init(uptimeUs, realtimeUs);
@@ -8648,7 +8648,11 @@
             resetIfNotNull(mBluetoothControllerActivity, false, realtimeUs);
             resetIfNotNull(mModemControllerActivity, false, realtimeUs);
 
-            MeasuredEnergyStats.resetIfNotNull(mUidMeasuredEnergyStats);
+            if (resetReason == RESET_REASON_MEASURED_ENERGY_BUCKETS_CHANGE) {
+                mUidMeasuredEnergyStats = null;
+            } else {
+                MeasuredEnergyStats.resetIfNotNull(mUidMeasuredEnergyStats);
+            }
 
             resetIfNotNull(mUserCpuTime, false, realtimeUs);
             resetIfNotNull(mSystemCpuTime, false, realtimeUs);
@@ -11332,7 +11336,7 @@
         mNumConnectivityChange = 0;
 
         for (int i=0; i<mUidStats.size(); i++) {
-            if (mUidStats.valueAt(i).reset(uptimeUs, elapsedRealtimeUs)) {
+            if (mUidStats.valueAt(i).reset(uptimeUs, elapsedRealtimeUs, resetReason)) {
                 mUidStats.valueAt(i).detachFromTimeBase();
                 mUidStats.remove(mUidStats.keyAt(i));
                 i--;
diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
index 9b51a8e..bb307a0 100644
--- a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
+++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
@@ -21,13 +21,18 @@
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
+import android.util.Slog;
 import android.util.SparseArray;
 
+import java.util.Arrays;
+
 /**
  * Calculates the amount of power consumed by custom energy consumers (i.e. consumers of type
  * {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
  */
 public class CustomMeasuredPowerCalculator extends PowerCalculator {
+    private static final String TAG = "CustomMeasuredPowerCalc";
+
     public CustomMeasuredPowerCalculator(PowerProfile powerProfile) {
     }
 
@@ -76,9 +81,9 @@
             if (totalPowerMah == null) {
                 newTotalPowerMah = new double[customMeasuredPowerMah.length];
             } else if (totalPowerMah.length != customMeasuredPowerMah.length) {
-                newTotalPowerMah = new double[customMeasuredPowerMah.length];
-                System.arraycopy(totalPowerMah, 0, newTotalPowerMah, 0,
-                        customMeasuredPowerMah.length);
+                Slog.wtf(TAG, "Number of custom energy components is not the same for all apps: "
+                        + totalPowerMah.length + ", " + customMeasuredPowerMah.length);
+                newTotalPowerMah = Arrays.copyOf(totalPowerMah, customMeasuredPowerMah.length);
             } else {
                 newTotalPowerMah = totalPowerMah;
             }
diff --git a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
index b321ac0..a09c823 100644
--- a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
+++ b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
@@ -124,7 +124,6 @@
                         Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
                         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                         try {
-                            sendCloseSystemWindows();
                             mContext.startActivity(intent);
                         } catch (ActivityNotFoundException e) {
                             startCallActivity();
@@ -147,7 +146,6 @@
                     dispatcher.performedLongPress(event);
                     if (isUserSetupComplete()) {
                         mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-                        sendCloseSystemWindows();
                         // Broadcast an intent that the Camera button was longpressed
                         Intent intent = new Intent(Intent.ACTION_CAMERA_BUTTON, null);
                         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -178,7 +176,6 @@
                             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                             try {
                                 mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-                                sendCloseSystemWindows();
                                 getSearchManager().stopSearch();
                                 mContext.startActivity(intent);
                                 // Only clear this if we successfully start the
@@ -272,7 +269,6 @@
 
     @UnsupportedAppUsage
     void startCallActivity() {
-        sendCloseSystemWindows();
         Intent intent = new Intent(Intent.ACTION_CALL_BUTTON);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         try {
@@ -319,10 +315,6 @@
         return mMediaSessionManager;
     }
 
-    void sendCloseSystemWindows() {
-        PhoneWindow.sendCloseSystemWindows(mContext, null);
-    }
-
     private void handleVolumeKeyEvent(KeyEvent keyEvent) {
         getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(keyEvent,
                 AudioManager.USE_DEFAULT_STREAM_TYPE);
diff --git a/core/java/com/android/internal/util/BinaryXmlSerializer.java b/core/java/com/android/internal/util/BinaryXmlSerializer.java
index 9df4bdb..f0ca1edb 100644
--- a/core/java/com/android/internal/util/BinaryXmlSerializer.java
+++ b/core/java/com/android/internal/util/BinaryXmlSerializer.java
@@ -124,7 +124,7 @@
             throw new UnsupportedOperationException();
         }
 
-        mOut = new FastDataOutput(os, BUFFER_SIZE);
+        mOut = FastDataOutput.obtain(os);
         mOut.write(PROTOCOL_MAGIC_VERSION_0);
 
         mTagCount = 0;
@@ -138,7 +138,9 @@
 
     @Override
     public void flush() throws IOException {
-        mOut.flush();
+        if (mOut != null) {
+            mOut.flush();
+        }
     }
 
     @Override
@@ -157,6 +159,9 @@
     public void endDocument() throws IOException {
         mOut.writeByte(END_DOCUMENT | TYPE_NULL);
         flush();
+
+        mOut.release();
+        mOut = null;
     }
 
     @Override
diff --git a/core/java/com/android/internal/util/FastDataOutput.java b/core/java/com/android/internal/util/FastDataOutput.java
index cf5b296..bc8496b 100644
--- a/core/java/com/android/internal/util/FastDataOutput.java
+++ b/core/java/com/android/internal/util/FastDataOutput.java
@@ -30,6 +30,7 @@
 import java.io.OutputStream;
 import java.util.HashMap;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Optimized implementation of {@link DataOutput} which buffers data in memory
@@ -41,23 +42,26 @@
 public class FastDataOutput implements DataOutput, Flushable, Closeable {
     private static final int MAX_UNSIGNED_SHORT = 65_535;
 
+    private static final int BUFFER_SIZE = 32_768;
+
+    private static AtomicReference<FastDataOutput> sOutCache = new AtomicReference<>();
+
     private final VMRuntime mRuntime;
-    private final OutputStream mOut;
 
     private final byte[] mBuffer;
     private final long mBufferPtr;
     private final int mBufferCap;
 
+    private OutputStream mOut;
     private int mBufferPos;
 
     /**
      * Values that have been "interned" by {@link #writeInternedUTF(String)}.
      */
-    private HashMap<String, Short> mStringRefs = new HashMap<>();
+    private final HashMap<String, Short> mStringRefs = new HashMap<>();
 
     public FastDataOutput(@NonNull OutputStream out, int bufferSize) {
         mRuntime = VMRuntime.getRuntime();
-        mOut = Objects.requireNonNull(out);
         if (bufferSize < 8) {
             throw new IllegalArgumentException();
         }
@@ -65,6 +69,48 @@
         mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize);
         mBufferPtr = mRuntime.addressOf(mBuffer);
         mBufferCap = mBuffer.length;
+
+        setOutput(out);
+    }
+
+    /**
+     * Create a new FastDataOutput object or retrieve one from cache.
+     */
+    public static FastDataOutput obtain(@NonNull OutputStream out) {
+        FastDataOutput instance = sOutCache.getAndSet(null);
+        if (instance != null) {
+            instance.setOutput(out);
+            return instance;
+        }
+        return new FastDataOutput(out, BUFFER_SIZE);
+    }
+
+    /**
+     * Put a FastDataOutput object back into the cache.
+     * You must not touch the object after this call.
+     */
+    public void release() {
+        if (mBufferPos > 0) {
+            throw new IllegalStateException("Lingering data, call flush() before releasing.");
+        }
+
+        mOut = null;
+        mBufferPos = 0;
+        mStringRefs.clear();
+
+        if (mBufferCap == BUFFER_SIZE) {
+            // Try to return to the cache.
+            sOutCache.compareAndSet(null, this);
+        }
+    }
+
+    /**
+     * Re-initializes the object for the new output.
+     */
+    private void setOutput(@NonNull OutputStream out) {
+        mOut = Objects.requireNonNull(out);
+        mBufferPos = 0;
+        mStringRefs.clear();
     }
 
     private void drain() throws IOException {
@@ -83,6 +129,7 @@
     @Override
     public void close() throws IOException {
         mOut.close();
+        release();
     }
 
     @Override
diff --git a/core/res/res/drawable-nodpi/default_wallpaper.png b/core/res/res/drawable-nodpi/default_wallpaper.png
index ce546f0..490ebee 100644
--- a/core/res/res/drawable-nodpi/default_wallpaper.png
+++ b/core/res/res/drawable-nodpi/default_wallpaper.png
Binary files differ
diff --git a/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png b/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png
deleted file mode 100644
index af8e251..0000000
--- a/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png b/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png
deleted file mode 100644
index cb00d82..0000000
--- a/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 265426c2..790a2a3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4570,6 +4570,20 @@
       -->
     </integer-array>
 
+    <!-- An array of arrays of side fingerprint sensor properties relative to each display.
+         Note: this value is temporary and is expected to be queried directly
+         from the HAL in the future. -->
+    <array name="config_sfps_sensor_props" translatable="false">
+        <!--
+            <array>
+                <item>displayId</item>
+                <item>sensorLocationX</item>
+                <item>sensorLocationY</item>
+                <item>sensorRadius</item>
+            <array>
+        -->
+    </array>
+
     <!-- How long it takes for the HW to start illuminating after the illumination is requested. -->
     <integer name="config_udfps_illumination_transition_ms">50</integer>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 58e9cda..b0c0a15 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2611,6 +2611,7 @@
   <java-symbol type="array" name="config_biometric_sensors" />
   <java-symbol type="bool" name="allow_test_udfps" />
   <java-symbol type="array" name="config_udfps_sensor_props" />
+  <java-symbol type="array" name="config_sfps_sensor_props" />
   <java-symbol type="integer" name="config_udfps_illumination_transition_ms" />
   <java-symbol type="bool" name="config_is_powerbutton_fps" />
 
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
index f833981..0e394c1 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
@@ -403,7 +403,7 @@
         assertNotNull(sensor.getSensorBackgroundTime());
 
         // Reset the stats. Since the sensor is still running, we should still see the timer
-        bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+        bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
 
         sensor = uid.getSensorStats().get(SENSOR_ID);
         assertNotNull(sensor);
@@ -413,7 +413,7 @@
         bi.noteStopSensorLocked(UID, SENSOR_ID);
 
         // Now the sensor timer has stopped so this reset should also take out the sensor.
-        bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+        bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
 
         sensor = uid.getSensorStats().get(SENSOR_ID);
         assertNull(sensor);
@@ -465,7 +465,7 @@
 
         // Reset the stats. Since the sensor is still running, we should still see the timer
         // but still with 0 times.
-        bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+        bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
         assertEquals(0, timer.getTotalTimeLocked(1000*clocks.realtime, which));
         assertEquals(0, timer.getTotalDurationMsLocked(clocks.realtime));
         assertEquals(0, bgTimer.getTotalTimeLocked(1000*clocks.realtime, which));
@@ -504,7 +504,7 @@
 
         // Reset the stats. Since the sensor is still running, we should still see the timer
         // but with 0 times.
-        bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+        bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
         assertEquals(0, timer.getTotalTimeLocked(1000*clocks.realtime, which));
         assertEquals(0, timer.getTotalDurationMsLocked(clocks.realtime));
         assertEquals(0, bgTimer.getTotalTimeLocked(1000*clocks.realtime, which));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
index 3800b8d..c2cb72a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
@@ -43,6 +43,7 @@
 import com.android.wm.shell.common.SurfaceUtils;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitWindowManager;
 
 import java.io.PrintWriter;
 
@@ -70,6 +71,19 @@
     private final DisplayImeController mDisplayImeController;
     private SplitLayout mSplitLayout;
 
+    private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
+            new SplitWindowManager.ParentContainerCallbacks() {
+        @Override
+        public void attachToParentSurface(SurfaceControl.Builder b) {
+            b.setParent(mRootTaskLeash);
+        }
+
+        @Override
+        public void onLeashReady(SurfaceControl leash) {
+            mSyncQueue.runInSync(t -> t.show(leash));
+        }
+    };
+
     AppPair(AppPairsController controller) {
         mController = controller;
         mSyncQueue = controller.getSyncTransactionQueue();
@@ -110,8 +124,7 @@
         mSplitLayout = new SplitLayout(TAG + "SplitDivider",
                 mDisplayController.getDisplayContext(mRootTaskInfo.displayId),
                 mRootTaskInfo.configuration, this /* layoutChangeListener */,
-                b -> b.setParent(mRootTaskLeash), mDisplayImeController,
-                mController.getTaskOrganizer());
+                mParentContainerCallbacks, mDisplayImeController, mController.getTaskOrganizer());
 
         final WindowContainerToken token1 = task1.token;
         final WindowContainerToken token2 = task2.token;
@@ -218,8 +231,6 @@
                 if (mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) {
                     onLayoutChanged(mSplitLayout);
                 }
-                // updateConfiguration re-inits the dividerbar, so show it now
-                mSyncQueue.runInSync(t -> t.show(mSplitLayout.getDividerLeash()));
             }
         } else if (taskInfo.taskId == getTaskId1()) {
             mTaskInfo1 = taskInfo;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 754b8da..27c8d7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -125,8 +125,8 @@
         mRotation = configuration.windowConfiguration.getRotation();
         mSplitLayoutHandler = splitLayoutHandler;
         mDisplayImeController = displayImeController;
-        mSplitWindowManager = new SplitWindowManager(
-                windowName, mContext, configuration, parentContainerCallbacks);
+        mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration,
+                parentContainerCallbacks);
         mTaskOrganizer = taskOrganizer;
         mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
         mDismissingParallaxPolicy = new DismissingParallaxPolicy();
@@ -181,22 +181,19 @@
     public boolean updateConfiguration(Configuration configuration) {
         boolean affectsLayout = false;
 
-        // Make sure to render the divider bar with proper resources that matching the screen
-        // orientation.
-        final int orientation = configuration.orientation;
-        if (orientation != mOrientation) {
-            mOrientation = orientation;
-            mContext = mContext.createConfigurationContext(configuration);
-            mSplitWindowManager.setConfiguration(configuration);
-            affectsLayout = true;
-        }
-
         // Update the split bounds when necessary. Besides root bounds changed, split bounds need to
         // be updated when the rotation changed to cover the case that users rotated the screen 180
         // degrees.
+        // Make sure to render the divider bar with proper resources that matching the screen
+        // orientation.
         final int rotation = configuration.windowConfiguration.getRotation();
         final Rect rootBounds = configuration.windowConfiguration.getBounds();
-        if (rotation != mRotation || !mRootBounds.equals(rootBounds)) {
+        final int orientation = configuration.orientation;
+        if (rotation != mRotation || !mRootBounds.equals(rootBounds)
+                || orientation != mOrientation) {
+            mContext = mContext.createConfigurationContext(configuration);
+            mSplitWindowManager.setConfiguration(configuration);
+            mOrientation = orientation;
             mTempRect.set(mRootBounds);
             mRootBounds.set(rootBounds);
             mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index fc7edfc..47dceb3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -64,6 +64,7 @@
 
     public interface ParentContainerCallbacks {
         void attachToParentSurface(SurfaceControl.Builder b);
+        void onLeashReady(SurfaceControl leash);
     }
 
     public SplitWindowManager(String windowName, Context context, Configuration config,
@@ -100,6 +101,7 @@
                 .setCallsite("SplitWindowManager#attachToParentSurface");
         mParentContainerCallbacks.attachToParentSurface(builder);
         mLeash = builder.build();
+        mParentContainerCallbacks.onLeashReady(mLeash);
         b.setParent(mLeash);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index d9708f0..493870d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -18,8 +18,6 @@
 
 import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -90,6 +88,7 @@
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+import com.android.wm.shell.common.split.SplitWindowManager;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.transition.Transitions;
 
@@ -163,6 +162,19 @@
         mDismissTop = NO_DISMISS;
     };
 
+    private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
+            new SplitWindowManager.ParentContainerCallbacks() {
+        @Override
+        public void attachToParentSurface(SurfaceControl.Builder b) {
+            mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b);
+        }
+
+        @Override
+        public void onLeashReady(SurfaceControl leash) {
+            mSyncQueue.runInSync(t -> applyDividerVisibility(t));
+        }
+    };
+
     StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
             DisplayImeController displayImeController,
@@ -501,7 +513,8 @@
         mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
         mMainStage.deactivate(wct, childrenToTop == mMainStage);
         mTaskOrganizer.applyTransaction(wct);
-        // Reset divider position.
+        // Hide divider and reset its position.
+        setDividerVisibility(false);
         mSplitLayout.resetDividerPosition();
         mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
         if (childrenToTop != null) {
@@ -635,10 +648,16 @@
     private void onStageVisibilityChanged(StageListenerImpl stageListener) {
         final boolean sideStageVisible = mSideStageListener.mVisible;
         final boolean mainStageVisible = mMainStageListener.mVisible;
-        // Divider is only visible if both the main stage and side stages are visible
-        setDividerVisibility(isSplitScreenVisible());
+        final boolean bothStageVisible = sideStageVisible && mainStageVisible;
+        final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible;
+        final boolean sameVisibility = sideStageVisible == mainStageVisible;
+        // Only add or remove divider when both visible or both invisible to avoid sometimes we only
+        // got one stage visibility changed for a moment and it will cause flicker.
+        if (sameVisibility) {
+            setDividerVisibility(bothStageVisible);
+        }
 
-        if (!mainStageVisible && !sideStageVisible) {
+        if (bothStageInvisible) {
             if (mExitSplitScreenOnHide
             // Don't dismiss staged split when both stages are not visible due to sleeping display,
             // like the cases keyguard showing or screen off.
@@ -655,59 +674,32 @@
             exitSplitScreen(toTop, SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP);
         }
 
-        // When both stage's visibility changed to visible, main stage might receives visibility
-        // changed before side stage if it has higher z-order than side stage. Make sure we only
-        // update main stage's windowing mode with the visibility changed of side stage to prevent
-        // stacking multiple windowing mode transactions which result to flicker issue.
-        if (mainStageVisible && stageListener == mSideStageListener) {
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
-            if (sideStageVisible) {
-                // The main stage configuration should to follow split layout when side stage is
-                // visible.
-                mMainStage.updateConfiguration(
-                        WINDOWING_MODE_MULTI_WINDOW, getMainStageBounds(), wct);
-            } else if (!mSideStage.mRootTaskInfo.isSleeping) {
-                // We want the main stage configuration to be fullscreen when the side stage isn't
-                // visible.
-                // We should not do it when side stage are not visible due to sleeping display too.
-                mMainStage.updateConfiguration(WINDOWING_MODE_FULLSCREEN, null, wct);
-            }
-            // TODO: Change to `mSyncQueue.queue(wct)` once BLAST is stable.
-            mTaskOrganizer.applyTransaction(wct);
-        }
-
         mSyncQueue.runInSync(t -> {
             final SurfaceControl sideStageLeash = mSideStage.mRootLeash;
             final SurfaceControl mainStageLeash = mMainStage.mRootLeash;
 
             if (sideStageVisible) {
                 final Rect sideStageBounds = getSideStageBounds();
-                t.show(sideStageLeash)
-                        .setPosition(sideStageLeash,
-                                sideStageBounds.left, sideStageBounds.top)
+                t.setPosition(sideStageLeash,
+                        sideStageBounds.left, sideStageBounds.top)
                         .setWindowCrop(sideStageLeash,
                                 sideStageBounds.width(), sideStageBounds.height());
-            } else {
-                t.hide(sideStageLeash);
             }
 
             if (mainStageVisible) {
                 final Rect mainStageBounds = getMainStageBounds();
-                t.show(mainStageLeash);
-                if (sideStageVisible) {
-                    t.setPosition(mainStageLeash, mainStageBounds.left, mainStageBounds.top)
-                            .setWindowCrop(mainStageLeash,
-                                    mainStageBounds.width(), mainStageBounds.height());
-                } else {
-                    // Clear window crop and position if side stage isn't visible.
-                    t.setPosition(mainStageLeash, 0, 0)
-                            .setWindowCrop(mainStageLeash, null);
-                }
-            } else {
-                t.hide(mainStageLeash);
+                t.setPosition(mainStageLeash, mainStageBounds.left, mainStageBounds.top)
+                        .setWindowCrop(mainStageLeash,
+                                mainStageBounds.width(), mainStageBounds.height());
             }
 
-            applyDividerVisibility(t);
+            // Same above, we only set root tasks and divider leash visibility when both stage
+            // change to visible or invisible to avoid flicker.
+            if (sameVisibility) {
+                t.setVisibility(sideStageLeash, bothStageVisible)
+                        .setVisibility(mainStageLeash, bothStageVisible);
+                applyDividerVisibility(t);
+            }
         });
     }
 
@@ -726,7 +718,6 @@
         } else {
             t.hide(dividerLeash);
         }
-
     }
 
     private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
@@ -852,8 +843,7 @@
         mDisplayAreaInfo = displayAreaInfo;
         if (mSplitLayout == null) {
             mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
-                    mDisplayAreaInfo.configuration, this,
-                    b -> mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b),
+                    mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
                     mDisplayImeController, mTaskOrganizer);
             mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
         }
@@ -871,7 +861,6 @@
                 && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
                 && mMainStage.isActive()) {
             onLayoutChanged(mSplitLayout);
-            mSyncQueue.runInSync(t -> applyDividerVisibility(t));
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 3557906..defa58d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -29,7 +29,6 @@
 
 import android.content.res.Configuration;
 import android.graphics.Rect;
-import android.view.SurfaceControl;
 
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -53,7 +52,7 @@
 @RunWith(AndroidJUnit4.class)
 public class SplitLayoutTests extends ShellTestCase {
     @Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler;
-    @Mock SurfaceControl mRootLeash;
+    @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
     @Mock DisplayImeController mDisplayImeController;
     @Mock ShellTaskOrganizer mTaskOrganizer;
     @Captor ArgumentCaptor<Runnable> mRunnableCaptor;
@@ -67,7 +66,7 @@
                 mContext,
                 getConfiguration(),
                 mSplitLayoutHandler,
-                b -> b.setParent(mRootLeash),
+                mCallbacks,
                 mDisplayImeController,
                 mTaskOrganizer));
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
index c456c7d..9bb54a1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
@@ -23,7 +23,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.view.InsetsState;
-import android.view.SurfaceControl;
 
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -41,8 +40,8 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class SplitWindowManagerTests extends ShellTestCase {
-    @Mock SurfaceControl mSurfaceControl;
     @Mock SplitLayout mSplitLayout;
+    @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
     private SplitWindowManager mSplitWindowManager;
 
     @Before
@@ -51,7 +50,7 @@
         final Configuration configuration = new Configuration();
         configuration.setToDefaults();
         mSplitWindowManager = new SplitWindowManager("TestSplitDivider", mContext, configuration,
-                b -> b.setParent(mSurfaceControl));
+                mCallbacks);
         when(mSplitLayout.getDividerBounds()).thenReturn(
                 new Rect(0, 0, configuration.windowConfiguration.getBounds().width(),
                         configuration.windowConfiguration.getBounds().height()));
diff --git a/media/Android.bp b/media/Android.bp
index 7592c15..b049cb6 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -56,6 +56,10 @@
         "aidl/android/media/audio/common/AudioConfig.aidl",
         "aidl/android/media/audio/common/AudioConfigBase.aidl",
         "aidl/android/media/audio/common/AudioContentType.aidl",
+        "aidl/android/media/audio/common/AudioDevice.aidl",
+        "aidl/android/media/audio/common/AudioDeviceAddress.aidl",
+        "aidl/android/media/audio/common/AudioDeviceDescription.aidl",
+        "aidl/android/media/audio/common/AudioDeviceType.aidl",
         "aidl/android/media/audio/common/AudioEncapsulationMetadataType.aidl",
         "aidl/android/media/audio/common/AudioEncapsulationMode.aidl",
         "aidl/android/media/audio/common/AudioEncapsulationType.aidl",
diff --git a/media/aidl/android/media/audio/common/AudioDevice.aidl b/media/aidl/android/media/audio/common/AudioDevice.aidl
new file mode 100644
index 0000000..f897eb2
--- /dev/null
+++ b/media/aidl/android/media/audio/common/AudioDevice.aidl
@@ -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.
+ */
+
+package android.media.audio.common;
+
+import android.media.audio.common.AudioDeviceAddress;
+import android.media.audio.common.AudioDeviceDescription;
+
+/**
+ * Represents a concrete audio device by bundling together the device type and
+ * the device address.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable AudioDevice {
+    AudioDeviceDescription type;
+    AudioDeviceAddress address;
+}
diff --git a/media/aidl/android/media/audio/common/AudioDeviceAddress.aidl b/media/aidl/android/media/audio/common/AudioDeviceAddress.aidl
new file mode 100644
index 0000000..ca48f7e
--- /dev/null
+++ b/media/aidl/android/media/audio/common/AudioDeviceAddress.aidl
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 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.audio.common;
+
+/**
+ * This structure defines various representations for the audio device
+ * address.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+union AudioDeviceAddress {
+    /**
+     * String uniquely identifying the device among other devices
+     * of the same type. Can be empty in case there is only one device
+     * of this type.
+     *
+     * Depending on the device type, its id may be assigned by the framework
+     * (one case used at the time of writing is REMOTE_SUBMIX), or assigned by
+     * the HAL service (the canonical examples are BUS and MIC devices). In any
+     * case, both framework and HAL must never attempt to parse the value of the
+     * 'id' field, regardless of whom has generated it. If the address must be
+     * parsed, one of the members below must be used instead of 'id'.
+     */
+    @utf8InCpp String id;
+    /**
+     * IEEE 802 MAC address. Set for Bluetooth devices. The array must have
+     * exactly 6 elements.
+     */
+    byte[] mac;
+    /**
+     * IPv4 Address. Set for IPv4 devices. The array must have exactly 4
+     * elements.
+     */
+    byte[] ipv4;
+    /**
+     * IPv6 Address. Set for IPv6 devices. The array must have exactly 8
+     * elements.
+     */
+    int[] ipv6;
+    /**
+     * PCI bus Address. Set for USB devices. The array must have exactly 2
+     * elements, in the following order: the card id, and the device id.
+     */
+    int[] alsa;
+}
diff --git a/media/aidl/android/media/audio/common/AudioDeviceDescription.aidl b/media/aidl/android/media/audio/common/AudioDeviceDescription.aidl
new file mode 100644
index 0000000..4e8a735
--- /dev/null
+++ b/media/aidl/android/media/audio/common/AudioDeviceDescription.aidl
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 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.audio.common;
+
+import android.media.audio.common.AudioDeviceType;
+
+/**
+ * Describes the kind of an audio device.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable AudioDeviceDescription {
+    /**
+     * Type and directionality of the device. For bidirectional audio devices
+     * two descriptions need to be created, having the same value for
+     * the 'connection' field.
+     *
+     * See 'AudioDeviceType' for the list of supported values.
+     */
+    AudioDeviceType type = AudioDeviceType.NONE;
+    /**
+     * Specifies the type of the connection of the device to the audio system.
+     * Usually it's some kind of a communication protocol, e.g. Bluetooth SCO or
+     * USB. There is a list of connection types recognized by the framework,
+     * defined using 'CONNECTION_' constants. Vendors can add their own
+     * connection types with "vx.<vendor>." prefix.
+     *
+     * When the 'connection' field is left empty and 'type != NONE | DEFAULT',
+     * it is assumed that the device is permanently attached to the audio
+     * system, e.g. a built-in speaker or microphone.
+     *
+     * The 'connection' field must be left empty if 'type' is 'NONE' or
+     * '{IN|OUT}_DEFAULT'.
+     */
+    @utf8InCpp String connection;
+    /**
+     * Analog connection, for example, via 3.5 mm analog jack.
+     */
+    const @utf8InCpp String CONNECTION_ANALOG = "analog";
+    /**
+     * Low-End (Analog) Desk Dock.
+     */
+    const @utf8InCpp String CONNECTION_ANALOG_DOCK = "analog-dock";
+    /**
+     * Bluetooth A2DP connection.
+     */
+    const @utf8InCpp String CONNECTION_BT_A2DP = "bt-a2dp";
+    /**
+     * Bluetooth Low Energy (LE) connection.
+     */
+    const @utf8InCpp String CONNECTION_BT_LE = "bt-le";
+    /**
+     * Bluetooth SCO connection.
+     */
+    const @utf8InCpp String CONNECTION_BT_SCO = "bt-sco";
+    /**
+     * Bus connection. Mostly used in automotive scenarios.
+     */
+    const @utf8InCpp String CONNECTION_BUS = "bus";
+    /**
+     * High-End (Digital) Desk Dock.
+     */
+    const @utf8InCpp String CONNECTION_DIGITAL_DOCK = "digital-dock";
+    /**
+     * HDMI connection.
+     */
+    const @utf8InCpp String CONNECTION_HDMI = "hdmi";
+    /**
+     * HDMI ARC connection.
+     */
+    const @utf8InCpp String CONNECTION_HDMI_ARC = "hdmi-arc";
+    /**
+     * HDMI eARC connection.
+     */
+    const @utf8InCpp String CONNECTION_HDMI_EARC = "hdmi-earc";
+    /**
+     * IP v4 connection.
+     */
+    const @utf8InCpp String CONNECTION_IP_V4 = "ip-v4";
+    /**
+     * SPDIF connection.
+     */
+    const @utf8InCpp String CONNECTION_SPDIF = "spdif";
+    /**
+     * A wireless connection when the actual protocol is unspecified.
+     */
+    const @utf8InCpp String CONNECTION_WIRELESS = "wireless";
+    /**
+     * USB connection.
+     */
+    const @utf8InCpp String CONNECTION_USB = "usb";
+}
diff --git a/media/aidl/android/media/audio/common/AudioDeviceType.aidl b/media/aidl/android/media/audio/common/AudioDeviceType.aidl
new file mode 100644
index 0000000..95dbe2a
--- /dev/null
+++ b/media/aidl/android/media/audio/common/AudioDeviceType.aidl
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2021 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.audio.common;
+
+/**
+ * The type of the audio device. Only used as part of 'AudioDeviceDescription'
+ * structure.
+ *
+ * Types are divided into "input" and "output" categories. Audio devices that
+ * have both audio input and output, for example, headsets, are represented by a
+ * pair of input and output device types.
+ *
+ * The 'AudioDeviceType' intentionally binds together directionality and 'kind'
+ * of the device to avoid making them fully orthogonal. This is because not all
+ * types of devices are bidirectional, for example, speakers can only be used
+ * for output and microphones can only be used for input (at least, in the
+ * context of the audio framework).
+ *
+ * {@hide}
+ */
+@VintfStability
+@Backing(type="int")
+enum AudioDeviceType {
+    /**
+     * "None" type is a "null" value. All fields of 'AudioDeviceDescription'
+     * must have default / empty / null values.
+     */
+    NONE = 0,
+    /**
+     * The "default" device is used when the client does not have any
+     * preference for a particular device.
+     */
+    IN_DEFAULT = 1,
+    /**
+     * A device implementing Android Open Accessory protocol.
+     */
+    IN_ACCESSORY = 2,
+    /**
+     * Input from a DSP front-end proxy device.
+     */
+    IN_AFE_PROXY = 3,
+    /**
+     * Used when only the connection protocol is known, e.g. a "HDMI Device."
+     */
+    IN_DEVICE = 4,
+    /**
+     * A device providing reference input for echo canceller.
+     */
+    IN_ECHO_REFERENCE = 5,
+    /**
+     * FM Tuner input.
+     */
+    IN_FM_TUNER = 6,
+    /**
+     * A microphone of a headset.
+     */
+    IN_HEADSET = 7,
+    /**
+     * Loopback input.
+     */
+    IN_LOOPBACK = 8,
+    /**
+     * The main microphone (the frontal mic on mobile devices).
+     */
+    IN_MICROPHONE = 9,
+    /**
+     * The secondary microphone (the back mic on mobile devices).
+     */
+    IN_MICROPHONE_BACK = 10,
+    /**
+     * Input from a submix of other streams.
+     */
+    IN_SUBMIX = 11,
+    /**
+     * Audio received via the telephone line.
+     */
+    IN_TELEPHONY_RX = 12,
+    /**
+     * TV Tuner audio input.
+     */
+    IN_TV_TUNER = 13,
+    /**
+     * The "default" device is used when the client does not have any
+     * preference for a particular device.
+     */
+    OUT_DEFAULT = 129,
+    /**
+     * A device implementing Android Open Accessory protocol.
+     */
+    OUT_ACCESSORY = 130,
+    /**
+     * Output from a DSP front-end proxy device.
+     */
+    OUT_AFE_PROXY = 131,
+    /**
+     * Car audio system.
+     */
+    OUT_CARKIT = 132,
+    /**
+     * Used when only the connection protocol is known, e.g. a "HDMI Device."
+     */
+    OUT_DEVICE = 133,
+    /**
+     * The echo canceller device.
+     */
+    OUT_ECHO_CANCELLER = 134,
+    /**
+     * The FM Tuner device.
+     */
+    OUT_FM = 135,
+    /**
+     * Headphones.
+     */
+    OUT_HEADPHONE = 136,
+    /**
+     * Headphones of a headset.
+     */
+    OUT_HEADSET = 137,
+    /**
+     * Hearing aid.
+     */
+    OUT_HEARING_AID = 138,
+    /**
+     * Secondary line level output.
+     */
+    OUT_LINE_AUX = 139,
+    /**
+     * The main speaker.
+     */
+    OUT_SPEAKER = 140,
+    /**
+     * The speaker of a mobile device in the case when it is close to the ear.
+     */
+    OUT_SPEAKER_EARPIECE = 141,
+    /**
+     * The main speaker with overload / overheating protection.
+     */
+    OUT_SPEAKER_SAFE = 142,
+    /**
+     * Output into a submix.
+     */
+    OUT_SUBMIX = 143,
+    /**
+     * Output into a telephone line.
+     */
+    OUT_TELEPHONY_TX = 144,
+}
diff --git a/media/aidl/android/media/audio/common/AudioFormatType.aidl b/media/aidl/android/media/audio/common/AudioFormatType.aidl
index 1759401..ea78c7a 100644
--- a/media/aidl/android/media/audio/common/AudioFormatType.aidl
+++ b/media/aidl/android/media/audio/common/AudioFormatType.aidl
@@ -19,6 +19,8 @@
 /**
  * The type of the audio format. Only used as part of 'AudioFormatDescription'
  * structure.
+ *
+ * {@hide}
  */
 @VintfStability
 @Backing(type="byte")
diff --git a/media/aidl/android/media/audio/common/PcmType.aidl b/media/aidl/android/media/audio/common/PcmType.aidl
index db77769..6e07d9b 100644
--- a/media/aidl/android/media/audio/common/PcmType.aidl
+++ b/media/aidl/android/media/audio/common/PcmType.aidl
@@ -19,6 +19,8 @@
 /**
  * The type of the encoding used for representing PCM samples. Only used as
  * part of 'AudioFormatDescription' structure.
+ *
+ * {@hide}
  */
 @VintfStability
 @Backing(type="byte")
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDevice.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDevice.aidl
new file mode 100644
index 0000000..fb5cb62
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDevice.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioDevice {
+  android.media.audio.common.AudioDeviceDescription type;
+  android.media.audio.common.AudioDeviceAddress address;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceAddress.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceAddress.aidl
new file mode 100644
index 0000000..905d3aa
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceAddress.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+union AudioDeviceAddress {
+  @utf8InCpp String id;
+  byte[] mac;
+  byte[] ipv4;
+  int[] ipv6;
+  int[] alsa;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceDescription.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceDescription.aidl
new file mode 100644
index 0000000..b4d71d7
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceDescription.aidl
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioDeviceDescription {
+  android.media.audio.common.AudioDeviceType type = android.media.audio.common.AudioDeviceType.NONE;
+  @utf8InCpp String connection;
+  const @utf8InCpp String CONNECTION_ANALOG = "analog";
+  const @utf8InCpp String CONNECTION_ANALOG_DOCK = "analog-dock";
+  const @utf8InCpp String CONNECTION_BT_A2DP = "bt-a2dp";
+  const @utf8InCpp String CONNECTION_BT_LE = "bt-le";
+  const @utf8InCpp String CONNECTION_BT_SCO = "bt-sco";
+  const @utf8InCpp String CONNECTION_BUS = "bus";
+  const @utf8InCpp String CONNECTION_DIGITAL_DOCK = "digital-dock";
+  const @utf8InCpp String CONNECTION_HDMI = "hdmi";
+  const @utf8InCpp String CONNECTION_HDMI_ARC = "hdmi-arc";
+  const @utf8InCpp String CONNECTION_HDMI_EARC = "hdmi-earc";
+  const @utf8InCpp String CONNECTION_IP_V4 = "ip-v4";
+  const @utf8InCpp String CONNECTION_SPDIF = "spdif";
+  const @utf8InCpp String CONNECTION_WIRELESS = "wireless";
+  const @utf8InCpp String CONNECTION_USB = "usb";
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceType.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceType.aidl
new file mode 100644
index 0000000..ffdb778
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceType.aidl
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum AudioDeviceType {
+  NONE = 0,
+  IN_DEFAULT = 1,
+  IN_ACCESSORY = 2,
+  IN_AFE_PROXY = 3,
+  IN_DEVICE = 4,
+  IN_ECHO_REFERENCE = 5,
+  IN_FM_TUNER = 6,
+  IN_HEADSET = 7,
+  IN_LOOPBACK = 8,
+  IN_MICROPHONE = 9,
+  IN_MICROPHONE_BACK = 10,
+  IN_SUBMIX = 11,
+  IN_TELEPHONY_RX = 12,
+  IN_TV_TUNER = 13,
+  OUT_DEFAULT = 129,
+  OUT_ACCESSORY = 130,
+  OUT_AFE_PROXY = 131,
+  OUT_CARKIT = 132,
+  OUT_DEVICE = 133,
+  OUT_ECHO_CANCELLER = 134,
+  OUT_FM = 135,
+  OUT_HEADPHONE = 136,
+  OUT_HEADSET = 137,
+  OUT_HEARING_AID = 138,
+  OUT_LINE_AUX = 139,
+  OUT_SPEAKER = 140,
+  OUT_SPEAKER_EARPIECE = 141,
+  OUT_SPEAKER_SAFE = 142,
+  OUT_SUBMIX = 143,
+  OUT_TELEPHONY_TX = 144,
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioFormatType.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioFormatType.aidl
index b94c850..7f55abe 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioFormatType.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioFormatType.aidl
@@ -32,6 +32,7 @@
 // later when a module using the interface is updated, e.g., Mainline modules.
 
 package android.media.audio.common;
+/* @hide */
 @Backing(type="byte") @VintfStability
 enum AudioFormatType {
   DEFAULT = 0,
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/PcmType.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/PcmType.aidl
index 42c4679..79bfa62 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/PcmType.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/PcmType.aidl
@@ -32,6 +32,7 @@
 // later when a module using the interface is updated, e.g., Mainline modules.
 
 package android.media.audio.common;
+/* @hide */
 @Backing(type="byte") @VintfStability
 enum PcmType {
   DEFAULT = 0,
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index fe92e26..d919af5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -106,6 +106,7 @@
     private boolean mIsA2dpProfileConnectedFail = false;
     private boolean mIsHeadsetProfileConnectedFail = false;
     private boolean mIsHearingAidProfileConnectedFail = false;
+    private boolean mUnpairing;
     // Group second device for Hearing Aid
     private CachedBluetoothDevice mSubDevice;
     @VisibleForTesting
@@ -142,6 +143,7 @@
         fillData();
         mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
         initDrawableCache();
+        mUnpairing = false;
     }
 
     private void initDrawableCache() {
@@ -402,6 +404,7 @@
         if (state != BluetoothDevice.BOND_NONE) {
             final BluetoothDevice dev = mDevice;
             if (dev != null) {
+                mUnpairing = true;
                 final boolean successful = dev.removeBond();
                 if (successful) {
                     releaseLruCache();
@@ -1243,4 +1246,8 @@
     void releaseLruCache() {
         mDrawableCache.evictAll();
     }
+
+    boolean getUnpairing() {
+        return mUnpairing;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index 20ece69..818f5ca 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -180,6 +180,9 @@
                 break;
             case BluetoothProfile.STATE_DISCONNECTED:
                 mainDevice = findMainDevice(cachedDevice);
+                if (cachedDevice.getUnpairing()) {
+                    return true;
+                }
                 if (mainDevice != null) {
                     // When main device exists, receiving sub device disconnection
                     // To update main device UI
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
index c501b3a..2e8f368 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -110,6 +110,18 @@
     }
 
     /**
+     * Check if target package is in allow list except idle app
+     */
+    public boolean isAllowlistedExceptIdle(String pkg) {
+        try {
+            return mDeviceIdleService.isPowerSaveWhitelistExceptIdleApp(pkg);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+            return true;
+        }
+    }
+
+    /**
      *
      * @param pkgs a list of packageName
      * @return true when one of package is in allow list
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
index 4f11fb1..6caf762 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
@@ -151,4 +151,14 @@
         assertThat(mPowerAllowlistBackend.isSysAllowlisted(PACKAGE_TWO)).isFalse();
         assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE)).isFalse();
     }
+
+    @Test
+    public void testIsPowerSaveWhitelistExceptIdleApp() throws Exception {
+        doReturn(true).when(mDeviceIdleService)
+                .isPowerSaveWhitelistExceptIdleApp(PACKAGE_ONE);
+
+        mPowerAllowlistBackend.refreshList();
+
+        assertThat(mPowerAllowlistBackend.isAllowlistedExceptIdle(PACKAGE_ONE)).isTrue();
+    }
 }
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index b841419..d286832 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -39,7 +39,7 @@
             android:layout_width="wrap_content"
             android:layout_height="32dp"
             android:textColor="?android:attr/textColorPrimary"
-            android:fontFamily="google-sans"
+            android:fontFamily="@*android:string/config_headlineFontFamily"
             android:textSize="24sp"/>
 
         <TextView
@@ -50,7 +50,7 @@
             android:layout_marginTop="4dp"
             android:ellipsize="end"
             android:maxLines="1"
-            android:fontFamily="google-sans"
+            android:fontFamily="@*android:string/config_headlineFontFamily"
             android:textSize="14sp"/>
     </LinearLayout>
 
@@ -87,24 +87,49 @@
             android:layout_height="wrap_content"
             android:orientation="vertical">
             <LinearLayout
-                android:id="@+id/internet_list"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:orientation="vertical">
 
                 <LinearLayout
+                    android:id="@+id/ethernet_layout"
+                    style="@style/InternetDialog.Network"
+                    android:background="@drawable/settingslib_switch_bar_bg_on"
+                    android:visibility="gone">
+
+                    <FrameLayout
+                        android:layout_width="24dp"
+                        android:layout_height="24dp"
+                        android:layout_gravity="center_vertical|start"
+                        android:clickable="false">
+                        <ImageView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="center"
+                            android:autoMirrored="true"
+                            android:src="@drawable/stat_sys_ethernet_fully"
+                            android:tint="@color/connected_network_primary_color"/>
+                    </FrameLayout>
+
+                    <LinearLayout
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:layout_weight="1"
+                        android:gravity="start|center_vertical"
+                        android:orientation="vertical"
+                        android:clickable="false">
+                        <TextView
+                            android:text="@string/ethernet_label"
+                            style="@style/InternetDialog.NetworkTitle.Active"/>
+                        <TextView
+                            android:text="@string/to_switch_networks_disconnect_ethernet"
+                            style="@style/InternetDialog.NetworkSummary.Active"/>
+                    </LinearLayout>
+                </LinearLayout>
+
+                <LinearLayout
                     android:id="@+id/mobile_network_layout"
-                    android:layout_width="match_parent"
-                    android:layout_height="88dp"
-                    android:clickable="true"
-                    android:focusable="true"
-                    android:background="?android:attr/selectableItemBackground"
-                    android:layout_gravity="center_vertical|start"
-                    android:orientation="horizontal"
-                    android:layout_marginEnd="@dimen/internet_dialog_network_layout_margin"
-                    android:layout_marginStart="@dimen/internet_dialog_network_layout_margin"
-                    android:paddingStart="22dp"
-                    android:paddingEnd="22dp">
+                    style="@style/InternetDialog.Network">
 
                     <FrameLayout
                         android:layout_width="24dp"
@@ -121,7 +146,6 @@
 
                     <LinearLayout
                         android:layout_weight="1"
-                        android:id="@+id/mobile_network_list"
                         android:orientation="vertical"
                         android:clickable="false"
                         android:layout_width="wrap_content"
@@ -129,28 +153,10 @@
                         android:gravity="start|center_vertical">
                         <TextView
                             android:id="@+id/mobile_title"
-                            android:textDirection="locale"
-                            android:layout_marginStart="@dimen/internet_dialog_network_layout_margin"
-                            android:layout_marginEnd="7dp"
-                            android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
-                            android:layout_gravity="center_vertical|start"
-                            android:ellipsize="end"
-                            android:textColor="?android:attr/textColorPrimary"
-                            android:textSize="16sp"
-                            android:fontFamily="google-sans"/>
+                            style="@style/InternetDialog.NetworkTitle"/>
                         <TextView
                             android:id="@+id/mobile_summary"
-                            android:textDirection="locale"
-                            android:layout_marginStart="@dimen/internet_dialog_network_layout_margin"
-                            android:layout_marginEnd="34dp"
-                            android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
-                            android:layout_gravity="center_vertical|start"
-                            android:ellipsize="end"
-                            android:textColor="?android:attr/textColorTertiary"
-                            android:textSize="14sp"
-                            android:fontFamily="google-sans"/>
+                            style="@style/InternetDialog.NetworkSummary"/>
                     </LinearLayout>
 
                     <FrameLayout
@@ -172,17 +178,9 @@
 
                 <LinearLayout
                     android:id="@+id/turn_on_wifi_layout"
-                    android:layout_width="match_parent"
+                    style="@style/InternetDialog.Network"
                     android:layout_height="72dp"
-                    android:clickable="true"
-                    android:focusable="true"
-                    android:background="?android:attr/selectableItemBackground"
-                    android:gravity="center"
-                    android:orientation="horizontal"
-                    android:layout_marginEnd="@dimen/internet_dialog_network_layout_margin"
-                    android:layout_marginStart="@dimen/internet_dialog_network_layout_margin"
-                    android:paddingStart="22dp"
-                    android:paddingEnd="22dp">
+                    android:gravity="center">
 
                     <FrameLayout
                         android:layout_weight="1"
@@ -193,13 +191,10 @@
                         <TextView
                             android:id="@+id/wifi_toggle_title"
                             android:text="@string/turn_on_wifi"
-                            android:textDirection="locale"
                             android:layout_width="wrap_content"
                             android:layout_height="match_parent"
                             android:gravity="start|center_vertical"
-                            android:textColor="?android:attr/textColorPrimary"
-                            android:textSize="16sp"
-                            android:fontFamily="google-sans"/>
+                            android:textAppearance="@style/TextAppearance.InternetDialog"/>
                     </FrameLayout>
 
                     <FrameLayout
@@ -222,18 +217,12 @@
 
                 <LinearLayout
                     android:id="@+id/wifi_connected_layout"
-                    android:layout_width="match_parent"
+                    style="@style/InternetDialog.Network"
                     android:layout_height="72dp"
-                    android:layout_gravity="center_vertical|start"
-                    android:clickable="true"
-                    android:focusable="true"
-                    android:visibility="gone"
-                    android:background="?android:attr/selectableItemBackground"
-                    android:orientation="horizontal"
-                    android:layout_marginStart="@dimen/internet_dialog_network_layout_margin"
-                    android:layout_marginEnd="@dimen/internet_dialog_network_layout_margin"
                     android:paddingStart="20dp"
-                    android:paddingEnd="24dp">
+                    android:paddingEnd="24dp"
+                    android:background="@drawable/settingslib_switch_bar_bg_on"
+                    android:visibility="gone">
 
                     <FrameLayout
                         android:layout_width="24dp"
@@ -248,7 +237,6 @@
                     </FrameLayout>
 
                     <LinearLayout
-                        android:id="@+id/wifi_connected_list"
                         android:orientation="vertical"
                         android:clickable="false"
                         android:layout_width="wrap_content"
@@ -258,26 +246,11 @@
                         android:gravity="start|center_vertical">
                         <TextView
                             android:id="@+id/wifi_connected_title"
-                            android:textDirection="locale"
-                            android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
-                            android:layout_gravity="center_vertical|start"
-                            android:layout_marginStart="@dimen/internet_dialog_network_layout_margin"
-                            android:ellipsize="end"
-                            android:textColor="?android:attr/textColorPrimary"
-                            android:textSize="14sp"
-                            android:fontFamily="google-sans"/>
+                            style="@style/InternetDialog.NetworkTitle.Active"
+                            android:textSize="14sp"/>
                         <TextView
                             android:id="@+id/wifi_connected_summary"
-                            android:textDirection="locale"
-                            android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
-                            android:layout_gravity="center_vertical|start"
-                            android:layout_marginStart="@dimen/internet_dialog_network_layout_margin"
-                            android:ellipsize="end"
-                            android:textColor="?android:attr/textColorTertiary"
-                            android:textSize="14sp"
-                            android:fontFamily="google-sans"/>
+                            style="@style/InternetDialog.NetworkSummary.Active"/>
                     </LinearLayout>
 
                     <FrameLayout
@@ -340,13 +313,11 @@
                     android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
                     <TextView
                         android:text="@string/see_all_networks"
-                        android:textDirection="locale"
                         android:layout_width="wrap_content"
                         android:layout_height="match_parent"
                         android:gravity="start|center_vertical"
-                        android:textColor="?android:attr/textColorPrimary"
-                        android:textSize="14sp"
-                        android:fontFamily="google-sans"/>
+                        android:textAppearance="@style/TextAppearance.InternetDialog"
+                        android:textSize="14sp"/>
                 </FrameLayout>
 
             </LinearLayout>
@@ -366,7 +337,7 @@
                     android:textColor="?android:attr/textColorPrimary"
                     android:text="@string/inline_done_button"
                     android:textSize="14sp"
-                    android:fontFamily="google-sans"/>
+                    android:fontFamily="@*android:string/config_headlineFontFamily"/>
             </FrameLayout>
         </LinearLayout>
     </androidx.core.widget.NestedScrollView>
diff --git a/packages/SystemUI/res/layout/internet_list_item.xml b/packages/SystemUI/res/layout/internet_list_item.xml
index 05352c5..868331e 100644
--- a/packages/SystemUI/res/layout/internet_list_item.xml
+++ b/packages/SystemUI/res/layout/internet_list_item.xml
@@ -24,21 +24,15 @@
 
     <LinearLayout
         android:id="@+id/wifi_list"
-        android:layout_width="match_parent"
+        style="@style/InternetDialog.Network"
         android:layout_height="72dp"
-        android:layout_gravity="center_vertical|start"
-        android:clickable="true"
-        android:focusable="true"
-        android:background="?android:attr/selectableItemBackground"
-        android:orientation="horizontal"
         android:paddingStart="20dp"
-        android:paddingEnd="40dp">
+        android:paddingEnd="24dp">
         <FrameLayout
             android:layout_width="24dp"
             android:layout_height="24dp"
             android:clickable="false"
-            android:layout_gravity="center_vertical|start"
-            android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+            android:layout_gravity="center_vertical|start">
             <ImageView
                 android:id="@+id/wifi_icon"
                 android:layout_width="wrap_content"
@@ -52,31 +46,16 @@
             android:clickable="false"
             android:layout_width="wrap_content"
             android:layout_height="72dp"
+            android:layout_marginEnd="30dp"
             android:layout_weight="1"
-            android:gravity="start|center_vertical"
-            android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+            android:gravity="start|center_vertical">
             <TextView
                 android:id="@+id/wifi_title"
-                android:textDirection="locale"
-                android:layout_width="wrap_content"
-                android:layout_height="20dp"
-                android:gravity="start|center_vertical"
-                android:ellipsize="end"
-                android:textColor="?android:attr/textColorPrimary"
-                android:textSize="14sp"
-                android:fontFamily="google-sans"
-                android:layout_marginEnd="18dp"/>
+                style="@style/InternetDialog.NetworkTitle"
+                android:textSize="14sp"/>
             <TextView
                 android:id="@+id/wifi_summary"
-                android:textDirection="locale"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:gravity="start|center_vertical"
-                android:ellipsize="end"
-                android:textColor="?android:attr/textColorSecondary"
-                android:textSize="14sp"
-                android:fontFamily="google-sans"
-                android:layout_marginEnd="18dp"/>
+                style="@style/InternetDialog.NetworkSummary"/>
         </LinearLayout>
 
         <FrameLayout
diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
index a21a63c..9cf09ff 100644
--- a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
+++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
@@ -15,19 +15,25 @@
   ~ limitations under the License
   -->
 <!-- This is a view that shows a user switcher in Keyguard. -->
-<com.android.systemui.statusbar.phone.UserAvatarView
+<FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/keyguard_qs_user_switch_view"
-    android:layout_width="@dimen/kg_framed_avatar_size"
-    android:layout_height="@dimen/kg_framed_avatar_size"
-    android:layout_centerHorizontal="true"
-    android:layout_gravity="top|end"
-    android:layout_marginEnd="16dp"
-    systemui:avatarPadding="0dp"
-    systemui:badgeDiameter="18dp"
-    systemui:badgeMargin="1dp"
-    systemui:frameColor="@color/kg_user_avatar_frame"
-    systemui:framePadding="0dp"
-    systemui:frameWidth="0dp">
-</com.android.systemui.statusbar.phone.UserAvatarView>
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="end">
+    <com.android.systemui.statusbar.phone.UserAvatarView
+        android:id="@+id/kg_multi_user_avatar"
+        android:layout_width="@dimen/kg_framed_avatar_size"
+        android:layout_height="@dimen/kg_framed_avatar_size"
+        android:layout_centerHorizontal="true"
+        android:layout_gravity="top|end"
+        android:layout_marginEnd="16dp"
+        systemui:avatarPadding="0dp"
+        systemui:badgeDiameter="18dp"
+        systemui:badgeMargin="1dp"
+        systemui:frameColor="@color/kg_user_avatar_frame"
+        systemui:framePadding="0dp"
+        systemui:frameWidth="0dp">
+    </com.android.systemui.statusbar.phone.UserAvatarView>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/sidefps_view.xml b/packages/SystemUI/res/layout/sidefps_view.xml
index 3977635..921f788 100644
--- a/packages/SystemUI/res/layout/sidefps_view.xml
+++ b/packages/SystemUI/res/layout/sidefps_view.xml
@@ -14,11 +14,13 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.biometrics.SidefpsView
+<com.airbnb.lottie.LottieAnimationView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:systemui="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/sidefps_view"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:contentDescription="@string/accessibility_fingerprint_label">
-</com.android.systemui.biometrics.SidefpsView>
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/sidefps_animation"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    app:lottie_autoPlay="true"
+    app:lottie_loop="true"
+    app:lottie_rawRes="@raw/sfps_pulse"
+    android:contentDescription="@string/accessibility_fingerprint_label"/>
diff --git a/packages/SystemUI/res/raw/sfps_pulse.json b/packages/SystemUI/res/raw/sfps_pulse.json
new file mode 100644
index 0000000..c4903a2
--- /dev/null
+++ b/packages/SystemUI/res/raw/sfps_pulse.json
@@ -0,0 +1 @@
+{"v":"5.7.6","fr":60,"ip":0,"op":300,"w":42,"h":80,"nm":"Fingerprint Pulse Motion","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":0,"s":[28,40,0],"to":[0.751,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":30,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":60,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":90,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":120,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":150,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":180,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":210,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":240,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":270,"s":[32.503,40,0],"to":[0,0,0],"ti":[0.751,0,0]},{"t":300,"s":[28,40,0]}],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1.566,0],[-3.929,-5.503],[-2.751,-6.68],[3.929,0],[-2.751,6.68],[-3.929,5.503]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.218,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue600","cl":"blue600","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[41.878,40,0],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[55,55],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 2","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[28.253,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[20]},{"t":30,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":-30,"s":[55,55]},{"t":30,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-30,"op":30,"st":-30,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[20]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[55,55]},{"t":60,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":70,"s":[20]},{"t":90,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":30,"s":[55,55]},{"t":90,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":90,"st":30,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":100,"s":[20]},{"t":120,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":60,"s":[55,55]},{"t":120,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":120,"st":60,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":130,"s":[20]},{"t":150,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":90,"s":[55,55]},{"t":150,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":90,"op":150,"st":90,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[20]},{"t":180,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":120,"s":[55,55]},{"t":180,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":120,"op":180,"st":120,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":190,"s":[20]},{"t":210,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":150,"s":[55,55]},{"t":210,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":150,"op":210,"st":150,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":220,"s":[20]},{"t":240,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":180,"s":[55,55]},{"t":240,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":240,"st":180,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[20]},{"t":270,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":210,"s":[55,55]},{"t":270,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":210,"op":270,"st":210,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":280,"s":[20]},{"t":300,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":240,"s":[55,55]},{"t":300,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":300,"st":240,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":310,"s":[20]},{"t":330,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":270,"s":[55,55]},{"t":330,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":270,"op":330,"st":270,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/sfps_pulse_landscape.json b/packages/SystemUI/res/raw/sfps_pulse_landscape.json
new file mode 100644
index 0000000..8c91762
--- /dev/null
+++ b/packages/SystemUI/res/raw/sfps_pulse_landscape.json
@@ -0,0 +1 @@
+{"v":"5.7.6","fr":60,"ip":0,"op":300,"w":80,"h":42,"nm":"Fingerprint Pulse Motion Portrait","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":0,"s":[40,14,0],"to":[0,-0.751,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":30,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":60,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":90,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":120,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":150,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":180,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":210,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":240,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":270,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,-0.751,0]},{"t":300,"s":[40,14,0]}],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1.566,0],[-3.929,-5.503],[-2.751,-6.68],[3.929,0],[-2.751,6.68],[-3.929,5.503]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.218,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue600","cl":"blue600","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,0.122,0],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[55,55],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 2","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[28.253,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[20]},{"t":30,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":-30,"s":[55,55]},{"t":30,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-30,"op":30,"st":-30,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[20]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[55,55]},{"t":60,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":70,"s":[20]},{"t":90,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":30,"s":[55,55]},{"t":90,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":90,"st":30,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":100,"s":[20]},{"t":120,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":60,"s":[55,55]},{"t":120,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":120,"st":60,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":130,"s":[20]},{"t":150,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":90,"s":[55,55]},{"t":150,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":90,"op":150,"st":90,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[20]},{"t":180,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":120,"s":[55,55]},{"t":180,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":120,"op":180,"st":120,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":190,"s":[20]},{"t":210,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":150,"s":[55,55]},{"t":210,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":150,"op":210,"st":150,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":220,"s":[20]},{"t":240,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":180,"s":[55,55]},{"t":240,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":240,"st":180,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[20]},{"t":270,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":210,"s":[55,55]},{"t":270,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":210,"op":270,"st":210,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":280,"s":[20]},{"t":300,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":240,"s":[55,55]},{"t":300,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":300,"st":240,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":310,"s":[20]},{"t":330,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":270,"s":[55,55]},{"t":330,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":270,"op":330,"st":270,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 461505f..ffcc3a8 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -48,4 +48,16 @@
         <item name="android:windowLightStatusBar">false</item>
     </style>
 
+    <style name="TextAppearance.InternetDialog.Active">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:textColor">@color/connected_network_primary_color</item>
+        <item name="android:textDirection">locale</item>
+    </style>
+
+    <style name="TextAppearance.InternetDialog.Secondary.Active">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">@color/connected_network_secondary_color</item>
+    </style>
+
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8f80421..49bbf7d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3022,6 +3022,8 @@
     <string name="wifi_failed_connect_message">Failed to connect to network</string>
     <!-- Provider Model: Title to see all the networks [CHAR LIMIT=50] -->
     <string name="see_all_networks">See all</string>
+    <!-- Summary for warning to disconnect ethernet first then switch to other networks. [CHAR LIMIT=60] -->
+    <string name="to_switch_networks_disconnect_ethernet">To switch networks, disconnect ethernet</string>
 
     <!-- Text for TileService request dialog. This is shown to the user that an app is requesting
          user approval to add the shown tile to Quick Settings [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index dc17afb..33edca1 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -994,4 +994,60 @@
         <item name="android:maxHeight">4dp</item>
     </style>
 
+    <!-- Internet Dialog -->
+    <style name="InternetDialog">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center_vertical|start</item>
+        <item name="android:layout_marginStart">@dimen/internet_dialog_network_layout_margin</item>
+    </style>
+
+    <style name="InternetDialog.Network">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">88dp</item>
+        <item name="android:layout_marginEnd">@dimen/internet_dialog_network_layout_margin</item>
+        <item name="android:paddingStart">22dp</item>
+        <item name="android:paddingEnd">22dp</item>
+        <item name="android:orientation">horizontal</item>
+        <item name="android:focusable">true</item>
+        <item name="android:clickable">true</item>
+    </style>
+
+    <style name="InternetDialog.NetworkTitle">
+        <item name="android:layout_marginEnd">7dp</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:textAppearance">@style/TextAppearance.InternetDialog</item>
+    </style>
+
+    <style name="InternetDialog.NetworkTitle.Active">
+        <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Active</item>
+    </style>
+
+    <style name="InternetDialog.NetworkSummary">
+        <item name="android:layout_marginEnd">34dp</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Secondary</item>
+    </style>
+
+    <style name="InternetDialog.NetworkSummary.Active">
+        <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Secondary.Active
+        </item>
+    </style>
+
+    <style name="TextAppearance.InternetDialog">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textDirection">locale</item>
+    </style>
+
+    <style name="TextAppearance.InternetDialog.Secondary">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">?android:attr/textColorTertiary</item>
+    </style>
+
+    <style name="TextAppearance.InternetDialog.Active"/>
+
+    <style name="TextAppearance.InternetDialog.Secondary.Active"/>
+
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 92f89d6..efcf40a 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -160,9 +160,7 @@
         mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
         mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
         mBatteryController.removeCallback(mBatteryCallback);
-        if (!mView.isAttachedToWindow()) {
-            mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener);
-        }
+        mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener);
     }
 
     /** Animate the clock appearance */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 26059068..64d214d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -54,6 +54,7 @@
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
+import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 import java.util.ArrayList;
@@ -472,6 +473,11 @@
                     mIsSecurityViewLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
                             : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT);
 
+            int keyguardState = mIsSecurityViewLeftAligned
+                    ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_LEFT
+                    : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_RIGHT;
+            SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, keyguardState);
+
             updateSecurityViewLocation(animate);
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 0df2d65..9d649e7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -151,9 +151,17 @@
         }
 
         public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
+            int bouncerSide = SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__DEFAULT;
+            if (canUseOneHandedBouncer()) {
+                bouncerSide = isOneHandedKeyguardLeftAligned()
+                        ? SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__LEFT
+                        : SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__RIGHT;
+            }
+
             if (success) {
                 SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
-                        SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS);
+                        SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS,
+                        bouncerSide);
                 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
@@ -168,7 +176,8 @@
                 });
             } else {
                 SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
-                        SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE);
+                        SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE,
+                        bouncerSide);
                 reportFailedUnlockAttempt(userId, timeoutMs);
             }
             mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
@@ -314,6 +323,14 @@
     @Override
     public void onResume(int reason) {
         if (mCurrentSecurityMode != SecurityMode.None) {
+            int state = SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN;
+            if (canUseOneHandedBouncer()) {
+                state = mView.isOneHandedModeLeftAligned()
+                        ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_LEFT
+                        : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_RIGHT;
+            }
+            SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, state);
+
             getCurrentSecurityController().onResume(reason);
         }
         mView.onResume(
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 2a4022c..a115195 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -27,6 +27,7 @@
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.media.AudioAttributes;
 import android.os.Process;
@@ -330,8 +331,9 @@
     private void updateLockIconLocation() {
         if (mUdfpsSupported) {
             FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0);
-            mView.setCenterLocation(new PointF(props.sensorLocationX, props.sensorLocationY),
-                    props.sensorRadius);
+            final SensorLocationInternal location = props.getLocation();
+            mView.setCenterLocation(new PointF(location.sensorLocationX, location.sensorLocationY),
+                    location.sensorRadius);
         } else {
             mView.setCenterLocation(
                     new PointF(mWidthPixels / 2,
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
index 3a0357d..4331f52 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
@@ -16,7 +16,8 @@
 
 package com.android.keyguard.dagger;
 
-import com.android.systemui.statusbar.phone.UserAvatarView;
+import android.widget.FrameLayout;
+
 import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
 
 import dagger.BindsInstance;
@@ -31,8 +32,7 @@
     /** Simple factory for {@link KeyguardUserSwitcherComponent}. */
     @Subcomponent.Factory
     interface Factory {
-        KeyguardQsUserSwitchComponent build(
-                @BindsInstance UserAvatarView userAvatarView);
+        KeyguardQsUserSwitchComponent build(@BindsInstance FrameLayout userAvatarContainer);
     }
 
     /** Builds a {@link KeyguardQsUserSwitchController}. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 0790af9..71445a7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -114,7 +114,7 @@
     @VisibleForTesting
     IBiometricSysuiReceiver mReceiver;
     @VisibleForTesting
-    @NonNull final BiometricOrientationEventListener mOrientationListener;
+    @NonNull final BiometricDisplayListener mOrientationListener;
     @Nullable private final List<FaceSensorPropertiesInternal> mFaceProps;
     @Nullable private List<FingerprintSensorPropertiesInternal> mFpProps;
     @Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps;
@@ -459,13 +459,15 @@
         mSidefpsControllerFactory = sidefpsControllerFactory;
         mWindowManager = windowManager;
         mUdfpsEnrolledForUser = new SparseBooleanArray();
-        mOrientationListener = new BiometricOrientationEventListener(context,
+        mOrientationListener = new BiometricDisplayListener(
+                context,
+                displayManager,
+                handler,
+                BiometricDisplayListener.SensorType.Generic.INSTANCE,
                 () -> {
                     onOrientationChanged();
                     return Unit.INSTANCE;
-                },
-                displayManager,
-                handler);
+                });
 
         mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index e232316..f057bb1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -241,7 +241,7 @@
     private fun updateUdfpsDependentParams() {
         authController.udfpsProps?.let {
             if (it.size > 0) {
-                udfpsRadius = it[0].sensorRadius.toFloat()
+                udfpsRadius = it[0].location.sensorRadius.toFloat()
                 udfpsController = udfpsControllerProvider.get()
 
                 if (mView.isAttachedToWindow) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt
new file mode 100644
index 0000000..b7404df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 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 android.content.Context
+import android.hardware.display.DisplayManager
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.os.Handler
+import android.view.Surface
+import com.android.systemui.biometrics.BiometricDisplayListener.SensorType.Generic
+
+/**
+ * A listener for keeping overlays for biometric sensors aligned with the physical device
+ * device's screen. The [onChanged] will be dispatched on the [handler]
+ * whenever a relevant change to the device's configuration (orientation, fold, display change,
+ * etc.) may require the UI to change for the given [sensorType].
+ */
+class BiometricDisplayListener(
+    private val context: Context,
+    private val displayManager: DisplayManager,
+    private val handler: Handler,
+    private val sensorType: SensorType = SensorType.Generic,
+    private val onChanged: () -> Unit
+) : DisplayManager.DisplayListener {
+
+    private var lastRotation = context.display?.rotation ?: Surface.ROTATION_0
+
+    override fun onDisplayAdded(displayId: Int) {}
+    override fun onDisplayRemoved(displayId: Int) {}
+    override fun onDisplayChanged(displayId: Int) {
+        val rotationChanged = didRotationChange()
+
+        when (sensorType) {
+            is SensorType.SideFingerprint -> onChanged()
+            else -> {
+                if (rotationChanged) {
+                    onChanged()
+                }
+            }
+        }
+    }
+
+    private fun didRotationChange(): Boolean {
+        val rotation = context.display?.rotation ?: return false
+        val last = lastRotation
+        lastRotation = rotation
+        return last != rotation
+    }
+
+    /** Listen for changes. */
+    fun enable() {
+        displayManager.registerDisplayListener(this, handler)
+    }
+
+    /** Stop listening for changes. */
+    fun disable() {
+        displayManager.unregisterDisplayListener(this)
+    }
+
+    /**
+     * Type of sensor to determine what kind of display changes require layouts.
+     *
+     * The [Generic] type should be used in cases where the modality can vary, such as
+     * biometric prompt (and this object will likely change as multi-mode auth is added).
+     */
+    sealed class SensorType {
+        object Generic : SensorType()
+        data class UnderDisplayFingerprint(
+            val properties: FingerprintSensorPropertiesInternal
+        ) : SensorType()
+        data class SideFingerprint(
+            val properties: FingerprintSensorPropertiesInternal
+        ) : SensorType()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt
deleted file mode 100644
index 98a03a1..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2021 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 android.content.Context
-import android.hardware.display.DisplayManager
-import android.os.Handler
-import android.view.OrientationEventListener
-import android.view.Surface
-
-/**
- * An [OrientationEventListener] that invokes the [onOrientationChanged] callback whenever
- * the orientation of the device has changed in order to keep overlays for biometric sensors
- * aligned with the device's screen.
- */
-class BiometricOrientationEventListener(
-    private val context: Context,
-    private val onOrientationChanged: () -> Unit,
-    private val displayManager: DisplayManager,
-    private val handler: Handler
-) : DisplayManager.DisplayListener {
-
-    private var lastRotation = context.display?.rotation ?: Surface.ROTATION_0
-
-    override fun onDisplayAdded(displayId: Int) {}
-    override fun onDisplayRemoved(displayId: Int) {}
-    override fun onDisplayChanged(displayId: Int) {
-        val rotation = context.display?.rotation ?: return
-        if (lastRotation != rotation) {
-            lastRotation = rotation
-
-            onOrientationChanged()
-        }
-    }
-
-    fun enable() {
-        displayManager.registerDisplayListener(this, handler)
-    }
-
-    fun disable() {
-        displayManager.unregisterDisplayListener(this)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java
deleted file mode 100644
index 8f6e249..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2021 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 com.android.internal.util.Preconditions.checkArgument;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.PixelFormat;
-import android.hardware.display.DisplayManager;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.ISidefpsController;
-import android.os.Handler;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.Surface;
-import android.view.WindowManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import javax.inject.Inject;
-
-import kotlin.Unit;
-
-/**
- * Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events.
- */
-@SysUISingleton
-public class SidefpsController {
-    private static final String TAG = "SidefpsController";
-    @NonNull private final Context mContext;
-    @NonNull private final LayoutInflater mInflater;
-    private final FingerprintManager mFingerprintManager;
-    private final WindowManager mWindowManager;
-    private final DelayableExecutor mFgExecutor;
-    @VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener;
-
-    // TODO: update mDisplayHeight and mDisplayWidth for multi-display devices
-    private final int mDisplayHeight;
-    private final int mDisplayWidth;
-
-    private boolean mIsVisible = false;
-    @Nullable private SidefpsView mView;
-
-    static final int SFPS_AFFORDANCE_WIDTH = 50; // in default portrait mode
-
-    @NonNull
-    private final ISidefpsController mSidefpsControllerImpl = new ISidefpsController.Stub() {
-        @Override
-        public void show() {
-            mFgExecutor.execute(() -> {
-                SidefpsController.this.show();
-                mIsVisible = true;
-            });
-        }
-
-        @Override
-        public void hide() {
-            mFgExecutor.execute(() -> {
-                SidefpsController.this.hide();
-                mIsVisible = false;
-            });
-        }
-    };
-
-    @VisibleForTesting
-    final FingerprintSensorPropertiesInternal mSensorProps;
-    private final WindowManager.LayoutParams mCoreLayoutParams;
-
-    @Inject
-    public SidefpsController(@NonNull Context context,
-            @NonNull LayoutInflater inflater,
-            @Nullable FingerprintManager fingerprintManager,
-            @NonNull WindowManager windowManager,
-            @Main DelayableExecutor fgExecutor,
-            @NonNull DisplayManager displayManager,
-            @Main Handler handler) {
-        mContext = context;
-        mInflater = inflater;
-        mFingerprintManager = checkNotNull(fingerprintManager);
-        mWindowManager = windowManager;
-        mFgExecutor = fgExecutor;
-        mOrientationListener = new BiometricOrientationEventListener(
-                context,
-                () -> {
-                    onOrientationChanged();
-                    return Unit.INSTANCE;
-                },
-                displayManager,
-                handler);
-
-        mSensorProps = findFirstSidefps();
-        checkArgument(mSensorProps != null);
-
-        mCoreLayoutParams = new WindowManager.LayoutParams(
-                WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
-                getCoreLayoutParamFlags(),
-                PixelFormat.TRANSLUCENT);
-        mCoreLayoutParams.setTitle(TAG);
-        // Overrides default, avoiding status bars during layout
-        mCoreLayoutParams.setFitInsetsTypes(0);
-        mCoreLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
-        mCoreLayoutParams.layoutInDisplayCutoutMode =
-                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-
-        DisplayMetrics displayMetrics = new DisplayMetrics();
-        windowManager.getDefaultDisplay().getMetrics(displayMetrics);
-        mDisplayHeight = displayMetrics.heightPixels;
-        mDisplayWidth = displayMetrics.widthPixels;
-
-        mFingerprintManager.setSidefpsController(mSidefpsControllerImpl);
-    }
-
-    private void show() {
-        mView = (SidefpsView) mInflater.inflate(R.layout.sidefps_view, null, false);
-        mView.setSensorProperties(mSensorProps);
-        mWindowManager.addView(mView, computeLayoutParams());
-
-        mOrientationListener.enable();
-    }
-
-    private void hide() {
-        if (mView != null) {
-            mWindowManager.removeView(mView);
-            mView.setOnTouchListener(null);
-            mView.setOnHoverListener(null);
-            mView = null;
-        } else {
-            Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
-        }
-
-        mOrientationListener.disable();
-    }
-
-    private void onOrientationChanged() {
-        // If mView is null or if view is hidden, then return.
-        if (mView == null || !mIsVisible) {
-            return;
-        }
-
-        // If the overlay needs to be displayed with a new configuration, destroy the current
-        // overlay, and re-create and show the overlay with the updated LayoutParams.
-        hide();
-        show();
-    }
-
-    @Nullable
-    private FingerprintSensorPropertiesInternal findFirstSidefps() {
-        for (FingerprintSensorPropertiesInternal props :
-                mFingerprintManager.getSensorPropertiesInternal()) {
-            if (props.isAnySidefpsType()) {
-                // TODO(b/188690214): L155-L173 can be removed once sensorLocationX,
-                //  sensorLocationY, and sensorRadius are defined in sensorProps by the HAL
-                int sensorLocationX = 25;
-                int sensorLocationY = 610;
-                int sensorRadius = 112;
-
-                FingerprintSensorPropertiesInternal tempProps =
-                        new FingerprintSensorPropertiesInternal(
-                                props.sensorId,
-                                props.sensorStrength,
-                                props.maxEnrollmentsPerUser,
-                                props.componentInfo,
-                                props.sensorType,
-                                props.resetLockoutRequiresHardwareAuthToken,
-                                sensorLocationX,
-                                sensorLocationY,
-                                sensorRadius
-                        );
-                props = tempProps;
-                return props;
-            }
-        }
-        return null;
-    }
-
-    private int getCoreLayoutParamFlags() {
-        return WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
-    }
-
-    /**
-     * Computes layout params depending on orientation & folding configuration of device
-     */
-    private WindowManager.LayoutParams computeLayoutParams() {
-        mCoreLayoutParams.flags = getCoreLayoutParamFlags();
-        // Y value of top of affordance in portrait mode, X value of left of affordance in landscape
-        int sfpsLocationY = mSensorProps.sensorLocationY - mSensorProps.sensorRadius;
-        int sfpsAffordanceHeight = mSensorProps.sensorRadius * 2;
-
-        // Calculate coordinates of drawable area for the fps affordance, accounting for orientation
-        switch (mContext.getDisplay().getRotation()) {
-            case Surface.ROTATION_90:
-                mCoreLayoutParams.x = sfpsLocationY;
-                mCoreLayoutParams.y = 0;
-                mCoreLayoutParams.height = SFPS_AFFORDANCE_WIDTH;
-                mCoreLayoutParams.width = sfpsAffordanceHeight;
-                break;
-            case Surface.ROTATION_270:
-                mCoreLayoutParams.x = mDisplayHeight - sfpsLocationY - sfpsAffordanceHeight;
-                mCoreLayoutParams.y = mDisplayWidth - SFPS_AFFORDANCE_WIDTH;
-                mCoreLayoutParams.height = SFPS_AFFORDANCE_WIDTH;
-                mCoreLayoutParams.width = sfpsAffordanceHeight;
-                break;
-            default: // Portrait
-                mCoreLayoutParams.x = mDisplayWidth - SFPS_AFFORDANCE_WIDTH;
-                mCoreLayoutParams.y = sfpsLocationY;
-                mCoreLayoutParams.height = sfpsAffordanceHeight;
-                mCoreLayoutParams.width = SFPS_AFFORDANCE_WIDTH;
-        }
-        return mCoreLayoutParams;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
new file mode 100644
index 0000000..c0731b2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2021 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 android.content.Context
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.hardware.display.DisplayManager
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.hardware.fingerprint.ISidefpsController
+import android.os.Handler
+import android.util.Log
+import android.view.Display
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.Surface
+import android.view.View
+import android.view.WindowManager
+import androidx.annotation.RawRes
+import com.airbnb.lottie.LottieAnimationView
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.concurrency.DelayableExecutor
+import javax.inject.Inject
+
+private const val TAG = "SidefpsController"
+
+/**
+ * Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events.
+ */
+@SysUISingleton
+class SidefpsController @Inject constructor(
+    private val context: Context,
+    private val layoutInflater: LayoutInflater,
+    fingerprintManager: FingerprintManager?,
+    private val windowManager: WindowManager,
+    @Main mainExecutor: DelayableExecutor,
+    displayManager: DisplayManager,
+    @Main handler: Handler
+) {
+    @VisibleForTesting
+    val sensorProps: FingerprintSensorPropertiesInternal = fingerprintManager
+        ?.sensorPropertiesInternal
+        ?.firstOrNull { it.isAnySidefpsType }
+        ?: throw IllegalStateException("no side fingerprint sensor")
+
+    @VisibleForTesting
+    val orientationListener = BiometricDisplayListener(
+        context,
+        displayManager,
+        handler,
+        BiometricDisplayListener.SensorType.SideFingerprint(sensorProps)
+    ) { onOrientationChanged() }
+
+    private var overlayView: View? = null
+        set(value) {
+            field?.let { oldView ->
+                windowManager.removeView(oldView)
+                orientationListener.disable()
+            }
+            field = value
+            field?.let { newView ->
+                windowManager.addView(newView, overlayViewParams)
+                orientationListener.enable()
+            }
+        }
+
+    private val overlayViewParams = WindowManager.LayoutParams(
+        WindowManager.LayoutParams.WRAP_CONTENT,
+        WindowManager.LayoutParams.WRAP_CONTENT,
+        WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
+        WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+        PixelFormat.TRANSLUCENT
+    ).apply {
+        title = TAG
+        fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
+        gravity = Gravity.TOP or Gravity.LEFT
+        layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+        privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+    }
+
+    init {
+        fingerprintManager?.setSidefpsController(object : ISidefpsController.Stub() {
+            override fun show() = mainExecutor.execute {
+                if (overlayView == null) {
+                    overlayView = createOverlayForDisplay()
+                } else {
+                    Log.v(TAG, "overlay already shown")
+                }
+            }
+            override fun hide() = mainExecutor.execute { overlayView = null }
+        })
+    }
+
+    private fun onOrientationChanged() {
+        if (overlayView != null) {
+            overlayView = createOverlayForDisplay()
+        }
+    }
+
+    private fun createOverlayForDisplay(): View {
+        val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
+        val display = context.display!!
+
+        val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
+        lottie.setAnimation(display.asSideFpsAnimation())
+        view.rotation = display.asSideFpsAnimationRotation()
+
+        updateOverlayParams(display, lottie.composition?.bounds ?: Rect())
+        lottie.addLottieOnCompositionLoadedListener {
+            if (overlayView == view) {
+                updateOverlayParams(display, it.bounds)
+                windowManager.updateViewLayout(overlayView, overlayViewParams)
+            }
+        }
+
+        return view
+    }
+
+    private fun updateOverlayParams(display: Display, bounds: Rect) {
+        val isPortrait = display.isPortrait()
+        val size = windowManager.maximumWindowMetrics.bounds
+        val displayWidth = if (isPortrait) size.width() else size.height()
+        val displayHeight = if (isPortrait) size.height() else size.width()
+        val offsets = sensorProps.getLocation(display.uniqueId).let { location ->
+            if (location == null) {
+                Log.w(TAG, "No location specified for display: ${display.uniqueId}")
+            }
+            location ?: sensorProps.location
+        }
+
+        // ignore sensorLocationX and sensorRadius since it's assumed to be on the side
+        // of the device and centered at sensorLocationY
+        val (x, y) = when (display.rotation) {
+            Surface.ROTATION_90 ->
+                Pair(offsets.sensorLocationY, 0)
+            Surface.ROTATION_270 ->
+                Pair(displayHeight - offsets.sensorLocationY - bounds.width(), displayWidth)
+            Surface.ROTATION_180 ->
+                Pair(0, displayHeight - offsets.sensorLocationY - bounds.height())
+            else ->
+                Pair(displayWidth, offsets.sensorLocationY)
+        }
+        overlayViewParams.x = x
+        overlayViewParams.y = y
+    }
+}
+
+@RawRes
+private fun Display.asSideFpsAnimation(): Int = when (rotation) {
+    Surface.ROTATION_0 -> R.raw.sfps_pulse
+    Surface.ROTATION_180 -> R.raw.sfps_pulse
+    else -> R.raw.sfps_pulse_landscape
+}
+
+private fun Display.asSideFpsAnimationRotation(): Float = when (rotation) {
+    Surface.ROTATION_180 -> 180f
+    Surface.ROTATION_270 -> 180f
+    else -> 0f
+}
+
+private fun Display.isPortrait(): Boolean =
+    rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsView.java
deleted file mode 100644
index 4fc59d6..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsView.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2021 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 com.android.systemui.biometrics.SidefpsController.SFPS_AFFORDANCE_WIDTH;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.RectF;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.util.AttributeSet;
-import android.view.Surface;
-import android.widget.FrameLayout;
-
-/**
- * A view containing a normal drawable view for sidefps events.
- */
-public class SidefpsView extends FrameLayout {
-    private static final String TAG = "SidefpsView";
-    private static final int POINTER_SIZE_PX = 50;
-    private static final int ROUND_RADIUS = 15;
-
-    @NonNull private final RectF mSensorRect;
-    @NonNull private final Paint mSensorRectPaint;
-    @NonNull private final Paint mPointerText;
-    @NonNull private final Context mContext;
-
-    // Used to obtain the sensor location.
-    @NonNull private FingerprintSensorPropertiesInternal mSensorProps;
-    @Surface.Rotation private int mOrientation;
-
-    public SidefpsView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        super.setWillNotDraw(false);
-        mContext = context;
-        mPointerText = new Paint(0 /* flags */);
-        mPointerText.setAntiAlias(true);
-        mPointerText.setColor(Color.WHITE);
-        mPointerText.setTextSize(POINTER_SIZE_PX);
-
-        mSensorRect = new RectF();
-        mSensorRectPaint = new Paint(0 /* flags */);
-        mSensorRectPaint.setAntiAlias(true);
-        mSensorRectPaint.setColor(Color.BLUE); // TODO: Fix Color
-        mSensorRectPaint.setStyle(Paint.Style.FILL);
-    }
-
-    void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) {
-        mSensorProps = properties;
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-        canvas.drawRoundRect(mSensorRect, ROUND_RADIUS, ROUND_RADIUS, mSensorRectPaint);
-        int x, y;
-        if (mOrientation == Surface.ROTATION_90 || mOrientation == Surface.ROTATION_270) {
-            x = mSensorProps.sensorRadius + 10;
-            y = SFPS_AFFORDANCE_WIDTH / 2 + 15;
-        } else {
-            x = SFPS_AFFORDANCE_WIDTH / 2 - 10;
-            y = mSensorProps.sensorRadius + 30;
-        }
-        canvas.drawText(
-                ">",
-                x,
-                y,
-                mPointerText
-        );
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        mOrientation = mContext.getDisplay().getRotation();
-        if (mOrientation == Surface.ROTATION_90 || mOrientation == Surface.ROTATION_270) {
-            right = mSensorProps.sensorRadius * 2;
-            bottom = SFPS_AFFORDANCE_WIDTH;
-        } else {
-            right = SFPS_AFFORDANCE_WIDTH;
-            bottom = mSensorProps.sensorRadius * 2;
-        }
-
-        mSensorRect.set(
-                0,
-                0,
-                right,
-                bottom);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index b7c17ca..2ca6ad1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.biometrics;
 
-import static android.hardware.fingerprint.IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD;
+import static android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
 
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.internal.util.Preconditions.checkNotNull;
@@ -32,6 +32,8 @@
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.RectF;
+import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.display.DisplayManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -92,7 +94,7 @@
  * controls/manages all UDFPS sensors. In other words, a single controller is registered with
  * {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService}, and interfaces such
  * as {@link FingerprintManager#onPointerDown(int, int, int, float, float)} or
- * {@link IUdfpsOverlayController#showUdfpsOverlay(int)}should all have
+ * {@link IUdfpsOverlayController#showUdfpsOverlay(int)} should all have
  * {@code sensorId} parameters.
  */
 @SuppressWarnings("deprecation")
@@ -127,7 +129,7 @@
     @NonNull private final SystemClock mSystemClock;
     @NonNull private final UnlockedScreenOffAnimationController
             mUnlockedScreenOffAnimationController;
-    @VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener;
+    @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
     // sensors, this, in addition to a lot of the code here, will be updated.
     @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
@@ -240,8 +242,8 @@
                 @NonNull IUdfpsOverlayControllerCallback callback) {
             mFgExecutor.execute(() -> {
                 final UdfpsEnrollHelper enrollHelper;
-                if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR
-                        || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) {
+                if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
+                        || reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING) {
                     enrollHelper = new UdfpsEnrollHelper(mContext, reason);
                 } else {
                     enrollHelper = null;
@@ -319,7 +321,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (mServerRequest != null
-                    && mServerRequest.mRequestReason != REASON_AUTH_FPM_KEYGUARD
+                    && mServerRequest.mRequestReason != REASON_AUTH_KEYGUARD
                     && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
                 Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, mRequestReason: "
                         + mServerRequest.mRequestReason);
@@ -554,14 +556,6 @@
         mHbmProvider = hbmProvider.orElse(null);
         screenLifecycle.addObserver(mScreenObserver);
         mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON;
-        mOrientationListener = new BiometricOrientationEventListener(
-                context,
-                () -> {
-                    onOrientationChanged();
-                    return Unit.INSTANCE;
-                },
-                displayManager,
-                mainHandler);
         mKeyguardBypassController = keyguardBypassController;
         mConfigurationController = configurationController;
         mSystemClock = systemClock;
@@ -570,6 +564,15 @@
         mSensorProps = findFirstUdfps();
         // At least one UDFPS sensor exists
         checkArgument(mSensorProps != null);
+        mOrientationListener = new BiometricDisplayListener(
+                context,
+                displayManager,
+                mainHandler,
+                new BiometricDisplayListener.SensorType.UnderDisplayFingerprint(mSensorProps),
+                () -> {
+                    onOrientationChanged();
+                    return Unit.INSTANCE;
+                });
 
         mCoreLayoutParams = new WindowManager.LayoutParams(
                 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
@@ -639,10 +642,11 @@
         // on lockscreen and for the udfps light reveal animation on keyguard.
         // Keyguard is only shown in portrait mode for now, so this will need to
         // be updated if that ever changes.
-        return new RectF(mSensorProps.sensorLocationX - mSensorProps.sensorRadius,
-                mSensorProps.sensorLocationY - mSensorProps.sensorRadius,
-                mSensorProps.sensorLocationX + mSensorProps.sensorRadius,
-                mSensorProps.sensorLocationY + mSensorProps.sensorRadius);
+        final SensorLocationInternal location = mSensorProps.getLocation();
+        return new RectF(location.sensorLocationX - location.sensorRadius,
+                location.sensorLocationY - location.sensorRadius,
+                location.sensorLocationX + location.sensorRadius,
+                location.sensorLocationY + location.sensorRadius);
     }
 
     private void updateOverlay() {
@@ -655,6 +659,20 @@
         }
     }
 
+    private boolean shouldRotate(@Nullable UdfpsAnimationViewController animation) {
+        if (!(animation instanceof UdfpsKeyguardViewController)) {
+            // always rotate view if we're not on the keyguard
+            return true;
+        }
+
+        // on the keyguard, make sure we don't rotate if we're going to sleep or not occluded
+        if (mKeyguardUpdateMonitor.isGoingToSleep() || !mKeyguardStateController.isOccluded()) {
+            return false;
+        }
+
+        return true;
+    }
+
     private WindowManager.LayoutParams computeLayoutParams(
             @Nullable UdfpsAnimationViewController animation) {
         final int paddingX = animation != null ? animation.getPaddingX() : 0;
@@ -666,10 +684,11 @@
         }
 
         // Default dimensions assume portrait mode.
-        mCoreLayoutParams.x = mSensorProps.sensorLocationX - mSensorProps.sensorRadius - paddingX;
-        mCoreLayoutParams.y = mSensorProps.sensorLocationY - mSensorProps.sensorRadius - paddingY;
-        mCoreLayoutParams.height = 2 * mSensorProps.sensorRadius + 2 * paddingX;
-        mCoreLayoutParams.width = 2 * mSensorProps.sensorRadius + 2 * paddingY;
+        final SensorLocationInternal location = mSensorProps.getLocation();
+        mCoreLayoutParams.x = location.sensorLocationX - location.sensorRadius - paddingX;
+        mCoreLayoutParams.y = location.sensorLocationY - location.sensorRadius - paddingY;
+        mCoreLayoutParams.height = 2 * location.sensorRadius + 2 * paddingX;
+        mCoreLayoutParams.width = 2 * location.sensorRadius + 2 * paddingY;
 
         Point p = new Point();
         // Gets the size based on the current rotation of the display.
@@ -678,24 +697,28 @@
         // Transform dimensions if the device is in landscape mode
         switch (mContext.getDisplay().getRotation()) {
             case Surface.ROTATION_90:
-                if (animation instanceof UdfpsKeyguardViewController
-                        && mKeyguardUpdateMonitor.isGoingToSleep()) {
+                if (!shouldRotate(animation)) {
+                    Log.v(TAG, "skip rotating udfps location ROTATION_90");
                     break;
+                } else {
+                    Log.v(TAG, "rotate udfps location ROTATION_90");
                 }
-                mCoreLayoutParams.x = mSensorProps.sensorLocationY - mSensorProps.sensorRadius
+                mCoreLayoutParams.x = location.sensorLocationY - location.sensorRadius
                         - paddingX;
-                mCoreLayoutParams.y = p.y - mSensorProps.sensorLocationX - mSensorProps.sensorRadius
+                mCoreLayoutParams.y = p.y - location.sensorLocationX - location.sensorRadius
                         - paddingY;
                 break;
 
             case Surface.ROTATION_270:
-                if (animation instanceof UdfpsKeyguardViewController
-                        && mKeyguardUpdateMonitor.isGoingToSleep()) {
+                if (!shouldRotate(animation)) {
+                    Log.v(TAG, "skip rotating udfps location ROTATION_270");
                     break;
+                } else {
+                    Log.v(TAG, "rotate udfps location ROTATION_270");
                 }
-                mCoreLayoutParams.x = p.x - mSensorProps.sensorLocationY - mSensorProps.sensorRadius
+                mCoreLayoutParams.x = p.x - location.sensorLocationY - location.sensorRadius
                         - paddingX;
-                mCoreLayoutParams.y = mSensorProps.sensorLocationX - mSensorProps.sensorRadius
+                mCoreLayoutParams.y = location.sensorLocationX - location.sensorRadius
                         - paddingY;
                 break;
 
@@ -745,9 +768,9 @@
 
                 // This view overlaps the sensor area, so prevent it from being selectable
                 // during a11y.
-                if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR
-                        || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING
-                        || reason == IUdfpsOverlayController.REASON_AUTH_BP) {
+                if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
+                        || reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING
+                        || reason == BiometricOverlayConstants.REASON_AUTH_BP) {
                     mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
                 }
 
@@ -765,8 +788,8 @@
 
     private UdfpsAnimationViewController inflateUdfpsAnimation(int reason) {
         switch (reason) {
-            case IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR:
-            case IUdfpsOverlayController.REASON_ENROLL_ENROLLING:
+            case BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR:
+            case BiometricOverlayConstants.REASON_ENROLL_ENROLLING:
                 UdfpsEnrollView enrollView = (UdfpsEnrollView) mInflater.inflate(
                         R.layout.udfps_enroll_view, null);
                 mView.addView(enrollView);
@@ -778,7 +801,7 @@
                         mStatusBarOptional,
                         mDumpManager
                 );
-            case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD:
+            case BiometricOverlayConstants.REASON_AUTH_KEYGUARD:
                 UdfpsKeyguardView keyguardView = (UdfpsKeyguardView)
                         mInflater.inflate(R.layout.udfps_keyguard_view, null);
                 mView.addView(keyguardView);
@@ -796,7 +819,7 @@
                         mUnlockedScreenOffAnimationController,
                         this
                 );
-            case IUdfpsOverlayController.REASON_AUTH_BP:
+            case BiometricOverlayConstants.REASON_AUTH_BP:
                 // note: empty controller, currently shows no visual affordance
                 UdfpsBpView bpView = (UdfpsBpView) mInflater.inflate(R.layout.udfps_bp_view, null);
                 mView.addView(bpView);
@@ -806,7 +829,7 @@
                         mStatusBarOptional,
                         mDumpManager
                 );
-            case IUdfpsOverlayController.REASON_AUTH_FPM_OTHER:
+            case BiometricOverlayConstants.REASON_AUTH_OTHER:
                 UdfpsFpmOtherView authOtherView = (UdfpsFpmOtherView)
                         mInflater.inflate(R.layout.udfps_fpm_other_view, null);
                 mView.addView(authOtherView);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
index 7ccfb86..6cc8acf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.util.Log;
 import android.view.Surface;
@@ -93,7 +94,7 @@
         // Go through each of the children and do the custom measurement.
         int totalHeight = 0;
         final int numChildren = mView.getChildCount();
-        final int sensorDiameter = mSensorProps.sensorRadius * 2;
+        final int sensorDiameter = mSensorProps.getLocation().sensorRadius * 2;
         for (int i = 0; i < numChildren; i++) {
             final View child = mView.getChildAt(i);
             if (child.getId() == R.id.biometric_icon_frame) {
@@ -160,7 +161,7 @@
         final int horizontalSpacerWidth = calculateHorizontalSpacerWidthForLandscape(
                 mSensorProps, displayWidth, dialogMargin, horizontalInset);
 
-        final int sensorDiameter = mSensorProps.sensorRadius * 2;
+        final int sensorDiameter = mSensorProps.getLocation().sensorRadius * 2;
         final int remeasuredWidth = sensorDiameter + 2 * horizontalSpacerWidth;
 
         int remeasuredHeight = 0;
@@ -257,10 +258,10 @@
             @NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayHeightPx,
             int textIndicatorHeightPx, int buttonBarHeightPx, int dialogMarginPx,
             int navbarBottomInsetPx) {
-
+        final SensorLocationInternal location = sensorProperties.getLocation();
         final int sensorDistanceFromBottom = displayHeightPx
-                - sensorProperties.sensorLocationY
-                - sensorProperties.sensorRadius;
+                - location.sensorLocationY
+                - location.sensorRadius;
 
         final int spacerHeight = sensorDistanceFromBottom
                 - textIndicatorHeightPx
@@ -318,10 +319,10 @@
     static int calculateHorizontalSpacerWidthForLandscape(
             @NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayWidthPx,
             int dialogMarginPx, int navbarHorizontalInsetPx) {
-
+        final SensorLocationInternal location = sensorProperties.getLocation();
         final int sensorDistanceFromEdge = displayWidthPx
-                - sensorProperties.sensorLocationY
-                - sensorProperties.sensorRadius;
+                - location.sensorLocationY
+                - location.sensorRadius;
 
         final int horizontalPadding = sensorDistanceFromEdge
                 - dialogMarginPx
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
index 19148e3..c6d2192 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -20,7 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.PointF;
-import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.hardware.biometrics.BiometricOverlayConstants;
 import android.os.Build;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -119,7 +119,7 @@
     }
 
     boolean shouldShowProgressBar() {
-        return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING;
+        return mEnrollReason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING;
     }
 
     void onEnrollmentProgress(int remaining) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index d9edef4..c83006d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -63,7 +63,7 @@
 
     void updateSensorLocation(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
         View fingerprintAccessibilityView = findViewById(R.id.udfps_enroll_accessibility_view);
-        final int sensorHeight = sensorProps.sensorRadius * 2;
+        final int sensorHeight = sensorProps.getLocation().sensorRadius * 2;
         final int sensorWidth = sensorHeight;
         ViewGroup.LayoutParams params = fingerprintAccessibilityView.getLayoutParams();
         params.width = sensorWidth;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index 15f77ff..6d31ef0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -25,6 +25,7 @@
 import android.graphics.Paint;
 import android.graphics.PointF;
 import android.graphics.RectF;
+import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Build;
 import android.os.UserHandle;
@@ -141,11 +142,12 @@
                 : mAnimationViewController.getPaddingX();
         int paddingY = mAnimationViewController == null ? 0
                 : mAnimationViewController.getPaddingY();
+        final SensorLocationInternal location = mSensorProps.getLocation();
         mSensorRect.set(
                 paddingX,
                 paddingY,
-                2 * mSensorProps.sensorRadius + paddingX,
-                2 * mSensorProps.sensorRadius + paddingY);
+                2 * location.sensorRadius + paddingX,
+                2 * location.sensorRadius + paddingY);
 
         if (mAnimationViewController != null) {
             mAnimationViewController.onSensorRectUpdated(new RectF(mSensorRect));
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
new file mode 100644
index 0000000..c8afd72
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.statusbar.phone.CollapsedStatusBarFragment}-related messages.
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface CollapsedSbFragmentLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 19193f9..84c5a57 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -100,6 +100,17 @@
         return factory.create("PrivacyLog", 100);
     }
 
+    /**
+     * Provides a logging buffer for
+     * {@link com.android.systemui.statusbar.phone.CollapsedStatusBarFragment}.
+     */
+    @Provides
+    @SysUISingleton
+    @CollapsedSbFragmentLog
+    public static LogBuffer provideCollapsedSbFragmentLogBuffer(LogBufferFactory factory) {
+        return factory.create("CollapsedSbFragmentLog", 20);
+    }
+
     /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
index a0cf44c..16b971f 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
@@ -102,6 +102,7 @@
     }
 
     @Provides
+    @Singleton
     static PluginManager providesPluginManager(
             Context context,
             PluginActionManager.Factory instanceManagerFactory,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index b1db8a9..08da680 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -52,6 +52,7 @@
 import android.widget.Switch;
 import android.widget.TextView;
 
+import androidx.annotation.MainThread;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
@@ -60,7 +61,6 @@
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
-import com.android.settingslib.Utils;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
@@ -105,12 +105,10 @@
     private View mDivider;
     private ProgressBar mProgressBar;
     private LinearLayout mInternetDialogLayout;
-    private LinearLayout mInternetListLayout;
     private LinearLayout mConnectedWifListLayout;
-    private LinearLayout mConnectedWifList;
     private LinearLayout mMobileNetworkLayout;
-    private LinearLayout mMobileNetworkList;
     private LinearLayout mTurnWifiOnLayout;
+    private LinearLayout mEthernetLayout;
     private TextView mWifiToggleTitleText;
     private LinearLayout mSeeAllLayout;
     private RecyclerView mWifiRecyclerView;
@@ -126,7 +124,6 @@
     private Button mDoneButton;
     private Drawable mBackgroundOn;
     private int mListMaxHeight;
-    private int mLayoutWidth;
     private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private boolean mCanConfigMobileData;
 
@@ -181,8 +178,6 @@
         };
         mListMaxHeight = context.getResources().getDimensionPixelSize(
                 R.dimen.internet_dialog_list_max_height);
-        mLayoutWidth = context.getResources().getDimensionPixelSize(
-                R.dimen.internet_dialog_list_max_width);
         mUiEventLogger = uiEventLogger;
         mAdapter = new InternetAdapter(mInternetDialogController);
         if (!aboveStatusBar) {
@@ -221,13 +216,11 @@
         mInternetDialogSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
         mDivider = mDialogView.requireViewById(R.id.divider);
         mProgressBar = mDialogView.requireViewById(R.id.wifi_searching_progress);
-        mInternetListLayout = mDialogView.requireViewById(R.id.internet_list);
+        mEthernetLayout = mDialogView.requireViewById(R.id.ethernet_layout);
         mMobileNetworkLayout = mDialogView.requireViewById(R.id.mobile_network_layout);
-        mMobileNetworkList = mDialogView.requireViewById(R.id.mobile_network_list);
         mTurnWifiOnLayout = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
         mWifiToggleTitleText = mDialogView.requireViewById(R.id.wifi_toggle_title);
         mConnectedWifListLayout = mDialogView.requireViewById(R.id.wifi_connected_layout);
-        mConnectedWifList = mDialogView.requireViewById(R.id.wifi_connected_list);
         mConnectedWifiIcon = mDialogView.requireViewById(R.id.wifi_connected_icon);
         mConnectedWifiTitleText = mDialogView.requireViewById(R.id.wifi_connected_title);
         mConnectedWifiSummaryText = mDialogView.requireViewById(R.id.wifi_connected_summary);
@@ -309,6 +302,7 @@
         } else {
             mInternetDialogSubTitle.setText(getSubtitleText());
         }
+        updateEthernet();
         setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular());
 
         if (!mCanConfigWifi) {
@@ -355,6 +349,12 @@
         mDoneButton.setOnClickListener(v -> dismiss());
     }
 
+    @MainThread
+    private void updateEthernet() {
+        mEthernetLayout.setVisibility(
+                mInternetDialogController.hasEthernet() ? View.VISIBLE : View.GONE);
+    }
+
     private void setMobileDataLayout(boolean isCellularNetwork) {
         if (mInternetDialogController.isAirplaneModeEnabled()
                 || !mInternetDialogController.hasCarrier()) {
@@ -371,38 +371,33 @@
                 mMobileSummaryText.setVisibility(View.GONE);
             }
             mSignalIcon.setImageDrawable(getSignalStrengthDrawable());
-            if (mInternetDialogController.isNightMode()) {
-                int titleColor = isCellularNetwork ? mContext.getColor(
-                        R.color.connected_network_primary_color) : Utils.getColorAttrDefaultColor(
-                        mContext, android.R.attr.textColorPrimary);
-                int summaryColor = isCellularNetwork ? mContext.getColor(
-                        R.color.connected_network_secondary_color) : Utils.getColorAttrDefaultColor(
-                        mContext, android.R.attr.textColorSecondary);
-
-                mMobileTitleText.setTextColor(titleColor);
-                mMobileSummaryText.setTextColor(summaryColor);
-            }
+            mMobileTitleText.setTextAppearance(isCellularNetwork
+                    ? R.style.TextAppearance_InternetDialog_Active
+                    : R.style.TextAppearance_InternetDialog);
+            mMobileSummaryText.setTextAppearance(isCellularNetwork
+                    ? R.style.TextAppearance_InternetDialog_Secondary_Active
+                    : R.style.TextAppearance_InternetDialog_Secondary);
             mMobileNetworkLayout.setBackground(isCellularNetwork ? mBackgroundOn : null);
 
             mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
         }
     }
 
+    @MainThread
     private void updateWifiToggle(boolean isWifiEnabled, boolean isDeviceLocked) {
         mWiFiToggle.setChecked(isWifiEnabled);
-        if (isDeviceLocked && mInternetDialogController.isNightMode()) {
-            int titleColor = mConnectedWifiEntry != null ? mContext.getColor(
-                    R.color.connected_network_primary_color) : Utils.getColorAttrDefaultColor(
-                    mContext, android.R.attr.textColorPrimary);
-            mWifiToggleTitleText.setTextColor(titleColor);
+        if (isDeviceLocked) {
+            mWifiToggleTitleText.setTextAppearance((mConnectedWifiEntry != null)
+                    ? R.style.TextAppearance_InternetDialog_Active
+                    : R.style.TextAppearance_InternetDialog);
         }
         mTurnWifiOnLayout.setBackground(
                 (isDeviceLocked && mConnectedWifiEntry != null) ? mBackgroundOn : null);
     }
 
+    @MainThread
     private void updateConnectedWifi(boolean isWifiEnabled, boolean isDeviceLocked) {
         if (!isWifiEnabled || mConnectedWifiEntry == null || isDeviceLocked) {
-            mConnectedWifListLayout.setBackground(null);
             mConnectedWifListLayout.setVisibility(View.GONE);
             return;
         }
@@ -411,15 +406,8 @@
         mConnectedWifiSummaryText.setText(mConnectedWifiEntry.getSummary(false));
         mConnectedWifiIcon.setImageDrawable(
                 mInternetDialogController.getInternetWifiDrawable(mConnectedWifiEntry));
-        if (mInternetDialogController.isNightMode()) {
-            mConnectedWifiTitleText.setTextColor(
-                    mContext.getColor(R.color.connected_network_primary_color));
-            mConnectedWifiSummaryText.setTextColor(
-                    mContext.getColor(R.color.connected_network_secondary_color));
-        }
         mWifiSettingsIcon.setColorFilter(
                 mContext.getColor(R.color.connected_network_primary_color));
-        mConnectedWifListLayout.setBackground(mBackgroundOn);
     }
 
     void onClickConnectedWifi() {
@@ -528,11 +516,18 @@
     }
 
     @Override
+    @WorkerThread
     public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
         mHandler.post(() -> updateDialog());
     }
 
     @Override
+    @WorkerThread
+    public void onLost(Network network) {
+        mHandler.post(() -> updateDialog());
+    }
+
+    @Override
     public void onSubscriptionsChanged(int defaultDataSubId) {
         mDefaultDataSubId = defaultDataSubId;
         mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 50e7e43..95d3915 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -23,7 +23,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.Configuration;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -31,7 +30,6 @@
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.provider.Settings;
@@ -49,6 +47,7 @@
 import android.view.Gravity;
 import android.widget.Toast;
 
+import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
@@ -133,6 +132,7 @@
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private GlobalSettings mGlobalSettings;
     private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private ConnectivityManager.NetworkCallback mConnectivityManagerNetworkCallback;
 
     @VisibleForTesting
     protected ActivityStarter mActivityStarter;
@@ -146,6 +146,8 @@
     protected boolean mCanConfigWifi;
     @VisibleForTesting
     protected KeyguardStateController mKeyguardStateController;
+    @VisibleForTesting
+    protected boolean mHasEthernet = false;
 
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -193,6 +195,7 @@
         mAccessPointController = accessPointController;
         mConfig = MobileMappings.Config.readConfig(mContext);
         mWifiIconInjector = new WifiUtils.InternetIconInjector(mContext);
+        mConnectivityManagerNetworkCallback = new DataConnectivityListener();
     }
 
     void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) {
@@ -216,9 +219,7 @@
         mInternetTelephonyCallback = new InternetTelephonyCallback();
         mTelephonyManager.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback);
         // Listen the connectivity changes
-        mConnectivityManager.registerNetworkCallback(new NetworkRequest.Builder()
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build(), new DataConnectivityListener(), mHandler);
+        mConnectivityManager.registerDefaultNetworkCallback(mConnectivityManagerNetworkCallback);
         mCanConfigWifi = canConfigWifi;
         scanWifiAccessPoints();
     }
@@ -233,6 +234,7 @@
                 mOnSubscriptionsChangedListener);
         mAccessPointController.removeAccessPointCallback(this);
         mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
+        mConnectivityManager.unregisterNetworkCallback(mConnectivityManagerNetworkCallback);
     }
 
     @VisibleForTesting
@@ -351,11 +353,6 @@
         return drawable;
     }
 
-    boolean isNightMode() {
-        return (mContext.getResources().getConfiguration().uiMode
-                & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
-    }
-
     Drawable getSignalStrengthDrawable() {
         Drawable drawable = mContext.getDrawable(
                 R.drawable.ic_signal_strength_zero_bar_no_internet);
@@ -800,6 +797,9 @@
         }
 
         int count = MAX_WIFI_ENTRY_COUNT;
+        if (mHasEthernet) {
+            count -= 1;
+        }
         if (hasCarrier()) {
             count -= 1;
         }
@@ -870,21 +870,30 @@
         @Override
         @WorkerThread
         public void onCapabilitiesChanged(@NonNull Network network,
-                @NonNull NetworkCapabilities networkCapabilities) {
-            if (mCanConfigWifi) {
-                for (int transport : networkCapabilities.getTransportTypes()) {
-                    if (transport == NetworkCapabilities.TRANSPORT_WIFI) {
-                        scanWifiAccessPoints();
-                        break;
-                    }
-                }
+                @NonNull NetworkCapabilities capabilities) {
+            mHasEthernet = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET);
+            if (mCanConfigWifi && (mHasEthernet || capabilities.hasTransport(
+                    NetworkCapabilities.TRANSPORT_WIFI))) {
+                scanWifiAccessPoints();
             }
-            final Network activeNetwork = mConnectivityManager.getActiveNetwork();
-            if (activeNetwork != null && activeNetwork.equals(network)) {
-                // update UI
-                mCallback.onCapabilitiesChanged(network, networkCapabilities);
-            }
+            // update UI
+            mCallback.onCapabilitiesChanged(network, capabilities);
         }
+
+        @Override
+        @WorkerThread
+        public void onLost(@NonNull Network network) {
+            mHasEthernet = false;
+            mCallback.onLost(network);
+        }
+    }
+
+    /**
+     * Return {@code true} If the Ethernet exists
+     */
+    @MainThread
+    public boolean hasEthernet() {
+        return mHasEthernet;
     }
 
     private final BroadcastReceiver mConnectionStateReceiver = new BroadcastReceiver() {
@@ -934,6 +943,8 @@
 
         void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities);
 
+        void onLost(@NonNull Network network);
+
         void onSubscriptionsChanged(int defaultDataSubId);
 
         void onServiceStateChanged(ServiceState serviceState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 0bcec07..17bcfe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -800,7 +800,9 @@
      * Show message on the keyguard for how the user can unlock/enter their device.
      */
     public void showActionToUnlock() {
-        if (mDozing) {
+        if (mDozing
+                && !mKeyguardUpdateMonitor.getUserCanSkipBouncer(
+                        KeyguardUpdateMonitor.getCurrentUser())) {
             return;
         }
 
@@ -811,7 +813,7 @@
                 String message = mContext.getString(R.string.keyguard_retry);
                 mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState);
             }
-        } else if (mKeyguardUpdateMonitor.isScreenOn()) {
+        } else {
             showTransientIndication(mContext.getString(R.string.keyguard_unlock),
                     false /* isError */, true /* hideOnScreenOff */);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 45348d9..1bac8fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -42,6 +42,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.DisableFlagsLogger.DisableState;
 import com.android.systemui.statusbar.OperatorNameView;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -93,6 +94,7 @@
     private Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
     private DarkIconManager mDarkIconManager;
     private final CommandQueue mCommandQueue;
+    private final CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger;
     private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
     private final OngoingCallController mOngoingCallController;
     private final SystemStatusAnimationScheduler mAnimationScheduler;
@@ -131,6 +133,7 @@
             StatusBarStateController statusBarStateController,
             Lazy<Optional<StatusBar>> statusBarOptionalLazy,
             CommandQueue commandQueue,
+            CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
             OperatorNameViewController.Factory operatorNameViewControllerFactory
     ) {
         mOngoingCallController = ongoingCallController;
@@ -144,6 +147,7 @@
         mStatusBarStateController = statusBarStateController;
         mStatusBarOptionalLazy = statusBarOptionalLazy;
         mCommandQueue = commandQueue;
+        mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger;
         mOperatorNameViewControllerFactory = operatorNameViewControllerFactory;
     }
 
@@ -244,7 +248,14 @@
         if (displayId != getContext().getDisplayId()) {
             return;
         }
+
+        int state1BeforeAdjustment = state1;
         state1 = adjustDisableFlags(state1);
+
+        mCollapsedStatusBarFragmentLogger.logDisableFlagChange(
+                new DisableState(state1BeforeAdjustment, state2),
+                new DisableState(state1, state2));
+
         final int old1 = mDisabled1;
         final int diff1 = state1 ^ old1;
         final int old2 = mDisabled2;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLogger.kt
new file mode 100644
index 0000000..3c2b555
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLogger.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.CollapsedSbFragmentLog
+import com.android.systemui.statusbar.DisableFlagsLogger
+import javax.inject.Inject
+
+/** Used by [CollapsedStatusBarFragment] to log messages to a [LogBuffer]. */
+class CollapsedStatusBarFragmentLogger @Inject constructor(
+        @CollapsedSbFragmentLog private val buffer: LogBuffer,
+        private val disableFlagsLogger: DisableFlagsLogger,
+) {
+
+    /** Logs a string representing the old and new disable flag states to [buffer]. */
+    fun logDisableFlagChange(
+            oldState: DisableFlagsLogger.DisableState,
+            newState: DisableFlagsLogger.DisableState) {
+        buffer.log(
+                TAG,
+                LogLevel.INFO,
+                {
+                    int1 = oldState.disable1
+                    int2 = oldState.disable2
+                    long1 = newState.disable1.toLong()
+                    long2 = newState.disable2.toLong()
+                },
+                {
+                    disableFlagsLogger.getDisableFlagsString(
+                            DisableFlagsLogger.DisableState(int1, int2),
+                            DisableFlagsLogger.DisableState(long1.toInt(), long2.toInt())
+                    )
+                }
+        )
+    }
+}
+
+private const val TAG = "CollapsedSbFragment"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 8c0dfc5..f1d5e02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -252,8 +252,6 @@
                 mKeyguardViewController.resetSecurityContainer();
                 showPromptReason(mBouncerPromptReason);
             }
-            SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
-                    SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN);
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 3aca18e..6d76e95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -883,13 +883,13 @@
         mBigClockContainer = mView.findViewById(R.id.big_clock_container);
         mCommunalView = mView.findViewById(R.id.communal_host);
 
-        UserAvatarView userAvatarView = null;
+        FrameLayout userAvatarContainer = null;
         KeyguardUserSwitcherView keyguardUserSwitcherView = null;
 
         if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) {
             if (mKeyguardQsUserSwitchEnabled) {
                 ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub);
-                userAvatarView = (UserAvatarView) stub.inflate();
+                userAvatarContainer = (FrameLayout) stub.inflate();
             } else {
                 ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub);
                 keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate();
@@ -919,7 +919,7 @@
 
         updateViewControllers(
                 mView.findViewById(R.id.keyguard_status_view),
-                userAvatarView,
+                userAvatarContainer,
                 keyguardUserSwitcherView,
                 mView.findViewById(R.id.idle_host_view),
                 mCommunalView);
@@ -1011,7 +1011,7 @@
     }
 
     private void updateViewControllers(KeyguardStatusView keyguardStatusView,
-            UserAvatarView userAvatarView,
+            FrameLayout userAvatarView,
             KeyguardUserSwitcherView keyguardUserSwitcherView,
             IdleHostView idleHostView,
             CommunalHostView communalView) {
@@ -1174,7 +1174,7 @@
                 !mKeyguardQsUserSwitchEnabled
                         && mKeyguardUserSwitcherEnabled
                         && isUserSwitcherEnabled;
-        UserAvatarView userAvatarView = (UserAvatarView) reInflateStub(
+        FrameLayout userAvatarView = (FrameLayout) reInflateStub(
                 R.id.keyguard_qs_user_switch_view /* viewId */,
                 R.id.keyguard_qs_user_switch_stub /* stubId */,
                 R.layout.keyguard_qs_user_switch /* layoutId */,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index a09b30f..af556a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -79,6 +79,8 @@
     private int mStatusBarHeight;
     @Nullable
     private List<StatusBar.ExpansionChangedListener> mExpansionChangedListeners;
+    @Nullable
+    private PanelExpansionStateChangedListener mPanelExpansionStateChangedListener;
 
     private PanelEnabledProvider mPanelEnabledProvider;
 
@@ -102,6 +104,10 @@
         mExpansionChangedListeners = listeners;
     }
 
+    void setPanelExpansionStateChangedListener(PanelExpansionStateChangedListener listener) {
+        mPanelExpansionStateChangedListener = listener;
+    }
+
     public void setScrimController(ScrimController scrimController) {
         mScrimController = scrimController;
     }
@@ -289,11 +295,10 @@
         super.panelExpansionChanged(frac, expanded);
         updateScrimFraction();
         if ((frac == 0 || frac == 1)) {
-            if (mBar.getNavigationBarView() != null) {
-                mBar.getNavigationBarView().onStatusBarPanelStateChanged();
-            }
-            if (mBar.getNotificationPanelViewController() != null) {
-                mBar.getNotificationPanelViewController().updateSystemUiStateFlags();
+            if (mPanelExpansionStateChangedListener != null) {
+                mPanelExpansionStateChangedListener.onPanelExpansionStateChanged();
+            } else {
+                Log.w(TAG, "No PanelExpansionStateChangedListener provided.");
             }
         }
 
@@ -412,4 +417,10 @@
         /** Returns true if the panel is enabled and false otherwise. */
         boolean panelEnabled();
     }
+
+    /** A listener that will be notified when a panel's expansion state may have changed. */
+    public interface PanelExpansionStateChangedListener {
+        /** Called when a panel's expansion state may have changed. */
+        void onPanelExpansionStateChanged();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 9799533..28040fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -27,7 +27,8 @@
 class PhoneStatusBarViewController(
     view: PhoneStatusBarView,
     commandQueue: CommandQueue,
-    statusBarMoveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?
+    statusBarMoveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
+    panelExpansionStateChangedListener: PhoneStatusBarView.PanelExpansionStateChangedListener,
 ) : ViewController<PhoneStatusBarView>(view) {
 
     override fun onViewAttached() {}
@@ -37,6 +38,7 @@
         mView.setPanelEnabledProvider {
             commandQueue.panelsEnabled()
         }
+        mView.setPanelExpansionStateChangedListener(panelExpansionStateChangedListener)
 
         statusBarMoveFromCenterAnimationController?.let { animationController ->
             val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 304b2a9..50db136 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -454,6 +454,7 @@
     @Nullable
     protected LockscreenWallpaper mLockscreenWallpaper;
     private final AutoHideController mAutoHideController;
+    private final CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger;
 
     private final Point mCurrentDisplaySize = new Point();
 
@@ -743,6 +744,7 @@
             DozeScrimController dozeScrimController,
             VolumeComponent volumeComponent,
             CommandQueue commandQueue,
+            CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
             StatusBarComponent.Factory statusBarComponentFactory,
             PluginManager pluginManager,
             Optional<LegacySplitScreen> splitScreenOptional,
@@ -842,6 +844,7 @@
         mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy;
         mVolumeComponent = volumeComponent;
         mCommandQueue = commandQueue;
+        mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger;
         mStatusBarComponentFactory = statusBarComponentFactory;
         mPluginManager = pluginManager;
         mSplitScreenOptional = splitScreenOptional;
@@ -1149,8 +1152,11 @@
                         moveFromCenterAnimation = mMoveFromCenterAnimation.get();
                     }
                     mPhoneStatusBarViewController =
-                            new PhoneStatusBarViewController(mStatusBarView, mCommandQueue,
-                                    moveFromCenterAnimation);
+                            new PhoneStatusBarViewController(
+                                    mStatusBarView,
+                                    mCommandQueue,
+                                    moveFromCenterAnimation,
+                                    this::onPanelExpansionStateChanged);
                     mPhoneStatusBarViewController.init();
 
                     mBatteryMeterViewController = new BatteryMeterViewController(
@@ -1215,6 +1221,7 @@
                                 mStatusBarStateController,
                                 () -> Optional.of(this),
                                 mCommandQueue,
+                                mCollapsedStatusBarFragmentLogger,
                                 mOperatorNameViewControllerFactory
                         ),
                         CollapsedStatusBarFragment.TAG)
@@ -1420,6 +1427,15 @@
         }
     }
 
+    private void onPanelExpansionStateChanged() {
+        if (getNavigationBarView() != null) {
+            getNavigationBarView().onStatusBarPanelStateChanged();
+        }
+        if (getNotificationPanelViewController() != null) {
+            getNotificationPanelViewController().updateSystemUiStateFlags();
+        }
+    }
+
     @NonNull
     @Override
     public Lifecycle getLifecycle() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index befea41..c45068e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -72,6 +72,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
+import com.android.systemui.statusbar.phone.CollapsedStatusBarFragmentLogger;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.DozeScrimController;
 import com.android.systemui.statusbar.phone.DozeServiceHost;
@@ -188,6 +189,7 @@
             DozeScrimController dozeScrimController,
             VolumeComponent volumeComponent,
             CommandQueue commandQueue,
+            CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
             StatusBarComponent.Factory statusBarComponentFactory,
             PluginManager pluginManager,
             Optional<LegacySplitScreen> splitScreenOptional,
@@ -284,6 +286,7 @@
                 dozeScrimController,
                 volumeComponent,
                 commandQueue,
+                collapsedStatusBarFragmentLogger,
                 statusBarComponentFactory,
                 pluginManager,
                 splitScreenOptional,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index b6c683f..793a9e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -27,6 +27,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
 
 import com.android.keyguard.KeyguardConstants;
 import com.android.keyguard.KeyguardVisibilityHelper;
@@ -59,7 +60,7 @@
  * Manages the user switch on the Keyguard that is used for opening the QS user panel.
  */
 @KeyguardUserSwitcherScope
-public class KeyguardQsUserSwitchController extends ViewController<UserAvatarView> {
+public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> {
 
     private static final String TAG = "KeyguardQsUserSwitchController";
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -81,6 +82,7 @@
     private final FeatureFlags mFeatureFlags;
     private final UserSwitchDialogController mUserSwitchDialogController;
     private NotificationPanelViewController mNotificationPanelViewController;
+    private UserAvatarView mUserAvatarView;
     UserSwitcherController.UserRecord mCurrentUser;
 
     // State info for the user switch and keyguard
@@ -116,7 +118,7 @@
 
     @Inject
     public KeyguardQsUserSwitchController(
-            UserAvatarView view,
+            FrameLayout view,
             Context context,
             @Main Resources resources,
             ScreenLifecycle screenLifecycle,
@@ -154,6 +156,7 @@
     protected void onInit() {
         super.onInit();
         if (DEBUG) Log.d(TAG, "onInit");
+        mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar);
         mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) {
             @Override
             public View getView(int position, View convertView, ViewGroup parent) {
@@ -161,11 +164,10 @@
             }
         };
 
-        mView.setOnClickListener(v -> {
+        mUserAvatarView.setOnClickListener(v -> {
             if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
                 return;
             }
-
             if (isListAnimating()) {
                 return;
             }
@@ -178,7 +180,7 @@
             }
         });
 
-        mView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+        mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
                 super.onInitializeAccessibilityNodeInfo(host, info);
                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
@@ -252,12 +254,12 @@
                     R.string.accessibility_multi_user_switch_switcher);
         }
 
-        if (!TextUtils.equals(mView.getContentDescription(), contentDescription)) {
-            mView.setContentDescription(contentDescription);
+        if (!TextUtils.equals(mUserAvatarView.getContentDescription(), contentDescription)) {
+            mUserAvatarView.setContentDescription(contentDescription);
         }
 
         int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL;
-        mView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
+        mUserAvatarView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
     }
 
     Drawable getCurrentUserIcon() {
@@ -284,7 +286,7 @@
      * Get the height of the keyguard user switcher view when closed.
      */
     public int getUserIconHeight() {
-        return mView.getHeight();
+        return mUserAvatarView.getHeight();
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
index b6d1e42..619d48d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
@@ -29,6 +29,7 @@
 
 import android.content.Context;
 import android.hardware.biometrics.ComponentInfoInternal;
+import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -257,9 +258,10 @@
                 componentInfo,
                 FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
                 true /* resetLockoutRequiresHardwareAuthToken */,
-                540 /* sensorLocationX */,
-                1600 /* sensorLocationY */,
-                100 /* sensorRadius */);
+                List.of(new SensorLocationInternal("" /* displayId */,
+                        540 /* sensorLocationX */,
+                        1600 /* sensorLocationY */,
+                        100 /* sensorRadius */)));
     }
 
     public class TestableView extends AuthBiometricFaceToFingerprintView {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index 977b05c..01e60e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.biometrics
 
+import android.graphics.Rect
 import android.hardware.biometrics.SensorProperties
 import android.hardware.display.DisplayManager
 import android.hardware.display.DisplayManagerGlobal
@@ -30,8 +31,12 @@
 import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
 import android.view.DisplayInfo
 import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowInsets
 import android.view.WindowManager
+import android.view.WindowMetrics
 import androidx.test.filters.SmallTest
+import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -42,9 +47,13 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 
@@ -66,11 +75,13 @@
     @Mock
     lateinit var windowManager: WindowManager
     @Mock
-    lateinit var sidefpsView: SidefpsView
+    lateinit var sidefpsView: View
     @Mock
     lateinit var displayManager: DisplayManager
     @Mock
     lateinit var handler: Handler
+    @Captor
+    lateinit var overlayCaptor: ArgumentCaptor<View>
 
     private val executor = FakeExecutor(FakeSystemClock())
     private lateinit var overlayController: ISidefpsController
@@ -79,6 +90,8 @@
     @Before
     fun setup() {
         `when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sidefpsView)
+        `when`(sidefpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
+            .thenReturn(mock(LottieAnimationView::class.java))
         `when`(fingerprintManager.sensorPropertiesInternal).thenReturn(
                 listOf(
                         FingerprintSensorPropertiesInternal(
@@ -99,6 +112,9 @@
                         DEFAULT_DISPLAY_ADJUSTMENTS
                 )
         )
+        `when`(windowManager.maximumWindowMetrics).thenReturn(
+            WindowMetrics(Rect(0, 0, 800, 800), WindowInsets.CONSUMED)
+        )
 
         sideFpsController = SidefpsController(
                 mContext, layoutInflater, fingerprintManager, windowManager, executor,
@@ -121,4 +137,44 @@
         executor.runAllReady()
         verify(displayManager).unregisterDisplayListener(any())
     }
+
+    @Test
+    fun testShowsAndHides() {
+        overlayController.show()
+        executor.runAllReady()
+
+        verify(windowManager).addView(overlayCaptor.capture(), any())
+
+        reset(windowManager)
+        overlayController.hide()
+        executor.runAllReady()
+
+        verify(windowManager, never()).addView(any(), any())
+        verify(windowManager).removeView(eq(overlayCaptor.value))
+    }
+
+    @Test
+    fun testShowsOnce() {
+        repeat(5) {
+            overlayController.show()
+            executor.runAllReady()
+        }
+
+        verify(windowManager).addView(any(), any())
+        verify(windowManager, never()).removeView(any())
+    }
+
+    @Test
+    fun testHidesOnce() {
+        overlayController.show()
+        executor.runAllReady()
+
+        repeat(5) {
+            overlayController.hide()
+            executor.runAllReady()
+        }
+
+        verify(windowManager).addView(any(), any())
+        verify(windowManager).removeView(any())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 07c20ac..1197660 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -32,6 +32,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.res.TypedArray;
+import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.display.DisplayManager;
@@ -191,6 +192,7 @@
         when(mLayoutInflater.inflate(R.layout.udfps_keyguard_view, null))
                 .thenReturn(mKeyguardView); // for showOverlay REASON_AUTH_FPM_KEYGUARD
         when(mEnrollView.getContext()).thenReturn(mContext);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
         final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
 
         final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
@@ -256,7 +258,7 @@
     @Test
     public void dozeTimeTick() throws RemoteException {
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
         mUdfpsController.dozeTimeTick();
         verify(mUdfpsView).dozeTimeTick();
@@ -271,7 +273,7 @@
 
         // GIVEN that the overlay is showing
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         // WHEN ACTION_DOWN is received
@@ -294,7 +296,7 @@
 
         // GIVEN that the overlay is showing
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         // WHEN ACTION_DOWN is received
@@ -317,7 +319,7 @@
 
         // GIVEN that the overlay is showing
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         // WHEN ACTION_DOWN is received
@@ -340,7 +342,7 @@
 
         // GIVEN that the overlay is showing
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         // WHEN ACTION_DOWN is received
@@ -362,7 +364,7 @@
 
         // GIVEN that the overlay is showing
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         // WHEN ACTION_MOVE is received
@@ -384,7 +386,7 @@
 
         // GIVEN that the overlay is showing
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         // WHEN multiple touches are received
@@ -404,7 +406,7 @@
     @Test
     public void showUdfpsOverlay_addsViewToWindow() throws RemoteException {
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
         verify(mWindowManager).addView(eq(mUdfpsView), any());
     }
@@ -412,7 +414,7 @@
     @Test
     public void hideUdfpsOverlay_removesViewFromWindow() throws RemoteException {
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
         mFgExecutor.runAllReady();
         verify(mWindowManager).removeView(eq(mUdfpsView));
@@ -422,7 +424,7 @@
     public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException {
         // GIVEN overlay was showing and the udfps bouncer is showing
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
 
         // WHEN the overlay is hidden
@@ -436,7 +438,7 @@
     @Test
     public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         verify(mDisplayManager).registerDisplayListener(any(), eq(mHandler));
@@ -455,7 +457,7 @@
 
         // GIVEN that the overlay is showing
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
         // WHEN ACTION_DOWN is received
         verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
@@ -479,7 +481,7 @@
     public void aodInterrupt() throws RemoteException {
         // GIVEN that the overlay is showing and screen is on
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
         // WHEN fingerprint is requested because of AOD interrupt
@@ -496,7 +498,7 @@
     public void cancelAodInterrupt() throws RemoteException {
         // GIVEN AOD interrupt
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
@@ -511,7 +513,7 @@
     public void aodInterruptTimeout() throws RemoteException {
         // GIVEN AOD interrupt
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
@@ -527,7 +529,7 @@
     public void aodInterruptScreenOff() throws RemoteException {
         // GIVEN screen off
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOff();
         mFgExecutor.runAllReady();
 
@@ -546,7 +548,7 @@
 
         // GIVEN that the overlay is showing
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         // WHEN ACTION_DOWN is received
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
index 88b4039..27755ede 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 
 import android.hardware.biometrics.ComponentInfoInternal;
+import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -61,8 +62,9 @@
                 0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
                 componentInfo,
                 FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
-                true /* resetLockoutRequiresHardwareAuthToken */, sensorLocationX, sensorLocationY,
-                sensorRadius);
+                true /* resetLockoutRequiresHardwareAuthToken */,
+                List.of(new SensorLocationInternal("" /* displayId */,
+                        sensorLocationX, sensorLocationY, sensorRadius)));
 
         assertEquals(970,
                 UdfpsDialogMeasureAdapter.calculateBottomSpacerHeightForPortrait(
@@ -125,8 +127,9 @@
                 0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
                 componentInfo,
                 FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
-                true /* resetLockoutRequiresHardwareAuthToken */, sensorLocationX, sensorLocationY,
-                sensorRadius);
+                true /* resetLockoutRequiresHardwareAuthToken */,
+                List.of(new SensorLocationInternal("" /* displayId */,
+                        sensorLocationX, sensorLocationY, sensorRadius)));
 
         assertEquals(1205,
                 UdfpsDialogMeasureAdapter.calculateHorizontalSpacerWidthForLandscape(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index d279bbb..3e9fbcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.PointF;
+import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Vibrator;
 import android.testing.AndroidTestingRunner;
@@ -132,7 +133,8 @@
                         /* component info */ new ArrayList<>(),
                         /* sensorType */ 3,
                         /* resetLockoutRequiresHwToken */ false,
-                        (int) udfpsLocation.x, (int) udfpsLocation.y, radius);
+                        List.of(new SensorLocationInternal("" /* displayId */,
+                                (int) udfpsLocation.x, (int) udfpsLocation.y, radius)));
         when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
         when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));
 
@@ -165,7 +167,8 @@
                         /* component info */ new ArrayList<>(),
                         /* sensorType */ 3,
                         /* resetLockoutRequiresHwToken */ false,
-                        (int) udfpsLocation.x, (int) udfpsLocation.y, radius);
+                        List.of(new SensorLocationInternal("" /* displayId */,
+                                (int) udfpsLocation.x, (int) udfpsLocation.y, radius)));
         when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
         when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index baddacc..18f48a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -444,6 +444,47 @@
         verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
     }
 
+    @Test
+    public void onAccessPointsChanged_fourWifiEntries_callbackCutMore() {
+        reset(mInternetDialogCallback);
+        mInternetDialogController.setAirplaneModeEnabled(true);
+        mAccessPoints.clear();
+        mAccessPoints.add(mWifiEntry1);
+        mAccessPoints.add(mWifiEntry2);
+        mAccessPoints.add(mWifiEntry3);
+        mAccessPoints.add(mWifiEntry4);
+
+        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+        mWifiEntries.clear();
+        mWifiEntries.add(mWifiEntry1);
+        mWifiEntries.add(mWifiEntry2);
+        mWifiEntries.add(mWifiEntry3);
+        mWifiEntries.add(mWifiEntry4);
+        verify(mInternetDialogCallback)
+                .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+
+        // If the Ethernet exists, then Wi-Fi entries will cut last one.
+        reset(mInternetDialogCallback);
+        mInternetDialogController.mHasEthernet = true;
+
+        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+        mWifiEntries.remove(mWifiEntry4);
+        verify(mInternetDialogCallback)
+                .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+
+        // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
+        reset(mInternetDialogCallback);
+        mInternetDialogController.setAirplaneModeEnabled(false);
+
+        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+        mWifiEntries.remove(mWifiEntry3);
+        verify(mInternetDialogCallback)
+                .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+    }
+
     private String getResourcesString(String name) {
         return mContext.getResources().getString(getResourcesId(name));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index fa9c053..c42b64a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -67,6 +67,7 @@
     private InternetDialog mInternetDialog;
     private View mDialogView;
     private View mSubTitle;
+    private LinearLayout mEthernet;
     private LinearLayout mMobileDataToggle;
     private LinearLayout mWifiToggle;
     private LinearLayout mConnectedWifi;
@@ -97,6 +98,7 @@
 
         mDialogView = mInternetDialog.mDialogView;
         mSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
+        mEthernet = mDialogView.requireViewById(R.id.ethernet_layout);
         mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_network_layout);
         mWifiToggle = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
         mConnectedWifi = mDialogView.requireViewById(R.id.wifi_connected_layout);
@@ -139,6 +141,46 @@
     }
 
     @Test
+    public void updateDialog_apmOffAndHasEthernet_showEthernet() {
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+        when(mInternetDialogController.hasEthernet()).thenReturn(true);
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void updateDialog_apmOffAndNoEthernet_hideEthernet() {
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+        when(mInternetDialogController.hasEthernet()).thenReturn(false);
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void updateDialog_apmOnAndHasEthernet_showEthernet() {
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+        when(mInternetDialogController.hasEthernet()).thenReturn(true);
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void updateDialog_apmOnAndNoEthernet_hideEthernet() {
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+        when(mInternetDialogController.hasEthernet()).thenReturn(false);
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
     public void updateDialog_withApmOn_mobileDataLayoutGone() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLoggerTest.kt
new file mode 100644
index 0000000..f3136c7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLoggerTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.statusbar.DisableFlagsLogger
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.Mockito.mock
+import java.io.PrintWriter
+import java.io.StringWriter
+
+@SmallTest
+class CollapsedStatusBarFragmentLoggerTest : SysuiTestCase() {
+
+    private val buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
+            .create("buffer", 10)
+    private val disableFlagsLogger = DisableFlagsLogger(
+            listOf(DisableFlagsLogger.DisableFlag(0b001, 'A', 'a')),
+            listOf(DisableFlagsLogger.DisableFlag(0b001, 'B', 'b'))
+    )
+    private val logger = CollapsedStatusBarFragmentLogger(buffer, disableFlagsLogger)
+
+    @Test
+    fun logToBuffer_bufferHasStates() {
+        val state = DisableFlagsLogger.DisableState(0, 1)
+
+        logger.logDisableFlagChange(state, state)
+
+        val stringWriter = StringWriter()
+        buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+        val actualString = stringWriter.toString()
+        val expectedLogString = disableFlagsLogger.getDisableFlagsString(state, state)
+
+        assertThat(actualString).contains(expectedLogString)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index e159cae..48cdd42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -39,8 +39,11 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogcatEchoTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.DisableFlagsLogger;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
@@ -265,6 +268,10 @@
                 mStatusBarStateController,
                 () -> Optional.of(mStatusBar),
                 mCommandQueue,
+                new CollapsedStatusBarFragmentLogger(
+                        new LogBuffer("TEST", 1, 1, mock(LogcatEchoTracker.class)),
+                        new DisableFlagsLogger()
+                        ),
                 mOperatorNameViewControllerFactory);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index c7d4794..52a5e06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -17,11 +17,14 @@
 package com.android.systemui.statusbar.phone
 
 import android.view.LayoutInflater
+import android.view.ViewGroup
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -29,14 +32,20 @@
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
-import com.android.systemui.R
-import com.android.systemui.util.mockito.any
 
 @SmallTest
 class PhoneStatusBarViewControllerTest : SysuiTestCase() {
 
+    private val stateChangeListener = TestStateChangedListener()
+
     @Mock
     private lateinit var commandQueue: CommandQueue
+    @Mock
+    private lateinit var panelViewController: PanelViewController
+    @Mock
+    private lateinit var panelView: ViewGroup
+    @Mock
+    private lateinit var scrimController: ScrimController
 
     @Mock
     private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController
@@ -47,13 +56,23 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        `when`(panelViewController.view).thenReturn(panelView)
+
         // create the view on main thread as it requires main looper
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
             val parent = FrameLayout(mContext) // add parent to keep layout params
             view = LayoutInflater.from(mContext)
                 .inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
+            view.setPanel(panelViewController)
+            view.setScrimController(scrimController)
         }
-        controller = PhoneStatusBarViewController(view, commandQueue, null)
+
+        controller = PhoneStatusBarViewController(
+                view,
+                commandQueue,
+                null,
+                stateChangeListener
+        )
     }
 
     @Test
@@ -73,8 +92,29 @@
 
     @Test
     fun constructor_moveFromCenterAnimationIsNotNull_moveFromCenterAnimationInitialized() {
-        controller = PhoneStatusBarViewController(view, commandQueue, moveFromCenterAnimation)
+        controller = PhoneStatusBarViewController(
+                view, commandQueue, moveFromCenterAnimation, stateChangeListener
+        )
 
         verify(moveFromCenterAnimation).init(any(), any())
     }
+
+    @Test
+    fun constructor_setsExpansionStateChangedListenerOnView() {
+        assertThat(stateChangeListener.stateChangeCalled).isFalse()
+
+        // If the constructor correctly set the listener, then it should be used when
+        // [PhoneStatusBarView.panelExpansionChanged] is called.
+        view.panelExpansionChanged(0f, false)
+
+        assertThat(stateChangeListener.stateChangeCalled).isTrue()
+    }
+
+    private class TestStateChangedListener : PhoneStatusBarView.PanelExpansionStateChangedListener {
+        var stateChangeCalled: Boolean = false
+
+        override fun onPanelExpansionStateChanged() {
+            stateChangeCalled = true
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index 49ab6eb..aee9f12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -16,20 +16,38 @@
 
 package com.android.systemui.statusbar.phone
 
+import android.view.ViewGroup
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 class PhoneStatusBarViewTest : SysuiTestCase() {
 
+    @Mock
+    private lateinit var panelViewController: PanelViewController
+    @Mock
+    private lateinit var panelView: ViewGroup
+    @Mock
+    private lateinit var scrimController: ScrimController
+
     private lateinit var view: PhoneStatusBarView
 
     @Before
     fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        // TODO(b/197137564): Setting up a panel view and its controller feels unnecessary when
+        //   testing just [PhoneStatusBarView].
+        `when`(panelViewController.view).thenReturn(panelView)
+
         view = PhoneStatusBarView(mContext, null)
+        view.setPanel(panelViewController)
+        view.setScrimController(scrimController)
     }
 
     @Test
@@ -51,4 +69,48 @@
         view.panelEnabled()
         // No assert needed, just testing no crash
     }
+
+    @Test
+    fun panelExpansionChanged_fracZero_stateChangeListenerNotified() {
+        val listener = TestStateChangedListener()
+        view.setPanelExpansionStateChangedListener(listener)
+
+        view.panelExpansionChanged(0f, false)
+
+        assertThat(listener.stateChangeCalled).isTrue()
+    }
+
+    @Test
+    fun panelExpansionChanged_fracOne_stateChangeListenerNotified() {
+        val listener = TestStateChangedListener()
+        view.setPanelExpansionStateChangedListener(listener)
+
+        view.panelExpansionChanged(1f, false)
+
+        assertThat(listener.stateChangeCalled).isTrue()
+    }
+
+    @Test
+    fun panelExpansionChanged_fracHalf_stateChangeListenerNotNotified() {
+        val listener = TestStateChangedListener()
+        view.setPanelExpansionStateChangedListener(listener)
+
+        view.panelExpansionChanged(0.5f, false)
+
+        assertThat(listener.stateChangeCalled).isFalse()
+    }
+
+    @Test
+    fun panelExpansionChanged_noStateChangeListener_noCrash() {
+        view.panelExpansionChanged(1f, false)
+        // No assert needed, just testing no crash
+    }
+
+    private class TestStateChangedListener : PhoneStatusBarView.PanelExpansionStateChangedListener {
+        var stateChangeCalled: Boolean = false
+
+        override fun onPanelExpansionStateChanged() {
+            stateChangeCalled = true
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 3c0382b..751bc81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -233,6 +233,7 @@
     @Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
     @Mock private VolumeComponent mVolumeComponent;
     @Mock private CommandQueue mCommandQueue;
+    @Mock private CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger;
     @Mock private StatusBarComponent.Factory mStatusBarComponentFactory;
     @Mock private StatusBarComponent mStatusBarComponent;
     @Mock private PluginManager mPluginManager;
@@ -404,6 +405,7 @@
                 mDozeScrimController,
                 mVolumeComponent,
                 mCommandQueue,
+                mCollapsedStatusBarFragmentLogger,
                 mStatusBarComponentFactory,
                 mPluginManager,
                 Optional.of(mLegacySplitScreen),
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index baf5af5..2b30943 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5221,7 +5221,9 @@
             sr.setProcess(null, null, 0, null);
             sr.isolatedProc = null;
             sr.executeNesting = 0;
-            sr.forceClearTracker();
+            synchronized (mAm.mProcessStats.mLock) {
+                sr.forceClearTracker();
+            }
             if (mDestroyingServices.remove(sr)) {
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "killServices remove destroying " + sr);
             }
@@ -5371,7 +5373,9 @@
             i--;
             ServiceRecord sr = mDestroyingServices.get(i);
             if (sr.app == app) {
-                sr.forceClearTracker();
+                synchronized (mAm.mProcessStats.mLock) {
+                    sr.forceClearTracker();
+                }
                 mDestroyingServices.remove(i);
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "killServices remove destroying " + sr);
             }
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index db49b56..bcb1be3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -123,7 +123,6 @@
     static final String KEY_KILL_BG_RESTRICTED_CACHED_IDLE = "kill_bg_restricted_cached_idle";
     static final String KEY_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME =
             "kill_bg_restricted_cached_idle_settle_time";
-    static final String KEY_ENABLE_COMPONENT_ALIAS = "enable_experimental_component_alias";
     static final String KEY_COMPONENT_ALIAS_OVERRIDES = "component_alias_overrides";
 
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
@@ -200,7 +199,6 @@
      * Whether or not to enable the extra delays to service restarts on memory pressure.
      */
     private static final boolean DEFAULT_ENABLE_EXTRA_SERVICE_RESTART_DELAY_ON_MEM_PRESSURE = true;
-    private static final boolean DEFAULT_ENABLE_COMPONENT_ALIAS = false;
     private static final String DEFAULT_COMPONENT_ALIAS_OVERRIDES = "";
 
     // Flag stored in the DeviceConfig API.
@@ -595,8 +593,6 @@
     @GuardedBy("mService")
     boolean mEnableExtraServiceRestartDelayOnMemPressure =
             DEFAULT_ENABLE_EXTRA_SERVICE_RESTART_DELAY_ON_MEM_PRESSURE;
-    /** Whether to enable "component alias" experimental feature. */
-    volatile boolean mEnableComponentAlias = DEFAULT_ENABLE_COMPONENT_ALIAS;
 
     /**
      * Defines component aliases. Format
@@ -835,7 +831,6 @@
                             case KEY_ENABLE_EXTRA_SERVICE_RESTART_DELAY_ON_MEM_PRESSURE:
                                 updateEnableExtraServiceRestartDelayOnMemPressure();
                                 break;
-                            case KEY_ENABLE_COMPONENT_ALIAS:
                             case KEY_COMPONENT_ALIAS_OVERRIDES:
                                 updateComponentAliases();
                                 break;
@@ -1274,15 +1269,11 @@
     }
 
     private void updateComponentAliases() {
-        mEnableComponentAlias = DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                KEY_ENABLE_COMPONENT_ALIAS,
-                DEFAULT_ENABLE_COMPONENT_ALIAS);
         mComponentAliasOverrides = DeviceConfig.getString(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                 KEY_COMPONENT_ALIAS_OVERRIDES,
                 DEFAULT_COMPONENT_ALIAS_OVERRIDES);
-        mService.mComponentAliasResolver.update(mEnableComponentAlias, mComponentAliasOverrides);
+        mService.mComponentAliasResolver.update(mComponentAliasOverrides);
     }
 
     private void updateProcessKillTimeout() {
@@ -1521,8 +1512,6 @@
         pw.print("="); pw.println(mPushMessagingOverQuotaBehavior);
         pw.print("  "); pw.print(KEY_FGS_ALLOW_OPT_OUT);
         pw.print("="); pw.println(mFgsAllowOptOut);
-        pw.print("  "); pw.print(KEY_ENABLE_COMPONENT_ALIAS);
-        pw.print("="); pw.println(mEnableComponentAlias);
         pw.print("  "); pw.print(KEY_COMPONENT_ALIAS_OVERRIDES);
         pw.print("="); pw.println(mComponentAliasOverrides);
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0e57236..bfd6a03 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4838,10 +4838,6 @@
         showConsoleNotificationIfActive();
 
         t.traceEnd();
-
-        // Load the component aliases.
-        mComponentAliasResolver.update(
-                mConstants.mEnableComponentAlias, mConstants.mComponentAliasOverrides);
     }
 
     private void showConsoleNotificationIfActive() {
@@ -5054,8 +5050,9 @@
     }
 
     @Override
-    public void registerIntentSenderCancelListener(IIntentSender sender, IResultReceiver receiver) {
-        mPendingIntentController.registerIntentSenderCancelListener(sender, receiver);
+    public boolean registerIntentSenderCancelListenerEx(
+            IIntentSender sender, IResultReceiver receiver) {
+        return mPendingIntentController.registerIntentSenderCancelListener(sender, receiver);
     }
 
     @Override
@@ -7827,6 +7824,12 @@
             t.traceEnd(); // setBinderProxies
 
             t.traceEnd(); // ActivityManagerStartApps
+
+            // Load the component aliases.
+            t.traceBegin("componentAlias");
+            mComponentAliasResolver.onSystemReady(mConstants.mComponentAliasOverrides);
+            t.traceEnd(); // componentAlias
+
             t.traceEnd(); // PhaseActivityManagerReady
         }
     }
diff --git a/services/core/java/com/android/server/am/ComponentAliasResolver.java b/services/core/java/com/android/server/am/ComponentAliasResolver.java
index cf3e968..00b0e83 100644
--- a/services/core/java/com/android/server/am/ComponentAliasResolver.java
+++ b/services/core/java/com/android/server/am/ComponentAliasResolver.java
@@ -17,15 +17,20 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ComponentInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManager.Property;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.os.Binder;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -36,6 +41,8 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
+import com.android.server.compat.CompatChange;
+import com.android.server.compat.PlatformCompat;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -59,6 +66,13 @@
     private static final String TAG = "ComponentAliasResolver";
     private static final boolean DEBUG = true;
 
+    /**
+     * This flag has to be enabled for the "android" package to use component aliases.
+     */
+    @ChangeId
+    @Disabled
+    public static final long USE_EXPERIMENTAL_COMPONENT_ALIAS = 196254758L;
+
     private final Object mLock = new Object();
     private final ActivityManagerService mAm;
     private final Context mContext;
@@ -72,6 +86,11 @@
     @GuardedBy("mLock")
     private final ArrayMap<ComponentName, ComponentName> mFromTo = new ArrayMap<>();
 
+    @GuardedBy("mLock")
+    private PlatformCompat mPlatformCompat;
+
+    private static final String OPT_IN_PROPERTY = "com.android.EXPERIMENTAL_COMPONENT_ALIAS_OPT_IN";
+
     private static final String ALIAS_FILTER_ACTION = "android.intent.action.EXPERIMENTAL_IS_ALIAS";
     private static final String META_DATA_ALIAS_TARGET = "alias_target";
 
@@ -114,14 +133,32 @@
         }
     };
 
+    private final CompatChange.ChangeListener mCompatChangeListener = (packageName) -> {
+        if (DEBUG) Slog.d(TAG, "USE_EXPERIMENTAL_COMPONENT_ALIAS changed.");
+        BackgroundThread.getHandler().post(this::refresh);
+    };
+
+    /**
+     * Call this on systemRead().
+     */
+    public void onSystemReady(String overrides) {
+        synchronized (mLock) {
+            mPlatformCompat = (PlatformCompat) ServiceManager.getService(
+                    Context.PLATFORM_COMPAT_SERVICE);
+            mPlatformCompat.registerListener(USE_EXPERIMENTAL_COMPONENT_ALIAS,
+                    mCompatChangeListener);
+        }
+        if (DEBUG) Slog.d(TAG, "Compat listener set.");
+        update(overrides);
+    }
+
     /**
      * (Re-)loads aliases from <meta-data> and the device config override.
      */
-    public void update(boolean enabled, String overrides) {
+    public void update(String overrides) {
         synchronized (mLock) {
-            if (enabled == mEnabled && Objects.equals(overrides, mOverrideString)) {
-                return;
-            }
+            final boolean enabled = mPlatformCompat.isChangeEnabledByPackageName(
+                    USE_EXPERIMENTAL_COMPONENT_ALIAS, "android", UserHandle.USER_SYSTEM);
             if (enabled != mEnabled) {
                 Slog.i(TAG, (enabled ? "Enabling" : "Disabling") + " component aliases...");
                 if (enabled) {
@@ -144,7 +181,7 @@
 
     private void refresh() {
         synchronized (mLock) {
-            refreshLocked();
+            update(mOverrideString);
         }
     }
 
@@ -167,18 +204,80 @@
         final List<ResolveInfo> services = mContext.getPackageManager().queryIntentServicesAsUser(
                 i, PACKAGE_QUERY_FLAGS, UserHandle.USER_SYSTEM);
 
-        extractAliases(services);
+        extractAliasesLocked(services);
 
         if (DEBUG) Slog.d(TAG, "Scanning receiver aliases...");
         final List<ResolveInfo> receivers = mContext.getPackageManager()
                 .queryBroadcastReceiversAsUser(i, PACKAGE_QUERY_FLAGS, UserHandle.USER_SYSTEM);
 
-        extractAliases(receivers);
+        extractAliasesLocked(receivers);
 
         // TODO: Scan for other component types as well.
     }
 
-    private void extractAliases(List<ResolveInfo> components) {
+    /**
+     * Make sure a given package is opted into component alias, by having a
+     * "com.android.EXPERIMENTAL_COMPONENT_ALIAS_OPT_IN" property set to true in the manifest.
+     *
+     * The implementation isn't optimized -- in every call we scan the package's properties,
+     * even thought we're likely going to call it with the same packages multiple times.
+     * But that's okay since this feature is experimental, and this code path won't be called
+     * until explicitly enabled.
+     */
+    @GuardedBy("mLock")
+    private boolean isEnabledForPackageLocked(String packageName) {
+        boolean enabled = false;
+        try {
+            final Property p = mContext.getPackageManager().getProperty(
+                    OPT_IN_PROPERTY, packageName);
+            enabled = p.getBoolean();
+        } catch (NameNotFoundException e) {
+        }
+        if (!enabled) {
+            Slog.w(TAG, "USE_EXPERIMENTAL_COMPONENT_ALIAS not enabled for " + packageName);
+        }
+        return enabled;
+    }
+
+    /**
+     * Make sure an alias and its target are the same package, or, the target is in a "sub" package.
+     */
+    private static boolean validateAlias(ComponentName from, ComponentName to) {
+        final String fromPackage = from.getPackageName();
+        final String toPackage = to.getPackageName();
+
+        if (Objects.equals(fromPackage, toPackage)) { // Same package?
+            return true;
+        }
+        if (toPackage.startsWith(fromPackage + ".")) { // Prefix?
+            return true;
+        }
+        Slog.w(TAG, "Invalid alias: "
+                + from.flattenToShortString() + " -> " + to.flattenToShortString());
+        return false;
+    }
+
+    @GuardedBy("mLock")
+    private void validateAndAddAliasLocked(ComponentName from, ComponentName to) {
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "" + from.flattenToShortString() + " -> " + to.flattenToShortString());
+        }
+        if (!validateAlias(from, to)) {
+            return;
+        }
+
+        // Make sure both packages have
+        if (!isEnabledForPackageLocked(from.getPackageName())
+                || !isEnabledForPackageLocked(to.getPackageName())) {
+            return;
+        }
+
+        mFromTo.put(from, to);
+    }
+
+    @GuardedBy("mLock")
+    private void extractAliasesLocked(List<ResolveInfo> components) {
         for (ResolveInfo ri : components) {
             final ComponentInfo ci = ri.getComponentInfo();
             final ComponentName from = ci.getComponentName();
@@ -186,10 +285,7 @@
             if (to == null) {
                 continue;
             }
-            if (DEBUG) {
-                Slog.d(TAG, "" + from.flattenToShortString() + " -> " + to.flattenToShortString());
-            }
-            mFromTo.put(from, to);
+            validateAndAddAliasLocked(from, to);
         }
     }
 
@@ -221,16 +317,12 @@
                     continue;
                 }
 
-                if (DEBUG) {
-                    Slog.d(TAG,
-                            "" + from.flattenToShortString() + " -> " + to.flattenToShortString());
-                }
-                mFromTo.put(from, to);
+                validateAndAddAliasLocked(from, to);
             }
         }
     }
 
-    private ComponentName unflatten(String name) {
+    private static ComponentName unflatten(String name) {
         final ComponentName cn = ComponentName.unflattenFromString(name);
         if (cn != null) {
             return cn;
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index 534bd84..c49e696 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -271,9 +271,11 @@
         }
     }
 
-    void registerIntentSenderCancelListener(IIntentSender sender, IResultReceiver receiver) {
+    boolean registerIntentSenderCancelListener(IIntentSender sender, IResultReceiver receiver) {
         if (!(sender instanceof PendingIntentRecord)) {
-            return;
+            Slog.w(TAG, "registerIntentSenderCancelListener called on non-PendingIntentRecord");
+            // In this case, it's not "success", but we don't know if it's canceld either.
+            return true;
         }
         boolean isCancelled;
         synchronized (mLock) {
@@ -281,12 +283,9 @@
             isCancelled = pendingIntent.canceled;
             if (!isCancelled) {
                 pendingIntent.registerCancelListenerLocked(receiver);
-            }
-        }
-        if (isCancelled) {
-            try {
-                receiver.send(Activity.RESULT_CANCELED, null);
-            } catch (RemoteException e) {
+                return true;
+            } else {
+                return false;
             }
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index b42f898..9c8ccd9 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -47,6 +47,7 @@
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.FaceSensorPropertiesInternal;
@@ -766,8 +767,9 @@
         if (isUdfps && udfpsProps.length == 3) {
             return new FingerprintSensorPropertiesInternal(sensorId,
                     Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,
-                    componentInfo, sensorType, resetLockoutRequiresHardwareAuthToken, udfpsProps[0],
-                    udfpsProps[1], udfpsProps[2]);
+                    componentInfo, sensorType, resetLockoutRequiresHardwareAuthToken,
+                    List.of(new SensorLocationInternal("" /* display */,
+                            udfpsProps[0], udfpsProps[1], udfpsProps[2])));
         } else {
             return new FingerprintSensorPropertiesInternal(sensorId,
                     Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 996f0fd..4f7c6b0 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -50,7 +50,6 @@
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.biometrics.SensorPropertiesInternal;
-import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
 import android.os.Build;
 import android.os.RemoteException;
@@ -62,7 +61,6 @@
 
 import com.android.internal.R;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 
 import java.util.List;
@@ -541,14 +539,4 @@
                 throw new IllegalArgumentException("Unknown strength: " + strength);
         }
     }
-
-    public static int getUdfpsAuthReason(@NonNull AuthenticationClient<?> client) {
-        if (client.isKeyguard()) {
-            return IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD;
-        } else if (client.isBiometricPrompt()) {
-            return IUdfpsOverlayController.REASON_AUTH_BP;
-        } else {
-            return IUdfpsOverlayController.REASON_AUTH_FPM_OTHER;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 6f38ed0..85d6d7f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -28,6 +28,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -457,4 +458,14 @@
     public boolean wasAuthAttempted() {
         return mAuthAttempted;
     }
+
+    protected int getShowOverlayReason() {
+        if (isKeyguard()) {
+            return BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
+        } else if (isBiometricPrompt()) {
+            return BiometricOverlayConstants.REASON_AUTH_BP;
+        } else {
+            return BiometricOverlayConstants.REASON_AUTH_OTHER;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 9191b8b..2826e0c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -19,7 +19,9 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.fingerprint.FingerprintManager;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -128,4 +130,15 @@
     public boolean interruptsPrecedingClients() {
         return true;
     }
+
+    protected int getOverlayReasonFromEnrollReason(@FingerprintManager.EnrollReason int reason) {
+        switch (reason) {
+            case FingerprintManager.ENROLL_FIND_SENSOR:
+                return BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR;
+            case FingerprintManager.ENROLL_ENROLL:
+                return BiometricOverlayConstants.REASON_ENROLL_ENROLLING;
+            default:
+                return BiometricOverlayConstants.REASON_UNKNOWN;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
new file mode 100644
index 0000000..6e2b9ef
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * Single entry point & holder for controllers managing UI overlays for biometrics.
+ *
+ * For common operations, like {@link #show(int, int, AcquisitionClient)}, modalities are
+ * skipped if they are not present (provided as null via the constructor).
+ *
+ * Use the getters, such as {@link #ifUdfps(OverlayControllerConsumer)}, to get a controller for
+ * operations that are unique to a single modality.
+ */
+public final class SensorOverlays {
+
+    private static final String TAG = "SensorOverlays";
+
+    @NonNull private final Optional<IUdfpsOverlayController> mUdfpsOverlayController;
+    @NonNull private final Optional<ISidefpsController> mSidefpsController;
+
+    /**
+     * Create an overlay controller for each modality.
+     *
+     * @param udfpsOverlayController under display fps or null if not present on device
+     * @param sidefpsController side fps or null if not present on device
+     */
+    public SensorOverlays(
+            @Nullable IUdfpsOverlayController udfpsOverlayController,
+            @Nullable ISidefpsController sidefpsController) {
+        mUdfpsOverlayController = Optional.ofNullable(udfpsOverlayController);
+        mSidefpsController = Optional.ofNullable(sidefpsController);
+    }
+
+    /**
+     * Show the overlay.
+     *
+     * @param sensorId sensor id
+     * @param reason reason for showing
+     * @param client client performing operation
+     */
+    public void show(int sensorId, @BiometricOverlayConstants.ShowReason int reason,
+            @NonNull AcquisitionClient<?> client) {
+        if (mSidefpsController.isPresent()) {
+            try {
+                mSidefpsController.get().show();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception when showing the side-fps overlay", e);
+            }
+        }
+
+        if (mUdfpsOverlayController.isPresent()) {
+            final IUdfpsOverlayControllerCallback callback =
+                    new IUdfpsOverlayControllerCallback.Stub() {
+                        @Override
+                        public void onUserCanceled() {
+                            client.onUserCanceled();
+                        }
+                    };
+
+            try {
+                mUdfpsOverlayController.get().showUdfpsOverlay(sensorId, reason, callback);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e);
+            }
+        }
+    }
+
+    /**
+     * Hide the overlay.
+     *
+     * @param sensorId sensor id
+     */
+    public void hide(int sensorId) {
+        if (mSidefpsController.isPresent()) {
+            try {
+                mSidefpsController.get().hide();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception when hiding the side-fps overlay", e);
+            }
+        }
+
+        if (mUdfpsOverlayController.isPresent()) {
+            try {
+                mUdfpsOverlayController.get().hideUdfpsOverlay(sensorId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
+            }
+        }
+    }
+
+    /**
+     * Use the udfps controller, if present.
+     * @param consumer action
+     */
+    public void ifUdfps(OverlayControllerConsumer<IUdfpsOverlayController> consumer) {
+        if (mUdfpsOverlayController.isPresent()) {
+            try {
+                consumer.accept(mUdfpsOverlayController.get());
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception using overlay controller", e);
+            }
+        }
+    }
+
+    /**
+     * Consumer for a biometric overlay controller.
+     *
+     * This behaves like a normal {@link Consumer} except that it will trap and log
+     * any thrown {@link RemoteException}.
+     *
+     * @param <T> the type of the input to the operation
+     **/
+    @FunctionalInterface
+    public interface OverlayControllerConsumer<T> {
+        /** Perform the operation. */
+        void accept(T t) throws RemoteException;
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/SidefpsHelper.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/SidefpsHelper.java
deleted file mode 100644
index 474066c..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/SidefpsHelper.java
+++ /dev/null
@@ -1,60 +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.server.biometrics.sensors.fingerprint;
-
-import android.annotation.Nullable;
-import android.hardware.fingerprint.ISidefpsController;
-import android.os.RemoteException;
-import android.util.Slog;
-
-/**
- * Contains helper methods for side-fps fingerprint controller.
- */
-public class SidefpsHelper {
-    private static final String TAG = "SidefpsHelper";
-
-    /**
-     * Shows the side-fps affordance
-     * @param sidefpsController controller that shows and hides the side-fps affordance
-     */
-    public static void showOverlay(@Nullable ISidefpsController sidefpsController) {
-        if (sidefpsController == null) {
-            return;
-        }
-
-        try {
-            sidefpsController.show();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when showing the side-fps overlay", e);
-        }
-    }
-
-    /**
-     * Hides the side-fps affordance
-     * @param sidefpsController controller that shows and hides the side-fps affordance
-     */
-    public static void hideOverlay(@Nullable ISidefpsController sidefpsController) {
-        if (sidefpsController == null) {
-            return;
-        }
-        try {
-            sidefpsController.hide();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when hiding the side-fps overlay", e);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
index 879c8a0..29661d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
@@ -17,17 +17,12 @@
 package com.android.server.biometrics.sensors.fingerprint;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
 import android.os.RemoteException;
 import android.util.Slog;
 
-import com.android.server.biometrics.sensors.AcquisitionClient;
-
 /**
  * Contains helper methods for under-display fingerprint HIDL.
  */
@@ -68,88 +63,6 @@
         }
     }
 
-    public static int getReasonFromEnrollReason(@FingerprintManager.EnrollReason int reason) {
-        switch (reason) {
-            case FingerprintManager.ENROLL_FIND_SENSOR:
-                return IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR;
-            case FingerprintManager.ENROLL_ENROLL:
-                return IUdfpsOverlayController.REASON_ENROLL_ENROLLING;
-            default:
-                return IUdfpsOverlayController.REASON_UNKNOWN;
-        }
-    }
-
-    public static void showUdfpsOverlay(int sensorId, int reason,
-            @Nullable IUdfpsOverlayController udfpsOverlayController,
-            @NonNull AcquisitionClient<?> client) {
-        if (udfpsOverlayController == null) {
-            return;
-        }
-
-        final IUdfpsOverlayControllerCallback callback =
-                new IUdfpsOverlayControllerCallback.Stub() {
-                    @Override
-                    public void onUserCanceled() {
-                        client.onUserCanceled();
-                    }
-                };
-
-        try {
-            udfpsOverlayController.showUdfpsOverlay(sensorId, reason, callback);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e);
-        }
-    }
-
-    public static void hideUdfpsOverlay(int sensorId,
-            @Nullable IUdfpsOverlayController udfpsOverlayController) {
-        if (udfpsOverlayController == null) {
-            return;
-        }
-        try {
-            udfpsOverlayController.hideUdfpsOverlay(sensorId);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
-        }
-    }
-
-    public static void onAcquiredGood(int sensorId,
-            @Nullable IUdfpsOverlayController udfpsOverlayController) {
-        if (udfpsOverlayController == null) {
-            return;
-        }
-
-        try {
-            udfpsOverlayController.onAcquiredGood(sensorId);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when sending onAcquiredGood", e);
-        }
-    }
-
-    public static void onEnrollmentProgress(int sensorId, int remaining,
-            @Nullable IUdfpsOverlayController udfpsOverlayController) {
-        if (udfpsOverlayController == null) {
-            return;
-        }
-        try {
-            udfpsOverlayController.onEnrollmentProgress(sensorId, remaining);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when sending onEnrollmentProgress", e);
-        }
-    }
-
-    public static void onEnrollmentHelp(int sensorId,
-            @Nullable IUdfpsOverlayController udfpsOverlayController) {
-        if (udfpsOverlayController == null) {
-            return;
-        }
-        try {
-            udfpsOverlayController.onEnrollmentHelp(sensorId);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception when sending onEnrollmentHelp", e);
-        }
-    }
-
     public static boolean isValidAcquisitionMessage(@NonNull Context context,
             int acquireInfo, int vendorCode) {
         return FingerprintManager.getAcquiredString(context, acquireInfo, vendorCode) != null;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 9d911e0..8a11e8d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -27,20 +27,20 @@
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
-import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
 import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.SensorOverlays;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
-import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
 
 import java.util.ArrayList;
 
@@ -53,7 +53,7 @@
     private static final String TAG = "FingerprintAuthenticationClient";
 
     @NonNull private final LockoutCache mLockoutCache;
-    @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
+    @NonNull private final SensorOverlays mSensorOverlays;
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
     @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
 
@@ -68,6 +68,7 @@
             int sensorId, boolean isStrongBiometric, int statsClient,
             @Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
+            @Nullable ISidefpsController sidefpsController,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner,
@@ -77,7 +78,7 @@
                 false /* isKeyguardBypassEnabled */);
         setRequestId(requestId);
         mLockoutCache = lockoutCache;
-        mUdfpsOverlayController = udfpsOverlayController;
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mSensorProps = sensorProps;
         mALSProbeCallback = createALSCallback(false /* startWithClient */);
     }
@@ -120,7 +121,7 @@
 
         if (authenticated) {
             mState = STATE_STOPPED;
-            UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+            mSensorOverlays.hide(getSensorId());
         } else {
             mState = STATE_STARTED_PAUSED_ATTEMPTED;
         }
@@ -131,7 +132,7 @@
         // For UDFPS, notify SysUI that the illumination can be turned off.
         // See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE
         if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD) {
-            UdfpsHelper.onAcquiredGood(getSensorId(), mUdfpsOverlayController);
+            mSensorOverlays.ifUdfps(controller -> controller.onAcquiredGood(getSensorId()));
         }
 
         super.onAcquired(acquiredInfo, vendorCode);
@@ -145,27 +146,28 @@
             BiometricNotificationUtils.showBadCalibrationNotification(getContext());
         }
 
-        UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+        mSensorOverlays.hide(getSensorId());
     }
 
     @Override
     protected void startHalOperation() {
-        UdfpsHelper.showUdfpsOverlay(getSensorId(), Utils.getUdfpsAuthReason(this),
-                mUdfpsOverlayController, this);
+        mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this);
+
         try {
             mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
-            UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+            mSensorOverlays.hide(getSensorId());
             mCallback.onClientFinished(this, false /* success */);
         }
     }
 
     @Override
     protected void stopHalOperation() {
-        UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+        mSensorOverlays.hide(getSensorId());
+
         try {
             mCancellationSignal.cancel();
         } catch (RemoteException e) {
@@ -235,7 +237,7 @@
             Slog.e(TAG, "Remote exception", e);
         }
 
-        UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+        mSensorOverlays.hide(getSensorId());
         mCallback.onClientFinished(this, false /* success */);
     }
 
@@ -252,7 +254,7 @@
             Slog.e(TAG, "Remote exception", e);
         }
 
-        UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+        mSensorOverlays.hide(getSensorId());
         mCallback.onClientFinished(this, false /* success */);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index da91cdd..ac3ce89 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.fingerprint.ISession;
@@ -31,7 +32,7 @@
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.DetectionConsumer;
-import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
+import com.android.server.biometrics.sensors.SensorOverlays;
 
 /**
  * Performs fingerprint detection without exposing any matching information (e.g. accept/reject
@@ -42,8 +43,7 @@
     private static final String TAG = "FingerprintDetectClient";
 
     private final boolean mIsStrongBiometric;
-    @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
-
+    @NonNull private final SensorOverlays mSensorOverlays;
     @Nullable private ICancellationSignal mCancellationSignal;
 
     FingerprintDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
@@ -57,7 +57,7 @@
                 BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
         setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
-        mUdfpsOverlayController = udfpsOverlayController;
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/);
     }
 
     @Override
@@ -68,7 +68,8 @@
 
     @Override
     protected void stopHalOperation() {
-        UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+        mSensorOverlays.hide(getSensorId());
+
         try {
             mCancellationSignal.cancel();
         } catch (RemoteException e) {
@@ -79,14 +80,13 @@
 
     @Override
     protected void startHalOperation() {
-        UdfpsHelper.showUdfpsOverlay(getSensorId(),
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD,
-                mUdfpsOverlayController, this);
+        mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this);
+
         try {
             mCancellationSignal = getFreshDaemon().detectInteraction();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting finger detect", e);
-            UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+            mSensorOverlays.hide(getSensorId());
             mCallback.onClientFinished(this, false /* success */);
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index c420c5c..ccb34aa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -39,8 +39,8 @@
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.EnrollClient;
+import com.android.server.biometrics.sensors.SensorOverlays;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
-import com.android.server.biometrics.sensors.fingerprint.SidefpsHelper;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
 import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
 
@@ -49,8 +49,7 @@
     private static final String TAG = "FingerprintEnrollClient";
 
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
-    @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
-    @Nullable private final ISidefpsController mSidefpsController;
+    @NonNull private final SensorOverlays mSensorOverlays;
 
     private final @FingerprintManager.EnrollReason int mEnrollReason;
     @Nullable private ICancellationSignal mCancellationSignal;
@@ -63,7 +62,7 @@
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
-            @Nullable IUdfpsOverlayController udfpsOvelayController,
+            @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
             int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
         // UDFPS haptics occur when an image is acquired (instead of when the result is known)
@@ -71,8 +70,7 @@
                 0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
                 !sensorProps.isAnyUdfpsType() /* shouldVibrate */);
         mSensorProps = sensorProps;
-        mUdfpsOverlayController = udfpsOvelayController;
-        mSidefpsController = sidefpsController;
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mMaxTemplatesPerUser = maxTemplatesPerUser;
 
         mEnrollReason = enrollReason;
@@ -91,11 +89,11 @@
     public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
         super.onEnrollResult(identifier, remaining);
 
-        UdfpsHelper.onEnrollmentProgress(getSensorId(), remaining, mUdfpsOverlayController);
+        mSensorOverlays.ifUdfps(
+                controller -> controller.onEnrollmentProgress(getSensorId(), remaining));
 
         if (remaining == 0) {
-            UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
-            SidefpsHelper.hideOverlay(mSidefpsController);
+            mSensorOverlays.hide(getSensorId());
         }
     }
 
@@ -106,12 +104,14 @@
         if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD
                 && mSensorProps.isAnyUdfpsType()) {
             vibrateSuccess();
-            UdfpsHelper.onAcquiredGood(getSensorId(), mUdfpsOverlayController);
+            mSensorOverlays.ifUdfps(controller -> controller.onAcquiredGood(getSensorId()));
         }
 
-        if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) {
-            UdfpsHelper.onEnrollmentHelp(getSensorId(), mUdfpsOverlayController);
-        }
+        mSensorOverlays.ifUdfps(controller -> {
+            if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) {
+                controller.onEnrollmentHelp(getSensorId());
+            }
+        });
 
         super.onAcquired(acquiredInfo, vendorCode);
     }
@@ -120,8 +120,7 @@
     public void onError(int errorCode, int vendorCode) {
         super.onError(errorCode, vendorCode);
 
-        UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
-        SidefpsHelper.hideOverlay(mSidefpsController);
+        mSensorOverlays.hide(getSensorId());
     }
 
     @Override
@@ -133,8 +132,8 @@
 
     @Override
     protected void stopHalOperation() {
-        UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
-        SidefpsHelper.hideOverlay(mSidefpsController);
+        mSensorOverlays.hide(getSensorId());
+
         if (mCancellationSignal != null) {
             try {
                 mCancellationSignal.cancel();
@@ -149,10 +148,8 @@
 
     @Override
     protected void startHalOperation() {
-        UdfpsHelper.showUdfpsOverlay(getSensorId(),
-                UdfpsHelper.getReasonFromEnrollReason(mEnrollReason),
-                mUdfpsOverlayController, this);
-        SidefpsHelper.showOverlay(mSidefpsController);
+        mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this);
+
         BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
         try {
             mCancellationSignal = getFreshDaemon().enroll(
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index ca83dda..0defc3f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -25,10 +25,12 @@
 import android.app.TaskStackListener;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.content.res.TypedArray;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.biometrics.common.ComponentInfo;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.SensorProps;
@@ -145,6 +147,8 @@
         mActivityTaskManager = ActivityTaskManager.getInstance();
         mTaskStackListener = new BiometricTaskStackListener();
 
+        final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
+
         for (SensorProps prop : props) {
             final int sensorId = prop.commonProps.sensorId;
 
@@ -164,9 +168,12 @@
                             componentInfo,
                             prop.sensorType,
                             true /* resetLockoutRequiresHardwareAuthToken */,
-                            prop.sensorLocations[0].sensorLocationX,
-                            prop.sensorLocations[0].sensorLocationY,
-                            prop.sensorLocations[0].sensorRadius);
+                            !workaroundLocations.isEmpty() ? workaroundLocations :
+                                    List.of(new SensorLocationInternal(
+                                        "" /* displayId */,
+                                        prop.sensorLocations[0].sensorLocationX,
+                                        prop.sensorLocations[0].sensorLocationY,
+                                        prop.sensorLocations[0].sensorRadius)));
             final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
                     internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher);
 
@@ -403,7 +410,7 @@
                     userId, operationId, restricted, opPackageName, cookie,
                     false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
                     mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
-                    mUdfpsOverlayController, allowBackgroundAuthentication,
+                    mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
                     mSensors.get(sensorId).getSensorProperties());
             scheduleForSensor(sensorId, client, mFingerprintStateCallback);
         });
@@ -647,4 +654,45 @@
     void setTestHalEnabled(boolean enabled) {
         mTestHalEnabled = enabled;
     }
+
+    // TODO(b/174868353): workaround for gaps in HAL interface (remove and get directly from HAL)
+    // reads values via an overlay instead of querying the HAL
+    @NonNull
+    private List<SensorLocationInternal> getWorkaroundSensorProps(@NonNull Context context) {
+        final List<SensorLocationInternal> sensorLocations = new ArrayList<>();
+
+        final TypedArray sfpsProps = context.getResources().obtainTypedArray(
+                com.android.internal.R.array.config_sfps_sensor_props);
+        for (int i = 0; i < sfpsProps.length(); i++) {
+            final int id = sfpsProps.getResourceId(i, -1);
+            if (id > 0) {
+                final SensorLocationInternal location = parseSensorLocation(
+                        context.getResources().obtainTypedArray(id));
+                if (location != null) {
+                    sensorLocations.add(location);
+                }
+            }
+        }
+        sfpsProps.recycle();
+
+        return sensorLocations;
+    }
+
+    @Nullable
+    private SensorLocationInternal parseSensorLocation(@Nullable TypedArray array) {
+        if (array == null) {
+            return null;
+        }
+
+        try {
+            return new SensorLocationInternal(
+                    array.getString(0),
+                    array.getInt(1, 0),
+                    array.getInt(2, 0),
+                    array.getInt(3, 0));
+        } catch (Exception e) {
+            Slog.w(getTag(), "malformed sensor location", e);
+        }
+        return null;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index d2882aa4..5f2f4cf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -629,7 +629,8 @@
                     mContext, mLazyDaemon, token, requestId, listener, userId, operationId,
                     restricted, opPackageName, cookie, false /* requireConfirmation */,
                     mSensorProperties.sensorId, isStrongBiometric, statsClient,
-                    mTaskStackListener, mLockoutTracker, mUdfpsOverlayController,
+                    mTaskStackListener, mLockoutTracker,
+                    mUdfpsOverlayController, mSidefpsController,
                     allowBackgroundAuthentication, mSensorProperties);
             mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
         });
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 79ad8e1..dd68b4d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -426,8 +426,7 @@
         mSensorProperties = new FingerprintSensorPropertiesInternal(sensorProps.sensorId,
                 sensorProps.sensorStrength, maxTemplatesAllowed, sensorProps.componentInfo,
                 FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
-                resetLockoutRequiresHardwareAuthToken, sensorProps.sensorLocationX,
-                sensorProps.sensorLocationY, sensorProps.sensorRadius);
+                resetLockoutRequiresHardwareAuthToken, sensorProps.getAllLocations());
         mMockHalResultController = controller;
         mUserHasTrust = new SparseBooleanArray();
         mTrustManager = context.getSystemService(TrustManager.class);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 7d95ec0..3058e25 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -26,16 +26,17 @@
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
-import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.SensorOverlays;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
 import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
 
@@ -52,7 +53,7 @@
     private static final String TAG = "Biometrics/FingerprintAuthClient";
 
     private final LockoutFrameworkImpl mLockoutFrameworkImpl;
-    @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
+    @NonNull private final SensorOverlays mSensorOverlays;
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
     @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
 
@@ -67,6 +68,7 @@
             @NonNull TaskStackListener taskStackListener,
             @NonNull LockoutFrameworkImpl lockoutTracker,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
+            @Nullable ISidefpsController sidefpsController,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
@@ -76,7 +78,7 @@
                 false /* isKeyguardBypassEnabled */);
         setRequestId(requestId);
         mLockoutFrameworkImpl = lockoutTracker;
-        mUdfpsOverlayController = udfpsOverlayController;
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mSensorProps = sensorProps;
         mALSProbeCallback = createALSCallback(false /* startWithClient */);
     }
@@ -112,7 +114,7 @@
         if (authenticated) {
             mState = STATE_STOPPED;
             resetFailedAttempts(getTargetUserId());
-            UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+            mSensorOverlays.hide(getSensorId());
         } else {
             mState = STATE_STARTED_PAUSED_ATTEMPTED;
             final @LockoutTracker.LockoutMode int lockoutMode =
@@ -125,7 +127,7 @@
                 // Send the error, but do not invoke the FinishCallback yet. Since lockout is not
                 // controlled by the HAL, the framework must stop the sensor before finishing the
                 // client.
-                UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+                mSensorOverlays.hide(getSensorId());
                 onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
                 cancel();
             }
@@ -140,7 +142,7 @@
             BiometricNotificationUtils.showBadCalibrationNotification(getContext());
         }
 
-        UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+        mSensorOverlays.hide(getSensorId());
     }
 
     private void resetFailedAttempts(int userId) {
@@ -168,8 +170,8 @@
 
     @Override
     protected void startHalOperation() {
-        UdfpsHelper.showUdfpsOverlay(getSensorId(), Utils.getUdfpsAuthReason(this),
-                mUdfpsOverlayController, this);
+        mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this);
+
         try {
             // GroupId was never used. In fact, groupId is always the same as userId.
             getFreshDaemon().authenticate(mOperationId, getTargetUserId());
@@ -177,14 +179,15 @@
             Slog.e(TAG, "Remote exception when requesting auth", e);
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
-            UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+            mSensorOverlays.hide(getSensorId());
             mCallback.onClientFinished(this, false /* success */);
         }
     }
 
     @Override
     protected void stopHalOperation() {
-        UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+        mSensorOverlays.hide(getSensorId());
+
         try {
             getFreshDaemon().cancel();
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 147a206..b854fb3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -33,6 +34,7 @@
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.PerformanceTracker;
+import com.android.server.biometrics.sensors.SensorOverlays;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
 import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
 
@@ -48,7 +50,7 @@
     private static final String TAG = "FingerprintDetectClient";
 
     private final boolean mIsStrongBiometric;
-    @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
+    @NonNull private final SensorOverlays mSensorOverlays;
     private boolean mIsPointerDown;
 
     public FingerprintDetectClient(@NonNull Context context,
@@ -61,13 +63,14 @@
                 true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
                 BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
         setRequestId(requestId);
-        mUdfpsOverlayController = udfpsOverlayController;
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
         mIsStrongBiometric = isStrongBiometric;
     }
 
     @Override
     protected void stopHalOperation() {
-        UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+        mSensorOverlays.hide(getSensorId());
+
         try {
             getFreshDaemon().cancel();
         } catch (RemoteException e) {
@@ -86,16 +89,15 @@
 
     @Override
     protected void startHalOperation() {
-        UdfpsHelper.showUdfpsOverlay(getSensorId(),
-                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD,
-                mUdfpsOverlayController, this);
+        mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this);
+
         try {
             getFreshDaemon().authenticate(0 /* operationId */, getTargetUserId());
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting auth", e);
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
-            UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+            mSensorOverlays.hide(getSensorId());
             mCallback.onClientFinished(this, false /* success */);
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index dc70534..1ebf44c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -35,7 +35,7 @@
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.EnrollClient;
-import com.android.server.biometrics.sensors.fingerprint.SidefpsHelper;
+import com.android.server.biometrics.sensors.SensorOverlays;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
 import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
 
@@ -49,8 +49,7 @@
 
     private static final String TAG = "FingerprintEnrollClient";
 
-    @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
-    @Nullable private final ISidefpsController mSidefpsController;
+    @NonNull private final SensorOverlays mSensorOverlays;
     private final @FingerprintManager.EnrollReason int mEnrollReason;
     private boolean mIsPointerDown;
 
@@ -65,8 +64,7 @@
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
                 true /* shouldVibrate */);
-        mUdfpsOverlayController = udfpsOverlayController;
-        mSidefpsController = sidefpsController;
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
 
         mEnrollReason = enrollReason;
         if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
@@ -95,10 +93,8 @@
 
     @Override
     protected void startHalOperation() {
-        UdfpsHelper.showUdfpsOverlay(getSensorId(),
-                UdfpsHelper.getReasonFromEnrollReason(mEnrollReason),
-                mUdfpsOverlayController, this);
-        SidefpsHelper.showOverlay(mSidefpsController);
+        mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this);
+
         BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
         try {
             // GroupId was never used. In fact, groupId is always the same as userId.
@@ -107,16 +103,15 @@
             Slog.e(TAG, "Remote exception when requesting enroll", e);
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
-            UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
-            SidefpsHelper.hideOverlay(mSidefpsController);
+            mSensorOverlays.hide(getSensorId());
             mCallback.onClientFinished(this, false /* success */);
         }
     }
 
     @Override
     protected void stopHalOperation() {
-        UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
-        SidefpsHelper.hideOverlay(mSidefpsController);
+        mSensorOverlays.hide(getSensorId());
+
         try {
             getFreshDaemon().cancel();
         } catch (RemoteException e) {
@@ -131,11 +126,11 @@
     public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
         super.onEnrollResult(identifier, remaining);
 
-        UdfpsHelper.onEnrollmentProgress(getSensorId(), remaining, mUdfpsOverlayController);
+        mSensorOverlays.ifUdfps(
+                controller -> controller.onEnrollmentProgress(getSensorId(), remaining));
 
         if (remaining == 0) {
-            UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
-            SidefpsHelper.hideOverlay(mSidefpsController);
+            mSensorOverlays.hide(getSensorId());
         }
     }
 
@@ -143,17 +138,18 @@
     public void onAcquired(int acquiredInfo, int vendorCode) {
         super.onAcquired(acquiredInfo, vendorCode);
 
-        if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) {
-            UdfpsHelper.onEnrollmentHelp(getSensorId(), mUdfpsOverlayController);
-        }
+        mSensorOverlays.ifUdfps(controller -> {
+            if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) {
+                controller.onEnrollmentHelp(getSensorId());
+            }
+        });
     }
 
     @Override
     public void onError(int errorCode, int vendorCode) {
         super.onError(errorCode, vendorCode);
 
-        UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
-        SidefpsHelper.hideOverlay(mSidefpsController);
+        mSensorOverlays.hide(getSensorId());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index f6ba369..b5846b5 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -183,14 +183,15 @@
      */
     synchronized boolean recheckOverride(String packageName, OverrideAllowedState allowedState,
             @Nullable Long versionCode) {
+        if (packageName == null) {
+            return false;
+        }
         boolean allowed = (allowedState.state == OverrideAllowedState.ALLOWED);
-
         // If the app is not installed or no longer has raw overrides, evaluate to false
         if (versionCode == null || !mRawOverrides.containsKey(packageName) || !allowed) {
             removePackageOverrideInternal(packageName);
             return false;
         }
-
         // Evaluate the override based on its version
         int overrideValue = mRawOverrides.get(packageName).evaluate(versionCode);
         switch (overrideValue) {
@@ -266,6 +267,9 @@
      * @return {@code true} if the change should be enabled for the package.
      */
     boolean willBeEnabled(String packageName) {
+        if (packageName == null) {
+            return defaultValue();
+        }
         final PackageOverride override = mRawOverrides.get(packageName);
         if (override != null) {
             switch (override.evaluateForAllVersions()) {
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index 489b9b3..dcf415f 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -605,9 +605,20 @@
         for (String permission : permissions) {
             int opCode = mAppOpsManager.permissionToOpCode(permission);
             if (opCode != AppOpsManager.OP_NONE) {
-                if (mAppOpsManager.noteOp(opCode, mUid, mPackage, mAttributionTag, noteMessage)
-                        != AppOpsManager.MODE_ALLOWED) {
+                // The noteOp call may check for required permissions. Use the below logic to ensure
+                // that the system server permission is enforced at the call.
+                long token = Binder.setCallingWorkSourceUid(android.os.Process.myUid());
+                try {
+                    if (mAppOpsManager.noteOp(opCode, mUid, mPackage, mAttributionTag, noteMessage)
+                            != AppOpsManager.MODE_ALLOWED) {
+                        return false;
+                    }
+                } catch (SecurityException e) {
+                    Log.e(TAG, "SecurityException: noteOp for pkg " + mPackage + " opcode "
+                            + opCode + ": " + e.getMessage());
                     return false;
+                } finally {
+                    Binder.restoreCallingWorkSource(token);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/pm/InstallParams.java b/services/core/java/com/android/server/pm/InstallParams.java
index edb76a6..bc7d95e 100644
--- a/services/core/java/com/android/server/pm/InstallParams.java
+++ b/services/core/java/com/android/server/pm/InstallParams.java
@@ -28,8 +28,8 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
 import static android.content.pm.PackageManager.INSTALL_FAILED_SESSION_INVALID;
-import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
+import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
 import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
@@ -1204,19 +1204,17 @@
                     }
 
                     // Check for shared user id changes
-                    String invalidPackageName = null;
                     if (!Objects.equals(oldPackage.getSharedUserId(),
                             parsedPackage.getSharedUserId())
                             // Don't mark as invalid if the app is trying to
                             // leave a sharedUserId
                             && parsedPackage.getSharedUserId() != null) {
-                        invalidPackageName = parsedPackage.getPackageName();
-                    }
-
-                    if (invalidPackageName != null) {
-                        throw new PrepareFailure(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
-                                "Package " + invalidPackageName + " tried to change user "
-                                        + oldPackage.getSharedUserId());
+                        throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
+                                "Package " + parsedPackage.getPackageName()
+                                        + " shared user changed from "
+                                        + (oldPackage.getSharedUserId() != null
+                                        ? oldPackage.getSharedUserId() : "<nothing>")
+                                        + " to " + parsedPackage.getSharedUserId());
                     }
 
                     // In case of rollback, remember per-user/profile install state
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index b34a3a2..314cd69 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -135,6 +135,8 @@
     private static final long MAX_ACTIVE_SESSIONS_NO_PERMISSION = 50;
     /** Upper bound on number of historical sessions for a UID */
     private static final long MAX_HISTORICAL_SESSIONS = 1048576;
+    /** Destroy sessions older than this on storage free request */
+    private static final long MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS = 8 * DateUtils.HOUR_IN_MILLIS;
 
     /**
      * Allow verification-skipping if it's a development app installed through ADB with
@@ -339,22 +341,28 @@
 
     @GuardedBy("mSessions")
     private void reconcileStagesLocked(String volumeUuid) {
-        final File stagingDir = getTmpSessionDir(volumeUuid);
-        final ArraySet<File> unclaimedStages = newArraySet(
-                stagingDir.listFiles(sStageFilter));
-
-        // We also need to clean up orphaned staging directory for staged sessions
-        final File stagedSessionStagingDir = Environment.getDataStagingDirectory(volumeUuid);
-        unclaimedStages.addAll(newArraySet(stagedSessionStagingDir.listFiles()));
-
+        final ArraySet<File> unclaimedStages = getStagingDirsOnVolume(volumeUuid);
         // Ignore stages claimed by active sessions
         for (int i = 0; i < mSessions.size(); i++) {
             final PackageInstallerSession session = mSessions.valueAt(i);
             unclaimedStages.remove(session.stageDir);
         }
+        removeStagingDirs(unclaimedStages);
+    }
 
+    private ArraySet<File> getStagingDirsOnVolume(String volumeUuid) {
+        final File stagingDir = getTmpSessionDir(volumeUuid);
+        final ArraySet<File> stagingDirs = newArraySet(stagingDir.listFiles(sStageFilter));
+
+        // We also need to clean up orphaned staging directory for staged sessions
+        final File stagedSessionStagingDir = Environment.getDataStagingDirectory(volumeUuid);
+        stagingDirs.addAll(newArraySet(stagedSessionStagingDir.listFiles()));
+        return stagingDirs;
+    }
+
+    private void removeStagingDirs(ArraySet<File> stagingDirsToRemove) {
         // Clean up orphaned staging directories
-        for (File stage : unclaimedStages) {
+        for (File stage : stagingDirsToRemove) {
             Slog.w(TAG, "Deleting orphan stage " + stage);
             synchronized (mPm.mInstallLock) {
                 mPm.removeCodePathLI(stage);
@@ -368,6 +376,33 @@
         }
     }
 
+    /**
+     * Called to free up some storage space from obsolete installation files
+     */
+    public void freeStageDirs(String volumeUuid) {
+        final ArraySet<File> unclaimedStagingDirsOnVolume = getStagingDirsOnVolume(volumeUuid);
+        final long currentTimeMillis = System.currentTimeMillis();
+        synchronized (mSessions) {
+            for (int i = 0; i < mSessions.size(); i++) {
+                final PackageInstallerSession session = mSessions.valueAt(i);
+                if (!unclaimedStagingDirsOnVolume.contains(session.stageDir)) {
+                    // Only handles sessions stored on the target volume
+                    continue;
+                }
+                final long age = currentTimeMillis - session.createdMillis;
+                if (age >= MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS) {
+                    // Aggressively close old sessions because we are running low on storage
+                    // Their staging dirs will be removed too
+                    session.abandon();
+                } else {
+                    // Session is new enough, so it deserves to be kept even on low storage
+                    unclaimedStagingDirsOnVolume.remove(session.stageDir);
+                }
+            }
+        }
+        removeStagingDirs(unclaimedStagingDirsOnVolume);
+    }
+
     public static boolean isStageName(String name) {
         final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp");
         final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp");
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 78ea422..e62102c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -8004,6 +8004,9 @@
             if (freeBytesRequired > 0) {
                 smInternal.freeCache(volumeUuid, freeBytesRequired);
             }
+
+            // 12. Clear temp install session files
+            mInstallerService.freeStageDirs(volumeUuid);
         } else {
             try {
                 mInstaller.freeCache(volumeUuid, bytes, 0, 0);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 95e2d0a..2c649d2 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -20,7 +20,7 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
-import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED;
 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
 import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
 import static android.content.pm.PackageManager.UNINSTALL_REASON_USER_TYPE;
@@ -1040,14 +1040,15 @@
             @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId)
                     throws PackageManagerException {
         final String pkgName = pkgSetting.name;
-        if (pkgSetting.sharedUser != sharedUser) {
+        if (!Objects.equals(pkgSetting.sharedUser, sharedUser) && sharedUser != null) {
             PackageManagerService.reportSettingsProblem(Log.WARN,
                     "Package " + pkgName + " shared user changed from "
                     + (pkgSetting.sharedUser != null ? pkgSetting.sharedUser.name : "<nothing>")
-                    + " to " + (sharedUser != null ? sharedUser.name : "<nothing>"));
-            throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
+                    + " to " + sharedUser.name);
+            throw new PackageManagerException(INSTALL_FAILED_UID_CHANGED,
                     "Updating application package " + pkgName + " failed");
         }
+        pkgSetting.sharedUser = sharedUser;
 
         if (!pkgSetting.getPath().equals(codePath)) {
             final boolean isSystem = pkgSetting.isSystem();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a124f76..946fa44 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2607,6 +2607,11 @@
         return parent != null ? parent.getOrganizedTaskFragment() : null;
     }
 
+    boolean isEmbedded() {
+        final TaskFragment parent = getTaskFragment();
+        return parent != null && parent.isEmbedded();
+    }
+
     @Override
     @Nullable
     TaskDisplayArea getDisplayArea() {
@@ -2698,7 +2703,9 @@
     boolean isResizeable() {
         return mAtmService.mForceResizableActivities
                 || ActivityInfo.isResizeableMode(info.resizeMode)
-                || info.supportsPictureInPicture();
+                || info.supportsPictureInPicture()
+                // If the activity can be embedded, it should inherit the bounds of task fragment.
+                || isEmbedded();
     }
 
     /** @return whether this activity is non-resizeable but is forced to be resizable. */
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3313d3d..452291a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -30,6 +30,7 @@
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -2698,7 +2699,11 @@
                             mStartActivity.appTimeTracker, DEFER_RESUME,
                             "bringingFoundTaskToFront");
                     mMovedToFront = !wasTopOfVisibleRootTask;
-                } else {
+                } else if (intentActivity.getWindowingMode() != WINDOWING_MODE_PINNED) {
+                    // Leaves reparenting pinned task operations to task organizer to make sure it
+                    // dismisses pinned task properly.
+                    // TODO(b/199997762): Consider leaving all reparent operation of organized tasks
+                    //  to task organizer.
                     intentTask.reparent(mTargetRootTask, ON_TOP, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
                             ANIMATE, DEFER_RESUME, "reparentToTargetRootTask");
                     mMovedToFront = true;
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 73d6cec..c9db14d 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -444,7 +444,9 @@
             }
 
             if (mDisplayContent.mFixedRotationTransitionListener
-                    .isTopFixedOrientationRecentsAnimating()) {
+                    .isTopFixedOrientationRecentsAnimating()
+                    // If screen is off or the device is going to sleep, then still allow to update.
+                    && mService.mPolicy.okToAnimate(false /* ignoreScreenOn */)) {
                 // During the recents animation, the closing app might still be considered on top.
                 // In order to ignore its requested orientation to avoid a sensor led rotation (e.g
                 // user rotating the device while the recents animation is running), we ignore
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index c7ca180..4a1a922 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1095,29 +1095,27 @@
                 return rootTask;
             }
         } else if (candidateTask != null) {
-            final Task rootTask = candidateTask;
             final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
             final Task launchRootTask = getLaunchRootTask(windowingMode, activityType, options,
                     sourceTask, launchFlags);
-
             if (launchRootTask != null) {
-                if (rootTask.getParent() == null) {
-                    launchRootTask.addChild(rootTask, position);
-                } else if (rootTask.getParent() != launchRootTask) {
-                    rootTask.reparent(launchRootTask, position);
+                if (candidateTask.getParent() == null) {
+                    launchRootTask.addChild(candidateTask, position);
+                } else if (candidateTask.getParent() != launchRootTask) {
+                    candidateTask.reparent(launchRootTask, position);
                 }
-            } else if (rootTask.getDisplayArea() != this || !rootTask.isRootTask()) {
-                if (rootTask.getParent() == null) {
-                    addChild(rootTask, position);
+            } else if (candidateTask.getDisplayArea() != this || !candidateTask.isRootTask()) {
+                if (candidateTask.getParent() == null) {
+                    addChild(candidateTask, position);
                 } else {
-                    rootTask.reparent(this, onTop);
+                    candidateTask.reparent(this, onTop);
                 }
             }
             // Update windowing mode if necessary, e.g. moving a pinned task to fullscreen.
             if (candidateTask.getWindowingMode() != windowingMode) {
                 candidateTask.setWindowingMode(windowingMode);
             }
-            return rootTask;
+            return candidateTask.getRootTask();
         }
         return new Task.Builder(mAtmService)
                 .setWindowingMode(windowingMode)
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 31bf49d..c78e162 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -355,6 +355,10 @@
             throw new IllegalArgumentException("reparent: can't reparent to null " + this);
         }
 
+        if (newParent == this) {
+            throw new IllegalArgumentException("Can not reparent to itself " + this);
+        }
+
         final WindowContainer oldParent = mParent;
         if (mParent == newParent) {
             throw new IllegalArgumentException("WC=" + this + " already child of " + mParent);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d424d12..a0afb17 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1150,8 +1150,9 @@
                         && packageName.equals(service.getCredentialManagementAppPackageName())) {
                     service.removeCredentialManagementApp();
                 }
-            } catch (RemoteException | InterruptedException | IllegalStateException e) {
-                Slogf.e(LOG_TAG, "Unable to remove the credential management app");
+            } catch (RemoteException | InterruptedException | IllegalStateException
+                    | AssertionError e) {
+                Slogf.e(LOG_TAG, "Unable to remove the credential management app", e);
             }
         });
     }
@@ -3788,7 +3789,8 @@
         }
         Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
 
-        final CallerIdentity caller = getCallerIdentity();
+        final CallerIdentity caller = hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)
+                ? getCallerIdentity() : getCallerIdentity(adminReceiver);
         Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REMOVE_ACTIVE_ADMIN);
         enforceUserUnlocked(userHandle);
@@ -3805,8 +3807,7 @@
                         + adminReceiver);
                 return;
             }
-            Preconditions.checkCallAuthorization(admin.getUid() == caller.getUid()
-                    || hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
+
             mInjector.binderWithCleanCallingIdentity(() ->
                     removeActiveAdminLocked(adminReceiver, userHandle));
         }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
new file mode 100644
index 0000000..2f45fb9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class SensorOverlaysTest {
+
+    private static final int SENSOR_ID = 11;
+
+    @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock private IUdfpsOverlayController mUdfpsOverlayController;
+    @Mock private ISidefpsController mSidefpsController;
+    @Mock private AcquisitionClient<?> mAcquisitionClient;
+
+    @Test
+    public void noopWhenBothNull() {
+        final SensorOverlays useless = new SensorOverlays(null, null);
+        useless.show(SENSOR_ID, 2, null);
+        useless.hide(SENSOR_ID);
+    }
+
+    @Test
+    public void testProvidesUdfps() {
+        final List<IUdfpsOverlayController> udfps = new ArrayList<>();
+        SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController);
+
+        sensorOverlays.ifUdfps(udfps::add);
+        assertThat(udfps).isEmpty();
+
+        sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController);
+        sensorOverlays.ifUdfps(udfps::add);
+        assertThat(udfps).containsExactly(mUdfpsOverlayController);
+    }
+
+    @Test
+    public void testShow() throws Exception {
+        testShow(mUdfpsOverlayController, mSidefpsController);
+    }
+
+    @Test
+    public void testShowUdfps() throws Exception {
+        testShow(mUdfpsOverlayController, null);
+    }
+
+    @Test
+    public void testShowSidefps() throws Exception {
+        testShow(null, mSidefpsController);
+    }
+
+    private void testShow(IUdfpsOverlayController udfps, ISidefpsController sidefps)
+            throws Exception {
+        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
+        final int reason = BiometricOverlayConstants.REASON_UNKNOWN;
+        sensorOverlays.show(SENSOR_ID, reason, mAcquisitionClient);
+
+        if (udfps != null) {
+            verify(mUdfpsOverlayController).showUdfpsOverlay(eq(SENSOR_ID), eq(reason), any());
+        }
+        if (sidefps != null) {
+            verify(mSidefpsController).show();
+        }
+    }
+
+    @Test
+    public void testHide() throws Exception {
+        testHide(mUdfpsOverlayController, mSidefpsController);
+    }
+
+    @Test
+    public void testHideUdfps() throws Exception {
+        testHide(mUdfpsOverlayController, null);
+    }
+
+    @Test
+    public void testHideSidefps() throws Exception {
+        testHide(null, mSidefpsController);
+    }
+
+    private void testHide(IUdfpsOverlayController udfps, ISidefpsController sidefps)
+            throws Exception {
+        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
+        sensorOverlays.hide(SENSOR_ID);
+
+        if (udfps != null) {
+            verify(mUdfpsOverlayController).hideUdfpsOverlay(eq(SENSOR_ID));
+        }
+        if (sidefps != null) {
+            verify(mSidefpsController).hide();
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index b51918e..8b7c90d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -19,10 +19,13 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.hardware.biometrics.common.CommonProps;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.SensorLocation;
@@ -56,6 +59,8 @@
     @Mock
     private Context mContext;
     @Mock
+    private Resources mResources;
+    @Mock
     private UserManager mUserManager;
     @Mock
     private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@@ -74,19 +79,21 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.obtainTypedArray(anyInt())).thenReturn(mock(TypedArray.class));
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
         when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
 
         final SensorProps sensor1 = new SensorProps();
         sensor1.commonProps = new CommonProps();
         sensor1.commonProps.sensorId = 0;
-        sensor1.sensorLocations = new SensorLocation[] {new SensorLocation()};
+        sensor1.sensorLocations = new SensorLocation[]{new SensorLocation()};
         final SensorProps sensor2 = new SensorProps();
         sensor2.commonProps = new CommonProps();
         sensor2.commonProps.sensorId = 1;
-        sensor2.sensorLocations = new SensorLocation[] {new SensorLocation()};
+        sensor2.sensorLocations = new SensorLocation[]{new SensorLocation()};
 
-        mSensorProps = new SensorProps[] {sensor1, sensor2};
+        mSensorProps = new SensorProps[]{sensor1, sensor2};
 
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 9ad479a..9a14e7d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2724,9 +2724,11 @@
                 mAtm, null /* fragmentToken */, false /* createdByOrganizer */);
         fragmentSetup.accept(taskFragment2, new Rect(width / 2, 0, width, height));
         task.addChild(taskFragment2, POSITION_TOP);
-        final ActivityRecord activity2 = new ActivityBuilder(mAtm).build();
+        final ActivityRecord activity2 = new ActivityBuilder(mAtm)
+                .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE).build();
         activity2.mVisibleRequested = true;
         taskFragment2.addChild(activity2);
+        assertTrue(activity2.isResizeable());
         activity1.reparent(taskFragment1, POSITION_TOP);
 
         assertEquals(task, activity1.mStartingData.mAssociatedTask);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 6918bdd..e340217 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1572,6 +1572,12 @@
         mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation();
         assertTrue(displayRotation.updateRotationUnchecked(false));
 
+        // Rotation can be updated if the policy is not ok to animate (e.g. going to sleep).
+        mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
+        displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
+        ((TestWindowManagerPolicy) mWm.mPolicy).mOkToAnimate = false;
+        assertTrue(displayRotation.updateRotationUnchecked(false));
+
         // Rotation can be updated if the recents animation is animating but it is not on top, e.g.
         // switching activities in different orientations by quickstep gesture.
         mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
@@ -2334,11 +2340,10 @@
         // mirror.
         setUpDefaultTaskDisplayAreaWindowToken();
 
-        // GIVEN SurfaceControl can successfully mirror the provided surface.
+        // GIVEN SurfaceControl does not mirror a null surface.
         Point surfaceSize = new Point(
                 mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
                 mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
-        surfaceControlMirrors(surfaceSize);
 
         // GIVEN a new VirtualDisplay with an associated surface.
         final VirtualDisplay display = createVirtualDisplay(surfaceSize, null /* surface */);
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 460a26d..0bb6198 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -32,7 +32,9 @@
         ":android-test-mock-sources",
         // Note: Below are NOT APIs of this library. We only take APIs under
         // the android.test.mock package. They however provide private APIs that
-        // android.test.mock APIs references to.
+        // android.test.mock APIs references to. We need to have the classes in
+        // source code form to have access to the @hide comment which disappears
+        // when the classes are compiled into a Jar library.
         ":framework-core-sources-for-test-mock",
         ":framework_native_aidl",
     ],
@@ -46,6 +48,14 @@
     api_packages: [
         "android.test.mock",
     ],
+    // Only include android.test.mock.* classes. Jarjar rules below removes
+    // classes in other packages like android.content. In order to keep the
+    // list up-to-date, permitted_packages ensures that the library contains
+    // clases under android.test.mock after the jarjar rules are applied.
+    jarjar_rules: "jarjar-rules.txt",
+    permitted_packages: [
+        "android.test.mock",
+    ],
     compile_dex: true,
     default_to_stubs: true,
     dist_group: "android",
diff --git a/test-mock/jarjar-rules.txt b/test-mock/jarjar-rules.txt
new file mode 100644
index 0000000..4420a44
--- /dev/null
+++ b/test-mock/jarjar-rules.txt
@@ -0,0 +1,7 @@
+zap android.accounts.**
+zap android.app.**
+zap android.content.**
+zap android.database.**
+zap android.os.**
+zap android.util.**
+zap android.view.**
diff --git a/tests/componentalias/Android.bp b/tests/componentalias/Android.bp
index 4e2009d..e5eb3c7 100644
--- a/tests/componentalias/Android.bp
+++ b/tests/componentalias/Android.bp
@@ -51,6 +51,7 @@
     package_name: "android.content.componentalias.tests",
     manifest: "AndroidManifest.xml",
     additional_manifests: [
+        "AndroidManifest_main.xml",
         "AndroidManifest_service_aliases.xml",
         "AndroidManifest_service_targets.xml",
     ],
@@ -65,7 +66,7 @@
     package_name: "android.content.componentalias.tests.sub1",
     manifest: "AndroidManifest.xml",
     additional_manifests: [
-        "AndroidManifest_service_aliases.xml",
+        "AndroidManifest_sub1.xml",
         "AndroidManifest_service_targets.xml",
     ],
     test_config_template: "AndroidTest-template.xml",
@@ -79,7 +80,7 @@
     package_name: "android.content.componentalias.tests.sub2",
     manifest: "AndroidManifest.xml",
     additional_manifests: [
-        "AndroidManifest_service_aliases.xml",
+        "AndroidManifest_sub2.xml",
         "AndroidManifest_service_targets.xml",
     ],
     test_config_template: "AndroidTest-template.xml",
diff --git a/tests/componentalias/AndroidManifest.xml b/tests/componentalias/AndroidManifest.xml
index 893be8e..7bb83a3 100755
--- a/tests/componentalias/AndroidManifest.xml
+++ b/tests/componentalias/AndroidManifest.xml
@@ -20,10 +20,6 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
+        <property android:name="com.android.EXPERIMENTAL_COMPONENT_ALIAS_OPT_IN" android:value="true" />
     </application>
-
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.content.componentalias.tests" >
-    </instrumentation>
 </manifest>
diff --git a/tests/componentalias/AndroidManifest_main.xml b/tests/componentalias/AndroidManifest_main.xml
new file mode 100755
index 0000000..70e817e
--- /dev/null
+++ b/tests/componentalias/AndroidManifest_main.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.componentalias.tests" >
+
+    <application>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.componentalias.tests" >
+    </instrumentation>
+</manifest>
diff --git a/tests/componentalias/AndroidManifest_sub1.xml b/tests/componentalias/AndroidManifest_sub1.xml
new file mode 100755
index 0000000..21616f5
--- /dev/null
+++ b/tests/componentalias/AndroidManifest_sub1.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.componentalias.tests" >
+
+    <application>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.componentalias.tests.sub1" >
+    </instrumentation>
+</manifest>
diff --git a/tests/componentalias/AndroidManifest_sub2.xml b/tests/componentalias/AndroidManifest_sub2.xml
new file mode 100755
index 0000000..c11b0cd
--- /dev/null
+++ b/tests/componentalias/AndroidManifest_sub2.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.componentalias.tests" >
+
+    <application>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.componentalias.tests.sub2" >
+    </instrumentation>
+</manifest>
diff --git a/tests/componentalias/AndroidTest-template.xml b/tests/componentalias/AndroidTest-template.xml
index afdfe79..2d46217 100644
--- a/tests/componentalias/AndroidTest-template.xml
+++ b/tests/componentalias/AndroidTest-template.xml
@@ -21,6 +21,8 @@
         <option name="test-file-name" value="ComponentAliasTests2.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="am compat enable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android" />
+
         <!-- Exempt the helper APKs from the BG restriction, so they can start BG services. -->
         <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests" />
         <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests.sub1" />
diff --git a/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java b/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java
index a62d9eb..89db2f7 100644
--- a/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java
+++ b/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java
@@ -39,10 +39,8 @@
     @Before
     public void enableComponentAlias() throws Exception {
         sDeviceConfig.set("component_alias_overrides", "");
-        sDeviceConfig.set("enable_experimental_component_alias", "true");
 
-        // Device config propagation happens on a handler, so we need to wait for AM to
-        // actually set it.
+        // Make sure the feature is actually enabled.
         TestUtils.waitUntil("Wait until component alias is actually enabled", () -> {
             return ShellUtils.runShellCommand("dumpsys activity component-alias")
                     .indexOf("Enabled: true") > 0;
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java
index eab0a6c..a568313 100644
--- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java
+++ b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java
@@ -20,11 +20,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.hamcrest.core.Is.is;
+
 import android.content.ComponentName;
 import android.content.Intent;
 
 import com.android.compatibility.common.util.BroadcastMessenger.Receiver;
 
+import org.junit.Assume;
 import org.junit.Test;
 
 import java.util.function.Consumer;
@@ -74,6 +77,8 @@
 
     @Test
     public void testBroadcast_explicitPackageName() {
+        // TODO Fix it -- it should work even when called from sub-packages.
+        Assume.assumeThat(sContext.getPackageName(), is(MAIN_PACKAGE));
         forEachCombo((c) -> {
             Intent i = new Intent().setPackage(c.alias.getPackageName());
             i.setAction(c.action);
diff --git a/tools/bit/print.cpp b/tools/bit/print.cpp
index 35feda1..8bc6f16 100644
--- a/tools/bit/print.cpp
+++ b/tools/bit/print.cpp
@@ -17,6 +17,7 @@
 #include "print.h"
 
 #include <sys/ioctl.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <unistd.h>
 
diff --git a/tools/bit/util.h b/tools/bit/util.h
index 7ccdab1..8c66911 100644
--- a/tools/bit/util.h
+++ b/tools/bit/util.h
@@ -17,6 +17,8 @@
 #ifndef UTIL_H
 #define UTIL_H
 
+#include <sys/types.h>
+
 #include <map>
 #include <string>
 #include <vector>
diff --git a/tools/streaming_proto/Errors.cpp b/tools/streaming_proto/Errors.cpp
index 0cd9037..6890d99 100644
--- a/tools/streaming_proto/Errors.cpp
+++ b/tools/streaming_proto/Errors.cpp
@@ -1,5 +1,6 @@
 #include "Errors.h"
 
+#include <stdarg.h>
 #include <stdlib.h>
 
 namespace android {