Merge "New Pipeline: Numerous bug and crash fixes:"
diff --git a/Android.bp b/Android.bp
index d52f08f..88c01e7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -82,6 +82,7 @@
         ":framework-mca-filterpacks-sources",
         ":framework-media-sources",
         ":framework-mms-sources",
+        ":framework-omapi-sources",
         ":framework-opengl-sources",
         ":framework-rs-sources",
         ":framework-sax-sources",
@@ -273,6 +274,7 @@
         "android.hardware.vibrator-V1.2-java",
         "android.hardware.vibrator-V1.3-java",
         "android.hardware.vibrator-V2-java",
+        "android.se.omapi-V1-java",
         "android.system.suspend.control.internal-java",
         "devicepolicyprotosnano",
 
@@ -324,8 +326,12 @@
         "error_prone_android_framework",
     ],
     required: [
+        // TODO(b/120066492): remove default_television.xml when the build system
+        // propagates "required" properly.
+        "default_television.xml",
         "framework-platform-compat-config",
-        // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly.
+        // TODO(b/120066492): remove gps_debug and protolog.conf.json when the build
+        // system propagates "required" properly.
         "gps_debug.conf",
         "icu4j-platform-compat-config",
         "protolog.conf.json.gz",
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 392df73..1620983 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -5142,7 +5142,7 @@
                 Slog.d(TAG, "mBroadcastRefCount -> " + mBroadcastRefCount);
             }
             if (mBroadcastRefCount == 0) {
-                mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 0).sendToTarget();
+                mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 0, 0).sendToTarget();
                 mWakeLock.release();
                 if (mInFlight.size() > 0) {
                     mLog.w("Finished all dispatches with " + mInFlight.size()
@@ -5314,7 +5314,7 @@
             if (mBroadcastRefCount == 0) {
                 setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true);
                 mWakeLock.acquire();
-                mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1).sendToTarget();
+                mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1, 0).sendToTarget();
             }
             final InFlight inflight = new InFlight(AlarmManagerService.this, alarm, nowELAPSED);
             mInFlight.add(inflight);
diff --git a/boot/boot-image-profile.txt b/boot/boot-image-profile.txt
index 5f27cc7..82269d4 100644
--- a/boot/boot-image-profile.txt
+++ b/boot/boot-image-profile.txt
@@ -2899,10 +2899,8 @@
 HSPLandroid/app/assist/AssistStructure$ViewNode;->getChildCount()I
 HSPLandroid/app/assist/AssistStructure$ViewNode;->writeSelfToParcel(Landroid/os/Parcel;Landroid/os/PooledStringWriter;Z[FZ)I+]Landroid/view/autofill/AutofillId;Landroid/view/autofill/AutofillId;]Landroid/graphics/Matrix;Landroid/graphics/Matrix;]Landroid/app/assist/AssistStructure$ViewNodeText;Landroid/app/assist/AssistStructure$ViewNodeText;]Landroid/os/Parcel;Landroid/os/Parcel;
 HSPLandroid/app/assist/AssistStructure$ViewNode;->writeString(Landroid/os/Parcel;Landroid/os/PooledStringWriter;Ljava/lang/String;)V+]Landroid/os/PooledStringWriter;Landroid/os/PooledStringWriter;]Landroid/os/Parcel;Landroid/os/Parcel;
-HSPLandroid/app/assist/AssistStructure$ViewNodeBuilder;-><init>()V
 HSPLandroid/app/assist/AssistStructure$ViewNodeBuilder;->getChildCount()I
 HSPLandroid/app/assist/AssistStructure$ViewNodeBuilder;->getNodeText()Landroid/app/assist/AssistStructure$ViewNodeText;
-HSPLandroid/app/assist/AssistStructure$ViewNodeBuilder;->getViewNode()Landroid/app/assist/AssistStructure$ViewNode;
 HSPLandroid/app/assist/AssistStructure$ViewNodeBuilder;->newChild(I)Landroid/view/ViewStructure;
 HSPLandroid/app/assist/AssistStructure$ViewNodeBuilder;->setAutofillHints([Ljava/lang/String;)V
 HSPLandroid/app/assist/AssistStructure$ViewNodeBuilder;->setAutofillId(Landroid/view/autofill/AutofillId;)V
@@ -3883,8 +3881,6 @@
 HSPLandroid/content/ContentResolver$ResultListener;-><init>(Landroid/content/ContentResolver$1;)V
 HSPLandroid/content/ContentResolver$ResultListener;->onResult(Landroid/os/Bundle;)V+]Landroid/os/ParcelableException;Landroid/os/ParcelableException;]Ljava/lang/Object;Landroid/content/ContentResolver$StringResultListener;,Landroid/content/ContentResolver$UriResultListener;]Landroid/os/Bundle;Landroid/os/Bundle;]Landroid/content/ContentResolver$ResultListener;Landroid/content/ContentResolver$UriResultListener;,Landroid/content/ContentResolver$StringResultListener;
 HSPLandroid/content/ContentResolver$ResultListener;->waitForResult(J)V+]Ljava/lang/Object;Landroid/content/ContentResolver$StringResultListener;
-HSPLandroid/content/ContentResolver$StringResultListener;-><init>()V
-HSPLandroid/content/ContentResolver$StringResultListener;-><init>(Landroid/content/ContentResolver$1;)V
 HSPLandroid/content/ContentResolver$StringResultListener;->getResultFromBundle(Landroid/os/Bundle;)Ljava/lang/Object;+]Landroid/content/ContentResolver$StringResultListener;Landroid/content/ContentResolver$StringResultListener;
 HSPLandroid/content/ContentResolver$StringResultListener;->getResultFromBundle(Landroid/os/Bundle;)Ljava/lang/String;+]Landroid/os/Bundle;Landroid/os/Bundle;
 HSPLandroid/content/ContentResolver;-><init>(Landroid/content/Context;)V
@@ -7029,7 +7025,6 @@
 HSPLandroid/graphics/Region$1;->createFromParcel(Landroid/os/Parcel;)Landroid/graphics/Region;
 HSPLandroid/graphics/Region$1;->createFromParcel(Landroid/os/Parcel;)Ljava/lang/Object;+]Landroid/graphics/Region$1;Landroid/graphics/Region$1;
 HSPLandroid/graphics/Region;-><init>()V
-HSPLandroid/graphics/Region;-><init>(IIII)V
 HSPLandroid/graphics/Region;-><init>(J)V
 HSPLandroid/graphics/Region;->access$000(Landroid/os/Parcel;)J
 HSPLandroid/graphics/Region;->equals(Ljava/lang/Object;)Z
@@ -7075,7 +7070,6 @@
 HSPLandroid/graphics/RenderNode;->hasDisplayList()Z
 HSPLandroid/graphics/RenderNode;->hasIdentityMatrix()Z
 HSPLandroid/graphics/RenderNode;->isAttached()Z+]Landroid/graphics/RenderNode$AnimationHost;Landroid/view/ViewAnimationHostBridge;
-HSPLandroid/graphics/RenderNode;->isPivotExplicitlySet()Z
 HSPLandroid/graphics/RenderNode;->offsetTopAndBottom(I)Z
 HSPLandroid/graphics/RenderNode;->setAlpha(F)Z
 HSPLandroid/graphics/RenderNode;->setAnimationMatrix(Landroid/graphics/Matrix;)Z+]Landroid/graphics/Matrix;Landroid/graphics/Matrix;
@@ -8283,7 +8277,6 @@
 HSPLandroid/hardware/GeomagneticField$LegendreTable;-><init>(IF)V
 HSPLandroid/hardware/GeomagneticField;-><init>(FFFJ)V
 HSPLandroid/hardware/GeomagneticField;->computeGeocentricCoordinates(FFF)V
-HSPLandroid/hardware/GeomagneticField;->getDeclination()F
 HSPLandroid/hardware/HardwareBuffer$1;->createFromParcel(Landroid/os/Parcel;)Landroid/hardware/HardwareBuffer;
 HSPLandroid/hardware/HardwareBuffer$1;->createFromParcel(Landroid/os/Parcel;)Ljava/lang/Object;
 HSPLandroid/hardware/HardwareBuffer;-><init>(J)V+]Llibcore/util/NativeAllocationRegistry;Llibcore/util/NativeAllocationRegistry;]Ljava/lang/Class;Ljava/lang/Class;]Ldalvik/system/CloseGuard;Ldalvik/system/CloseGuard;
@@ -11353,7 +11346,6 @@
 HSPLandroid/media/SoundPool$EventHandler;->handleMessage(Landroid/os/Message;)V
 HSPLandroid/media/SoundPool;-><init>(ILandroid/media/AudioAttributes;)V
 HSPLandroid/media/SoundPool;-><init>(ILandroid/media/AudioAttributes;Landroid/media/SoundPool$1;)V
-HSPLandroid/media/SoundPool;->load(Ljava/lang/String;I)I
 HSPLandroid/media/SoundPool;->postEventFromNative(Ljava/lang/Object;IIILjava/lang/Object;)V
 HSPLandroid/media/SoundPool;->setOnLoadCompleteListener(Landroid/media/SoundPool$OnLoadCompleteListener;)V
 HSPLandroid/media/SubtitleController$1;->handleMessage(Landroid/os/Message;)Z
@@ -11389,10 +11381,8 @@
 HSPLandroid/media/Utils;->sortDistinctRanges([Landroid/util/Range;)V
 HSPLandroid/media/audiofx/AudioEffect$Descriptor;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
 HSPLandroid/media/audiopolicy/AudioProductStrategy$AudioAttributesGroup;-><init>(II[Landroid/media/AudioAttributes;)V
-HSPLandroid/media/audiopolicy/AudioProductStrategy$AudioAttributesGroup;->supportsStreamType(I)Z
 HSPLandroid/media/audiopolicy/AudioProductStrategy;-><init>(Ljava/lang/String;I[Landroid/media/audiopolicy/AudioProductStrategy$AudioAttributesGroup;)V
 HSPLandroid/media/audiopolicy/AudioProductStrategy;->attributesMatches(Landroid/media/AudioAttributes;Landroid/media/AudioAttributes;)Z+]Landroid/media/AudioAttributes;Landroid/media/AudioAttributes;
-HSPLandroid/media/audiopolicy/AudioProductStrategy;->getAudioAttributesForLegacyStreamType(I)Landroid/media/AudioAttributes;+]Landroid/media/audiopolicy/AudioProductStrategy$AudioAttributesGroup;Landroid/media/audiopolicy/AudioProductStrategy$AudioAttributesGroup;
 HSPLandroid/media/audiopolicy/AudioProductStrategy;->getAudioAttributesForStrategyWithLegacyStreamType(I)Landroid/media/AudioAttributes;
 HSPLandroid/media/audiopolicy/AudioProductStrategy;->getAudioProductStrategies()Ljava/util/List;
 HSPLandroid/media/audiopolicy/AudioProductStrategy;->getLegacyStreamTypeForStrategyWithAudioAttributes(Landroid/media/AudioAttributes;)I+]Landroid/media/audiopolicy/AudioProductStrategy;Landroid/media/audiopolicy/AudioProductStrategy;]Ljava/util/List;Ljava/util/ArrayList;]Ljava/util/Iterator;Ljava/util/ArrayList$Itr;
@@ -13125,7 +13115,6 @@
 HSPLandroid/os/ParcelableParcel$1;->createFromParcel(Landroid/os/Parcel;)Landroid/os/ParcelableParcel;
 HSPLandroid/os/ParcelableParcel$1;->createFromParcel(Landroid/os/Parcel;)Ljava/lang/Object;
 HSPLandroid/os/ParcelableParcel;-><init>(Landroid/os/Parcel;Ljava/lang/ClassLoader;)V
-HSPLandroid/os/ParcelableParcel;-><init>(Ljava/lang/ClassLoader;)V
 HSPLandroid/os/ParcelableParcel;->getClassLoader()Ljava/lang/ClassLoader;
 HSPLandroid/os/ParcelableParcel;->getParcel()Landroid/os/Parcel;
 HSPLandroid/os/ParcelableParcel;->writeToParcel(Landroid/os/Parcel;I)V
@@ -13481,7 +13470,6 @@
 HSPLandroid/os/Temperature;-><init>(FILjava/lang/String;I)V
 HSPLandroid/os/Temperature;->getStatus()I
 HSPLandroid/os/Temperature;->isValidStatus(I)Z
-HSPLandroid/os/Temperature;->isValidType(I)Z
 HSPLandroid/os/ThreadLocalWorkSource$$ExternalSyntheticLambda0;->get()Ljava/lang/Object;
 HSPLandroid/os/ThreadLocalWorkSource;->getToken()J+]Ljava/lang/Integer;Ljava/lang/Integer;]Ljava/lang/ThreadLocal;Ljava/lang/ThreadLocal$SuppliedThreadLocal;
 HSPLandroid/os/ThreadLocalWorkSource;->getUid()I+]Ljava/lang/Integer;Ljava/lang/Integer;]Ljava/lang/ThreadLocal;Ljava/lang/ThreadLocal$SuppliedThreadLocal;
@@ -18488,7 +18476,6 @@
 HSPLandroid/view/ViewRootImpl$ImeInputStage;-><init>(Landroid/view/ViewRootImpl;Landroid/view/ViewRootImpl$InputStage;Ljava/lang/String;)V
 HSPLandroid/view/ViewRootImpl$ImeInputStage;->onFinishedInputEvent(Ljava/lang/Object;Z)V
 HSPLandroid/view/ViewRootImpl$ImeInputStage;->onProcess(Landroid/view/ViewRootImpl$QueuedInputEvent;)I
-HSPLandroid/view/ViewRootImpl$InputMetricsListener;-><init>(Landroid/view/ViewRootImpl;)V
 HSPLandroid/view/ViewRootImpl$InputMetricsListener;->onFrameMetricsAvailable(I)V+]Landroid/view/ViewRootImpl$WindowInputEventReceiver;Landroid/view/ViewRootImpl$WindowInputEventReceiver;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Landroid/view/InputEventReceiver;Landroid/view/ViewRootImpl$WindowInputEventReceiver;
 HSPLandroid/view/ViewRootImpl$InputStage;-><init>(Landroid/view/ViewRootImpl;Landroid/view/ViewRootImpl$InputStage;)V
 HSPLandroid/view/ViewRootImpl$InputStage;->apply(Landroid/view/ViewRootImpl$QueuedInputEvent;I)V+]Landroid/view/ViewRootImpl$InputStage;megamorphic_types
@@ -20395,7 +20382,6 @@
 HSPLandroid/widget/OverScroller$SplineOverScroller;->fling(IIIII)V
 HSPLandroid/widget/OverScroller$SplineOverScroller;->getSplineDeceleration(I)D
 HSPLandroid/widget/OverScroller$SplineOverScroller;->getSplineFlingDistance(I)D
-HSPLandroid/widget/OverScroller$SplineOverScroller;->getSplineFlingDuration(I)I
 HSPLandroid/widget/OverScroller$SplineOverScroller;->onEdgeReached()V
 HSPLandroid/widget/OverScroller$SplineOverScroller;->springback(III)Z
 HSPLandroid/widget/OverScroller$SplineOverScroller;->startScroll(III)V
@@ -22568,10 +22554,7 @@
 HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker$1;->onIsNonStrongBiometricAllowedChanged(ZI)V
 HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker$1;->onStrongAuthRequiredChanged(II)V
 HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker$H;->handleMessage(Landroid/os/Message;)V
-HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker;-><init>(Landroid/content/Context;)V
-HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker;-><init>(Landroid/content/Context;Landroid/os/Looper;)V
 HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker;->getStrongAuthForUser(I)I+]Landroid/util/SparseIntArray;Landroid/util/SparseIntArray;
-HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker;->getStub()Landroid/app/trust/IStrongAuthTracker$Stub;
 HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker;->handleIsNonStrongBiometricAllowedChanged(ZI)V
 HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker;->handleStrongAuthRequiredChanged(II)V
 HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker;->isNonStrongBiometricAllowedAfterIdleTimeout(I)Z
diff --git a/core/api/current.txt b/core/api/current.txt
index 9d74a8c..4d94dc1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -3255,6 +3255,28 @@
     method public boolean willContinue();
   }
 
+  public final class MagnificationConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public float getCenterX();
+    method public float getCenterY();
+    method public int getMode();
+    method public float getScale();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.MagnificationConfig> CREATOR;
+    field public static final int DEFAULT_MODE = 0; // 0x0
+    field public static final int FULLSCREEN_MODE = 1; // 0x1
+    field public static final int WINDOW_MODE = 2; // 0x2
+  }
+
+  public static final class MagnificationConfig.Builder {
+    ctor public MagnificationConfig.Builder();
+    method @NonNull public android.accessibilityservice.MagnificationConfig build();
+    method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterX(float);
+    method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterY(float);
+    method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setMode(int);
+    method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setScale(float);
+  }
+
   public final class TouchInteractionController {
     method public int getDisplayId();
     method public int getMaxPointerCount();
@@ -11279,6 +11301,7 @@
     field public static final String ACTION_QUICK_VIEW = "android.intent.action.QUICK_VIEW";
     field public static final String ACTION_REBOOT = "android.intent.action.REBOOT";
     field public static final String ACTION_RUN = "android.intent.action.RUN";
+    field public static final String ACTION_SAFETY_CENTER = "android.intent.action.SAFETY_CENTER";
     field public static final String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
     field public static final String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON";
     field public static final String ACTION_SEARCH = "android.intent.action.SEARCH";
@@ -12532,6 +12555,7 @@
     method @NonNull public java.io.OutputStream openWrite(@NonNull String, long, long) throws java.io.IOException;
     method public void removeChildSessionId(int);
     method public void removeSplit(@NonNull String) throws java.io.IOException;
+    method public void requestChecksums(@NonNull String, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, java.io.FileNotFoundException;
     method @Deprecated public void setChecksums(@NonNull String, @NonNull java.util.List<android.content.pm.Checksum>, @Nullable byte[]) throws java.io.IOException;
     method public void setStagingProgress(float);
     method public void transfer(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -51423,14 +51447,18 @@
   public static final class AccessibilityNodeInfo.CollectionItemInfo {
     ctor public AccessibilityNodeInfo.CollectionItemInfo(int, int, int, int, boolean);
     ctor public AccessibilityNodeInfo.CollectionItemInfo(int, int, int, int, boolean, boolean);
+    ctor public AccessibilityNodeInfo.CollectionItemInfo(@Nullable String, int, int, @Nullable String, int, int, boolean, boolean);
     method public int getColumnIndex();
     method public int getColumnSpan();
+    method @Nullable public String getColumnTitle();
     method public int getRowIndex();
     method public int getRowSpan();
+    method @Nullable public String getRowTitle();
     method @Deprecated public boolean isHeading();
     method public boolean isSelected();
     method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean);
     method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean, boolean);
+    method @NonNull public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(@Nullable String, int, int, @Nullable String, int, int, boolean, boolean);
   }
 
   public static final class AccessibilityNodeInfo.ExtraRenderingInfo {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 6d342db..81d1c9e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -271,6 +271,7 @@
     field public static final String SECURE_ELEMENT_PRIVILEGED_OPERATION = "android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION";
     field public static final String SEND_CATEGORY_CAR_NOTIFICATIONS = "android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS";
     field public static final String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY";
+    field public static final String SEND_SAFETY_CENTER_UPDATE = "android.permission.SEND_SAFETY_CENTER_UPDATE";
     field public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS";
     field public static final String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION";
     field public static final String SERIAL_PORT = "android.permission.SERIAL_PORT";
@@ -2444,6 +2445,7 @@
     field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
     field public static final String REBOOT_READINESS_SERVICE = "reboot_readiness";
     field public static final String ROLLBACK_SERVICE = "rollback";
+    field public static final String SAFETY_CENTER_SERVICE = "safety_center";
     field public static final String SEARCH_UI_SERVICE = "search_ui";
     field public static final String SECURE_ELEMENT_SERVICE = "secure_element";
     field public static final String SMARTSPACE_SERVICE = "smartspace";
@@ -6308,6 +6310,7 @@
     method public int getAudioStreamType();
     method public int getVideoStreamType();
     method public boolean isPassthrough();
+    method public boolean useSecureMemory();
     field public static final int AUDIO_STREAM_TYPE_AAC = 6; // 0x6
     field public static final int AUDIO_STREAM_TYPE_AC3 = 7; // 0x7
     field public static final int AUDIO_STREAM_TYPE_AC4 = 9; // 0x9
@@ -6343,6 +6346,7 @@
     method @NonNull public android.media.tv.tuner.filter.AvSettings build();
     method @NonNull public android.media.tv.tuner.filter.AvSettings.Builder setAudioStreamType(int);
     method @NonNull public android.media.tv.tuner.filter.AvSettings.Builder setPassthrough(boolean);
+    method @NonNull public android.media.tv.tuner.filter.AvSettings.Builder setUseSecureMemory(boolean);
     method @NonNull public android.media.tv.tuner.filter.AvSettings.Builder setVideoStreamType(int);
   }
 
@@ -6365,14 +6369,14 @@
   }
 
   public class Filter implements java.lang.AutoCloseable {
+    method @Nullable public String acquireSharedFilterToken();
     method public void close();
     method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
-    method @Nullable public String createSharedFilter();
     method public int flush();
+    method public void freeSharedFilterToken(@NonNull String);
     method @Deprecated public int getId();
     method public long getIdLong();
     method public int read(@NonNull byte[], long, long);
-    method public void releaseSharedFilter(@NonNull String);
     method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
     method public int setMonitorEventMask(int);
     method public int start();
@@ -6518,6 +6522,7 @@
     method public int getTsIndexMask();
     field public static final int INDEX_TYPE_NONE = 0; // 0x0
     field public static final int INDEX_TYPE_SC = 1; // 0x1
+    field public static final int INDEX_TYPE_SC_AVC = 3; // 0x3
     field public static final int INDEX_TYPE_SC_HEVC = 2; // 0x2
     field public static final int MPT_INDEX_AUDIO = 262144; // 0x40000
     field public static final int MPT_INDEX_MPT = 65536; // 0x10000
@@ -6610,6 +6615,7 @@
     method @NonNull public static android.media.tv.tuner.filter.SectionSettingsWithTableInfo.Builder builder(int);
     method public int getTableId();
     method public int getVersion();
+    field public static final int INVALID_TABLE_INFO_VERSION = -1; // 0xffffffff
   }
 
   public static class SectionSettingsWithTableInfo.Builder extends android.media.tv.tuner.filter.SectionSettings.Builder<android.media.tv.tuner.filter.SectionSettingsWithTableInfo.Builder> {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f83b3a4..ba0b6aa 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3250,12 +3250,6 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskAppearedInfo> CREATOR;
   }
 
-  public final class TaskFragmentAppearedInfo implements android.os.Parcelable {
-    method @NonNull public android.view.SurfaceControl getLeash();
-    method @NonNull public android.window.TaskFragmentInfo getTaskFragmentInfo();
-    field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentAppearedInfo> CREATOR;
-  }
-
   public final class TaskFragmentCreationParams implements android.os.Parcelable {
     method @NonNull public android.os.IBinder getFragmentToken();
     method @NonNull public android.graphics.Rect getInitialBounds();
@@ -3292,7 +3286,7 @@
     ctor public TaskFragmentOrganizer(@NonNull java.util.concurrent.Executor);
     method @NonNull public java.util.concurrent.Executor getExecutor();
     method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken();
-    method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentAppearedInfo);
+    method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentInfo);
     method public void onTaskFragmentError(@NonNull android.os.IBinder, @NonNull Throwable);
     method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo);
     method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration);
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 0f852b4..09af72d 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -2071,7 +2071,7 @@
             try {
                 connection.setServiceInfo(mInfo);
                 mInfo = null;
-                AccessibilityInteractionClient.getInstance(this).clearCache();
+                AccessibilityInteractionClient.getInstance(this).clearCache(mConnectionId);
             } catch (RemoteException re) {
                 Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
                 re.rethrowFromSystemServer();
@@ -2421,7 +2421,7 @@
                     if (event != null) {
                         // Send the event to AccessibilityCache via AccessibilityInteractionClient
                         AccessibilityInteractionClient.getInstance(mContext).onAccessibilityEvent(
-                                event);
+                                event, mConnectionId);
                         if (serviceWantsEvent
                                 && (mConnectionId != AccessibilityInteractionClient.NO_ID)) {
                             // Send the event to AccessibilityService
@@ -2451,7 +2451,7 @@
                     args.recycle();
                     if (connection != null) {
                         AccessibilityInteractionClient.getInstance(mContext).addConnection(
-                                mConnectionId, connection);
+                                mConnectionId, connection, /*initializeCache=*/true);
                         if (mContext != null) {
                             try {
                                 connection.setAttributionTag(mContext.getAttributionTag());
@@ -2466,7 +2466,8 @@
                         AccessibilityInteractionClient.getInstance(mContext).removeConnection(
                                 mConnectionId);
                         mConnectionId = AccessibilityInteractionClient.NO_ID;
-                        AccessibilityInteractionClient.getInstance(mContext).clearCache();
+                        AccessibilityInteractionClient.getInstance(mContext)
+                                .clearCache(mConnectionId);
                         mCallback.init(AccessibilityInteractionClient.NO_ID, null);
                     }
                     return;
@@ -2478,7 +2479,7 @@
                     return;
                 }
                 case DO_CLEAR_ACCESSIBILITY_CACHE: {
-                    AccessibilityInteractionClient.getInstance(mContext).clearCache();
+                    AccessibilityInteractionClient.getInstance(mContext).clearCache(mConnectionId);
                     return;
                 }
                 case DO_ON_KEY_EVENT: {
diff --git a/core/java/android/window/TaskFragmentAppearedInfo.aidl b/core/java/android/accessibilityservice/MagnificationConfig.aidl
similarity index 77%
rename from core/java/android/window/TaskFragmentAppearedInfo.aidl
rename to core/java/android/accessibilityservice/MagnificationConfig.aidl
index 3729c09..fe415a8 100644
--- a/core/java/android/window/TaskFragmentAppearedInfo.aidl
+++ b/core/java/android/accessibilityservice/MagnificationConfig.aidl
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package android.window;
+package android.accessibilityservice;
 
-/**
- * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
- * @hide
- */
-parcelable TaskFragmentAppearedInfo;
+parcelable MagnificationConfig;
diff --git a/core/java/android/accessibilityservice/MagnificationConfig.java b/core/java/android/accessibilityservice/MagnificationConfig.java
new file mode 100644
index 0000000..8884508
--- /dev/null
+++ b/core/java/android/accessibilityservice/MagnificationConfig.java
@@ -0,0 +1,250 @@
+/*
+ * 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.accessibilityservice;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class describes the magnification config for {@link AccessibilityService} to control the
+ * magnification.
+ *
+ * <p>
+ * When the magnification config uses {@link #DEFAULT_MODE},
+ * {@link AccessibilityService} will be able to control the activated magnifier on the display.
+ * If there is no magnifier activated, it controls the last-activated magnification mode.
+ * If there is no magnifier activated before, it controls full-screen magnifier by default.
+ * </p>
+ *
+ * <p>
+ * When the magnification config uses {@link #FULLSCREEN_MODE}. {@link AccessibilityService} will
+ * be able to control full-screen magnifier on the display.
+ * </p>
+ *
+ * <p>
+ * When the magnification config uses {@link #WINDOW_MODE}. {@link AccessibilityService} will be
+ * able to control the activated window magnifier on the display.
+ * </p>
+ *
+ * <p>
+ * If the other magnification configs, scale centerX and centerY, are not set by the
+ * {@link Builder}, the configs should be current values or default values. And the center
+ * position ordinarily is the center of the screen.
+ * </p>
+ */
+public final class MagnificationConfig implements Parcelable {
+
+    /** The controlling magnification mode. It controls the activated magnifier. */
+    public static final int DEFAULT_MODE = 0;
+    /** The controlling magnification mode. It controls fullscreen magnifier. */
+    public static final int FULLSCREEN_MODE = 1;
+    /** The controlling magnification mode. It controls window magnifier. */
+    public static final int WINDOW_MODE = 2;
+
+    @IntDef(prefix = {"MAGNIFICATION_MODE"}, value = {
+            DEFAULT_MODE,
+            FULLSCREEN_MODE,
+            WINDOW_MODE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface MAGNIFICATION_MODE {
+    }
+
+    private int mMode = DEFAULT_MODE;
+    private float mScale = Float.NaN;
+    private float mCenterX = Float.NaN;
+    private float mCenterY = Float.NaN;
+
+    private MagnificationConfig() {
+        /* do nothing */
+    }
+
+    private MagnificationConfig(@NonNull Parcel parcel) {
+        mMode = parcel.readInt();
+        mScale = parcel.readFloat();
+        mCenterX = parcel.readFloat();
+        mCenterY = parcel.readFloat();
+    }
+
+    /**
+     * Returns the magnification mode that is the current activated mode or the controlling mode of
+     * the config.
+     *
+     * @return The magnification mode
+     */
+    public int getMode() {
+        return mMode;
+    }
+
+    /**
+     * Returns the magnification scale of the controlling magnifier
+     *
+     * @return the scale If the controlling magnifier is not activated, it returns 1 by default
+     */
+    public float getScale() {
+        return mScale;
+    }
+
+    /**
+     * Returns the screen-relative X coordinate of the center of the magnification viewport.
+     *
+     * @return the X coordinate. If the controlling magnifier is {@link #WINDOW_MODE} but not
+     * enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link
+     * #FULLSCREEN_MODE} but not enabled, it returns 0
+     */
+    public float getCenterX() {
+        return mCenterX;
+    }
+
+    /**
+     * Returns the screen-relative Y coordinate of the center of the magnification viewport.
+     *
+     * @return the Y coordinate If the controlling magnifier is {@link #WINDOW_MODE} but not
+     * enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link
+     * #FULLSCREEN_MODE} but not enabled, it returns 0
+     */
+    public float getCenterY() {
+        return mCenterY;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder("MagnificationConfig[");
+        stringBuilder.append("mode: ").append(getMode());
+        stringBuilder.append(", ");
+        stringBuilder.append("scale: ").append(getScale());
+        stringBuilder.append(", ");
+        stringBuilder.append("centerX: ").append(getCenterX());
+        stringBuilder.append(", ");
+        stringBuilder.append("centerY: ").append(getCenterY());
+        stringBuilder.append("] ");
+        return stringBuilder.toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeInt(mMode);
+        parcel.writeFloat(mScale);
+        parcel.writeFloat(mCenterX);
+        parcel.writeFloat(mCenterY);
+    }
+
+    /**
+     * Builder for creating {@link MagnificationConfig} objects.
+     */
+    public static final class Builder {
+
+        private int mMode = DEFAULT_MODE;
+        private float mScale = Float.NaN;
+        private float mCenterX = Float.NaN;
+        private float mCenterY = Float.NaN;
+
+        /**
+         * Creates a new Builder.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Sets the magnification mode.
+         *
+         * @param mode The magnification mode
+         * @return This builder
+         */
+        @NonNull
+        public MagnificationConfig.Builder setMode(@MAGNIFICATION_MODE int mode) {
+            mMode = mode;
+            return this;
+        }
+
+        /**
+         * Sets the magnification scale.
+         *
+         * @param scale The magnification scale
+         * @return This builder
+         */
+        @NonNull
+        public MagnificationConfig.Builder setScale(float scale) {
+            mScale = scale;
+            return this;
+        }
+
+        /**
+         * Sets the X coordinate of the center of the magnification viewport.
+         *
+         * @param centerX the screen-relative X coordinate around which to
+         *                center and scale, or {@link Float#NaN} to leave unchanged
+         * @return This builder
+         */
+        @NonNull
+        public MagnificationConfig.Builder setCenterX(float centerX) {
+            mCenterX = centerX;
+            return this;
+        }
+
+        /**
+         * Sets the Y coordinate of the center of the magnification viewport.
+         *
+         * @param centerY the screen-relative Y coordinate around which to
+         *                center and scale, or {@link Float#NaN} to leave unchanged
+         * @return This builder
+         */
+        @NonNull
+        public MagnificationConfig.Builder setCenterY(float centerY) {
+            mCenterY = centerY;
+            return this;
+        }
+
+        /**
+         * Builds and returns a {@link MagnificationConfig}
+         */
+        @NonNull
+        public MagnificationConfig build() {
+            MagnificationConfig magnificationConfig = new MagnificationConfig();
+            magnificationConfig.mMode = mMode;
+            magnificationConfig.mScale = mScale;
+            magnificationConfig.mCenterX = mCenterX;
+            magnificationConfig.mCenterY = mCenterY;
+            return magnificationConfig;
+        }
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    public static final @NonNull Parcelable.Creator<MagnificationConfig> CREATOR =
+            new Parcelable.Creator<MagnificationConfig>() {
+                public MagnificationConfig createFromParcel(Parcel parcel) {
+                    return new MagnificationConfig(parcel);
+                }
+
+                public MagnificationConfig[] newArray(int size) {
+                    return new MagnificationConfig[size];
+                }
+            };
+}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f9cc323..9f8d246 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4082,6 +4082,34 @@
     }
 
     /**
+     * Gets the message that is shown when a user is switched from.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_USERS)
+    public @Nullable String getSwitchingFromUserMessage() {
+        try {
+            return getService().getSwitchingFromUserMessage();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets the message that is shown when a user is switched to.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_USERS)
+    public @Nullable String getSwitchingToUserMessage() {
+        try {
+            return getService().getSwitchingToUserMessage();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Uses the value defined by the platform.
      *
      * @hide
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 7a545f6..565f690 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -9469,23 +9469,23 @@
                     e.rethrowFromSystemServer();
                 }
 
-                if (missedAsyncOps != null) {
+                // Copy pointer so callback can be dispatched out of lock
+                OnOpNotedCallback onOpNotedCallback = sOnOpNotedCallback;
+                if (onOpNotedCallback != null && missedAsyncOps != null) {
                     int numMissedAsyncOps = missedAsyncOps.size();
                     for (int i = 0; i < numMissedAsyncOps; i++) {
                         final AsyncNotedAppOp asyncNotedAppOp = missedAsyncOps.get(i);
-                        if (sOnOpNotedCallback != null) {
-                            sOnOpNotedCallback.getAsyncNotedExecutor().execute(
-                                    () -> sOnOpNotedCallback.onAsyncNoted(asyncNotedAppOp));
-                        }
+                        onOpNotedCallback.getAsyncNotedExecutor().execute(
+                                () -> onOpNotedCallback.onAsyncNoted(asyncNotedAppOp));
                     }
                 }
                 synchronized (this) {
                     int numMissedSyncOps = sUnforwardedOps.size();
-                    for (int i = 0; i < numMissedSyncOps; i++) {
-                        final AsyncNotedAppOp syncNotedAppOp = sUnforwardedOps.get(i);
-                        if (sOnOpNotedCallback != null) {
-                            sOnOpNotedCallback.getAsyncNotedExecutor().execute(
-                                    () -> sOnOpNotedCallback.onAsyncNoted(syncNotedAppOp));
+                    if (onOpNotedCallback != null) {
+                        for (int i = 0; i < numMissedSyncOps; i++) {
+                            final AsyncNotedAppOp syncNotedAppOp = sUnforwardedOps.get(i);
+                            onOpNotedCallback.getAsyncNotedExecutor().execute(
+                                    () -> onOpNotedCallback.onAsyncNoted(syncNotedAppOp));
                         }
                     }
                     sUnforwardedOps.clear();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 1943f9d..d1b7145 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -920,16 +920,16 @@
         Objects.requireNonNull(packageName);
         Objects.requireNonNull(onChecksumsReadyListener);
         Objects.requireNonNull(trustedInstallers);
+        if (trustedInstallers == TRUST_ALL) {
+            trustedInstallers = null;
+        } else if (trustedInstallers == TRUST_NONE) {
+            trustedInstallers = Collections.emptyList();
+        } else if (trustedInstallers.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "trustedInstallers has to be one of TRUST_ALL/TRUST_NONE or a non-empty "
+                            + "list of certificates.");
+        }
         try {
-            if (trustedInstallers == TRUST_ALL) {
-                trustedInstallers = null;
-            } else if (trustedInstallers == TRUST_NONE) {
-                trustedInstallers = Collections.emptyList();
-            } else if (trustedInstallers.isEmpty()) {
-                throw new IllegalArgumentException(
-                        "trustedInstallers has to be one of TRUST_ALL/TRUST_NONE or a non-empty "
-                                + "list of certificates.");
-            }
             IOnChecksumsReadyListener onChecksumsReadyListenerDelegate =
                     new IOnChecksumsReadyListener.Stub() {
                         @Override
@@ -938,7 +938,7 @@
                             onChecksumsReadyListener.onChecksumsReady(checksums);
                         }
                     };
-            mPM.requestChecksums(packageName, includeSplits, DEFAULT_CHECKSUMS, required,
+            mPM.requestPackageChecksums(packageName, includeSplits, DEFAULT_CHECKSUMS, required,
                     encodeCertificates(trustedInstallers), onChecksumsReadyListenerDelegate,
                     getUserId());
         } catch (ParcelableException e) {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 8bb4059..183e714 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -349,6 +349,8 @@
     void setPackageScreenCompatMode(in String packageName, int mode);
     @UnsupportedAppUsage
     boolean switchUser(int userid);
+    String getSwitchingFromUserMessage();
+    String getSwitchingToUserMessage();
     @UnsupportedAppUsage
     void setStopUserOnSwitch(int value);
     boolean removeTask(int taskId);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 74e2858..0476307 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -196,6 +196,7 @@
 import android.permission.PermissionManager;
 import android.print.IPrintManager;
 import android.print.PrintManager;
+import android.safetycenter.SafetyCenterFrameworkInitializer;
 import android.scheduling.SchedulingFrameworkInitializer;
 import android.security.FileIntegrityManager;
 import android.security.IFileIntegrityService;
@@ -1530,6 +1531,7 @@
             SchedulingFrameworkInitializer.registerServiceWrappers();
             SupplementalProcessFrameworkInitializer.registerServiceWrappers();
             UwbFrameworkInitializer.registerServiceWrappers();
+            SafetyCenterFrameworkInitializer.registerServiceWrappers();
         } finally {
             // If any of the above code throws, we're in a pretty bad shape and the process
             // will likely crash, but we'll reset it just in case there's an exception handler...
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 828b171..58ded71 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -638,7 +638,7 @@
         final IAccessibilityServiceConnection connection;
         synchronized (mLock) {
             throwIfNotConnectedLocked();
-            AccessibilityInteractionClient.getInstance().clearCache();
+            AccessibilityInteractionClient.getInstance().clearCache(mConnectionId);
             connection = AccessibilityInteractionClient.getInstance()
                     .getConnection(mConnectionId);
         }
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index cf00cbd..2c875fe 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -51,7 +51,6 @@
 import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
@@ -59,7 +58,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
-import android.os.SynchronousResultReceiver;
 import android.os.SystemProperties;
 import android.util.Log;
 import android.util.Pair;
@@ -82,7 +80,6 @@
 import java.util.UUID;
 import java.util.WeakHashMap;
 import java.util.concurrent.Executor;
-import java.util.concurrent.TimeoutException;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 /**
@@ -2424,38 +2421,6 @@
     }
 
     /**
-     * Return the record of {@link BluetoothActivityEnergyInfo} object that
-     * has the activity and energy info. This can be used to ascertain what
-     * the controller has been up to, since the last sample.
-     *
-     * @param updateType Type of info, cached vs refreshed.
-     * @return a record with {@link BluetoothActivityEnergyInfo} or null if report is unavailable or
-     * unsupported
-     * @hide
-     * @deprecated use the asynchronous {@link #requestControllerActivityEnergyInfo(ResultReceiver)}
-     * instead.
-     */
-    @Deprecated
-    @RequiresBluetoothConnectPermission
-    @RequiresPermission(allOf = {
-            android.Manifest.permission.BLUETOOTH_CONNECT,
-            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
-    })
-    public BluetoothActivityEnergyInfo getControllerActivityEnergyInfo(int updateType) {
-        SynchronousResultReceiver receiver = new SynchronousResultReceiver();
-        requestControllerActivityEnergyInfo(receiver);
-        try {
-            SynchronousResultReceiver.Result result = receiver.awaitResult(1000);
-            if (result.bundle != null) {
-                return result.bundle.getParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY);
-            }
-        } catch (TimeoutException e) {
-            Log.e(TAG, "getControllerActivityEnergyInfo timed out");
-        }
-        return null;
-    }
-
-    /**
      * Request the record of {@link BluetoothActivityEnergyInfo} object that
      * has the activity and energy info. This can be used to ascertain what
      * the controller has been up to, since the last sample.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a5a75a4..05e2e87 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3842,6 +3842,7 @@
             UWB_SERVICE,
             MEDIA_METRICS_SERVICE,
             SUPPLEMENTAL_PROCESS_SERVICE,
+            //@hide: SAFETY_CENTER_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -5871,6 +5872,17 @@
     public static final String SUPPLEMENTAL_PROCESS_SERVICE = "supplemental_process";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a {@link
+     * android.safetycenter.SafetyCenterManager} instance for interacting with the safety center.
+     *
+     * @see #getSystemService(String)
+     * @see android.safetycenter.SafetyCenterManager
+     * @hide
+     */
+    @SystemApi
+    public static final String SAFETY_CENTER_SERVICE = "safety_center";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
@@ -6544,30 +6556,24 @@
             @NonNull Configuration overrideConfiguration);
 
     /**
-     * Return a new Context object for the current Context but whose resources
-     * are adjusted to match the metrics of the given Display.  Each call to this method
-     * returns a new instance of a Context object; Context objects are not
-     * shared, however common state (ClassLoader, other Resources for the
-     * same configuration) may be so the Context itself can be fairly lightweight.
-     *
-     * To obtain an instance of a {@link WindowManager} (see {@link #getSystemService(String)}) that
-     * is configured to show windows on the given display call
-     * {@link #createWindowContext(int, Bundle)} on the returned display Context or use an
-     * {@link android.app.Activity}.
-     *
+     * Returns a new <code>Context</code> object from the current context but with resources
+     * adjusted to match the metrics of <code>display</code>. Each call to this method
+     * returns a new instance of a context object. Context objects are not shared; however,
+     * common state (such as the {@link ClassLoader} and other resources for the same
+     * configuration) can be shared, so the <code>Context</code> itself is lightweight.
      * <p>
-     * Note that invoking #createDisplayContext(Display) from an UI context is not regarded
-     * as an UI context. In other words, it is not suggested to access UI components (such as
-     * obtain a {@link WindowManager} by {@link #getSystemService(String)})
-     * from the context created from #createDisplayContext(Display).
-     * </p>
+     * To obtain an instance of {@link WindowManager} configured to show windows on the given
+     * display, call {@link #createWindowContext(int, Bundle)} on the returned display context,
+     * then call {@link #getSystemService(String)} or {@link #getSystemService(Class)} on the
+     * returned window context.
+     * <p>
+     * <b>Note:</b> The context returned by <code>createDisplayContext(Display)</code> is not a UI
+     * context. Do not access UI components or obtain a {@link WindowManager} from the context
+     * created by <code>createDisplayContext(Display)</code>.
      *
-     * @param display A {@link Display} object specifying the display for whose metrics the
-     * Context's resources should be tailored.
+     * @param display The display to which the current context's resources are adjusted.
      *
-     * @return A {@link Context} for the display.
-     *
-     * @see #getSystemService(String)
+     * @return A context for the display.
      */
     @DisplayContext
     public abstract Context createDisplayContext(@NonNull Display display);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index a3efbd7..a36d532 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2378,6 +2378,14 @@
     public static final String ACTION_VIEW_APP_FEATURES =
             "android.intent.action.VIEW_APP_FEATURES";
 
+    /**
+     * Activity action: Launch UI to open the Safety Center, which highlights the user's security
+     * and privacy status.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SAFETY_CENTER =
+            "android.intent.action.SAFETY_CENTER";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Standard intent broadcast actions (see action variable).
@@ -5522,8 +5530,8 @@
 
     /**
      * A boolean extra, when used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD},
-     * that specifies whether the system displayed attribution information in the
-     * permission usage system UI for the chosen entry.
+     * that specifies whether the permission usage system UI is showing attribution information
+     * for the chosen entry.
      *
      * <p> The extra can only be true if application has specified attributionsAreUserVisible
      * in its manifest. </p>
@@ -6916,7 +6924,6 @@
      */
     public static final int FLAG_RECEIVER_OFFLOAD = 0x80000000;
     /**
-    /**
      * If set, when sending a broadcast the recipient will run on the system dedicated queue.
      *
      * @hide
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index f72288c..18e205f 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -18,6 +18,7 @@
 
 import android.content.pm.Checksum;
 import android.content.pm.DataLoaderParamsParcel;
+import android.content.pm.IOnChecksumsReadyListener;
 import android.content.pm.IPackageInstallObserver2;
 import android.content.IntentSender;
 import android.os.ParcelFileDescriptor;
@@ -36,6 +37,7 @@
     void stageViaHardLink(String target);
 
     void setChecksums(String name, in Checksum[] checksums, in byte[] signature);
+    void requestChecksums(in String name, int optional, int required, in List trustedInstallers, in IOnChecksumsReadyListener onChecksumsReadyListener);
 
     void removeSplit(String splitName);
 
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 46f1797..516156d 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -750,7 +750,7 @@
 
     void notifyPackagesReplacedReceived(in String[] packages);
 
-    void requestChecksums(in String packageName, boolean includeSplits, int optional, int required, in List trustedInstallers, in IOnChecksumsReadyListener onChecksumsReadyListener, int userId);
+    void requestPackageChecksums(in String packageName, boolean includeSplits, int optional, int required, in List trustedInstallers, in IOnChecksumsReadyListener onChecksumsReadyListener, int userId);
 
     IntentSender getLaunchIntentSenderForPackage(String packageName, String callingPackage,
                 String featureId, int userId);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 1586013..80584d1 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -19,6 +19,13 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_DEFAULT;
 import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256;
+import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512;
+import static android.content.pm.Checksum.TYPE_WHOLE_MD5;
+import static android.content.pm.Checksum.TYPE_WHOLE_MERKLE_ROOT_4K_SHA256;
+import static android.content.pm.Checksum.TYPE_WHOLE_SHA1;
+import static android.content.pm.Checksum.TYPE_WHOLE_SHA256;
+import static android.content.pm.Checksum.TYPE_WHOLE_SHA512;
 
 import android.Manifest;
 import android.annotation.CurrentTimeMillisLong;
@@ -62,12 +69,16 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.io.Closeable;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.security.MessageDigest;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -406,6 +417,13 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface FileLocation{}
 
+    /** Default set of checksums - includes all available checksums.
+     * @see Session#requestChecksums  */
+    private static final int DEFAULT_CHECKSUMS =
+            TYPE_WHOLE_MERKLE_ROOT_4K_SHA256 | TYPE_WHOLE_MD5 | TYPE_WHOLE_SHA1 | TYPE_WHOLE_SHA256
+                    | TYPE_WHOLE_SHA512 | TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256
+                    | TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512;
+
     private final IPackageInstaller mInstaller;
     private final int mUserId;
     private final String mInstallerPackageName;
@@ -1256,7 +1274,7 @@
          * @param name      previously written as part of this session.
          *                  {@link #openWrite}
          * @param checksums installer intends to make available via
-         *                  {@link PackageManager#requestChecksums}.
+         *                  {@link PackageManager#requestChecksums} or {@link #requestChecksums}.
          * @param signature DER PKCS#7 detached signature bytes over binary serialized checksums
          *                  to enable integrity checking for the checksums or null for no integrity
          *                  checking. {@link PackageManager#requestChecksums} will return
@@ -1293,6 +1311,89 @@
             }
         }
 
+        private static List<byte[]> encodeCertificates(List<Certificate> certs) throws
+                CertificateEncodingException {
+            if (certs == null) {
+                return null;
+            }
+            List<byte[]> result = new ArrayList<>(certs.size());
+            for (Certificate cert : certs) {
+                if (!(cert instanceof X509Certificate)) {
+                    throw new CertificateEncodingException("Only X509 certificates supported.");
+                }
+                result.add(cert.getEncoded());
+            }
+            return result;
+        }
+
+        /**
+         * Requests checksums for the APK file in session.
+         * <p>
+         * A possible use case is replying to {@link Intent#ACTION_PACKAGE_NEEDS_VERIFICATION}
+         * broadcast.
+         * The checksums will be returned asynchronously via onChecksumsReadyListener.
+         * <p>
+         * By default returns all readily available checksums:
+         * <ul>
+         * <li>enforced by platform,
+         * <li>enforced by the installer.
+         * </ul>
+         * If the caller needs a specific checksum type, they can specify it as required.
+         * <p>
+         * <b>Caution: Android can not verify installer-provided checksums. Make sure you specify
+         * trusted installers.</b>
+         * <p>
+         * @param name      previously written as part of this session.
+         *                  {@link #openWrite}
+         * @param required to explicitly request the checksum types. Will incur significant
+         *                 CPU/memory/disk usage.
+         * @param trustedInstallers for checksums enforced by installer, which installers are to be
+         *                          trusted.
+         *                          {@link PackageManager#TRUST_ALL} will return checksums from any
+         *                          installer,
+         *                          {@link PackageManager#TRUST_NONE} disables optimized
+         *                          installer-enforced checksums, otherwise the list has to be
+         *                          a non-empty list of certificates.
+         * @param onChecksumsReadyListener called once when the results are available.
+         * @throws CertificateEncodingException if an encoding error occurs for trustedInstallers.
+         * @throws FileNotFoundException if the file does not exist.
+         * @throws IllegalArgumentException if the list of trusted installer certificates is empty.
+         */
+        public void requestChecksums(@NonNull String name, @Checksum.TypeMask int required,
+                @NonNull List<Certificate> trustedInstallers,
+                @NonNull PackageManager.OnChecksumsReadyListener onChecksumsReadyListener)
+                throws CertificateEncodingException, FileNotFoundException {
+            Objects.requireNonNull(name);
+            Objects.requireNonNull(onChecksumsReadyListener);
+            Objects.requireNonNull(trustedInstallers);
+            if (trustedInstallers == PackageManager.TRUST_ALL) {
+                trustedInstallers = null;
+            } else if (trustedInstallers == PackageManager.TRUST_NONE) {
+                trustedInstallers = Collections.emptyList();
+            } else if (trustedInstallers.isEmpty()) {
+                throw new IllegalArgumentException(
+                        "trustedInstallers has to be one of TRUST_ALL/TRUST_NONE or a non-empty "
+                                + "list of certificates.");
+            }
+            try {
+                IOnChecksumsReadyListener onChecksumsReadyListenerDelegate =
+                        new IOnChecksumsReadyListener.Stub() {
+                            @Override
+                            public void onChecksumsReady(List<ApkChecksum> checksums)
+                                    throws RemoteException {
+                                onChecksumsReadyListener.onChecksumsReady(checksums);
+                            }
+                        };
+                mSession.requestChecksums(name, DEFAULT_CHECKSUMS, required,
+                        encodeCertificates(trustedInstallers), onChecksumsReadyListenerDelegate);
+            } catch (ParcelableException e) {
+                e.maybeRethrow(FileNotFoundException.class);
+                throw new RuntimeException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         /**
          * Attempt to commit everything staged in this session. This may require
          * user intervention, and so it may not happen immediately. The final
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1b51314..c777bf5 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -9044,7 +9044,7 @@
     }
 
     /**
-     * Requesting the checksums for APKs within a package.
+     * Requests the checksums for APKs within a package.
      * The checksums will be returned asynchronously via onChecksumsReadyListener.
      *
      * By default returns all readily available checksums:
diff --git a/core/java/android/content/pm/PackagePartitions.java b/core/java/android/content/pm/PackagePartitions.java
index d157768..ff80e61 100644
--- a/core/java/android/content/pm/PackagePartitions.java
+++ b/core/java/android/content/pm/PackagePartitions.java
@@ -19,8 +19,11 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Build;
+import android.os.Build.Partition;
 import android.os.Environment;
 import android.os.FileUtils;
+import android.os.SystemProperties;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -64,20 +67,34 @@
      */
     private static final ArrayList<SystemPartition> SYSTEM_PARTITIONS =
             new ArrayList<>(Arrays.asList(
-                    new SystemPartition(Environment.getRootDirectory(), PARTITION_SYSTEM,
+                    new SystemPartition(Environment.getRootDirectory(),
+                            PARTITION_SYSTEM, Partition.PARTITION_NAME_SYSTEM,
                             true /* containsPrivApp */, false /* containsOverlay */),
-                    new SystemPartition(Environment.getVendorDirectory(), PARTITION_VENDOR,
+                    new SystemPartition(Environment.getVendorDirectory(),
+                            PARTITION_VENDOR, Partition.PARTITION_NAME_VENDOR,
                             true /* containsPrivApp */, true /* containsOverlay */),
-                    new SystemPartition(Environment.getOdmDirectory(), PARTITION_ODM,
+                    new SystemPartition(Environment.getOdmDirectory(),
+                            PARTITION_ODM, Partition.PARTITION_NAME_ODM,
                             true /* containsPrivApp */, true /* containsOverlay */),
-                    new SystemPartition(Environment.getOemDirectory(), PARTITION_OEM,
+                    new SystemPartition(Environment.getOemDirectory(),
+                            PARTITION_OEM, Partition.PARTITION_NAME_OEM,
                             false /* containsPrivApp */, true /* containsOverlay */),
-                    new SystemPartition(Environment.getProductDirectory(), PARTITION_PRODUCT,
+                    new SystemPartition(Environment.getProductDirectory(),
+                            PARTITION_PRODUCT, Partition.PARTITION_NAME_PRODUCT,
                             true /* containsPrivApp */, true /* containsOverlay */),
-                    new SystemPartition(Environment.getSystemExtDirectory(), PARTITION_SYSTEM_EXT,
+                    new SystemPartition(Environment.getSystemExtDirectory(),
+                            PARTITION_SYSTEM_EXT, Partition.PARTITION_NAME_SYSTEM_EXT,
                             true /* containsPrivApp */, true /* containsOverlay */)));
 
     /**
+     * A string to represent the fingerprint of this build and all package partitions. Using it to
+     * determine whether the system update has occurred. Different from {@link Build#FINGERPRINT},
+     * this string is digested from the fingerprints of the build and all package partitions to
+     * help detect the partition update.
+     */
+    public static final String FINGERPRINT = getFingerprint();
+
+    /**
      * Returns a list in which the elements are products of the specified function applied to the
      * list of {@link #SYSTEM_PARTITIONS} in increasing specificity order.
      */
@@ -101,6 +118,23 @@
         }
     }
 
+    /**
+     * Returns a fingerprint string for this build and all package partitions. The string is
+     * digested from the fingerprints of the build and all package partitions.
+     *
+     * @return A string to represent the fingerprint of this build and all package partitions.
+     */
+    @NonNull
+    private static String getFingerprint() {
+        final String[] digestProperties = new String[SYSTEM_PARTITIONS.size() + 1];
+        for (int i = 0; i < SYSTEM_PARTITIONS.size(); i++) {
+            final String partitionName = SYSTEM_PARTITIONS.get(i).getName();
+            digestProperties[i] = "ro." + partitionName + ".build.fingerprint";
+        }
+        digestProperties[SYSTEM_PARTITIONS.size()] = "ro.build.fingerprint"; // build fingerprint
+        return SystemProperties.digestOf(digestProperties);
+    }
+
     /** Represents a partition that contains application packages. */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     public static class SystemPartition {
@@ -108,6 +142,9 @@
         public final int type;
 
         @NonNull
+        private final String mName;
+
+        @NonNull
         private final DeferredCanonicalFile mFolder;
 
         @Nullable
@@ -122,9 +159,10 @@
         @NonNull
         private final File mNonConicalFolder;
 
-        private SystemPartition(@NonNull File folder, @PartitionType int type,
+        private SystemPartition(@NonNull File folder, @PartitionType int type, String name,
                 boolean containsPrivApp, boolean containsOverlay) {
             this.type = type;
+            this.mName = name;
             this.mFolder = new DeferredCanonicalFile(folder);
             this.mAppFolder = new DeferredCanonicalFile(folder, "app");
             this.mPrivAppFolder = containsPrivApp ? new DeferredCanonicalFile(folder, "priv-app")
@@ -136,6 +174,7 @@
 
         public SystemPartition(@NonNull SystemPartition original) {
             this.type = original.type;
+            this.mName = original.mName;
             this.mFolder = new DeferredCanonicalFile(original.mFolder.getFile());
             this.mAppFolder = original.mAppFolder;
             this.mPrivAppFolder = original.mPrivAppFolder;
@@ -148,10 +187,19 @@
          * different root folder.
          */
         public SystemPartition(@NonNull File rootFolder, @NonNull SystemPartition partition) {
-            this(rootFolder, partition.type, partition.mPrivAppFolder != null,
+            this(rootFolder, partition.type, partition.mName, partition.mPrivAppFolder != null,
                     partition.mOverlayFolder != null);
         }
 
+        /**
+         * Returns the name identifying the partition.
+         * @see Partition
+         */
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
         /** Returns the canonical folder of the partition. */
         @NonNull
         public File getFolder() {
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 8ebb8ec..01bf49e 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -2432,27 +2432,10 @@
                 break;
         }
 
-        switch (config.uiMode & Configuration.UI_MODE_TYPE_MASK) {
-            case Configuration.UI_MODE_TYPE_APPLIANCE:
-                parts.add("appliance");
-                break;
-            case Configuration.UI_MODE_TYPE_DESK:
-                parts.add("desk");
-                break;
-            case Configuration.UI_MODE_TYPE_TELEVISION:
-                parts.add("television");
-                break;
-            case Configuration.UI_MODE_TYPE_CAR:
-                parts.add("car");
-                break;
-            case Configuration.UI_MODE_TYPE_WATCH:
-                parts.add("watch");
-                break;
-            case Configuration.UI_MODE_TYPE_VR_HEADSET:
-                parts.add("vrheadset");
-                break;
-            default:
-                break;
+        final String uiModeTypeString =
+                getUiModeTypeString(config.uiMode & Configuration.UI_MODE_TYPE_MASK);
+        if (uiModeTypeString != null) {
+            parts.add(uiModeTypeString);
         }
 
         switch (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) {
@@ -2587,6 +2570,28 @@
     }
 
     /**
+     * @hide
+     */
+    public static String getUiModeTypeString(int uiModeType) {
+        switch (uiModeType) {
+            case Configuration.UI_MODE_TYPE_APPLIANCE:
+                return "appliance";
+            case Configuration.UI_MODE_TYPE_DESK:
+                return "desk";
+            case Configuration.UI_MODE_TYPE_TELEVISION:
+                return "television";
+            case Configuration.UI_MODE_TYPE_CAR:
+                return "car";
+            case Configuration.UI_MODE_TYPE_WATCH:
+                return "watch";
+            case Configuration.UI_MODE_TYPE_VR_HEADSET:
+                return "vrheadset";
+            default:
+                return null;
+        }
+    }
+
+    /**
      * Generate a delta Configuration between <code>base</code> and <code>change</code>. The
      * resulting delta can be used with {@link #updateFrom(Configuration)}.
      * <p />
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 96a18dc..3c8b6e9 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1323,7 +1323,7 @@
      * <p>Maximum flashlight brightness level.</p>
      * <p>If this value is greater than 1, then the device supports controlling the
      * flashlight brightness level via
-     * {android.hardware.camera2.CameraManager#setTorchStrengthLevel}.
+     * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}.
      * If this value is equal to 1, flashlight brightness control is not supported.
      * The value for this key will be null for devices with no flash unit.</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
@@ -1335,7 +1335,7 @@
 
     /**
      * <p>Default flashlight brightness level to be set via
-     * {android.hardware.camera2.CameraManager#setTorchStrengthLevel}.</p>
+     * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}.</p>
      * <p>If flash unit is available this will be greater than or equal to 1 and less
      * or equal to <code>{@link CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL android.flash.info.strengthMaximumLevel}</code>.</p>
      * <p>Setting flashlight brightness above the default level
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 8864939..9b19fc4 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -110,6 +110,11 @@
     private int mRepeatingRequestId = REQUEST_ID_NONE;
     // Latest repeating request list's types
     private int[] mRepeatingRequestTypes;
+
+    // Cache failed requests to process later in case of a repeating error callback
+    private int mFailedRepeatingRequestId = REQUEST_ID_NONE;
+    private int[] mFailedRepeatingRequestTypes;
+
     // Map stream IDs to input/output configurations
     private SimpleEntry<Integer, InputConfiguration> mConfiguredInput =
             new SimpleEntry<>(REQUEST_ID_NONE, null);
@@ -1326,16 +1331,25 @@
 
                 int requestId = mRepeatingRequestId;
                 mRepeatingRequestId = REQUEST_ID_NONE;
+                mFailedRepeatingRequestId = REQUEST_ID_NONE;
                 int[] requestTypes = mRepeatingRequestTypes;
                 mRepeatingRequestTypes = null;
+                mFailedRepeatingRequestTypes = null;
 
                 long lastFrameNumber;
                 try {
                     lastFrameNumber = mRemoteDevice.cancelRequest(requestId);
                 } catch (IllegalArgumentException e) {
                     if (DEBUG) {
-                        Log.v(TAG, "Repeating request was already stopped for request " + requestId);
+                        Log.v(TAG, "Repeating request was already stopped for request " +
+                                requestId);
                     }
+                    // Cache request id and request types in case of a race with
+                    // "onRepeatingRequestError" which may no yet be scheduled on another thread
+                    // or blocked by us.
+                    mFailedRepeatingRequestId = requestId;
+                    mFailedRepeatingRequestTypes = requestTypes;
+
                     // Repeating request was already stopped. Nothing more to do.
                     return;
                 }
@@ -1965,7 +1979,17 @@
             synchronized(mInterfaceLock) {
                 // Camera is already closed or no repeating request is present.
                 if (mRemoteDevice == null || mRepeatingRequestId == REQUEST_ID_NONE) {
-                    return; // Camera already closed
+                    if ((mFailedRepeatingRequestId == repeatingRequestId) &&
+                            (mFailedRepeatingRequestTypes != null) && (mRemoteDevice != null)) {
+                        Log.v(TAG, "Resuming stop of failed repeating request with id: " +
+                                mFailedRepeatingRequestId);
+
+                        checkEarlyTriggerSequenceCompleteLocked(mFailedRepeatingRequestId,
+                                lastFrameNumber, mFailedRepeatingRequestTypes);
+                        mFailedRepeatingRequestId = REQUEST_ID_NONE;
+                        mFailedRepeatingRequestTypes = null;
+                    }
+                    return;
                 }
 
                 // Redirect device callback to the offline session in case we are in the middle
diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
index 7d71984..0b1ed65 100644
--- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java
+++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
@@ -109,7 +109,8 @@
 
     /** {@hide} */
     public boolean quickPickupSensorEnabled(int user) {
-        return !TextUtils.isEmpty(quickPickupSensorType())
+        return boolSettingDefaultOn(Settings.Secure.DOZE_QUICK_PICKUP_GESTURE, user)
+                && !TextUtils.isEmpty(quickPickupSensorType())
                 && pickupGestureEnabled(user)
                 && !alwaysOnEnabled(user);
     }
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index fd34fa4..83e1061 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -30,6 +30,7 @@
 import android.view.DisplayInfo;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
+import android.window.DisplayWindowPolicyController;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -379,6 +380,14 @@
     public abstract void onEarlyInteractivityChange(boolean interactive);
 
     /**
+     * Get {@link DisplayWindowPolicyController} associated to the {@link DisplayInfo#displayId}
+     *
+     * @param displayId The id of the display.
+     * @return The associated {@link DisplayWindowPolicyController}.
+     */
+    public abstract DisplayWindowPolicyController getDisplayWindowPolicyController(int displayId);
+
+    /**
      * Describes the requested power state of the display.
      *
      * This object is intended to describe the general characteristics of the
diff --git a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
index 80e8579..557e41a 100644
--- a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
+++ b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
@@ -25,7 +25,6 @@
 import android.nfc.INfcFCardEmulation;
 import android.nfc.NfcAdapter;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.util.Log;
 
 import java.util.HashMap;
diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
index c2b33dd..f8f7dfe 100644
--- a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
@@ -237,6 +237,7 @@
     public String toString() {
         StringBuilder out = new StringBuilder("NfcFService: ");
         out.append(getComponent());
+        out.append(", UID: " + mUid);
         out.append(", description: " + mDescription);
         out.append(", System Code: " + mSystemCode);
         if (mDynamicSystemCode != null) {
@@ -257,6 +258,7 @@
         NfcFServiceInfo thatService = (NfcFServiceInfo) o;
 
         if (!thatService.getComponent().equals(this.getComponent())) return false;
+        if (thatService.getUid() != this.getUid()) return false;
         if (!thatService.mSystemCode.equalsIgnoreCase(this.mSystemCode)) return false;
         if (!thatService.mNfcid2.equalsIgnoreCase(this.mNfcid2)) return false;
         if (!thatService.mT3tPmm.equalsIgnoreCase(this.mT3tPmm)) return false;
@@ -321,8 +323,9 @@
     };
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("    " + getComponent() +
-                " (Description: " + getDescription() + ")");
+        pw.println("    " + getComponent()
+                + " (Description: " + getDescription() + ")"
+                + " (UID: " + getUid() + ")");
         pw.println("    System Code: " + getSystemCode());
         pw.println("    NFCID2: " + getNfcid2());
         pw.println("    T3tPmm: " + getT3tPmm());
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 743468a..35b9ccc 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1294,6 +1294,18 @@
     public static class Partition {
         /** The name identifying the system partition. */
         public static final String PARTITION_NAME_SYSTEM = "system";
+        /** @hide */
+        public static final String PARTITION_NAME_BOOTIMAGE = "bootimage";
+        /** @hide */
+        public static final String PARTITION_NAME_ODM = "odm";
+        /** @hide */
+        public static final String PARTITION_NAME_OEM = "oem";
+        /** @hide */
+        public static final String PARTITION_NAME_PRODUCT = "product";
+        /** @hide */
+        public static final String PARTITION_NAME_SYSTEM_EXT = "system_ext";
+        /** @hide */
+        public static final String PARTITION_NAME_VENDOR = "vendor";
 
         private final String mName;
         private final String mFingerprint;
@@ -1350,8 +1362,12 @@
         ArrayList<Partition> partitions = new ArrayList();
 
         String[] names = new String[] {
-            "bootimage", "odm", "product", "system_ext", Partition.PARTITION_NAME_SYSTEM,
-            "vendor"
+                Partition.PARTITION_NAME_BOOTIMAGE,
+                Partition.PARTITION_NAME_ODM,
+                Partition.PARTITION_NAME_PRODUCT,
+                Partition.PARTITION_NAME_SYSTEM_EXT,
+                Partition.PARTITION_NAME_SYSTEM,
+                Partition.PARTITION_NAME_VENDOR
         };
         for (String name : names) {
             String fingerprint = SystemProperties.get("ro." + name + ".build.fingerprint");
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 50ca9ff..8308e8e 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -60,6 +60,7 @@
     List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated);
     List<UserInfo> getProfiles(int userId, boolean enabledOnly);
     int[] getProfileIds(int userId, boolean enabledOnly);
+    boolean canAddMoreUsersOfType(in String userType);
     boolean canAddMoreProfilesToUser(in String userType, int userId, boolean allowedToRemoveOne);
     boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne);
     UserInfo getProfileParent(int userId);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index cf4ce9b..e7e0c04 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3854,13 +3854,19 @@
     }
 
     /**
-     * Checks whether it's possible to add more users. Caller must hold the MANAGE_USERS
-     * permission.
+     * Checks whether it's possible to add more users.
      *
      * @return true if more users can be added, false if limit has been reached.
+     *
+     * @deprecated use {@link #canAddMoreUsers(String)} instead.
+     *
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @Deprecated
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.CREATE_USERS
+    })
     public boolean canAddMoreUsers() {
         // TODO(b/142482943): UMS has different logic, excluding Demo and Profile from counting. Why
         //                    not here? The logic is inconsistent. See UMS.canAddMoreManagedProfiles
@@ -3877,6 +3883,25 @@
     }
 
     /**
+     * Checks whether it is possible to add more users of the given user type.
+     *
+     * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_SECONDARY}.
+     * @return true if more users of the given type can be added, otherwise false.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.CREATE_USERS
+    })
+    public boolean canAddMoreUsers(@NonNull String userType) {
+        try {
+            return canAddMoreUsers() && mService.canAddMoreUsersOfType(userType);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Checks whether it's possible to add more managed profiles. Caller must hold the MANAGE_USERS
      * permission.
      * if allowedToRemoveOne is true and if the user already has a managed profile, then return if
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index f57d157..39a2e13 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -56,11 +56,12 @@
  * <ul>
  * <li>{@link #MOUNT_FLAG_PRIMARY} means the volume provides primary external
  * storage, historically found at {@code /sdcard}.
- * <li>{@link #MOUNT_FLAG_VISIBLE} means the volume is visible to third-party
- * apps for direct filesystem access. The system should send out relevant
- * storage broadcasts and index any media on visible volumes. Visible volumes
- * are considered a more stable part of the device, which is why we take the
- * time to index them. In particular, transient volumes like USB OTG devices
+ * <li>{@link #MOUNT_FLAG_VISIBLE_FOR_READ} and
+ * {@link #MOUNT_FLAG_VISIBLE_FOR_WRITE} mean the volume is visible to
+ * third-party apps for direct filesystem access. The system should send out
+ * relevant storage broadcasts and index any media on visible volumes. Visible
+ * volumes are considered a more stable part of the device, which is why we take
+ * the time to index them. In particular, transient volumes like USB OTG devices
  * <em>should not</em> be marked as visible; their contents should be surfaced
  * to apps through the Storage Access Framework.
  * </ul>
@@ -100,7 +101,8 @@
     public static final int STATE_BAD_REMOVAL = IVold.VOLUME_STATE_BAD_REMOVAL;
 
     public static final int MOUNT_FLAG_PRIMARY = IVold.MOUNT_FLAG_PRIMARY;
-    public static final int MOUNT_FLAG_VISIBLE = IVold.MOUNT_FLAG_VISIBLE;
+    public static final int MOUNT_FLAG_VISIBLE_FOR_READ = IVold.MOUNT_FLAG_VISIBLE_FOR_READ;
+    public static final int MOUNT_FLAG_VISIBLE_FOR_WRITE = IVold.MOUNT_FLAG_VISIBLE_FOR_WRITE;
 
     private static SparseArray<String> sStateToEnvironment = new SparseArray<>();
     private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>();
@@ -312,17 +314,31 @@
         return isPrimary() && (getType() == TYPE_PUBLIC);
     }
 
-    @UnsupportedAppUsage
-    public boolean isVisible() {
-        return (mountFlags & MOUNT_FLAG_VISIBLE) != 0;
+    private boolean isVisibleForRead() {
+        return (mountFlags & MOUNT_FLAG_VISIBLE_FOR_READ) != 0;
     }
 
-    public boolean isVisibleForUser(int userId) {
-        if ((type == TYPE_PUBLIC || type == TYPE_STUB || type == TYPE_EMULATED)
-                && mountUserId == userId) {
-            return isVisible();
+    private boolean isVisibleForWrite() {
+        return (mountFlags & MOUNT_FLAG_VISIBLE_FOR_WRITE) != 0;
+    }
+
+    @UnsupportedAppUsage
+    public boolean isVisible() {
+        return isVisibleForRead() || isVisibleForWrite();
+    }
+
+    private boolean isVolumeSupportedForUser(int userId) {
+        if (mountUserId != userId) {
+            return false;
         }
-        return false;
+        return type == TYPE_PUBLIC || type == TYPE_STUB || type == TYPE_EMULATED;
+    }
+
+    /**
+     * Returns {@code true} if this volume is visible for {@code userId}, {@code false} otherwise.
+     */
+    public boolean isVisibleForUser(int userId) {
+        return isVolumeSupportedForUser(userId) && isVisible();
     }
 
     /**
@@ -335,12 +351,12 @@
     }
 
     public boolean isVisibleForRead(int userId) {
-        return isVisibleForUser(userId);
+        return isVolumeSupportedForUser(userId) && isVisibleForRead();
     }
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public boolean isVisibleForWrite(int userId) {
-        return isVisibleForUser(userId);
+        return isVolumeSupportedForUser(userId) && isVisibleForWrite();
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ae09b45..d08475d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -269,6 +269,16 @@
     public static final String EXTRA_NETWORK_TEMPLATE = "network_template";
 
     /**
+     * Activity Action: Show One-handed mode Settings page.
+     * <p>
+     * Input: Nothing
+     * <p>
+     * Output: Nothing
+     * @hide
+     */
+    public static final String ACTION_ONE_HANDED_SETTINGS =
+            "android.settings.action.ONE_HANDED_SETTINGS";
+    /**
      * The return values for {@link Settings.Config#set}
      * @hide
      */
@@ -4537,6 +4547,25 @@
                 "haptic_feedback_intensity";
 
         /**
+         * The intensity of haptic feedback vibrations for interaction with hardware components from
+         * the device, like buttons and sensors, if configurable.
+         *
+         * Not all devices are capable of changing their feedback intensity; on these devices
+         * there will likely be no difference between the various vibration intensities except for
+         * intensity 0 (off) and the rest.
+         *
+         * <b>Values:</b><br/>
+         * 0 - Vibration is disabled<br/>
+         * 1 - Weak vibrations<br/>
+         * 2 - Medium vibrations<br/>
+         * 3 - Strong vibrations
+         * @hide
+         */
+        @Readable
+        public static final String HARDWARE_HAPTIC_FEEDBACK_INTENSITY =
+                "hardware_haptic_feedback_intensity";
+
+        /**
          * Ringer volume. This is used internally, changing this value will not
          * change the volume. See AudioManager.
          *
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 1b38f59..5d84af0 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3584,6 +3584,23 @@
                 "content://telephony/carriers/enforce_managed");
 
         /**
+         * The {@code content://} style URL for the perferred APN used for internet.
+         *
+         * @hide
+         */
+        public static final Uri PREFERRED_APN_URI = Uri.parse(
+                "content://telephony/carriers/preferapn/subId/");
+
+        /**
+         * The {@code content://} style URL for the perferred APN set id.
+         *
+         * @hide
+         */
+        public static final Uri PREFERRED_APN_SET_URI = Uri.parse(
+                "content://telephony/carriers/preferapnset/subId/");
+
+
+        /**
          * The column name for ENFORCE_MANAGED_URI, indicates whether DPC-owned APNs are enforced.
          * @hide
          */
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 9cd8313..de56d3a 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -151,8 +151,7 @@
             if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId
                     && mHandler.hasAccessibilityCallback(message)) {
                 AccessibilityInteractionClient.getInstanceForThread(
-                        interrogatingTid, /* initializeCache= */true)
-                        .setSameThreadMessage(message);
+                        interrogatingTid).setSameThreadMessage(message);
             } else {
                 // For messages without callback of interrogating client, just handle the
                 // message immediately if this is UI thread.
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index ec613ed..c5bc99d 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -153,11 +153,20 @@
 
     /**
      * Invocation of the voice assistant via hardware button.
+     * This is a private constant.  Feel free to renumber as desired.
      * @hide
      */
     public static final int ASSISTANT_BUTTON = 10002;
 
     /**
+     * The user has performed a long press on the power button hardware that is resulting
+     * in an action being performed.
+     * This is a private constant.  Feel free to renumber as desired.
+     * @hide
+     */
+    public static final int LONG_PRESS_POWER_BUTTON = 10003;
+
+    /**
      * Flag for {@link View#performHapticFeedback(int, int)
      * View.performHapticFeedback(int, int)}: Ignore the setting in the
      * view for whether to perform haptic feedback, do it always.
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 1460cb2..c3a638c 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -295,7 +295,8 @@
     /** Key code constant: Fast Forward media key. */
     public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
     /** Key code constant: Mute key.
-     * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */
+     * Mute key for the microphone (unlike {@link #KEYCODE_VOLUME_MUTE}, which is the speaker mute
+     * key). */
     public static final int KEYCODE_MUTE            = 91;
     /** Key code constant: Page Up key. */
     public static final int KEYCODE_PAGE_UP         = 92;
@@ -482,9 +483,10 @@
     /** Key code constant: Numeric keypad ')' key. */
     public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163;
     /** Key code constant: Volume Mute key.
-     * Mutes the speaker, unlike {@link #KEYCODE_MUTE}.
-     * This key should normally be implemented as a toggle such that the first press
-     * mutes the speaker and the second press restores the original volume. */
+     * Mute key for speaker (unlike {@link #KEYCODE_MUTE}, which is the mute key for the
+     * microphone). This key should normally be implemented as a toggle such that the first press
+     * mutes the speaker and the second press restores the original volume.
+     */
     public static final int KEYCODE_VOLUME_MUTE     = 164;
     /** Key code constant: Info key.
      * Common on TV remotes to show additional information related to what is
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 3b4fcc0..f69bb6a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -129,17 +129,15 @@
 
 /**
  * The interface that apps use to talk to the window manager.
- * </p><p>
- * Each window manager instance is bound to a particular {@link Display}.
- * To obtain a {@link WindowManager} for a different display, use
- * {@link Context#createDisplayContext} to obtain a {@link Context} for that
- * display, then use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code>
- * to get the WindowManager.
- * </p><p>
- * The simplest way to show a window on another display is to create a
- * {@link Presentation}.  The presentation will automatically obtain a
- * {@link WindowManager} and {@link Context} for that display.
- * </p>
+ * <p>
+ * Each window manager instance is bound to a {@link Display}. To obtain the
+ * <code>WindowManager</code> associated with a display,
+ * call {@link Context#createWindowContext(Display, int, Bundle)} to get the display's UI context,
+ * then call {@link Context#getSystemService(String)} or {@link Context#getSystemService(Class)} on
+ * the UI context.
+ * <p>
+ * The simplest way to show a window on a particular display is to create a {@link Presentation},
+ * which automatically obtains a <code>WindowManager</code> and context for the display.
  */
 @SystemService(Context.WINDOW_SERVICE)
 public interface WindowManager extends ViewManager {
@@ -4792,6 +4790,16 @@
                     return Integer.toString(inputFeature);
             }
         }
+
+        /**
+         * True if the window should consume all pointer events itself, regardless of whether they
+         * are inside of the window. If the window is modal, its touchable region will expand to the
+         * size of its task.
+         * @hide
+         */
+        public boolean isModal() {
+            return (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
+        }
     }
 
     /**
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index e634d60..94f6333 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -236,6 +236,9 @@
      */
     int LAYER_OFFSET_THUMBNAIL = WINDOW_LAYER_MULTIPLIER - 1;
 
+    // TODO(b/207185041): Remove this divider workaround after we full remove leagacy split and
+    //                    make app pair split only have single root then we can just attach the
+    //                    divider to the single root task in shell.
     int SPLIT_DIVIDER_LAYER = TYPE_LAYER_MULTIPLIER * 3;
     int WATERMARK_LAYER = TYPE_LAYER_MULTIPLIER * 100;
     int STRICT_MODE_LAYER = TYPE_LAYER_MULTIPLIER * 101;
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index bc21488..dc4c59a 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -115,13 +115,13 @@
         from a window, mapping from windowId -> timestamp. */
     private static final SparseLongArray sScrollingWindows = new SparseLongArray();
 
-    private static AccessibilityCache sAccessibilityCache;
+    private static SparseArray<AccessibilityCache> sCaches = new SparseArray<>();
 
     private final AtomicInteger mInteractionIdCounter = new AtomicInteger();
 
     private final Object mInstanceLock = new Object();
 
-    private final AccessibilityManager  mAccessibilityManager;
+    private final AccessibilityManager mAccessibilityManager;
 
     private volatile int mInteractionId = -1;
     private volatile int mCallingUid = Process.INVALID_UID;
@@ -150,7 +150,37 @@
     @UnsupportedAppUsage()
     public static AccessibilityInteractionClient getInstance() {
         final long threadId = Thread.currentThread().getId();
-        return getInstanceForThread(threadId, true);
+        return getInstanceForThread(threadId);
+    }
+
+    /**
+     * <strong>Note:</strong> We keep one instance per interrogating thread since
+     * the instance contains state which can lead to undesired thread interleavings.
+     * We do not have a thread local variable since other threads should be able to
+     * look up the correct client knowing a thread id. See ViewRootImpl for details.
+     *
+     * @return The client for a given <code>threadId</code>.
+     */
+    public static AccessibilityInteractionClient getInstanceForThread(long threadId) {
+        synchronized (sStaticLock) {
+            AccessibilityInteractionClient client = sClients.get(threadId);
+            if (client == null) {
+                client = new AccessibilityInteractionClient();
+                sClients.put(threadId, client);
+            }
+            return client;
+        }
+    }
+
+    /**
+     * @return The client for the current thread.
+     */
+    public static AccessibilityInteractionClient getInstance(Context context) {
+        final long threadId = Thread.currentThread().getId();
+        if (context != null) {
+            return getInstanceForThread(threadId, context);
+        }
+        return getInstanceForThread(threadId);
     }
 
     /**
@@ -162,61 +192,11 @@
      * @return The client for a given <code>threadId</code>.
      */
     public static AccessibilityInteractionClient getInstanceForThread(long threadId,
-            boolean initializeCache) {
-        synchronized (sStaticLock) {
-            AccessibilityInteractionClient client = sClients.get(threadId);
-            if (client == null) {
-                if (Binder.getCallingUid() == Process.SYSTEM_UID) {
-                    // Don't initialize a cache for the system process
-                    client = new AccessibilityInteractionClient(false);
-                } else {
-                    client = new AccessibilityInteractionClient(initializeCache);
-                }
-                sClients.put(threadId, client);
-            }
-            return client;
-        }
-    }
-
-    /**
-     * @return The client for the current thread.
-     */
-    public static AccessibilityInteractionClient getInstance(Context context) {
-        return getInstance(/* initializeCache= */true, context);
-    }
-
-    /**
-     * @param initializeCache whether to initialize the cache in a new client instance
-     * @return The client for the current thread.
-     */
-    public static AccessibilityInteractionClient getInstance(boolean initializeCache,
             Context context) {
-        final long threadId = Thread.currentThread().getId();
-        if (context != null) {
-            return getInstanceForThread(threadId, initializeCache, context);
-        }
-        return getInstanceForThread(threadId, initializeCache);
-    }
-
-    /**
-     * <strong>Note:</strong> We keep one instance per interrogating thread since
-     * the instance contains state which can lead to undesired thread interleavings.
-     * We do not have a thread local variable since other threads should be able to
-     * look up the correct client knowing a thread id. See ViewRootImpl for details.
-     *
-     * @param initializeCache whether to initialize the cache in a new client instance
-     * @return The client for a given <code>threadId</code>.
-     */
-    public static AccessibilityInteractionClient getInstanceForThread(
-            long threadId, boolean initializeCache, Context context) {
         synchronized (sStaticLock) {
             AccessibilityInteractionClient client = sClients.get(threadId);
             if (client == null) {
-                if (Binder.getCallingUid() == Process.SYSTEM_UID) {
-                    client = new AccessibilityInteractionClient(false, context);
-                } else {
-                    client = new AccessibilityInteractionClient(initializeCache, context);
-                }
+                client = new AccessibilityInteractionClient(context);
                 sClients.put(threadId, client);
             }
             return client;
@@ -238,12 +218,30 @@
     /**
      * Adds a cached accessibility service connection.
      *
+     * Adds a cache if {@code initializeCache} is true
      * @param connectionId The connection id.
      * @param connection The connection.
+     * @param initializeCache whether to initialize a cache
      */
-    public static void addConnection(int connectionId, IAccessibilityServiceConnection connection) {
+    public static void addConnection(int connectionId, IAccessibilityServiceConnection connection,
+            boolean initializeCache) {
         synchronized (sConnectionCache) {
             sConnectionCache.put(connectionId, connection);
+            if (!initializeCache) {
+                return;
+            }
+            sCaches.put(connectionId, new AccessibilityCache(
+                        new AccessibilityCache.AccessibilityNodeRefresher()));
+        }
+    }
+
+    /**
+     * Gets a cached associated with the connection id if available.
+     *
+     */
+    public static AccessibilityCache getCache(int connectionId) {
+        synchronized (sConnectionCache) {
+            return sCaches.get(connectionId);
         }
     }
 
@@ -255,6 +253,7 @@
     public static void removeConnection(int connectionId) {
         synchronized (sConnectionCache) {
             sConnectionCache.remove(connectionId);
+            sCaches.remove(connectionId);
         }
     }
 
@@ -263,32 +262,21 @@
      * tests need to be able to verify this class's interactions with the cache
      */
     @VisibleForTesting
-    public static void setCache(AccessibilityCache cache) {
-        sAccessibilityCache = cache;
+    public static void setCache(int connectionId, AccessibilityCache cache) {
+        synchronized (sConnectionCache) {
+            sCaches.put(connectionId, cache);
+        }
     }
 
     private AccessibilityInteractionClient() {
         /* reducing constructor visibility */
-        this(true);
-    }
-
-    private AccessibilityInteractionClient(boolean initializeCache) {
-        initializeCache(initializeCache);
         mAccessibilityManager = null;
     }
 
-    private AccessibilityInteractionClient(boolean initializeCache, Context context) {
-        initializeCache(initializeCache);
+    private AccessibilityInteractionClient(Context context) {
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
     }
 
-    private static void initializeCache(boolean initialize) {
-        if (initialize && sAccessibilityCache == null) {
-            sAccessibilityCache = new AccessibilityCache(
-                    new AccessibilityCache.AccessibilityNodeRefresher());
-        }
-    }
-
     /**
      * Sets the message to be processed if the interacted view hierarchy
      * and the interacting client are running in the same thread.
@@ -333,7 +321,7 @@
      *
      * @param connectionId The id of a connection for interacting with the system.
      * @param accessibilityWindowId A unique window id. Use
-     *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
+     *     {@link AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
      *     to query the currently active window.
      * @param bypassCache Whether to bypass the cache.
      * @return The {@link AccessibilityWindowInfo}.
@@ -344,21 +332,28 @@
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
                 AccessibilityWindowInfo window;
-                if (!bypassCache && sAccessibilityCache != null) {
-                    window = sAccessibilityCache.getWindow(accessibilityWindowId);
-                    if (window != null) {
+                AccessibilityCache cache = getCache(connectionId);
+                if (cache != null) {
+                    if (!bypassCache) {
+                        window = cache.getWindow(accessibilityWindowId);
+                        if (window != null) {
+                            if (DEBUG) {
+                                Log.i(LOG_TAG, "Window cache hit");
+                            }
+                            if (shouldTraceClient()) {
+                                logTraceClient(connection, "getWindow cache",
+                                        "connectionId=" + connectionId + ";accessibilityWindowId="
+                                                + accessibilityWindowId + ";bypassCache=false");
+                            }
+                            return window;
+                        }
                         if (DEBUG) {
-                            Log.i(LOG_TAG, "Window cache hit");
+                            Log.i(LOG_TAG, "Window cache miss");
                         }
-                        if (shouldTraceClient()) {
-                            logTraceClient(connection, "getWindow cache",
-                                    "connectionId=" + connectionId + ";accessibilityWindowId="
-                                    + accessibilityWindowId + ";bypassCache=false");
-                        }
-                        return window;
                     }
+                } else {
                     if (DEBUG) {
-                        Log.i(LOG_TAG, "Window cache miss");
+                        Log.w(LOG_TAG, "Cache is null for connection id: " + connectionId);
                     }
                 }
 
@@ -374,9 +369,9 @@
                             + bypassCache);
                 }
 
-                if (window != null && sAccessibilityCache != null) {
-                    if (!bypassCache) {
-                        sAccessibilityCache.addWindow(window);
+                if (window != null) {
+                    if (!bypassCache && cache != null) {
+                        cache.addWindow(window);
                     }
                     return window;
                 }
@@ -418,8 +413,9 @@
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
                 SparseArray<List<AccessibilityWindowInfo>> windows;
-                if (sAccessibilityCache != null) {
-                    windows = sAccessibilityCache.getWindowsOnAllDisplays();
+                AccessibilityCache cache = getCache(connectionId);
+                if (cache != null) {
+                    windows = cache.getWindowsOnAllDisplays();
                     if (windows != null) {
                         if (DEBUG) {
                             Log.i(LOG_TAG, "Windows cache hit");
@@ -433,6 +429,10 @@
                     if (DEBUG) {
                         Log.i(LOG_TAG, "Windows cache miss");
                     }
+                } else {
+                    if (DEBUG) {
+                        Log.w(LOG_TAG, "Cache is null for connection id: " + connectionId);
+                    }
                 }
 
                 long populationTimeStamp;
@@ -447,8 +447,8 @@
                     logTraceClient(connection, "getWindows", "connectionId=" + connectionId);
                 }
                 if (windows != null) {
-                    if (sAccessibilityCache != null) {
-                        sAccessibilityCache.setWindowsOnAllDisplays(windows, populationTimeStamp);
+                    if (cache != null) {
+                        cache.setWindowsOnAllDisplays(windows, populationTimeStamp);
                     }
                     return windows;
                 }
@@ -533,28 +533,35 @@
         try {
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
-                if (!bypassCache && sAccessibilityCache != null) {
-                    AccessibilityNodeInfo cachedInfo = sAccessibilityCache.getNode(
-                            accessibilityWindowId, accessibilityNodeId);
-                    if (cachedInfo != null) {
+                if (!bypassCache) {
+                    AccessibilityCache cache = getCache(connectionId);
+                    if (cache != null) {
+                        AccessibilityNodeInfo cachedInfo = cache.getNode(
+                                accessibilityWindowId, accessibilityNodeId);
+                        if (cachedInfo != null) {
+                            if (DEBUG) {
+                                Log.i(LOG_TAG, "Node cache hit for "
+                                        + idToString(accessibilityWindowId, accessibilityNodeId));
+                            }
+                            if (shouldTraceClient()) {
+                                logTraceClient(connection,
+                                        "findAccessibilityNodeInfoByAccessibilityId cache",
+                                        "connectionId=" + connectionId + ";accessibilityWindowId="
+                                                + accessibilityWindowId + ";accessibilityNodeId="
+                                                + accessibilityNodeId + ";bypassCache="
+                                                + bypassCache + ";prefetchFlags=" + prefetchFlags
+                                                + ";arguments=" + arguments);
+                            }
+                            return cachedInfo;
+                        }
                         if (DEBUG) {
-                            Log.i(LOG_TAG, "Node cache hit for "
+                            Log.i(LOG_TAG, "Node cache miss for "
                                     + idToString(accessibilityWindowId, accessibilityNodeId));
                         }
-                        if (shouldTraceClient()) {
-                            logTraceClient(connection,
-                                    "findAccessibilityNodeInfoByAccessibilityId cache",
-                                    "connectionId=" + connectionId + ";accessibilityWindowId="
-                                    + accessibilityWindowId + ";accessibilityNodeId="
-                                    + accessibilityNodeId + ";bypassCache=" + bypassCache
-                                    + ";prefetchFlags=" + prefetchFlags + ";arguments="
-                                    + arguments);
+                    } else {
+                        if (DEBUG) {
+                            Log.w(LOG_TAG, "Cache is null for connection id: " + connectionId);
                         }
-                        return cachedInfo;
-                    }
-                    if (DEBUG) {
-                        Log.i(LOG_TAG, "Node cache miss for "
-                                + idToString(accessibilityWindowId, accessibilityNodeId));
                     }
                 } else {
                     // No need to prefech nodes in bypass cache case.
@@ -758,19 +765,19 @@
     }
 
     /**
-     * Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the
+     * Finds the {@link AccessibilityNodeInfo} that has the
      * specified focus type. The search is performed in the window whose id is specified
      * and starts from the node whose accessibility id is specified.
      *
      * @param connectionId The id of a connection for interacting with the system.
      * @param accessibilityWindowId A unique window id. Use
-     *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
+     *     {@link AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
      *     to query the currently active window. Use
-     *     {@link android.view.accessibility.AccessibilityWindowInfo#ANY_WINDOW_ID} to query all
+     *     {@link AccessibilityWindowInfo#ANY_WINDOW_ID} to query all
      *     windows
      * @param accessibilityNodeId A unique view id or virtual descendant id from
      *     where to start the search. Use
-     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+     *     {@link AccessibilityNodeInfo#ROOT_NODE_ID}
      *     to start from the root.
      * @param focusType The focus type.
      * @return The accessibility focused {@link AccessibilityNodeInfo}.
@@ -781,8 +788,9 @@
         try {
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
-                if (sAccessibilityCache != null) {
-                    AccessibilityNodeInfo cachedInfo = sAccessibilityCache.getFocus(focusType,
+                AccessibilityCache cache = getCache(connectionId);
+                if (cache != null) {
+                    AccessibilityNodeInfo cachedInfo = cache.getFocus(focusType,
                             accessibilityNodeId, accessibilityWindowId);
                     if (cachedInfo != null) {
                         if (DEBUG) {
@@ -796,6 +804,10 @@
                         Log.i(LOG_TAG, "Focused node cache miss with "
                                 + idToString(accessibilityWindowId, accessibilityNodeId));
                     }
+                } else {
+                    if (DEBUG) {
+                        Log.w(LOG_TAG, "Cache is null for connection id: " + connectionId);
+                    }
                 }
                 final int interactionId = mInteractionIdCounter.getAndIncrement();
                 if (shouldTraceClient()) {
@@ -956,16 +968,25 @@
     }
 
     /**
-     * Clears the accessibility cache.
+     * Clears the cache associated with {@code connectionId}
+     * @param connectionId the connection id
+     * TODO(207417185): Modify UnsupportedAppUsage
      */
     @UnsupportedAppUsage()
-    public void clearCache() {
-        if (sAccessibilityCache != null) {
-            sAccessibilityCache.clear();
+    public void clearCache(int connectionId) {
+        AccessibilityCache cache = getCache(connectionId);
+        if (cache == null) {
+            return;
         }
+        cache.clear();
     }
 
-    public void onAccessibilityEvent(AccessibilityEvent event) {
+    /**
+     * Informs the cache associated with {@code connectionId} of {@code event}
+     * @param event the event
+     * @param connectionId the connection id
+     */
+    public void onAccessibilityEvent(AccessibilityEvent event, int connectionId) {
         switch (event.getEventType()) {
             case AccessibilityEvent.TYPE_VIEW_SCROLLED:
                 updateScrollingWindow(event.getWindowId(), SystemClock.uptimeMillis());
@@ -978,9 +999,14 @@
             default:
                 break;
         }
-        if (sAccessibilityCache != null) {
-            sAccessibilityCache.onAccessibilityEvent(event);
+        AccessibilityCache cache = getCache(connectionId);
+        if (cache == null) {
+            if (DEBUG) {
+                Log.w(LOG_TAG, "Cache is null for connection id: " + connectionId);
+            }
+            return;
         }
+        cache.onAccessibilityEvent(event);
     }
 
     /**
@@ -1216,8 +1242,15 @@
                 }
             }
             info.setSealed(true);
-            if (!bypassCache && sAccessibilityCache != null) {
-                sAccessibilityCache.add(info);
+            if (!bypassCache) {
+                AccessibilityCache cache = getCache(connectionId);
+                if (cache == null) {
+                    if (DEBUG) {
+                        Log.w(LOG_TAG, "Cache is null for connection id: " + connectionId);
+                    }
+                    return;
+                }
+                cache.add(info);
             }
         }
     }
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 4730eaa..7680aa6 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -3957,8 +3957,10 @@
         }
 
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeString(mCollectionItemInfo.getRowTitle());
             parcel.writeInt(mCollectionItemInfo.getRowIndex());
             parcel.writeInt(mCollectionItemInfo.getRowSpan());
+            parcel.writeString(mCollectionItemInfo.getColumnTitle());
             parcel.writeInt(mCollectionItemInfo.getColumnIndex());
             parcel.writeInt(mCollectionItemInfo.getColumnSpan());
             parcel.writeInt(mCollectionItemInfo.isHeading() ? 1 : 0);
@@ -4100,8 +4102,9 @@
                                      ci.mHierarchical, ci.mSelectionMode);
         CollectionItemInfo cii = other.mCollectionItemInfo;
         mCollectionItemInfo = (cii == null)  ? null
-                : new CollectionItemInfo(cii.mRowIndex, cii.mRowSpan, cii.mColumnIndex,
-                                         cii.mColumnSpan, cii.mHeading, cii.mSelected);
+                : new CollectionItemInfo(cii.mRowTitle, cii.mRowIndex, cii.mRowSpan,
+                        cii.mColumnTitle, cii.mColumnIndex, cii.mColumnSpan,
+                        cii.mHeading, cii.mSelected);
         ExtraRenderingInfo ti = other.mExtraRenderingInfo;
         mExtraRenderingInfo = (ti == null) ? null
                 : new ExtraRenderingInfo(ti);
@@ -4221,8 +4224,10 @@
         if (mCollectionItemInfo != null) mCollectionItemInfo.recycle();
         mCollectionItemInfo = isBitSet(nonDefaultFields, fieldIndex++)
                 ? CollectionItemInfo.obtain(
+                        parcel.readString(),
                         parcel.readInt(),
                         parcel.readInt(),
+                        parcel.readString(),
                         parcel.readInt(),
                         parcel.readInt(),
                         parcel.readInt() == 1,
@@ -5570,8 +5575,9 @@
          * @hide
          */
         public static CollectionItemInfo obtain(CollectionItemInfo other) {
-            return CollectionItemInfo.obtain(other.mRowIndex, other.mRowSpan, other.mColumnIndex,
-                    other.mColumnSpan, other.mHeading, other.mSelected);
+            return CollectionItemInfo.obtain(other.mRowTitle, other.mRowIndex, other.mRowSpan,
+                    other.mColumnTitle, other.mColumnIndex, other.mColumnSpan, other.mHeading,
+                    other.mSelected);
         }
 
         /**
@@ -5612,10 +5618,36 @@
          */
         public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
                 int columnIndex, int columnSpan, boolean heading, boolean selected) {
+            return obtain(null, rowIndex, rowSpan, null, columnIndex,
+                    columnSpan, heading, selected);
+        }
+
+        /**
+         * Obtains a pooled instance.
+         *
+         * <p>In most situations object pooling is not beneficial. Creates a new instance using the
+         * constructor {@link
+         * AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo(int,
+         * int, int, int, boolean, boolean)} instead.
+         *
+         * @param rowTitle The row title at which the item is located.
+         * @param rowIndex The row index at which the item is located.
+         * @param rowSpan The number of rows the item spans.
+         * @param columnTitle The column title at which the item is located.
+         * @param columnIndex The column index at which the item is located.
+         * @param columnSpan The number of columns the item spans.
+         * @param heading Whether the item is a heading. (Prefer
+         *                {@link AccessibilityNodeInfo#setHeading(boolean)})
+         * @param selected Whether the item is selected.
+         */
+        @NonNull
+        public static CollectionItemInfo obtain(@Nullable String rowTitle, int rowIndex,
+                int rowSpan, @Nullable String columnTitle, int columnIndex, int columnSpan,
+                boolean heading, boolean selected) {
             final CollectionItemInfo info = sPool.acquire();
             if (info == null) {
-                return new CollectionItemInfo(
-                        rowIndex, rowSpan, columnIndex, columnSpan, heading, selected);
+                return new CollectionItemInfo(rowTitle, rowIndex, rowSpan, columnTitle,
+                        columnIndex, columnSpan, heading, selected);
             }
 
             info.mRowIndex = rowIndex;
@@ -5624,6 +5656,8 @@
             info.mColumnSpan = columnSpan;
             info.mHeading = heading;
             info.mSelected = selected;
+            info.mRowTitle = rowTitle;
+            info.mColumnTitle = columnTitle;
             return info;
         }
 
@@ -5633,6 +5667,8 @@
         private int mColumnSpan;
         private int mRowSpan;
         private boolean mSelected;
+        private String mRowTitle;
+        private String mColumnTitle;
 
         /**
          * Creates a new instance.
@@ -5660,12 +5696,33 @@
          */
         public CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan,
                 boolean heading, boolean selected) {
+            this(null, rowIndex, rowSpan, null, columnIndex, columnSpan,
+                    heading, selected);
+        }
+
+        /**
+         * Creates a new instance.
+         *
+         * @param rowTitle The row title at which the item is located.
+         * @param rowIndex The row index at which the item is located.
+         * @param rowSpan The number of rows the item spans.
+         * @param columnTitle The column title at which the item is located.
+         * @param columnIndex The column index at which the item is located.
+         * @param columnSpan The number of columns the item spans.
+         * @param heading Whether the item is a heading.
+         * @param selected Whether the item is selected.
+         */
+        public CollectionItemInfo(@Nullable String rowTitle, int rowIndex, int rowSpan,
+                @Nullable String columnTitle, int columnIndex, int columnSpan, boolean heading,
+                boolean selected) {
             mRowIndex = rowIndex;
             mRowSpan = rowSpan;
             mColumnIndex = columnIndex;
             mColumnSpan = columnSpan;
             mHeading = heading;
             mSelected = selected;
+            mRowTitle = rowTitle;
+            mColumnTitle = columnTitle;
         }
 
         /**
@@ -5725,6 +5782,26 @@
         }
 
         /**
+         * Gets the row title at which the item is located.
+         *
+         * @return The row title.
+         */
+        @Nullable
+        public String getRowTitle() {
+            return mRowTitle;
+        }
+
+        /**
+         * Gets the column title at which the item is located.
+         *
+         * @return The column title.
+         */
+        @Nullable
+        public String getColumnTitle() {
+            return mColumnTitle;
+        }
+
+        /**
          * Recycles this instance.
          *
          * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
@@ -5741,6 +5818,8 @@
             mRowSpan = 0;
             mHeading = false;
             mSelected = false;
+            mRowTitle = null;
+            mColumnTitle = null;
         }
     }
 
diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
new file mode 100644
index 0000000..7677b89
--- /dev/null
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -0,0 +1,84 @@
+/*
+ * 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.window;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Abstract class to control the policies of the windows that can be displayed on the virtual
+ * display.
+ *
+ * @hide
+ */
+public abstract class DisplayWindowPolicyController {
+    /**
+     * The window flags that we are interested in.
+     * @see android.view.WindowManager.LayoutParams
+     * @see #keepActivityOnWindowFlagsChanged
+     */
+    private int mWindowFlags;
+
+    /**
+     * Returns {@code true} if the given window flags contain the flags that we're interested in.
+     */
+    public final boolean isInterestedWindowFlags(int windowFlags) {
+        return (mWindowFlags & windowFlags) != 0;
+    }
+
+    /**
+     * Sets the window flags that we’re interested in and expected
+     * #keepActivityOnWindowFlagsChanged to be called if any changes.
+     */
+    public final void setInterestedWindowFlags(int windowFlags) {
+        mWindowFlags = windowFlags;
+    }
+
+    /**
+     * Returns {@code true} if the given activities can be displayed on this virtual display.
+     */
+    public abstract boolean canContainActivities(@NonNull List<ActivityInfo> activities);
+
+    /**
+     * Called when an Activity window is layouted with the new changes where contains the
+     * window flags that we’re interested in.
+     * Returns {@code false} if the Activity cannot remain on the display and the activity task will
+     * be moved back to default display.
+     */
+    public abstract boolean keepActivityOnWindowFlagsChanged(
+            ActivityInfo activityInfo, int windowFlags);
+
+    /**
+     * This is called when the top activity of the display is changed.
+     */
+    public void onTopActivityChanged(ComponentName topActivity, int uid) {}
+
+    /**
+     * This is called when the apps that contains running activities on the display has changed.
+     */
+    public void onRunningAppsChanged(int[] runningUids) {}
+
+    /** Dump debug data */
+    public void dump(String prefix, final PrintWriter pw) {
+        pw.println(prefix + "DisplayWindowPolicyController{" + super.toString() + "}");
+        pw.println(prefix + "  mWindowFlags=" + mWindowFlags);
+    }
+}
diff --git a/core/java/android/window/ITaskFragmentOrganizer.aidl b/core/java/android/window/ITaskFragmentOrganizer.aidl
index 5eb432e..cdfa206 100644
--- a/core/java/android/window/ITaskFragmentOrganizer.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizer.aidl
@@ -19,12 +19,11 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.window.TaskFragmentAppearedInfo;
 import android.window.TaskFragmentInfo;
 
 /** @hide */
 oneway interface ITaskFragmentOrganizer {
-    void onTaskFragmentAppeared(in TaskFragmentAppearedInfo taskFragmentAppearedInfo);
+    void onTaskFragmentAppeared(in TaskFragmentInfo taskFragmentInfo);
     void onTaskFragmentInfoChanged(in TaskFragmentInfo taskFragmentInfo);
     void onTaskFragmentVanished(in TaskFragmentInfo taskFragmentInfo);
 
diff --git a/core/java/android/window/TaskFragmentAppearedInfo.java b/core/java/android/window/TaskFragmentAppearedInfo.java
deleted file mode 100644
index 89d9a95..0000000
--- a/core/java/android/window/TaskFragmentAppearedInfo.java
+++ /dev/null
@@ -1,93 +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 android.window;
-
-import android.annotation.NonNull;
-import android.annotation.TestApi;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.SurfaceControl;
-
-/**
- * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
- * @hide
- */
-@TestApi
-public final class TaskFragmentAppearedInfo implements Parcelable {
-
-    @NonNull
-    private final TaskFragmentInfo mTaskFragmentInfo;
-
-    @NonNull
-    private final SurfaceControl mLeash;
-
-    /** @hide */
-    public TaskFragmentAppearedInfo(
-            @NonNull TaskFragmentInfo taskFragmentInfo, @NonNull SurfaceControl leash) {
-        mTaskFragmentInfo = taskFragmentInfo;
-        mLeash = leash;
-    }
-
-    @NonNull
-    public TaskFragmentInfo getTaskFragmentInfo() {
-        return mTaskFragmentInfo;
-    }
-
-    @NonNull
-    public SurfaceControl getLeash() {
-        return mLeash;
-    }
-
-    private TaskFragmentAppearedInfo(Parcel in) {
-        mTaskFragmentInfo = in.readTypedObject(TaskFragmentInfo.CREATOR);
-        mLeash = in.readTypedObject(SurfaceControl.CREATOR);
-    }
-
-    /** @hide */
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeTypedObject(mTaskFragmentInfo, flags);
-        dest.writeTypedObject(mLeash, flags);
-    }
-
-    @NonNull
-    public static final Creator<TaskFragmentAppearedInfo> CREATOR =
-            new Creator<TaskFragmentAppearedInfo>() {
-                @Override
-                public TaskFragmentAppearedInfo createFromParcel(Parcel in) {
-                    return new TaskFragmentAppearedInfo(in);
-                }
-
-                @Override
-                public TaskFragmentAppearedInfo[] newArray(int size) {
-                    return new TaskFragmentAppearedInfo[size];
-                }
-            };
-
-    @Override
-    public String toString() {
-        return "TaskFragmentAppearedInfo{"
-                + " taskFragmentInfo=" + mTaskFragmentInfo
-                + "}";
-    }
-
-    /** @hide */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-}
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 337c5a1..7e7d370 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -120,8 +120,7 @@
     }
 
     /** Called when a TaskFragment is created and organized by this organizer. */
-    public void onTaskFragmentAppeared(
-            @NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {}
+    public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {}
 
     /** Called when the status of an organized TaskFragment is changed. */
     public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {}
@@ -169,7 +168,7 @@
 
     private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() {
         @Override
-        public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentInfo) {
+        public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
             mExecutor.execute(
                     () -> TaskFragmentOrganizer.this.onTaskFragmentAppeared(taskFragmentInfo));
         }
diff --git a/core/java/com/android/internal/app/ConfirmUserCreationActivity.java b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
index 0047f43..ee4d46d 100644
--- a/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
+++ b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
@@ -23,12 +23,10 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.os.PersistableBundle;
-import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 
@@ -44,6 +42,8 @@
 
     private static final String TAG = "CreateUser";
 
+    private static final String USER_TYPE = UserManager.USER_TYPE_FULL_SECONDARY;
+
     private String mUserName;
     private String mAccountName;
     private String mAccountType;
@@ -103,7 +103,7 @@
         boolean cantCreateUser = mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER)
                 || !mUserManager.isAdminUser();
         // Check the system state and user count
-        boolean cantCreateAnyMoreUsers = !mUserManager.canAddMoreUsers();
+        boolean cantCreateAnyMoreUsers = !mUserManager.canAddMoreUsers(USER_TYPE);
         // Check the account existence
         final Account account = new Account(mAccountName, mAccountType);
         boolean accountExists = mAccountName != null && mAccountType != null
@@ -130,7 +130,7 @@
         setResult(RESULT_CANCELED);
         if (which == BUTTON_POSITIVE && mCanProceed) {
             Log.i(TAG, "Ok, creating user");
-            UserInfo user = mUserManager.createUser(mUserName, 0);
+            UserInfo user = mUserManager.createUser(mUserName, USER_TYPE, 0);
             if (user == null) {
                 Log.e(TAG, "Couldn't create user");
                 finish();
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 6cea8bf..f2bcb02 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -73,6 +73,9 @@
 # Although marked "view" this is mostly graphics stuff
 per-file android_view_* = file:/graphics/java/android/graphics/OWNERS
 
+# Verity
+per-file com_android_internal_security_Verity* = ebiggers@google.com, victorhsieh@google.com
+
 # VINTF
 per-file android_os_VintfObject* = file:platform/system/libvintf:/OWNERS
 per-file android_os_VintfRuntimeInfo* = file:platform/system/libvintf:/OWNERS
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 4c2b114..5e0d9b3 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -34,6 +34,7 @@
 #include <vector>
 
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <bionic/malloc.h>
 #include <debuggerd/client.h>
 #include <log/log.h>
@@ -859,7 +860,22 @@
     return poolsSizeKb;
 }
 
+static bool halSupportsGpuPrivateMemory() {
+    int productApiLevel =
+            android::base::GetIntProperty("ro.product.first_api_level",
+                                          android::base::GetIntProperty("ro.build.version.sdk",
+                                                                         __ANDROID_API_FUTURE__));
+    int boardApiLevel =
+            android::base::GetIntProperty("ro.board.api_level",
+                                          android::base::GetIntProperty("ro.board.first_api_level",
+                                                                         __ANDROID_API_FUTURE__));
+
+    return std::min(productApiLevel, boardApiLevel) >= __ANDROID_API_S__;
+}
+
 static jlong android_os_Debug_getGpuPrivateMemoryKb(JNIEnv* env, jobject clazz) {
+    static bool gpuPrivateMemorySupported = halSupportsGpuPrivateMemory();
+
     struct memtrack_proc* p = memtrack_proc_new();
     if (p == nullptr) {
         LOG(ERROR) << "getGpuPrivateMemoryKb: Failed to create memtrack_proc";
@@ -876,6 +892,12 @@
     ssize_t gpuPrivateMem = memtrack_proc_gl_pss(p);
 
     memtrack_proc_destroy(p);
+
+    // Old HAL implementations may return 0 for GPU private memory if not supported
+    if (gpuPrivateMem == 0 && !gpuPrivateMemorySupported) {
+        return -1;
+    }
+
     return gpuPrivateMem / 1024;
 }
 
diff --git a/core/jni/android_view_CompositionSamplingListener.cpp b/core/jni/android_view_CompositionSamplingListener.cpp
index 783b0d4..59c01dc 100644
--- a/core/jni/android_view_CompositionSamplingListener.cpp
+++ b/core/jni/android_view_CompositionSamplingListener.cpp
@@ -16,21 +16,19 @@
 
 #define LOG_TAG "CompositionSamplingListener"
 
-#include "android_util_Binder.h"
-#include "core_jni_helpers.h"
-
-#include <nativehelper/JNIHelp.h>
-
+#include <android/gui/BnRegionSamplingListener.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
-#include <utils/Log.h>
-#include <utils/RefBase.h>
 #include <binder/IServiceManager.h>
-
-#include <gui/IRegionSamplingListener.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/SurfaceComposerClient.h>
+#include <nativehelper/JNIHelp.h>
 #include <ui/Rect.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include "android_util_Binder.h"
+#include "core_jni_helpers.h"
 
 namespace android {
 
@@ -41,18 +39,18 @@
     jmethodID mDispatchOnSampleCollected;
 } gListenerClassInfo;
 
-struct CompositionSamplingListener : public BnRegionSamplingListener {
+struct CompositionSamplingListener : public gui::BnRegionSamplingListener {
     CompositionSamplingListener(JNIEnv* env, jobject listener)
             : mListener(env->NewWeakGlobalRef(listener)) {}
 
-    void onSampleCollected(float medianLuma) override {
+    binder::Status onSampleCollected(float medianLuma) override {
         JNIEnv* env = AndroidRuntime::getJNIEnv();
         LOG_ALWAYS_FATAL_IF(env == nullptr, "Unable to retrieve JNIEnv in onSampleCollected.");
 
         jobject listener = env->NewGlobalRef(mListener);
         if (listener == NULL) {
             // Weak reference went out of scope
-            return;
+            return binder::Status::ok();
         }
         env->CallStaticVoidMethod(gListenerClassInfo.mClass,
                 gListenerClassInfo.mDispatchOnSampleCollected, listener,
@@ -64,6 +62,8 @@
             LOGE_EX(env);
             env->ExceptionClear();
         }
+
+        return binder::Status::ok();
     }
 
 protected:
diff --git a/core/jni/com_android_internal_security_VerityUtils.cpp b/core/jni/com_android_internal_security_VerityUtils.cpp
index 411a392..c5b3d8a 100644
--- a/core/jni/com_android_internal_security_VerityUtils.cpp
+++ b/core/jni/com_android_internal_security_VerityUtils.cpp
@@ -16,25 +16,25 @@
 
 #define LOG_TAG "VerityUtils"
 
+#include <android-base/unique_fd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <linux/fsverity.h>
+#include <linux/stat.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedUtfChars.h>
-#include <utils/Log.h>
-#include "jni.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <linux/fsverity.h>
-#include <linux/stat.h>
 #include <string.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-
-#include <android-base/unique_fd.h>
+#include <utils/Log.h>
 
 #include <type_traits>
 
+#include "jni.h"
+
 namespace android {
 
 namespace {
@@ -73,18 +73,31 @@
 int statxForFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath) {
     ScopedUtfChars path(env, filePath);
 
+    // Call statx and check STATX_ATTR_VERITY.
     struct statx out = {};
     if (statx(AT_FDCWD, path.c_str(), 0 /* flags */, STATX_ALL, &out) != 0) {
         return -errno;
     }
 
-    // Validity check.
-    if ((out.stx_attributes_mask & STATX_ATTR_VERITY) == 0) {
-        ALOGE("Unexpected, STATX_ATTR_VERITY not supported by kernel");
-        return -ENOSYS;
+    if (out.stx_attributes_mask & STATX_ATTR_VERITY) {
+        return (out.stx_attributes & STATX_ATTR_VERITY) != 0;
     }
 
-    return (out.stx_attributes & STATX_ATTR_VERITY) != 0;
+    // STATX_ATTR_VERITY is not supported for the file path.
+    // In this case, call ioctl(FS_IOC_GETFLAGS) and check FS_VERITY_FL.
+    ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
+    if (rfd.get() < 0) {
+        ALOGE("open failed at %s", path.c_str());
+        return -errno;
+    }
+
+    unsigned int flags;
+    if (ioctl(rfd.get(), FS_IOC_GETFLAGS, &flags) < 0) {
+        ALOGE("ioctl(FS_IOC_GETFLAGS) failed at %s", path.c_str());
+        return -errno;
+    }
+
+    return (flags & FS_VERITY_FL) != 0;
 }
 
 int measureFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath, jbyteArray digest) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 601280a..f10880e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5963,6 +5963,14 @@
     <permission android:name="android.permission.CREATE_VIRTUAL_DEVICE"
                 android:protectionLevel="internal|role" />
 
+    <!-- @SystemApi Must be required by a safety source to send an update using the
+             {@link android.safetycenter.SafetyCenterManager}.
+             <p>Protection level: signature|privileged
+             @hide
+        -->
+    <permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE"
+                android:protectionLevel="signature|privileged" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/layout/splash_screen_view.xml b/core/res/res/layout/splash_screen_view.xml
index 2b9f952..304affe 100644
--- a/core/res/res/layout/splash_screen_view.xml
+++ b/core/res/res/layout/splash_screen_view.xml
@@ -36,6 +36,7 @@
           android:layout_marginBottom="60dp"
           android:padding="0dp"
           android:background="@null"
+          android:forceHasOverlappingRendering="false"
           android:contentDescription="@string/splash_screen_view_branding_description"/>
 
 </android.window.SplashScreenView>
\ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2c60fbd8..a36785f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4943,9 +4943,10 @@
          the app in the letterbox mode. -->
     <item name="config_fixedOrientationLetterboxAspectRatio" format="float" type="dimen">0.0</item>
 
-    <!-- Corners radius for activity presented the letterbox mode. Values < 0 will be ignored and
-         min between device bottom corner radii will be used instead. -->
-    <integer name="config_letterboxActivityCornersRadius">-1</integer>
+    <!-- Corners radius for activity presented the letterbox mode. Values < 0 enable rounded
+         corners with radius equal to min between device bottom corner radii. Default 0 value turns
+         off rounded corners logic in LetterboxUiController. -->
+    <integer name="config_letterboxActivityCornersRadius">0</integer>
 
     <!-- Blur radius for the Option 3 in R.integer.config_letterboxBackgroundType. Values < 0 are
         ignored and 0 is used. -->
diff --git a/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java b/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java
new file mode 100644
index 0000000..570b713
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.util.function.Function.identity;
+
+import android.content.pm.PackagePartitions.SystemPartition;
+import android.os.SystemProperties;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public class PackagePartitionsTest {
+
+    @Test
+    public void testPackagePartitionsFingerprint() {
+        final ArrayList<SystemPartition> partitions = PackagePartitions.getOrderedPartitions(
+                identity());
+        final String[] properties = new String[partitions.size() + 1];
+        for (int i = 0; i < partitions.size(); i++) {
+            final String name = partitions.get(i).getName();
+            properties[i] = "ro." + name + ".build.fingerprint";
+        }
+        properties[partitions.size()] = "ro.build.fingerprint";
+
+        assertThat(SystemProperties.digestOf(properties)).isEqualTo(PackagePartitions.FINGERPRINT);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index 33c6a4b..e689b5d3 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -45,7 +45,6 @@
 
 import com.google.common.base.Throwables;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -82,13 +81,6 @@
         mAccessibilityCache = new AccessibilityCache(mAccessibilityNodeRefresher);
     }
 
-    @After
-    public void tearDown() {
-        // Make sure we're recycling all of our window and node infos.
-        mAccessibilityCache.clear();
-        AccessibilityInteractionClient.getInstance().clearCache();
-    }
-
     @Test
     public void testEmptyCache_returnsNull() {
         assertNull(mAccessibilityCache.getNode(0, 0));
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
index 7e1e7f4..3e061d2 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
@@ -17,6 +17,9 @@
 package android.view.accessibility;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.MockitoAnnotations.initMocks;
 
@@ -40,6 +43,8 @@
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityInteractionClientTest {
     private static final int MOCK_CONNECTION_ID = 0xabcd;
+    private static final int MOCK_CONNECTION_OTHER_ID = 0xabce;
+
 
     private MockConnection mMockConnection = new MockConnection();
     @Mock private AccessibilityCache mMockCache;
@@ -47,8 +52,8 @@
     @Before
     public void setUp() {
         initMocks(this);
-        AccessibilityInteractionClient.setCache(mMockCache);
-        AccessibilityInteractionClient.addConnection(MOCK_CONNECTION_ID, mMockConnection);
+        AccessibilityInteractionClient.addConnection(
+                MOCK_CONNECTION_ID, mMockConnection, /*initializeCache=*/true);
     }
 
     /**
@@ -58,6 +63,7 @@
      */
     @Test
     public void findA11yNodeInfoByA11yId_whenBypassingCache_doesntTouchCache() {
+        AccessibilityInteractionClient.setCache(MOCK_CONNECTION_ID, mMockCache);
         final int windowId = 0x1234;
         final long accessibilityNodeId = 0x4321L;
         AccessibilityNodeInfo nodeFromConnection = AccessibilityNodeInfo.obtain();
@@ -71,6 +77,42 @@
         verifyZeroInteractions(mMockCache);
     }
 
+    @Test
+    public void getCache_differentConnections_returnsDifferentCaches() {
+        MockConnection mOtherMockConnection = new MockConnection();
+        AccessibilityInteractionClient.addConnection(
+                MOCK_CONNECTION_OTHER_ID, mOtherMockConnection, /*initializeCache=*/true);
+
+        AccessibilityCache firstCache = AccessibilityInteractionClient.getCache(MOCK_CONNECTION_ID);
+        AccessibilityCache secondCache = AccessibilityInteractionClient.getCache(
+                MOCK_CONNECTION_OTHER_ID);
+        assertNotEquals(firstCache, secondCache);
+    }
+
+    @Test
+    public void getCache_addConnectionWithoutCache_returnsNullCache() {
+        // Need to first remove from process cache
+        AccessibilityInteractionClient.removeConnection(MOCK_CONNECTION_OTHER_ID);
+
+        MockConnection mOtherMockConnection = new MockConnection();
+        AccessibilityInteractionClient.addConnection(
+                MOCK_CONNECTION_OTHER_ID, mOtherMockConnection, /*initializeCache=*/false);
+
+        AccessibilityCache cache = AccessibilityInteractionClient.getCache(
+                MOCK_CONNECTION_OTHER_ID);
+        assertNull(cache);
+    }
+
+    @Test
+    public void getCache_removeConnection_returnsNull() {
+        AccessibilityCache cache = AccessibilityInteractionClient.getCache(MOCK_CONNECTION_ID);
+        assertNotNull(cache);
+
+        AccessibilityInteractionClient.removeConnection(MOCK_CONNECTION_ID);
+        cache = AccessibilityInteractionClient.getCache(MOCK_CONNECTION_ID);
+        assertNull(cache);
+    }
+
     private static class MockConnection extends AccessibilityServiceConnectionImpl {
         AccessibilityNodeInfo mInfoToReturn;
 
diff --git a/data/etc/displayconfig/Android.bp b/data/etc/displayconfig/Android.bp
new file mode 100644
index 0000000..b2a45d2
--- /dev/null
+++ b/data/etc/displayconfig/Android.bp
@@ -0,0 +1,28 @@
+// Copyright 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+prebuilt_etc {
+    name: "default_television.xml",
+    sub_dir: "displayconfig",
+    src: "default_television.xml",
+}
diff --git a/data/etc/displayconfig/OWNERS b/data/etc/displayconfig/OWNERS
new file mode 100644
index 0000000..6ce1ee4
--- /dev/null
+++ b/data/etc/displayconfig/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/display/OWNERS
diff --git a/data/etc/displayconfig/default_television.xml b/data/etc/displayconfig/default_television.xml
new file mode 100644
index 0000000..2540f59
--- /dev/null
+++ b/data/etc/displayconfig/default_television.xml
@@ -0,0 +1,24 @@
+<displayConfiguration>
+    <densityMap>
+         <density>
+            <height>480</height>
+            <width>720</width>
+            <density>120</density>
+         </density>
+         <density>
+            <height>720</height>
+            <width>1280</width>
+            <density>213</density>
+         </density>
+         <density>
+            <height>1080</height>
+            <width>1920</width>
+            <density>320</density>
+         </density>
+         <density>
+            <height>2160</height>
+            <width>3840</width>
+            <density>640</density>
+         </density>
+    </densityMap>
+</displayConfiguration>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 8ccf02ca..6bfbd8d 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -889,6 +889,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/NonAppWindowAnimationAdapter.java"
     },
+    "-1145384901": {
+      "message": "shouldWaitAnimatingExit: isTransition: %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-1142279614": {
       "message": "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
       "level": "VERBOSE",
@@ -1273,6 +1279,12 @@
       "group": "WM_DEBUG_SCREEN_ON",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-743856570": {
+      "message": "shouldWaitAnimatingExit: isAnimating: %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-743431900": {
       "message": "Configuration no differences in %s",
       "level": "VERBOSE",
@@ -1777,6 +1789,12 @@
       "group": "WM_DEBUG_SYNC_ENGINE",
       "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
     },
+    "-208825711": {
+      "message": "shouldWaitAnimatingExit: isWallpaperTarget: %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-198463978": {
       "message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b",
       "level": "VERBOSE",
@@ -2989,6 +3007,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
     },
+    "1087494661": {
+      "message": "Clear window stuck on animatingExit status: %s",
+      "level": "WARN",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "1088929964": {
       "message": "onLockTaskPackagesUpdated: starting new locktask task=%s",
       "level": "DEBUG",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 85ef270..df751fc 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -27,8 +27,6 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.util.ArrayMap;
-import android.view.SurfaceControl;
-import android.window.TaskFragmentAppearedInfo;
 import android.window.TaskFragmentCreationParams;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOrganizer;
@@ -51,9 +49,6 @@
     /** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */
     private final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>();
 
-    /** Mapping from the client assigned unique token to the TaskFragment {@link SurfaceControl}. */
-    private final Map<IBinder, SurfaceControl> mFragmentLeashes = new ArrayMap<>();
-
     /**
      * Mapping from the client assigned unique token to the TaskFragment parent
      * {@link Configuration}.
@@ -67,7 +62,7 @@
      * Callback that notifies the controller about changes to task fragments.
      */
     interface TaskFragmentCallback {
-        void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo);
+        void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo);
         void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo);
         void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo);
         void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
@@ -259,15 +254,12 @@
     }
 
     @Override
-    public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {
-        final TaskFragmentInfo info = taskFragmentAppearedInfo.getTaskFragmentInfo();
-        final IBinder fragmentToken = info.getFragmentToken();
-        final SurfaceControl leash = taskFragmentAppearedInfo.getLeash();
-        mFragmentInfos.put(fragmentToken, info);
-        mFragmentLeashes.put(fragmentToken, leash);
+    public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
+        final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+        mFragmentInfos.put(fragmentToken, taskFragmentInfo);
 
         if (mCallback != null) {
-            mCallback.onTaskFragmentAppeared(taskFragmentAppearedInfo);
+            mCallback.onTaskFragmentAppeared(taskFragmentInfo);
         }
     }
 
@@ -284,7 +276,6 @@
     @Override
     public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
         mFragmentInfos.remove(taskFragmentInfo.getFragmentToken());
-        mFragmentLeashes.remove(taskFragmentInfo.getFragmentToken());
         mFragmentParentConfigs.remove(taskFragmentInfo.getFragmentToken());
 
         if (mCallback != null) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 68c1904..fe6c7ba 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -37,7 +37,6 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
-import android.window.TaskFragmentAppearedInfo;
 import android.window.TaskFragmentInfo;
 import android.window.WindowContainerTransaction;
 
@@ -110,14 +109,13 @@
     }
 
     @Override
-    public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {
-        TaskFragmentContainer container = getContainer(
-                taskFragmentAppearedInfo.getTaskFragmentInfo().getFragmentToken());
+    public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
+        TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
         if (container == null) {
             return;
         }
 
-        container.setInfo(taskFragmentAppearedInfo.getTaskFragmentInfo());
+        container.setInfo(taskFragmentInfo);
         if (container.isFinished()) {
             mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
         }
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 c8449a3..c807f66 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
@@ -291,8 +291,10 @@
         final String innerPrefix = prefix + "  ";
         final String childPrefix = innerPrefix + "  ";
         pw.println(prefix + this);
-        pw.println(innerPrefix + "Root taskId=" + getRootTaskId()
-                + " winMode=" + mRootTaskInfo.getWindowingMode());
+        if (mRootTaskInfo != null) {
+            pw.println(innerPrefix + "Root taskId=" + mRootTaskInfo.taskId
+                    + " winMode=" + mRootTaskInfo.getWindowingMode());
+        }
         if (mTaskInfo1 != null) {
             pw.println(innerPrefix + "1 taskId=" + mTaskInfo1.taskId
                     + " winMode=" + mTaskInfo1.getWindowingMode());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index d239e56..9ceed24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -244,15 +244,24 @@
     // Fullscreen
     //
 
+    // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
+    @BindsOptionalOf
+    @DynamicOverride
+    abstract FullscreenTaskListener optionalFullscreenTaskListener();
+
     @WMSingleton
     @Provides
     static FullscreenTaskListener provideFullscreenTaskListener(
+            @DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener,
             SyncTransactionQueue syncQueue,
             Optional<FullscreenUnfoldController> optionalFullscreenUnfoldController,
-            Optional<RecentTasksController> recentTasksOptional
-    ) {
-        return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController,
-                recentTasksOptional);
+            Optional<RecentTasksController> recentTasksOptional) {
+        if (fullscreenTaskListener.isPresent()) {
+            return fullscreenTaskListener.get();
+        } else {
+            return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController,
+                    recentTasksOptional);
+        }
     }
 
     //
@@ -337,7 +346,7 @@
     @Provides
     static Optional<OneHandedController> providesOneHandedController(
             @DynamicOverride Optional<OneHandedController> oneHandedController) {
-        if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) {
+        if (SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) {
             return oneHandedController;
         }
         return Optional.empty();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 5c8e7d0..52ff21b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.freeform;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
 
@@ -28,7 +27,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.SurfaceControl;
-import android.window.WindowContainerTransaction;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -85,13 +83,6 @@
             Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
             return;
         }
-
-        // Clears windowing mode and window bounds to let the task inherits from its new parent.
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.setBounds(taskInfo.token, null)
-                .setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
-        mSyncQueue.queue(wct);
-
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d",
                 taskInfo.taskId);
         mTasks.remove(taskInfo.taskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 6cc5f09..90135f2a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1282,6 +1282,9 @@
     }
 
     private boolean isPipTopLeft() {
+        if (!mSplitScreenOptional.isPresent()) {
+            return false;
+        }
         final Rect topLeft = new Rect();
         final Rect bottomRight = new Rect();
         mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index 5f48c73..014f02b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -72,6 +72,7 @@
     private final int mAppRevealDuration;
     private final int mAnimationDuration;
     private final float mIconStartAlpha;
+    private final float mBrandingStartAlpha;
     private final TransactionPool mTransactionPool;
 
     private ValueAnimator mMainAnimator;
@@ -94,9 +95,17 @@
                 || iconView.getLayoutParams().height == 0) {
             mIconFadeOutDuration = 0;
             mIconStartAlpha = 0;
+            mBrandingStartAlpha = 0;
             mAppRevealDelay = 0;
         } else {
             iconView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            // The branding view could only exists when the icon is present.
+            final View brandingView = view.getBrandingView();
+            if (brandingView != null) {
+                mBrandingStartAlpha = brandingView.getAlpha();
+            } else {
+                mBrandingStartAlpha = 0;
+            }
             mIconFadeOutDuration = context.getResources().getInteger(
                     R.integer.starting_window_app_reveal_icon_fade_out_duration);
             mAppRevealDelay = context.getResources().getInteger(
@@ -334,13 +343,21 @@
         // ignore
     }
 
-    private void onAnimationProgress(float linearProgress) {
-        View iconView = mSplashScreenView.getIconView();
+    private void onFadeOutProgress(float linearProgress) {
+        final float iconProgress = ICON_INTERPOLATOR.getInterpolation(
+                getProgress(linearProgress, 0 /* delay */, mIconFadeOutDuration));
+        final View iconView = mSplashScreenView.getIconView();
+        final View brandingView = mSplashScreenView.getBrandingView();
         if (iconView != null) {
-            final float iconProgress = ICON_INTERPOLATOR.getInterpolation(
-                    getProgress(linearProgress, 0 /* delay */, mIconFadeOutDuration));
             iconView.setAlpha(mIconStartAlpha * (1 - iconProgress));
         }
+        if (brandingView != null) {
+            brandingView.setAlpha(mBrandingStartAlpha * (1 - iconProgress));
+        }
+    }
+
+    private void onAnimationProgress(float linearProgress) {
+        onFadeOutProgress(linearProgress);
 
         final float revealLinearProgress = getProgress(linearProgress, mAppRevealDelay,
                 mAppRevealDuration);
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 8f04cfb..f43586f 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -17,24 +17,29 @@
 #define LOG_TAG "PointerController"
 //#define LOG_NDEBUG 0
 
-// Log debug messages about pointer updates
-#define DEBUG_POINTER_UPDATES 0
-
 #include "PointerController.h"
-#include "MouseCursorController.h"
 #include "PointerControllerContext.h"
-#include "TouchSpotController.h"
 
-#include <log/log.h>
-
-#include <SkBitmap.h>
 #include <SkBlendMode.h>
 #include <SkCanvas.h>
 #include <SkColor.h>
-#include <SkPaint.h>
 
 namespace android {
 
+namespace {
+
+const ui::Transform kIdentityTransform;
+
+} // namespace
+
+// --- PointerController::DisplayInfoListener ---
+
+void PointerController::DisplayInfoListener::onWindowInfosChanged(
+        const std::vector<android::gui::WindowInfo>&,
+        const std::vector<android::gui::DisplayInfo>& displayInfo) {
+    mPointerController.onDisplayInfosChanged(displayInfo);
+}
+
 // --- PointerController ---
 
 std::shared_ptr<PointerController> PointerController::create(
@@ -63,9 +68,16 @@
 PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
                                      const sp<Looper>& looper,
                                      const sp<SpriteController>& spriteController)
-      : mContext(policy, looper, spriteController, *this), mCursorController(mContext) {
+      : mContext(policy, looper, spriteController, *this),
+        mCursorController(mContext),
+        mDisplayInfoListener(new DisplayInfoListener(*this)) {
     std::scoped_lock lock(mLock);
     mLocked.presentation = Presentation::SPOT;
+    SurfaceComposerClient::getDefault()->addWindowInfosListener(mDisplayInfoListener);
+}
+
+PointerController::~PointerController() {
+    SurfaceComposerClient::getDefault()->removeWindowInfosListener(mDisplayInfoListener);
 }
 
 bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
@@ -74,7 +86,14 @@
 }
 
 void PointerController::move(float deltaX, float deltaY) {
-    mCursorController.move(deltaX, deltaY);
+    const int32_t displayId = mCursorController.getDisplayId();
+    vec2 transformed;
+    {
+        std::scoped_lock lock(mLock);
+        const auto& transform = getTransformForDisplayLocked(displayId);
+        transformed = transformWithoutTranslation(transform, {deltaX, deltaY});
+    }
+    mCursorController.move(transformed.x, transformed.y);
 }
 
 void PointerController::setButtonState(int32_t buttonState) {
@@ -86,12 +105,26 @@
 }
 
 void PointerController::setPosition(float x, float y) {
-    std::scoped_lock lock(mLock);
-    mCursorController.setPosition(x, y);
+    const int32_t displayId = mCursorController.getDisplayId();
+    vec2 transformed;
+    {
+        std::scoped_lock lock(mLock);
+        const auto& transform = getTransformForDisplayLocked(displayId);
+        transformed = transform.transform(x, y);
+    }
+    mCursorController.setPosition(transformed.x, transformed.y);
 }
 
 void PointerController::getPosition(float* outX, float* outY) const {
+    const int32_t displayId = mCursorController.getDisplayId();
     mCursorController.getPosition(outX, outY);
+    {
+        std::scoped_lock lock(mLock);
+        const auto& transform = getTransformForDisplayLocked(displayId);
+        const auto xy = transform.inverse().transform(*outX, *outY);
+        *outX = xy.x;
+        *outY = xy.y;
+    }
 }
 
 int32_t PointerController::getDisplayId() const {
@@ -130,11 +163,25 @@
 void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
                                  BitSet32 spotIdBits, int32_t displayId) {
     std::scoped_lock lock(mLock);
+    std::array<PointerCoords, MAX_POINTERS> outSpotCoords{};
+    const ui::Transform& transform = getTransformForDisplayLocked(displayId);
+
+    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
+        const uint32_t index = spotIdToIndex[idBits.clearFirstMarkedBit()];
+
+        const vec2 xy = transform.transform(spotCoords[index].getXYValue());
+        outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_X, xy.x);
+        outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y);
+
+        float pressure = spotCoords[index].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
+        outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
+    }
+
     auto it = mLocked.spotControllers.find(displayId);
     if (it == mLocked.spotControllers.end()) {
         mLocked.spotControllers.try_emplace(displayId, displayId, mContext);
     }
-    mLocked.spotControllers.at(displayId).setSpots(spotCoords, spotIdToIndex, spotIdBits);
+    mLocked.spotControllers.at(displayId).setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits);
 }
 
 void PointerController::clearSpots() {
@@ -194,7 +241,7 @@
 
 void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports) {
     std::unordered_set<int32_t> displayIdSet;
-    for (DisplayViewport viewport : viewports) {
+    for (const DisplayViewport& viewport : viewports) {
         displayIdSet.insert(viewport.displayId);
     }
 
@@ -214,4 +261,17 @@
     }
 }
 
+void PointerController::onDisplayInfosChanged(const std::vector<gui::DisplayInfo>& displayInfo) {
+    std::scoped_lock lock(mLock);
+    mLocked.mDisplayInfos = displayInfo;
+}
+
+const ui::Transform& PointerController::getTransformForDisplayLocked(int displayId) const {
+    const auto& di = mLocked.mDisplayInfos;
+    auto it = std::find_if(di.begin(), di.end(), [displayId](const gui::DisplayInfo& info) {
+        return info.displayId == displayId;
+    });
+    return it != di.end() ? it->transform : kIdentityTransform;
+}
+
 } // namespace android
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 97567ba..796077f 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -47,7 +47,7 @@
             const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
             const sp<SpriteController>& spriteController);
 
-    virtual ~PointerController() = default;
+    ~PointerController() override;
 
     virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
     virtual void move(float deltaX, float deltaY);
@@ -72,6 +72,8 @@
     void reloadPointerResources();
     void onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports);
 
+    void onDisplayInfosChanged(const std::vector<gui::DisplayInfo>& displayInfos);
+
 private:
     friend PointerControllerContext::LooperCallback;
     friend PointerControllerContext::MessageHandler;
@@ -85,9 +87,23 @@
     struct Locked {
         Presentation presentation;
 
+        std::vector<gui::DisplayInfo> mDisplayInfos;
         std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
     } mLocked GUARDED_BY(mLock);
 
+    class DisplayInfoListener : public gui::WindowInfosListener {
+    public:
+        explicit DisplayInfoListener(PointerController& pc) : mPointerController(pc){};
+        void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>&,
+                                  const std::vector<android::gui::DisplayInfo>&) override;
+
+    private:
+        PointerController& mPointerController;
+    };
+    sp<DisplayInfoListener> mDisplayInfoListener;
+
+    const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(mLock);
+
     PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
                       const sp<SpriteController>& spriteController);
     void clearSpotsLocked();
diff --git a/media/Android.bp b/media/Android.bp
index 15b24b2..fcdfd72 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -115,6 +115,7 @@
 aidl_interface {
     name: "android.media.soundtrigger.types",
     vendor_available: true,
+    host_supported: true,
     flags: [
         "-Werror",
         "-Weverything",
diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java
index 6077f04..4d8eda1 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -22,12 +22,21 @@
 import android.annotation.NonNull;
 
 /** @hide */
-public final class BroadcastInfoRequest implements Parcelable {
+public abstract class BroadcastInfoRequest implements Parcelable {
+    protected static final int PARCEL_TOKEN_TS_REQUEST = 1;
+
     public static final @NonNull Parcelable.Creator<BroadcastInfoRequest> CREATOR =
             new Parcelable.Creator<BroadcastInfoRequest>() {
                 @Override
                 public BroadcastInfoRequest createFromParcel(Parcel source) {
-                    return new BroadcastInfoRequest(source);
+                    int token = source.readInt();
+                    switch (token) {
+                        case PARCEL_TOKEN_TS_REQUEST:
+                            return TsRequest.createFromParcelBody(source);
+                        default:
+                            throw new IllegalStateException(
+                                    "Unexpected broadcast info request type token in parcel.");
+                    }
                 }
 
                 @Override
@@ -42,10 +51,14 @@
         this.requestId = requestId;
     }
 
-    private BroadcastInfoRequest(Parcel source) {
+    protected BroadcastInfoRequest(Parcel source) {
         requestId = source.readInt();
     }
 
+    public int getRequestId() {
+        return requestId;
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/window/TaskFragmentAppearedInfo.aidl b/media/java/android/media/tv/TsRequest.aidl
similarity index 77%
copy from core/java/android/window/TaskFragmentAppearedInfo.aidl
copy to media/java/android/media/tv/TsRequest.aidl
index 3729c09..0abc7ec 100644
--- a/core/java/android/window/TaskFragmentAppearedInfo.aidl
+++ b/media/java/android/media/tv/TsRequest.aidl
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package android.window;
+package android.media.tv;
 
-/**
- * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
- * @hide
- */
-parcelable TaskFragmentAppearedInfo;
+parcelable TsRequest;
diff --git a/media/java/android/media/tv/TsRequest.java b/media/java/android/media/tv/TsRequest.java
new file mode 100644
index 0000000..3690d05
--- /dev/null
+++ b/media/java/android/media/tv/TsRequest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TsRequest extends BroadcastInfoRequest implements Parcelable {
+    public static final @NonNull Parcelable.Creator<TsRequest> CREATOR =
+            new Parcelable.Creator<TsRequest>() {
+                @Override
+                public TsRequest createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public TsRequest[] newArray(int size) {
+                    return new TsRequest[size];
+                }
+            };
+
+    int tsPid;
+
+    public static TsRequest createFromParcelBody(Parcel in) {
+        return new TsRequest(in);
+    }
+
+    public TsRequest(int requestId, int tsPid) {
+        super(requestId);
+        this.tsPid = tsPid;
+    }
+
+    protected TsRequest(Parcel source) {
+        super(source);
+        tsPid = source.readInt();
+    }
+
+    public int getTsPid() {
+        return tsPid;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(PARCEL_TOKEN_TS_REQUEST);
+        super.writeToParcel(dest, flags);
+        dest.writeInt(tsPid);
+    }
+}
diff --git a/media/java/android/media/tv/interactive/ITvIAppClient.aidl b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
index 0dd64b8..dabea30 100644
--- a/media/java/android/media/tv/interactive/ITvIAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
@@ -16,13 +16,15 @@
 
 package android.media.tv.interactive;
 
+import android.view.InputChannel;
+
 /**
  * Interface a client of the ITvIAppManager implements, to identify itself and receive information
  * about changes to the state of each TV interactive application service.
  * @hide
  */
 oneway interface ITvIAppClient {
-    void onSessionCreated(in String iAppServiceId, IBinder token, int seq);
+    void onSessionCreated(in String iAppServiceId, IBinder token, in InputChannel channel, int seq);
     void onSessionReleased(int seq);
     void onLayoutSurface(int left, int top, int right, int bottom, int seq);
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppService.aidl b/media/java/android/media/tv/interactive/ITvIAppService.aidl
index 2f165f0..1dee9cc 100644
--- a/media/java/android/media/tv/interactive/ITvIAppService.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppService.aidl
@@ -18,6 +18,7 @@
 
 import android.media.tv.interactive.ITvIAppServiceCallback;
 import android.media.tv.interactive.ITvIAppSessionCallback;
+import android.view.InputChannel;
 
 /**
  * Top-level interface to a TV IApp component (implemented in a Service). It's used for
@@ -27,5 +28,6 @@
 oneway interface ITvIAppService {
     void registerCallback(in ITvIAppServiceCallback callback);
     void unregisterCallback(in ITvIAppServiceCallback callback);
-    void createSession(in ITvIAppSessionCallback callback, in String iAppServiceId, int type);
+    void createSession(in InputChannel channel, in ITvIAppSessionCallback callback,
+            in String iAppServiceId, int type);
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java
index 093e1be..7479b2b 100644
--- a/media/java/android/media/tv/interactive/TvIAppManager.java
+++ b/media/java/android/media/tv/interactive/TvIAppManager.java
@@ -23,9 +23,15 @@
 import android.media.tv.TvInputManager;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Pools;
 import android.util.SparseArray;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventSender;
 import android.view.Surface;
 
 import com.android.internal.util.Preconditions;
@@ -67,8 +73,8 @@
         mUserId = userId;
         mClient = new ITvIAppClient.Stub() {
             @Override
-            public void onSessionCreated(String iAppServiceId, IBinder token, int seq) {
-                // TODO: use InputChannel for input events
+            public void onSessionCreated(String iAppServiceId, IBinder token, InputChannel channel,
+                    int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
                     if (record == null) {
@@ -77,7 +83,7 @@
                     }
                     Session session = null;
                     if (token != null) {
-                        session = new Session(token, mService, mUserId, seq,
+                        session = new Session(token, channel, mService, mUserId, seq,
                                 mSessionCallbackRecordMap);
                     } else {
                         mSessionCallbackRecordMap.delete(seq);
@@ -351,18 +357,33 @@
      * @hide
      */
     public static final class Session {
+        static final int DISPATCH_IN_PROGRESS = -1;
+        static final int DISPATCH_NOT_HANDLED = 0;
+        static final int DISPATCH_HANDLED = 1;
+
+        private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
+
         private final ITvIAppManager mService;
         private final int mUserId;
         private final int mSeq;
         private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
 
-        private IBinder mToken;
+        // For scheduling input event handling on the main thread. This also serves as a lock to
+        // protect pending input events and the input channel.
+        private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
 
         private TvInputManager.Session mInputSession;
+        private final Pools.Pool<PendingEvent> mPendingEventPool = new Pools.SimplePool<>(20);
+        private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
 
-        private Session(IBinder token, ITvIAppManager service, int userId, int seq,
-                SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
+        private IBinder mToken;
+        private TvInputEventSender mSender;
+        private InputChannel mInputChannel;
+
+        private Session(IBinder token, InputChannel channel, ITvIAppManager service, int userId,
+                int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
             mToken = token;
+            mInputChannel = channel;
             mService = service;
             mUserId = userId;
             mSeq = seq;
@@ -428,6 +449,43 @@
         }
 
         /**
+         * Dispatches an input event to this session.
+         *
+         * @param event An {@link InputEvent} to dispatch. Cannot be {@code null}.
+         * @param token A token used to identify the input event later in the callback.
+         * @param callback A callback used to receive the dispatch result. Cannot be {@code null}.
+         * @param handler A {@link Handler} that the dispatch result will be delivered to. Cannot be
+         *            {@code null}.
+         * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
+         *         {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
+         *         {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
+         *         be invoked later.
+         * @hide
+         */
+        public int dispatchInputEvent(@NonNull InputEvent event, Object token,
+                @NonNull FinishedInputEventCallback callback, @NonNull Handler handler) {
+            Preconditions.checkNotNull(event);
+            Preconditions.checkNotNull(callback);
+            Preconditions.checkNotNull(handler);
+            synchronized (mHandler) {
+                if (mInputChannel == null) {
+                    return DISPATCH_NOT_HANDLED;
+                }
+                PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
+                if (Looper.myLooper() == Looper.getMainLooper()) {
+                    // Already running on the main thread so we can send the event immediately.
+                    return sendInputEventOnMainLooperLocked(p);
+                }
+
+                // Post the event to the main thread.
+                Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
+                msg.setAsynchronous(true);
+                mHandler.sendMessage(msg);
+                return DISPATCH_IN_PROGRESS;
+            }
+        }
+
+        /**
          * Releases this session.
          */
         public void release() {
@@ -444,12 +502,208 @@
             releaseInternal();
         }
 
+        private void flushPendingEventsLocked() {
+            mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
+
+            final int count = mPendingEvents.size();
+            for (int i = 0; i < count; i++) {
+                int seq = mPendingEvents.keyAt(i);
+                Message msg = mHandler.obtainMessage(
+                        InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
+                msg.setAsynchronous(true);
+                msg.sendToTarget();
+            }
+        }
+
         private void releaseInternal() {
             mToken = null;
+            synchronized (mHandler) {
+                if (mInputChannel != null) {
+                    if (mSender != null) {
+                        flushPendingEventsLocked();
+                        mSender.dispose();
+                        mSender = null;
+                    }
+                    mInputChannel.dispose();
+                    mInputChannel = null;
+                }
+            }
             synchronized (mSessionCallbackRecordMap) {
                 mSessionCallbackRecordMap.delete(mSeq);
             }
         }
+
+        private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
+                FinishedInputEventCallback callback, Handler handler) {
+            PendingEvent p = mPendingEventPool.acquire();
+            if (p == null) {
+                p = new PendingEvent();
+            }
+            p.mEvent = event;
+            p.mEventToken = token;
+            p.mCallback = callback;
+            p.mEventHandler = handler;
+            return p;
+        }
+
+        // Assumes the event has already been removed from the queue.
+        void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
+            p.mHandled = handled;
+            if (p.mEventHandler.getLooper().isCurrentThread()) {
+                // Already running on the callback handler thread so we can send the callback
+                // immediately.
+                p.run();
+            } else {
+                // Post the event to the callback handler thread.
+                // In this case, the callback will be responsible for recycling the event.
+                Message msg = Message.obtain(p.mEventHandler, p);
+                msg.setAsynchronous(true);
+                msg.sendToTarget();
+            }
+        }
+
+        // Must be called on the main looper
+        private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
+            synchronized (mHandler) {
+                int result = sendInputEventOnMainLooperLocked(p);
+                if (result == DISPATCH_IN_PROGRESS) {
+                    return;
+                }
+            }
+
+            invokeFinishedInputEventCallback(p, false);
+        }
+
+        private int sendInputEventOnMainLooperLocked(PendingEvent p) {
+            if (mInputChannel != null) {
+                if (mSender == null) {
+                    mSender = new TvInputEventSender(mInputChannel, mHandler.getLooper());
+                }
+
+                final InputEvent event = p.mEvent;
+                final int seq = event.getSequenceNumber();
+                if (mSender.sendInputEvent(seq, event)) {
+                    mPendingEvents.put(seq, p);
+                    Message msg = mHandler.obtainMessage(
+                            InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+                    msg.setAsynchronous(true);
+                    mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
+                    return DISPATCH_IN_PROGRESS;
+                }
+
+                Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
+                        + event);
+            }
+            return DISPATCH_NOT_HANDLED;
+        }
+
+        void finishedInputEvent(int seq, boolean handled, boolean timeout) {
+            final PendingEvent p;
+            synchronized (mHandler) {
+                int index = mPendingEvents.indexOfKey(seq);
+                if (index < 0) {
+                    return; // spurious, event already finished or timed out
+                }
+
+                p = mPendingEvents.valueAt(index);
+                mPendingEvents.removeAt(index);
+
+                if (timeout) {
+                    Log.w(TAG, "Timeout waiting for session to handle input event after "
+                            + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
+                } else {
+                    mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+                }
+            }
+
+            invokeFinishedInputEventCallback(p, handled);
+        }
+
+        private void recyclePendingEventLocked(PendingEvent p) {
+            p.recycle();
+            mPendingEventPool.release(p);
+        }
+
+        /**
+         * Callback that is invoked when an input event that was dispatched to this session has been
+         * finished.
+         *
+         * @hide
+         */
+        public interface FinishedInputEventCallback {
+            /**
+             * Called when the dispatched input event is finished.
+             *
+             * @param token A token passed to {@link #dispatchInputEvent}.
+             * @param handled {@code true} if the dispatched input event was handled properly.
+             *            {@code false} otherwise.
+             */
+            void onFinishedInputEvent(Object token, boolean handled);
+        }
+
+        private final class InputEventHandler extends Handler {
+            public static final int MSG_SEND_INPUT_EVENT = 1;
+            public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
+            public static final int MSG_FLUSH_INPUT_EVENT = 3;
+
+            InputEventHandler(Looper looper) {
+                super(looper, null, true);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_SEND_INPUT_EVENT: {
+                        sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
+                        return;
+                    }
+                    case MSG_TIMEOUT_INPUT_EVENT: {
+                        finishedInputEvent(msg.arg1, false, true);
+                        return;
+                    }
+                    case MSG_FLUSH_INPUT_EVENT: {
+                        finishedInputEvent(msg.arg1, false, false);
+                        return;
+                    }
+                }
+            }
+        }
+
+        private final class TvInputEventSender extends InputEventSender {
+            TvInputEventSender(InputChannel inputChannel, Looper looper) {
+                super(inputChannel, looper);
+            }
+
+            @Override
+            public void onInputEventFinished(int seq, boolean handled) {
+                finishedInputEvent(seq, handled, false);
+            }
+        }
+
+        private final class PendingEvent implements Runnable {
+            public InputEvent mEvent;
+            public Object mEventToken;
+            public FinishedInputEventCallback mCallback;
+            public Handler mEventHandler;
+            public boolean mHandled;
+
+            public void recycle() {
+                mEvent = null;
+                mEventToken = null;
+                mCallback = null;
+                mEventHandler = null;
+                mHandled = false;
+            }
+
+            @Override
+            public void run() {
+                mCallback.onFinishedInputEvent(mEventToken, mHandled);
+
+                synchronized (mEventHandler) {
+                    recyclePendingEventLocked(this);
+                }
+            }
+        }
     }
 
     private static final class SessionCallbackRecord {
diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java
index 78b8173..25dec62 100644
--- a/media/java/android/media/tv/interactive/TvIAppService.java
+++ b/media/java/android/media/tv/interactive/TvIAppService.java
@@ -25,11 +25,17 @@
 import android.content.Intent;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.Surface;
 
 import com.android.internal.os.SomeArgs;
@@ -85,14 +91,16 @@
             }
 
             @Override
-            public void createSession(ITvIAppSessionCallback cb, String iAppServiceId, int type) {
+            public void createSession(InputChannel channel, ITvIAppSessionCallback cb,
+                    String iAppServiceId, int type) {
                 if (cb == null) {
                     return;
                 }
                 SomeArgs args = SomeArgs.obtain();
-                args.arg1 = cb;
-                args.arg2 = iAppServiceId;
-                args.arg3 = type;
+                args.arg1 = channel;
+                args.arg2 = cb;
+                args.arg3 = iAppServiceId;
+                args.arg4 = type;
                 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args)
                         .sendToTarget();
             }
@@ -122,6 +130,8 @@
      * @hide
      */
     public abstract static class Session implements KeyEvent.Callback {
+        private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
+
         private final Object mLock = new Object();
         // @GuardedBy("mLock")
         private ITvIAppSessionCallback mSessionCallback;
@@ -182,6 +192,60 @@
         }
 
         /**
+         * TODO: JavaDoc of APIs related to input events.
+         * @hide
+         */
+        @Override
+        public boolean onKeyDown(int keyCode, KeyEvent event) {
+            return false;
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+            return false;
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
+            return false;
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean onKeyUp(int keyCode, KeyEvent event) {
+            return false;
+        }
+
+        /**
+         * @hide
+         */
+        public boolean onTouchEvent(MotionEvent event) {
+            return false;
+        }
+
+        /**
+         * @hide
+         */
+        public boolean onTrackballEvent(MotionEvent event) {
+            return false;
+        }
+
+        /**
+         * @hide
+         */
+        public boolean onGenericMotionEvent(MotionEvent event) {
+            return false;
+        }
+
+        /**
          * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
          * is relative to the overlay view that sits on top of this surface.
          *
@@ -226,6 +290,39 @@
             }
         }
 
+        /**
+         * Takes care of dispatching incoming input events and tells whether the event was handled.
+         */
+        int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
+            if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
+            if (event instanceof KeyEvent) {
+                KeyEvent keyEvent = (KeyEvent) event;
+                if (keyEvent.dispatch(this, mDispatcherState, this)) {
+                    return TvIAppManager.Session.DISPATCH_HANDLED;
+                }
+
+                // TODO: special handlings of navigation keys and media keys
+            } else if (event instanceof MotionEvent) {
+                MotionEvent motionEvent = (MotionEvent) event;
+                final int source = motionEvent.getSource();
+                if (motionEvent.isTouchEvent()) {
+                    if (onTouchEvent(motionEvent)) {
+                        return TvIAppManager.Session.DISPATCH_HANDLED;
+                    }
+                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+                    if (onTrackballEvent(motionEvent)) {
+                        return TvIAppManager.Session.DISPATCH_HANDLED;
+                    }
+                } else {
+                    if (onGenericMotionEvent(motionEvent)) {
+                        return TvIAppManager.Session.DISPATCH_HANDLED;
+                    }
+                }
+            }
+            // TODO: handle overlay view
+            return TvIAppManager.Session.DISPATCH_NOT_HANDLED;
+        }
+
         private void initialize(ITvIAppSessionCallback callback) {
             synchronized (mLock) {
                 mSessionCallback = callback;
@@ -281,10 +378,17 @@
      * @hide
      */
     public static class ITvIAppSessionWrapper extends ITvIAppSession.Stub {
+        // TODO: put ITvIAppSessionWrapper in a separate Java file
         private final Session mSessionImpl;
+        private InputChannel mChannel;
+        private TvIAppEventReceiver mReceiver;
 
-        public ITvIAppSessionWrapper(Session mSessionImpl) {
+        public ITvIAppSessionWrapper(Context context, Session mSessionImpl, InputChannel channel) {
             this.mSessionImpl = mSessionImpl;
+            mChannel = channel;
+            if (channel != null) {
+                mReceiver = new TvIAppEventReceiver(channel, context.getMainLooper());
+            }
         }
 
         @Override
@@ -306,6 +410,26 @@
         public void dispatchSurfaceChanged(int format, int width, int height) {
             mSessionImpl.dispatchSurfaceChanged(format, width, height);
         }
+
+        private final class TvIAppEventReceiver extends InputEventReceiver {
+            TvIAppEventReceiver(InputChannel inputChannel, Looper looper) {
+                super(inputChannel, looper);
+            }
+
+            @Override
+            public void onInputEvent(InputEvent event) {
+                if (mSessionImpl == null) {
+                    // The session has been finished.
+                    finishInputEvent(event, false);
+                    return;
+                }
+
+                int handled = mSessionImpl.dispatchInputEvent(event, this);
+                if (handled != TvIAppManager.Session.DISPATCH_IN_PROGRESS) {
+                    finishInputEvent(event, handled == TvIAppManager.Session.DISPATCH_HANDLED);
+                }
+            }
+        }
     }
 
     @SuppressLint("HandlerLeak")
@@ -318,9 +442,10 @@
             switch (msg.what) {
                 case DO_CREATE_SESSION: {
                     SomeArgs args = (SomeArgs) msg.obj;
-                    ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg1;
-                    String iAppServiceId = (String) args.arg2;
-                    int type = (int) args.arg3;
+                    InputChannel channel = (InputChannel) args.arg1;
+                    ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg2;
+                    String iAppServiceId = (String) args.arg3;
+                    int type = (int) args.arg4;
                     args.recycle();
                     Session sessionImpl = onCreateSession(iAppServiceId, type);
                     if (sessionImpl == null) {
@@ -332,7 +457,8 @@
                         }
                         return;
                     }
-                    ITvIAppSession stub = new ITvIAppSessionWrapper(sessionImpl);
+                    ITvIAppSession stub = new ITvIAppSessionWrapper(
+                            TvIAppService.this, sessionImpl, channel);
 
                     SomeArgs someArgs = SomeArgs.obtain();
                     someArgs.arg1 = sessionImpl;
diff --git a/media/java/android/media/tv/tuner/filter/AvSettings.java b/media/java/android/media/tv/tuner/filter/AvSettings.java
index 8bcf3d2..ed04754 100644
--- a/media/java/android/media/tv/tuner/filter/AvSettings.java
+++ b/media/java/android/media/tv/tuner/filter/AvSettings.java
@@ -186,9 +186,10 @@
     private final boolean mIsPassthrough;
     private int mAudioStreamType = AUDIO_STREAM_TYPE_UNDEFINED;
     private int mVideoStreamType = VIDEO_STREAM_TYPE_UNDEFINED;
+    private final boolean mUseSecureMemory;
 
-    private AvSettings(int mainType, boolean isAudio, boolean isPassthrough,
-            int audioStreamType, int videoStreamType) {
+    private AvSettings(int mainType, boolean isAudio, boolean isPassthrough, int audioStreamType,
+            int videoStreamType, boolean useSecureMemory) {
         super(TunerUtils.getFilterSubtype(
                 mainType,
                 isAudio
@@ -197,6 +198,7 @@
         mIsPassthrough = isPassthrough;
         mAudioStreamType = audioStreamType;
         mVideoStreamType = videoStreamType;
+        mUseSecureMemory = useSecureMemory;
     }
 
     /**
@@ -223,6 +225,16 @@
     }
 
     /**
+     * Checks whether secure memory is used.
+     *
+     * <p>This query is only supported by Tuner HAL 2.0 or higher. The return value on HAL 1.1 and
+     * lower is undefined. Use {@link TunerVersionChecker#getTunerVersion()} to check the version.
+     */
+    public boolean useSecureMemory() {
+        return mUseSecureMemory;
+    }
+
+    /**
      * Creates a builder for {@link AvSettings}.
      *
      * @param mainType the filter main type.
@@ -239,9 +251,10 @@
     public static class Builder {
         private final int mMainType;
         private final boolean mIsAudio;
-        private boolean mIsPassthrough;
+        private boolean mIsPassthrough = false;
         private int mAudioStreamType = AUDIO_STREAM_TYPE_UNDEFINED;
         private int mVideoStreamType = VIDEO_STREAM_TYPE_UNDEFINED;
+        boolean mUseSecureMemory = false;
 
         private Builder(int mainType, boolean isAudio) {
             mMainType = mainType;
@@ -250,6 +263,8 @@
 
         /**
          * Sets whether it's passthrough.
+         *
+         * <p>Default value is {@code false}.
          */
         @NonNull
         public Builder setPassthrough(boolean isPassthrough) {
@@ -263,6 +278,8 @@
          * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
          * no-op. Use {@link TunerVersionChecker#getTunerVersion()} to check the version.
          *
+         * <p>Default is {@link #AUDIO_STREAM_TYPE_UNDEFINED}.
+         *
          * @param audioStreamType the audio stream type to set.
          */
         @NonNull
@@ -281,6 +298,8 @@
          * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
          * no-op. Use {@link TunerVersionChecker#getTunerVersion()} to check the version.
          *
+         * <p>Default value is {@link #VIDEO_STREAM_TYPE_UNDEFINED}.
+         *
          * @param videoStreamType the video stream type to set.
          */
         @NonNull
@@ -294,12 +313,29 @@
         }
 
         /**
+         * Sets whether secure memory should be used.
+         *
+         * <p>This API is only supported by Tuner HAL 2.0 or higher. Unsupported version would cause
+         * no-op. Use {@link TunerVersionChecker#getTunerVersion()} to check the version.
+         *
+         * <p>Default value is {@code false}.
+         */
+        @NonNull
+        public Builder setUseSecureMemory(boolean useSecureMemory) {
+            if (TunerVersionChecker.checkHigherOrEqualVersionTo(
+                        TunerVersionChecker.TUNER_VERSION_2_0, "setSecureMemory")) {
+                mUseSecureMemory = useSecureMemory;
+            }
+            return this;
+        }
+
+        /**
          * Builds a {@link AvSettings} object.
          */
         @NonNull
         public AvSettings build() {
-            return new AvSettings(mMainType, mIsAudio, mIsPassthrough,
-                    mAudioStreamType, mVideoStreamType);
+            return new AvSettings(mMainType, mIsAudio, mIsPassthrough, mAudioStreamType,
+                    mVideoStreamType, mUseSecureMemory);
         }
     }
 }
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index ae271120..f0f576e 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -251,8 +251,8 @@
     private native int nativeFlushFilter();
     private native int nativeRead(byte[] buffer, long offset, long size);
     private native int nativeClose();
-    private native String nativeCreateSharedFilter();
-    private native void nativeReleaseSharedFilter(String token);
+    private native String nativeAcquireSharedFilterToken();
+    private native void nativeFreeSharedFilterToken(String token);
 
     // Called by JNI
     private Filter(long id) {
@@ -562,20 +562,20 @@
     }
 
     /**
-     * Creates a shared filter.
+     * Acquires a shared filter token.
      *
      * @return a string shared filter token.
      */
     @Nullable
-    public String createSharedFilter() {
+    public String acquireSharedFilterToken() {
         synchronized (mLock) {
             TunerUtils.checkResourceState(TAG, mIsClosed);
             if (mIsStarted || mIsShared) {
-                Log.d(TAG, "Create shared filter in a wrong state, started: " +
+                Log.d(TAG, "Acquire shared filter in a wrong state, started: " +
                      mIsStarted + "shared: " + mIsShared);
                 return null;
             }
-            String token = nativeCreateSharedFilter();
+            String token = nativeAcquireSharedFilterToken();
             if (token != null) {
                 mIsShared = true;
             }
@@ -584,17 +584,17 @@
     }
 
     /**
-     * Releases a shared filter.
+     * Frees a shared filter token.
      *
      * @param filterToken the token of the shared filter being released.
      */
-    public void releaseSharedFilter(@NonNull String filterToken) {
+    public void freeSharedFilterToken(@NonNull String filterToken) {
         synchronized (mLock) {
             TunerUtils.checkResourceState(TAG, mIsClosed);
             if (!mIsShared) {
                 return;
             }
-            nativeReleaseSharedFilter(filterToken);
+            nativeFreeSharedFilterToken(filterToken);
             mIsShared = false;
         }
     }
diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java
index cd70365..d34581d 100644
--- a/media/java/android/media/tv/tuner/filter/RecordSettings.java
+++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java
@@ -20,11 +20,11 @@
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.hardware.tv.tuner.DemuxRecordScIndexType;
+import android.hardware.tv.tuner.DemuxScAvcIndex;
 import android.hardware.tv.tuner.DemuxScHevcIndex;
 import android.hardware.tv.tuner.DemuxScIndex;
 import android.hardware.tv.tuner.DemuxTsIndex;
 import android.media.tv.tuner.TunerUtils;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -139,7 +139,7 @@
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "INDEX_TYPE_", value =
-            {INDEX_TYPE_NONE, INDEX_TYPE_SC, INDEX_TYPE_SC_HEVC})
+            {INDEX_TYPE_NONE, INDEX_TYPE_SC, INDEX_TYPE_SC_HEVC, INDEX_TYPE_SC_AVC})
     public @interface ScIndexType {}
 
     /**
@@ -154,6 +154,10 @@
      * Start Code index for HEVC.
      */
     public static final int INDEX_TYPE_SC_HEVC = DemuxRecordScIndexType.SC_HEVC;
+    /**
+     * Start Code index for AVC.
+     */
+    public static final int INDEX_TYPE_SC_AVC = DemuxRecordScIndexType.SC_AVC;
 
     /**
      * Indexes can be tagged by Start Code in PES (Packetized Elementary Stream)
@@ -187,23 +191,23 @@
     /**
      * All blocks are coded as I blocks.
      */
-    public static final int SC_INDEX_I_SLICE = DemuxScIndex.I_SLICE;
+    public static final int SC_INDEX_I_SLICE = DemuxScAvcIndex.I_SLICE << 4;
     /**
      * Blocks are coded as I or P blocks.
      */
-    public static final int SC_INDEX_P_SLICE = DemuxScIndex.P_SLICE;
+    public static final int SC_INDEX_P_SLICE = DemuxScAvcIndex.P_SLICE << 4;
     /**
      * Blocks are coded as I, P or B blocks.
      */
-    public static final int SC_INDEX_B_SLICE = DemuxScIndex.B_SLICE;
+    public static final int SC_INDEX_B_SLICE = DemuxScAvcIndex.B_SLICE << 4;
     /**
      * A so-called switching I slice that is coded.
      */
-    public static final int SC_INDEX_SI_SLICE = DemuxScIndex.SI_SLICE;
+    public static final int SC_INDEX_SI_SLICE = DemuxScAvcIndex.SI_SLICE << 4;
     /**
      * A so-called switching P slice that is coded.
      */
-    public static final int SC_INDEX_SP_SLICE = DemuxScIndex.SP_SLICE;
+    public static final int SC_INDEX_SP_SLICE = DemuxScAvcIndex.SP_SLICE << 4;
 
     /**
      * Indexes can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2.
diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
index fc6451f..ff8f796 100644
--- a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
+++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.hardware.tv.tuner.Constant;
 
 /**
  * Table information for Section Filter.
@@ -26,6 +27,12 @@
  */
 @SystemApi
 public class SectionSettingsWithTableInfo extends SectionSettings {
+    /**
+     * The invalid version number of {@link SectionSettingsWithTableInfo}. Tuner HAL will ignore the
+     * {@link SectionSettingsWithTableInfo} version number if this invalid version is set.
+     */
+    public static final int INVALID_TABLE_INFO_VERSION = Constant.INVALID_TABINFO_VERSION;
+
     private final int mTableId;
     private final int mVersion;
 
@@ -64,7 +71,7 @@
      */
     public static class Builder extends SectionSettings.Builder<Builder> {
         private int mTableId;
-        private int mVersion;
+        private int mVersion = INVALID_TABLE_INFO_VERSION;
 
         private Builder(int mainType) {
             super(mainType);
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index c4dfee4..4c1ed45 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -693,6 +693,10 @@
         sc = tsRecordEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scIndex>();
     } else if (tsRecordEvent.scIndexMask.getTag() == DemuxFilterScIndexMask::Tag::scHevc) {
         sc = tsRecordEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scHevc>();
+    } else if (tsRecordEvent.scIndexMask.getTag() == DemuxFilterScIndexMask::Tag::scAvc) {
+        sc = tsRecordEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scAvc>();
+        // Java uses the values defined by HIDL HAL. Left shift 4 bits.
+        sc = sc << 4;
     }
 
     jint ts = tsRecordEvent.tsIndexMask;
@@ -3391,8 +3395,11 @@
     jclass clazz = env->FindClass("android/media/tv/tuner/filter/AvSettings");
     bool isPassthrough =
             env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsPassthrough", "Z"));
-    DemuxFilterAvSettings filterAvSettings {
-        .isPassthrough = isPassthrough,
+    bool isSecureMemory =
+            env->GetBooleanField(settings, env->GetFieldID(clazz, "mUseSecureMemory", "Z"));
+    DemuxFilterAvSettings filterAvSettings{
+            .isPassthrough = isPassthrough,
+            .isSecureMemory = isSecureMemory,
     };
     return filterAvSettings;
 }
@@ -3440,6 +3447,11 @@
             env->GetIntField(settings, env->GetFieldID(clazz, "mScIndexType", "I")));
     jint scIndexMask = env->GetIntField(settings, env->GetFieldID(clazz, "mScIndexMask", "I"));
 
+    // Backward compatibility for S- apps.
+    if (scIndexType == DemuxRecordScIndexType::SC &&
+        scIndexMask > static_cast<int32_t>(DemuxScIndex::SEQUENCE)) {
+        scIndexType = DemuxRecordScIndexType::SC_AVC;
+    }
     DemuxFilterRecordSettings filterRecordSettings {
         .tsIndexMask = tsIndexMask,
         .scIndexType = scIndexType,
@@ -3448,6 +3460,9 @@
         filterRecordSettings.scIndexMask.set<DemuxFilterScIndexMask::Tag::scIndex>(scIndexMask);
     } else if (scIndexType == DemuxRecordScIndexType::SC_HEVC) {
         filterRecordSettings.scIndexMask.set<DemuxFilterScIndexMask::Tag::scHevc>(scIndexMask);
+    } else if (scIndexType == DemuxRecordScIndexType::SC_AVC) {
+        // Java uses the values defined by HIDL HAL. Right shift 4 bits.
+        filterRecordSettings.scIndexMask.set<DemuxFilterScIndexMask::Tag::scAvc>(scIndexMask >> 4);
     }
     return filterRecordSettings;
 }
@@ -3890,22 +3905,22 @@
     return (jint)r;
 }
 
-static jstring android_media_tv_Tuner_create_shared_filter(JNIEnv *env, jobject filter) {
+static jstring android_media_tv_Tuner_acquire_shared_filter_token(JNIEnv *env, jobject filter) {
     sp<FilterClient> filterClient = getFilterClient(env, filter);
     if (filterClient == nullptr) {
         jniThrowException(env, "java/lang/IllegalStateException",
-                          "Failed to create shared filter: filter client not found");
+                          "Failed to acquire shared filter token: filter client not found");
         return nullptr;
     }
 
-    string token = filterClient->createSharedFilter();
+    string token = filterClient->acquireSharedFilterToken();
     if (token.empty()) {
         return nullptr;
     }
     return env->NewStringUTF(token.data());
 }
 
-static void android_media_tv_Tuner_release_shared_filter(
+static void android_media_tv_Tuner_free_shared_filter_token(
         JNIEnv *env, jobject filter, jstring token) {
     sp<FilterClient> filterClient = getFilterClient(env, filter);
     if (filterClient == nullptr) {
@@ -3915,7 +3930,7 @@
     }
 
     std::string filterToken(env->GetStringUTFChars(token, nullptr));
-    filterClient->releaseSharedFilter(filterToken);
+    filterClient->freeSharedFilterToken(filterToken);
 }
 
 static sp<TimeFilterClient> getTimeFilterClient(JNIEnv *env, jobject filter) {
@@ -4408,10 +4423,10 @@
     { "nativeFlushFilter", "()I", (void *)android_media_tv_Tuner_flush_filter },
     { "nativeRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq },
     { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter },
-    {"nativeCreateSharedFilter", "()Ljava/lang/String;",
-            (void *)android_media_tv_Tuner_create_shared_filter},
-    {"nativeReleaseSharedFilter", "(Ljava/lang/String;)V",
-            (void *)android_media_tv_Tuner_release_shared_filter},
+    {"nativeAcquireSharedFilterToken", "()Ljava/lang/String;",
+            (void *)android_media_tv_Tuner_acquire_shared_filter_token},
+    {"nativeFreeSharedFilterToken", "(Ljava/lang/String;)V",
+            (void *)android_media_tv_Tuner_free_shared_filter_token},
 };
 
 static const JNINativeMethod gSharedFilterMethods[] = {
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index e8b3de8..bd283dc 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -196,10 +196,10 @@
     return Result::INVALID_STATE;
 }
 
-string FilterClient::createSharedFilter() {
+string FilterClient::acquireSharedFilterToken() {
     if (mTunerFilter != nullptr) {
         string filterToken;
-        if (mTunerFilter->createSharedFilter(&filterToken).isOk()) {
+        if (mTunerFilter->acquireSharedFilterToken(&filterToken).isOk()) {
             return filterToken;
         }
     }
@@ -207,9 +207,9 @@
     return "";
 }
 
-Result FilterClient::releaseSharedFilter(const string& filterToken) {
+Result FilterClient::freeSharedFilterToken(const string& filterToken) {
     if (mTunerFilter != nullptr) {
-        Status s = mTunerFilter->releaseSharedFilter(filterToken);
+        Status s = mTunerFilter->freeSharedFilterToken(filterToken);
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
diff --git a/media/jni/tuner/FilterClient.h b/media/jni/tuner/FilterClient.h
index c031b2a..7ebe7bc7 100644
--- a/media/jni/tuner/FilterClient.h
+++ b/media/jni/tuner/FilterClient.h
@@ -143,14 +143,14 @@
     Result close();
 
     /**
-     * Create a new SharedFiler.
+     * Accquire a new SharedFiler token.
      */
-    string createSharedFilter();
+    string acquireSharedFilterToken();
 
     /**
-     * Release SharedFiler.
+     * Release SharedFiler token.
      */
-    Result releaseSharedFilter(const string& filterToken);
+    Result freeSharedFilterToken(const string& filterToken);
 
 private:
     Result getFilterMq();
diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp
new file mode 100644
index 0000000..2b81200
--- /dev/null
+++ b/omapi/aidl/Android.bp
@@ -0,0 +1,35 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+    name: "android.se.omapi",
+    vendor_available: true,
+    srcs: ["android/se/omapi/*.aidl"],
+    stability: "vintf",
+    backend: {
+        java: {
+            sdk_version: "module_current",
+        },
+        rust: {
+            enabled: true,
+        },
+        ndk: {
+            separate_platform_variant: false,
+        },
+    },
+}
diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementChannel.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementChannel.aidl
new file mode 100644
index 0000000..725013a
--- /dev/null
+++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementChannel.aidl
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ *//*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.se.omapi;
+/* @hide */
+@VintfStability
+interface ISecureElementChannel {
+  void close();
+  boolean isClosed();
+  boolean isBasicChannel();
+  byte[] getSelectResponse();
+  byte[] transmit(in byte[] command);
+  boolean selectNext();
+}
diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementListener.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementListener.aidl
new file mode 100644
index 0000000..77e1c53f
--- /dev/null
+++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementListener.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017, 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.
+ *//*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.se.omapi;
+/* @hide */
+@VintfStability
+interface ISecureElementListener {
+}
diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementReader.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementReader.aidl
new file mode 100644
index 0000000..2b10c47
--- /dev/null
+++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementReader.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017, 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.
+ *//*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.se.omapi;
+/* @hide */
+@VintfStability
+interface ISecureElementReader {
+  boolean isSecureElementPresent();
+  android.se.omapi.ISecureElementSession openSession();
+  void closeSessions();
+  boolean reset();
+}
diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl
new file mode 100644
index 0000000..ae63462
--- /dev/null
+++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017, 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.
+ *//*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ *//*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.se.omapi;
+/* @hide */
+@VintfStability
+interface ISecureElementService {
+  String[] getReaders();
+  android.se.omapi.ISecureElementReader getReader(in String reader);
+  boolean[] isNFCEventAllowed(in String reader, in byte[] aid, in String[] packageNames);
+}
diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementSession.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementSession.aidl
new file mode 100644
index 0000000..06287c5
--- /dev/null
+++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementSession.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017, 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.
+ *//*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ *//*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.se.omapi;
+/* @hide */
+@VintfStability
+interface ISecureElementSession {
+  byte[] getAtr();
+  void close();
+  void closeChannels();
+  boolean isClosed();
+  android.se.omapi.ISecureElementChannel openBasicChannel(in byte[] aid, in byte p2, in android.se.omapi.ISecureElementListener listener);
+  android.se.omapi.ISecureElementChannel openLogicalChannel(in byte[] aid, in byte p2, in android.se.omapi.ISecureElementListener listener);
+}
diff --git a/core/java/android/se/omapi/ISecureElementChannel.aidl b/omapi/aidl/android/se/omapi/ISecureElementChannel.aidl
similarity index 94%
rename from core/java/android/se/omapi/ISecureElementChannel.aidl
rename to omapi/aidl/android/se/omapi/ISecureElementChannel.aidl
index 4ae57ab..bbd3c14 100644
--- a/core/java/android/se/omapi/ISecureElementChannel.aidl
+++ b/omapi/aidl/android/se/omapi/ISecureElementChannel.aidl
@@ -22,6 +22,7 @@
 import android.se.omapi.ISecureElementSession;
 
 /** @hide */
+@VintfStability
 interface ISecureElementChannel {
 
     /**
@@ -58,6 +59,9 @@
      * Transmits the specified command APDU and returns the response APDU.
      * MANAGE channel commands are not supported.
      * Selection of applets is not supported in logical channels.
+     *
+     * @param command Command APDU, its structure is defined in  ISO/IEC 7816-4
+     *                in Standard byte format
      */
     byte[] transmit(in byte[] command);
 
diff --git a/core/java/android/se/omapi/ISecureElementListener.aidl b/omapi/aidl/android/se/omapi/ISecureElementListener.aidl
similarity index 97%
rename from core/java/android/se/omapi/ISecureElementListener.aidl
rename to omapi/aidl/android/se/omapi/ISecureElementListener.aidl
index e9dd181..479dcd7 100644
--- a/core/java/android/se/omapi/ISecureElementListener.aidl
+++ b/omapi/aidl/android/se/omapi/ISecureElementListener.aidl
@@ -23,5 +23,6 @@
  * Interface to receive call-backs when the service is connected.
  * @hide
  */
+@VintfStability
 interface ISecureElementListener {
 }
diff --git a/core/java/android/se/omapi/ISecureElementReader.aidl b/omapi/aidl/android/se/omapi/ISecureElementReader.aidl
similarity index 94%
rename from core/java/android/se/omapi/ISecureElementReader.aidl
rename to omapi/aidl/android/se/omapi/ISecureElementReader.aidl
index 41244ab..a6979fa 100644
--- a/core/java/android/se/omapi/ISecureElementReader.aidl
+++ b/omapi/aidl/android/se/omapi/ISecureElementReader.aidl
@@ -22,6 +22,7 @@
 import android.se.omapi.ISecureElementSession;
 
 /** @hide */
+@VintfStability
 interface ISecureElementReader {
 
     /**
@@ -34,7 +35,7 @@
      * Connects to a secure element in this reader. <br>
      * This method prepares (initialises) the Secure Element for communication
      * before the Session object is returned (e.g. powers the Secure Element by
-     * ICC ON if its not already on). There might be multiple sessions opened at
+     * ICC ON if it is not already on). There might be multiple sessions opened at
      * the same time on the same reader. The system ensures the interleaving of
      * APDUs between the respective sessions.
      *
diff --git a/core/java/android/se/omapi/ISecureElementService.aidl b/omapi/aidl/android/se/omapi/ISecureElementService.aidl
similarity index 69%
rename from core/java/android/se/omapi/ISecureElementService.aidl
rename to omapi/aidl/android/se/omapi/ISecureElementService.aidl
index 4fa799e..61ae481 100644
--- a/core/java/android/se/omapi/ISecureElementService.aidl
+++ b/omapi/aidl/android/se/omapi/ISecureElementService.aidl
@@ -28,23 +28,31 @@
  * SecureElement service interface.
  * @hide
  */
+@VintfStability
 interface ISecureElementService {
 
     /**
      * Returns the friendly names of available Secure Element readers.
+     * <ul>
+     * <li>If the reader is a SIM reader, then its name must be "SIM[Slot]".</li>
+     * <li>If the reader is a SD or micro SD reader, then its name must be "SD[Slot]"</li>
+     * <li>If the reader is a embedded SE reader, then its name must be "eSE[Slot]"</li>
+     * </ul>
+     * Slot is a decimal number without leading zeros. The Numbering must start with 1
+     * (e.g. SIM1, SIM2, ... or SD1, SD2, ... or eSE1, eSE2, ...).
      */
     String[] getReaders();
 
     /**
      * Returns SecureElement Service reader object to the given name.
      */
-    ISecureElementReader getReader(String reader);
+    ISecureElementReader getReader(in String reader);
 
     /**
      * Checks if the application defined by the package name is allowed to
      * receive NFC transaction events for the defined AID.
      */
-    boolean[] isNFCEventAllowed(String reader, in byte[] aid,
+    boolean[] isNFCEventAllowed(in String reader, in byte[] aid,
             in String[] packageNames);
 
 }
diff --git a/core/java/android/se/omapi/ISecureElementSession.aidl b/omapi/aidl/android/se/omapi/ISecureElementSession.aidl
similarity index 84%
rename from core/java/android/se/omapi/ISecureElementSession.aidl
rename to omapi/aidl/android/se/omapi/ISecureElementSession.aidl
index 8ea599f..129ecc4 100644
--- a/core/java/android/se/omapi/ISecureElementSession.aidl
+++ b/omapi/aidl/android/se/omapi/ISecureElementSession.aidl
@@ -27,6 +27,7 @@
 import android.se.omapi.ISecureElementListener;
 
 /** @hide */
+@VintfStability
 interface ISecureElementSession {
 
     /**
@@ -45,7 +46,6 @@
      */
     void closeChannels();
 
-
     /**
      * Tells if this session is closed.
      *
@@ -59,15 +59,19 @@
      * applet if aid != null.
      * Logical channels cannot be opened with this connection.
      * Use interface method openLogicalChannel() to open a logical channel.
+     * Listener is passed to secure element service and used to monitor whether
+     * the client application that uses OMAPI is still alive or not.
      */
     ISecureElementChannel openBasicChannel(in byte[] aid, in byte p2,
-            ISecureElementListener listener);
+            in ISecureElementListener listener);
 
     /**
      * Opens a connection using the next free logical channel of the card in the
      * specified reader. Selects the specified applet.
      * Selection of other applets with this connection is not supported.
+     * Listener is passed to secure element service and used to monitor whether
+     * the client application that uses OMAPI is still alive or not.
      */
     ISecureElementChannel openLogicalChannel(in byte[] aid, in byte p2,
-            ISecureElementListener listener);
+            in ISecureElementListener listener);
 }
diff --git a/omapi/aidl/vts/functional/AccessControlApp/Android.bp b/omapi/aidl/vts/functional/AccessControlApp/Android.bp
new file mode 100644
index 0000000..f03c3f6
--- /dev/null
+++ b/omapi/aidl/vts/functional/AccessControlApp/Android.bp
@@ -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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "VtsHalOmapiSeAccessControlTestCases",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: [
+        "VtsHalOmapiSeAccessControlTestCases.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libcutils",
+        "libhidlbase",
+        "libnativehelper",
+        "libutils",
+        "libbinder_ndk",
+    ],
+    static_libs: [
+        "VtsHalHidlTargetTestBase",
+        "android.se.omapi-V1-ndk",
+    ],
+    cflags: [
+        "-O0",
+        "-g",
+        "-Wall",
+        "-Werror",
+    ],
+    require_root: true,
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/omapi/aidl/vts/functional/AccessControlApp/VtsHalOmapiSeAccessControlTestCases.cpp b/omapi/aidl/vts/functional/AccessControlApp/VtsHalOmapiSeAccessControlTestCases.cpp
new file mode 100644
index 0000000..9ea6543
--- /dev/null
+++ b/omapi/aidl/vts/functional/AccessControlApp/VtsHalOmapiSeAccessControlTestCases.cpp
@@ -0,0 +1,428 @@
+/*
+ * 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.
+ */
+
+#include <aidl/android/se/omapi/BnSecureElementListener.h>
+#include <aidl/android/se/omapi/ISecureElementChannel.h>
+#include <aidl/android/se/omapi/ISecureElementListener.h>
+#include <aidl/android/se/omapi/ISecureElementReader.h>
+#include <aidl/android/se/omapi/ISecureElementService.h>
+#include <aidl/android/se/omapi/ISecureElementSession.h>
+
+#include <VtsCoreUtil.h>
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <binder/IServiceManager.h>
+#include <cutils/properties.h>
+#include <gtest/gtest.h>
+#include <hidl/GtestPrinter.h>
+#include <hidl/ServiceManagement.h>
+#include <utils/String16.h>
+
+using namespace std;
+using namespace ::testing;
+using namespace android;
+
+int main(int argc, char** argv) {
+    InitGoogleTest(&argc, argv);
+    int status = RUN_ALL_TESTS();
+    return status;
+}
+
+namespace {
+
+class OMAPISEAccessControlTest : public TestWithParam<std::string> {
+   protected:
+
+    class SEListener : public ::aidl::android::se::omapi::BnSecureElementListener {};
+
+    /**
+     * Verifies TLV data
+     *
+     * @return true if the data is tlv formatted, false otherwise
+     */
+    bool verifyBerTlvData(std::vector<uint8_t> tlv) {
+        if (tlv.size() == 0) {
+            LOG(ERROR) << "Invalid tlv, null";
+            return false;
+        }
+        int i = 0;
+        if ((tlv[i++] & 0x1F) == 0x1F) {
+            // extra byte for TAG field
+            i++;
+        }
+
+        int len = tlv[i++] & 0xFF;
+        if (len > 127) {
+            // more than 1 byte for length
+            int bytesLength = len - 128;
+            len = 0;
+            for (int j = bytesLength; j > 0; j--) {
+                len += (len << 8) + (tlv[i++] & 0xFF);
+            }
+        }
+        // Additional 2 bytes for the SW
+        return (tlv.size() == (i + len + 2));
+    }
+
+    void testSelectableAid(
+            std::vector<std::vector<uint8_t>> authorizedAids) {
+        for (auto aid : authorizedAids) {
+            std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+            std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+            auto seListener = ndk::SharedRefBase::make<SEListener>();
+
+            if (mVSReaders.size() > 0) {
+                for (const auto& [name, reader] : mVSReaders) {
+                    std::vector<uint8_t> selectResponse = {};
+                    ASSERT_NE(reader, nullptr) << "reader is null";
+
+                    bool status = false;
+                    auto res = reader->isSecureElementPresent(&status);
+                    ASSERT_TRUE(res.isOk()) << res.getMessage();
+                    ASSERT_TRUE(status);
+
+                    res = reader->openSession(&session);
+                    ASSERT_TRUE(res.isOk()) << res.getMessage();
+                    ASSERT_NE(session, nullptr) << "Could not open session";
+
+                    res = session->openLogicalChannel(aid, 0x00, seListener, &channel);
+                    ASSERT_TRUE(res.isOk()) << res.getMessage();
+                    ASSERT_NE(channel, nullptr) << "Could not open channel";
+
+                    res = channel->getSelectResponse(&selectResponse);
+                    ASSERT_TRUE(res.isOk()) << "failed to get Select Response";
+                    ASSERT_GE(selectResponse.size(), 2);
+
+                    if (channel != nullptr) channel->close();
+                    if (session != nullptr) session->close();
+
+                    ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00));
+                    ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90));
+                    ASSERT_TRUE(
+                        verifyBerTlvData(selectResponse)) << "Select Response is not complete";
+                }
+            }
+        }
+    }
+
+    void testUnauthorisedAid(
+            std::vector<std::vector<uint8_t>> unAuthorizedAids) {
+        for (auto aid : unAuthorizedAids) {
+            std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+            std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+            auto seListener = ndk::SharedRefBase::make<SEListener>();
+
+            if (mVSReaders.size() > 0) {
+                for (const auto& [name, reader] : mVSReaders) {
+                    ASSERT_NE(reader, nullptr) << "reader is null";
+
+                    bool status = false;
+                    auto res = reader->isSecureElementPresent(&status);
+                    ASSERT_TRUE(res.isOk()) << res.getMessage();
+                    ASSERT_TRUE(status);
+
+                    res = reader->openSession(&session);
+                    ASSERT_TRUE(res.isOk()) << res.getMessage();
+                    ASSERT_NE(session, nullptr) << "Could not open session";
+
+                    res = session->openLogicalChannel(aid, 0x00, seListener, &channel);
+
+                    if (channel != nullptr) channel->close();
+                    if (session != nullptr) session->close();
+
+                    if (!res.isOk()) {
+                        ASSERT_EQ(res.getExceptionCode(), EX_SECURITY);
+                        ASSERT_FALSE(res.isOk()) << "expected failed status for this test";
+                    }
+                }
+            }
+        }
+    }
+
+    void testTransmitAPDU(
+            std::vector<uint8_t> aid,
+            std::vector<std::vector<uint8_t>> apdus) {
+        for (auto apdu : apdus) {
+            std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+            std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+            auto seListener = ndk::SharedRefBase::make<SEListener>();
+
+            if (mVSReaders.size() > 0) {
+                for (const auto& [name, reader] : mVSReaders) {
+                    ASSERT_NE(reader, nullptr) << "reader is null";
+                    bool status = false;
+                    std::vector<uint8_t> selectResponse = {};
+                    std::vector<uint8_t> transmitResponse = {};
+                    auto res = reader->isSecureElementPresent(&status);
+                    ASSERT_TRUE(res.isOk()) << res.getMessage();
+                    ASSERT_TRUE(status);
+
+                    res = reader->openSession(&session);
+                    ASSERT_TRUE(res.isOk()) << res.getMessage();
+                    ASSERT_NE(session, nullptr) << "Could not open session";
+
+                    res = session->openLogicalChannel(aid, 0x00, seListener, &channel);
+                    ASSERT_TRUE(res.isOk()) << res.getMessage();
+                    ASSERT_NE(channel, nullptr) << "Could not open channel";
+
+                    res = channel->getSelectResponse(&selectResponse);
+                    ASSERT_TRUE(res.isOk()) << "failed to get Select Response";
+                    ASSERT_GE(selectResponse.size(), 2);
+                    ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00));
+                    ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90));
+                    ASSERT_TRUE(
+                        verifyBerTlvData(selectResponse)) << "Select Response is not complete";
+
+                    res = channel->transmit(apdu, &transmitResponse);
+                    LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode()
+                              << " Message: " << res.getMessage();
+                    if (channel != nullptr) channel->close();
+                    if (session != nullptr) session->close();
+                    ASSERT_TRUE(res.isOk()) << "failed to transmit";
+                }
+            }
+        }
+    }
+
+    void testUnauthorisedAPDU(
+            std::vector<uint8_t> aid,
+            std::vector<std::vector<uint8_t>> apdus) {
+        for (auto apdu : apdus) {
+            std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+            std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+            auto seListener = ndk::SharedRefBase::make<SEListener>();
+
+            if (mVSReaders.size() > 0) {
+                for (const auto& [name, reader] : mVSReaders) {
+                    ASSERT_NE(reader, nullptr) << "reader is null";
+                    bool status = false;
+                    std::vector<uint8_t> selectResponse = {};
+                    std::vector<uint8_t> transmitResponse = {};
+                    auto res = reader->isSecureElementPresent(&status);
+                    ASSERT_TRUE(res.isOk()) << res.getMessage();
+                    ASSERT_TRUE(status);
+
+                    res = reader->openSession(&session);
+                    ASSERT_TRUE(res.isOk()) << res.getMessage();
+                    ASSERT_NE(session, nullptr) << "Could not open session";
+
+                    res = session->openLogicalChannel(aid, 0x00, seListener, &channel);
+                    ASSERT_TRUE(res.isOk()) << res.getMessage();
+                    ASSERT_NE(channel, nullptr) << "Could not open channel";
+
+                    res = channel->getSelectResponse(&selectResponse);
+                    ASSERT_TRUE(res.isOk()) << "failed to get Select Response";
+                    ASSERT_GE(selectResponse.size(), 2);
+                    ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00));
+                    ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90));
+                    ASSERT_TRUE(
+                        verifyBerTlvData(selectResponse)) << "Select Response is not complete";
+
+                    res = channel->transmit(apdu, &transmitResponse);
+                    LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode()
+                              << " Message: " << res.getMessage();
+
+                    if (channel != nullptr) channel->close();
+                    if (session != nullptr) session->close();
+                    if (!res.isOk()) {
+                        ASSERT_EQ(res.getExceptionCode(), EX_SECURITY);
+                        ASSERT_FALSE(res.isOk()) << "expected failed status for this test";
+                    }
+                }
+            }
+        }
+    }
+
+    bool supportOMAPIReaders() {
+        return (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str()));
+    }
+
+    void getFirstApiLevel(int32_t* outApiLevel) {
+        int32_t firstApiLevel = property_get_int32(FEATURE_SE_API_LEVEL.c_str(), -1);
+        if (firstApiLevel < 0) {
+            firstApiLevel = property_get_int32(FEATURE_SE_SDK_VERSION.c_str(), -1);
+        }
+        ASSERT_GT(firstApiLevel, 0);  // first_api_level must exist
+        *outApiLevel = firstApiLevel;
+        return;
+    }
+
+    bool supportsHardware() {
+        bool lowRamDevice = property_get_bool(FEATURE_SE_LOW_RAM.c_str(), true);
+        return !lowRamDevice || deviceSupportsFeature(FEATURE_SE_HARDWARE_WATCH.c_str()) ||
+                deviceSupportsFeature(FEATURE_SE_OMAPI_SERVICE.c_str());  // android.se.omapi
+    }
+
+    void SetUp() override {
+        ASSERT_TRUE(supportsHardware());
+        int32_t apiLevel;
+        getFirstApiLevel(&apiLevel);
+        ASSERT_TRUE(apiLevel > 27);
+        ASSERT_TRUE(supportOMAPIReaders());
+        LOG(INFO) << "get OMAPI service with name:" << GetParam();
+        ::ndk::SpAIBinder ks2Binder(AServiceManager_getService(GetParam().c_str()));
+        mOmapiSeService = aidl::android::se::omapi::ISecureElementService::fromBinder(ks2Binder);
+        ASSERT_TRUE(mOmapiSeService);
+
+        std::vector<std::string> readers = {};
+
+        if (mOmapiSeService != NULL) {
+            auto status = mOmapiSeService->getReaders(&readers);
+            ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+            for (auto readerName : readers) {
+                // Filter eSE readers only
+                if (readerName.find(ESE_READER_PREFIX, 0) != std::string::npos) {
+                    std::shared_ptr<::aidl::android::se::omapi::ISecureElementReader> reader;
+                    status = mOmapiSeService->getReader(readerName, &reader);
+                    ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+                    mVSReaders[readerName] = reader;
+                }
+            }
+        }
+    }
+
+    void TearDown() override {
+        if (mOmapiSeService != nullptr) {
+            if (mVSReaders.size() > 0) {
+                for (const auto& [name, reader] : mVSReaders) {
+                    reader->closeSessions();
+                }
+            }
+        }
+    }
+
+    static inline std::string const ESE_READER_PREFIX = "eSE";
+    static inline std::string const FEATURE_SE_OMAPI_ESE = "android.hardware.se.omapi.ese";
+    static inline std::string const FEATURE_SE_LOW_RAM = "ro.config.low_ram";
+    static inline std::string const FEATURE_SE_HARDWARE_WATCH = "android.hardware.type.watch";
+    static inline std::string const FEATURE_SE_OMAPI_SERVICE = "com.android.se";
+    static inline std::string const FEATURE_SE_SDK_VERSION = "ro.build.version.sdk";
+    static inline std::string const FEATURE_SE_API_LEVEL = "ro.product.first_api_level";
+
+    std::vector<uint8_t> AID_40 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x40};
+    std::vector<uint8_t> AID_41 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x41};
+    std::vector<uint8_t> AID_42 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x42};
+    std::vector<uint8_t> AID_43 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x43};
+    std::vector<uint8_t> AID_44 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x44};
+    std::vector<uint8_t> AID_45 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x45};
+    std::vector<uint8_t> AID_46 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x46};
+    std::vector<uint8_t> AID_47 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x47};
+    std::vector<uint8_t> AID_48 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x48};
+    std::vector<uint8_t> AID_49 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x49};
+    std::vector<uint8_t> AID_4A = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4A};
+    std::vector<uint8_t> AID_4B = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4B};
+    std::vector<uint8_t> AID_4C = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4C};
+    std::vector<uint8_t> AID_4D = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4D};
+    std::vector<uint8_t> AID_4E = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4E};
+    std::vector<uint8_t> AID_4F = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                   0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4F};
+
+    std::vector<std::vector<uint8_t>> AUTHORIZED_AID = {AID_40, AID_41, AID_42, AID_44, AID_45,
+                                                        AID_47, AID_48, AID_49, AID_4A, AID_4B,
+                                                        AID_4C, AID_4D, AID_4E, AID_4F};
+    std::vector<std::vector<uint8_t>> UNAUTHORIZED_AID = {AID_43, AID_46};
+
+    /* Authorized APDU for AID_40 */
+    std::vector<std::vector<uint8_t>> AUTHORIZED_APDU_AID_40 = {
+        {0x00, 0x06, 0x00, 0x00},
+        {0xA0, 0x06, 0x00, 0x00},
+    };
+    /* Unauthorized APDU for AID_40 */
+    std::vector<std::vector<uint8_t>> UNAUTHORIZED_APDU_AID_40 = {
+        {0x00, 0x08, 0x00, 0x00, 0x00},
+        {0x80, 0x06, 0x00, 0x00},
+        {0xA0, 0x08, 0x00, 0x00, 0x00},
+        {0x94, 0x06, 0x00, 0x00, 0x00},
+    };
+
+    /* Authorized APDU for AID_41 */
+    std::vector<std::vector<uint8_t>> AUTHORIZED_APDU_AID_41 = {
+        {0x94, 0x06, 0x00, 0x00},
+        {0x94, 0x08, 0x00, 0x00, 0x00},
+        {0x94, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
+        {0x94, 0x0A, 0x00, 0x00, 0x01, 0xAA}};
+    /* Unauthorized APDU for AID_41 */
+    std::vector<std::vector<uint8_t>> UNAUTHORIZED_APDU_AID_41 = {
+        {0x00, 0x06, 0x00, 0x00},
+        {0x80, 0x06, 0x00, 0x00},
+        {0xA0, 0x06, 0x00, 0x00},
+        {0x00, 0x08, 0x00, 0x00, 0x00},
+        {0x00, 0x0A, 0x00, 0x00, 0x01, 0xAA},
+        {0x80, 0x0A, 0x00, 0x00, 0x01, 0xAA},
+        {0xA0, 0x0A, 0x00, 0x00, 0x01, 0xAA},
+        {0x80, 0x08, 0x00, 0x00, 0x00},
+        {0xA0, 0x08, 0x00, 0x00, 0x00},
+        {0x00, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
+        {0x80, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
+        {0xA0, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
+    };
+
+    std::shared_ptr<aidl::android::se::omapi::ISecureElementService> mOmapiSeService;
+
+    std::map<std::string, std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>>
+        mVSReaders = {};
+};
+
+TEST_P(OMAPISEAccessControlTest, TestAuthorizedAID) {
+    testSelectableAid(AUTHORIZED_AID);
+}
+
+TEST_P(OMAPISEAccessControlTest, TestUnauthorizedAID) {
+    testUnauthorisedAid(UNAUTHORIZED_AID);
+}
+
+TEST_P(OMAPISEAccessControlTest, TestAuthorizedAPDUAID40) {
+    testTransmitAPDU(AID_40, AUTHORIZED_APDU_AID_40);
+}
+
+TEST_P(OMAPISEAccessControlTest, TestUnauthorisedAPDUAID40) {
+    testUnauthorisedAPDU(AID_40, UNAUTHORIZED_APDU_AID_40);
+}
+
+TEST_P(OMAPISEAccessControlTest, TestAuthorizedAPDUAID41) {
+    testTransmitAPDU(AID_41, AUTHORIZED_APDU_AID_41);
+}
+
+TEST_P(OMAPISEAccessControlTest, TestUnauthorisedAPDUAID41) {
+    testUnauthorisedAPDU(AID_41, UNAUTHORIZED_APDU_AID_41);
+}
+
+INSTANTIATE_TEST_SUITE_P(PerInstance, OMAPISEAccessControlTest,
+                         testing::ValuesIn(::android::getAidlHalInstanceNames(
+                             aidl::android::se::omapi::ISecureElementService::descriptor)),
+                         android::hardware::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OMAPISEAccessControlTest);
+
+}  // namespace
diff --git a/omapi/aidl/vts/functional/omapi/Android.bp b/omapi/aidl/vts/functional/omapi/Android.bp
new file mode 100644
index 0000000..c3ab8d1
--- /dev/null
+++ b/omapi/aidl/vts/functional/omapi/Android.bp
@@ -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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "VtsHalOmapiSeServiceV1_TargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: [
+        "VtsHalOmapiSeServiceV1_TargetTest.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libcutils",
+        "libhidlbase",
+        "libnativehelper",
+        "libutils",
+        "libbinder_ndk",
+    ],
+    static_libs: [
+        "VtsHalHidlTargetTestBase",
+        "android.se.omapi-V1-ndk",
+    ],
+    cflags: [
+        "-O0",
+        "-g",
+        "-Wall",
+        "-Werror",
+    ],
+    require_root: true,
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp
new file mode 100644
index 0000000..319cb7e
--- /dev/null
+++ b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp
@@ -0,0 +1,609 @@
+/*
+ * 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.
+ */
+
+#include <aidl/android/se/omapi/BnSecureElementListener.h>
+#include <aidl/android/se/omapi/ISecureElementChannel.h>
+#include <aidl/android/se/omapi/ISecureElementListener.h>
+#include <aidl/android/se/omapi/ISecureElementReader.h>
+#include <aidl/android/se/omapi/ISecureElementService.h>
+#include <aidl/android/se/omapi/ISecureElementSession.h>
+
+#include <VtsCoreUtil.h>
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <binder/IServiceManager.h>
+#include <cutils/properties.h>
+#include <gtest/gtest.h>
+#include <hidl/GtestPrinter.h>
+#include <hidl/ServiceManagement.h>
+#include <utils/String16.h>
+
+using namespace std;
+using namespace ::testing;
+using namespace android;
+
+int main(int argc, char** argv) {
+    InitGoogleTest(&argc, argv);
+    int status = RUN_ALL_TESTS();
+    return status;
+}
+
+namespace {
+
+class OMAPISEServiceHalTest : public TestWithParam<std::string> {
+   protected:
+    class SEListener : public ::aidl::android::se::omapi::BnSecureElementListener {};
+
+    void testSelectableAid(
+        std::shared_ptr<aidl::android::se::omapi::ISecureElementReader> reader,
+        std::vector<uint8_t> aid, std::vector<uint8_t>& selectResponse) {
+        std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+        std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+        auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>();
+
+        ASSERT_NE(reader, nullptr) << "reader is null";
+
+        bool status = false;
+        auto res = reader->isSecureElementPresent(&status);
+        ASSERT_TRUE(res.isOk()) << res.getMessage();
+        ASSERT_TRUE(status);
+
+        res = reader->openSession(&session);
+        ASSERT_TRUE(res.isOk()) << res.getMessage();
+        ASSERT_NE(session, nullptr) << "Could not open session";
+
+        res = session->openLogicalChannel(aid, 0x00, seListener, &channel);
+        ASSERT_TRUE(res.isOk()) << res.getMessage();
+        ASSERT_NE(channel, nullptr) << "Could not open channel";
+
+        res = channel->getSelectResponse(&selectResponse);
+        ASSERT_TRUE(res.isOk()) << "failed to get Select Response";
+        ASSERT_GE(selectResponse.size(), 2);
+
+        if (channel != nullptr) channel->close();
+        if (session != nullptr) session->close();
+
+        ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00));
+        ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90));
+    }
+
+    void testNonSelectableAid(
+        std::shared_ptr<aidl::android::se::omapi::ISecureElementReader> reader,
+        std::vector<uint8_t> aid) {
+        std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+        std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+        auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>();
+
+        ASSERT_NE(reader, nullptr) << "reader is null";
+
+        bool status = false;
+        auto res = reader->isSecureElementPresent(&status);
+        ASSERT_TRUE(res.isOk()) << res.getMessage();
+        ASSERT_TRUE(status);
+
+        res = reader->openSession(&session);
+        ASSERT_TRUE(res.isOk()) << res.getMessage();
+        ASSERT_NE(session, nullptr) << "Could not open session";
+
+        res = session->openLogicalChannel(aid, 0x00, seListener, &channel);
+        if (channel != nullptr) channel->close();
+        if (session != nullptr) session->close();
+
+        LOG(ERROR) << res.getMessage();
+        ASSERT_FALSE(res.isOk()) << "expected to fail to open channel for this test";
+    }
+
+    /**
+     * Verifies TLV data
+     *
+     * @return true if the data is tlv formatted, false otherwise
+     */
+    bool verifyBerTlvData(std::vector<uint8_t> tlv) {
+        if (tlv.size() == 0) {
+            LOG(ERROR) << "Invalid tlv, null";
+            return false;
+        }
+        int i = 0;
+        if ((tlv[i++] & 0x1F) == 0x1F) {
+            // extra byte for TAG field
+            i++;
+        }
+
+        int len = tlv[i++] & 0xFF;
+        if (len > 127) {
+            // more than 1 byte for length
+            int bytesLength = len - 128;
+            len = 0;
+            for (int j = bytesLength; j > 0; j--) {
+                len += (len << 8) + (tlv[i++] & 0xFF);
+            }
+        }
+        // Additional 2 bytes for the SW
+        return (tlv.size() == (i + len + 2));
+    }
+
+    void internalTransmitApdu(
+        std::shared_ptr<aidl::android::se::omapi::ISecureElementReader> reader,
+        std::vector<uint8_t> apdu, std::vector<uint8_t>& transmitResponse) {
+        std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+        std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+        auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>();
+        std::vector<uint8_t> selectResponse = {};
+
+        ASSERT_NE(reader, nullptr) << "reader is null";
+
+        bool status = false;
+        auto res = reader->isSecureElementPresent(&status);
+        ASSERT_TRUE(res.isOk()) << res.getMessage();
+        ASSERT_TRUE(status);
+
+        res = reader->openSession(&session);
+        ASSERT_TRUE(res.isOk()) << res.getMessage();
+        ASSERT_NE(session, nullptr) << "Could not open session";
+
+        res = session->openLogicalChannel(SELECTABLE_AID, 0x00, seListener, &channel);
+        ASSERT_TRUE(res.isOk()) << res.getMessage();
+        ASSERT_NE(channel, nullptr) << "Could not open channel";
+
+        res = channel->getSelectResponse(&selectResponse);
+        ASSERT_TRUE(res.isOk()) << "failed to get Select Response";
+        ASSERT_GE(selectResponse.size(), 2);
+
+        res = channel->transmit(apdu, &transmitResponse);
+        if (channel != nullptr) channel->close();
+        if (session != nullptr) session->close();
+        LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode()
+                  << " Message: " << res.getMessage();
+        ASSERT_TRUE(res.isOk()) << "failed to transmit";
+    }
+
+    bool supportOMAPIReaders() {
+        return (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str()));
+    }
+
+    void SetUp() override {
+        LOG(INFO) << "get OMAPI service with name:" << GetParam();
+        ::ndk::SpAIBinder ks2Binder(AServiceManager_getService(GetParam().c_str()));
+        mOmapiSeService = aidl::android::se::omapi::ISecureElementService::fromBinder(ks2Binder);
+        ASSERT_TRUE(mOmapiSeService);
+
+        std::vector<std::string> readers = {};
+
+        if (omapiSecureService() != NULL) {
+            auto status = omapiSecureService()->getReaders(&readers);
+            ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+            for (auto readerName : readers) {
+                // Filter eSE readers only
+                if (readerName.find(ESE_READER_PREFIX, 0) != std::string::npos) {
+                    std::shared_ptr<::aidl::android::se::omapi::ISecureElementReader> reader;
+                    status = omapiSecureService()->getReader(readerName, &reader);
+                    ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+                    mVSReaders[readerName] = reader;
+                }
+            }
+        }
+    }
+
+    void TearDown() override {
+        if (mOmapiSeService != nullptr) {
+            if (mVSReaders.size() > 0) {
+                for (const auto& [name, reader] : mVSReaders) {
+                    reader->closeSessions();
+                }
+            }
+        }
+    }
+
+    bool isDebuggableBuild() {
+        char value[PROPERTY_VALUE_MAX] = {0};
+        property_get("ro.system.build.type", value, "");
+        if (strcmp(value, "userdebug") == 0) {
+            return true;
+        }
+        if (strcmp(value, "eng") == 0) {
+            return true;
+        }
+        return false;
+    }
+
+    std::shared_ptr<aidl::android::se::omapi::ISecureElementService> omapiSecureService() {
+        return mOmapiSeService;
+    }
+
+    static inline std::string const ESE_READER_PREFIX = "eSE";
+    static inline std::string const FEATURE_SE_OMAPI_ESE = "android.hardware.se.omapi.ese";
+
+    std::vector<uint8_t> SELECTABLE_AID = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                           0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x31};
+    std::vector<uint8_t> LONG_SELECT_RESPONSE_AID = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41,
+                                                     0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64,
+                                                     0x43, 0x54, 0x53, 0x32};
+    std::vector<uint8_t> NON_SELECTABLE_AID = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+                                               0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0xFF};
+
+    std::vector<std::vector<uint8_t>> ILLEGAL_COMMANDS_TRANSMIT = {
+        {0x00, 0x70, 0x00, 0x00},
+        {0x00, 0x70, 0x80, 0x00},
+        {0x00, 0xA4, 0x04, 0x04, 0x10, 0x4A, 0x53, 0x52, 0x31, 0x37, 0x37,
+         0x54, 0x65, 0x73, 0x74, 0x65, 0x72, 0x20, 0x31, 0x2E, 0x30}};
+
+    /* OMAPI APDU Test case 1 and 3 */
+    std::vector<std::vector<uint8_t>> NO_DATA_APDU = {{0x00, 0x06, 0x00, 0x00},
+                                                      {0x80, 0x06, 0x00, 0x00},
+                                                      {0xA0, 0x06, 0x00, 0x00},
+                                                      {0x94, 0x06, 0x00, 0x00},
+                                                      {0x00, 0x0A, 0x00, 0x00, 0x01, 0xAA},
+                                                      {0x80, 0x0A, 0x00, 0x00, 0x01, 0xAA},
+                                                      {0xA0, 0x0A, 0x00, 0x00, 0x01, 0xAA},
+                                                      {0x94, 0x0A, 0x00, 0x00, 0x01, 0xAA}};
+
+    /* OMAPI APDU Test case 2 and 4 */
+    std::vector<std::vector<uint8_t>> DATA_APDU = {{0x00, 0x08, 0x00, 0x00, 0x00},
+                                                   {0x80, 0x08, 0x00, 0x00, 0x00},
+                                                   {0xA0, 0x08, 0x00, 0x00, 0x00},
+                                                   {0x94, 0x08, 0x00, 0x00, 0x00},
+                                                   {0x00, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
+                                                   {0x80, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
+                                                   {0xA0, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
+                                                   {0x94, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}};
+
+    /* Case 2 APDU command expects the P2 received in the SELECT command as 1-byte outgoing data */
+    std::vector<uint8_t> CHECK_SELECT_P2_APDU = {0x00, 0xF4, 0x00, 0x00, 0x00};
+
+    /* OMAPI APDU Test case 1 and 3 */
+    std::vector<std::vector<uint8_t>> SW_62xx_NO_DATA_APDU = {{0x00, 0xF3, 0x00, 0x06},
+                                                              {0x00, 0xF3, 0x00, 0x0A, 0x01, 0xAA}};
+
+    /* OMAPI APDU Test case 2 and 4 */
+    std::vector<uint8_t> SW_62xx_DATA_APDU = {0x00, 0xF3, 0x00, 0x08, 0x00};
+    std::vector<uint8_t> SW_62xx_VALIDATE_DATA_APDU = {0x00, 0xF3, 0x00, 0x0C, 0x01, 0xAA, 0x00};
+    std::vector<std::vector<uint8_t>> SW_62xx = {
+        {0x62, 0x00}, {0x62, 0x81}, {0x62, 0x82}, {0x62, 0x83}, {0x62, 0x85}, {0x62, 0xF1},
+        {0x62, 0xF2}, {0x63, 0xF1}, {0x63, 0xF2}, {0x63, 0xC2}, {0x62, 0x02}, {0x62, 0x80},
+        {0x62, 0x84}, {0x62, 0x86}, {0x63, 0x00}, {0x63, 0x81}};
+
+    std::vector<std::vector<uint8_t>> SEGMENTED_RESP_APDU = {
+        // Get response Case2 61FF+61XX with answer length (P1P2) of 0x0800, 2048 bytes
+        {0x00, 0xC2, 0x08, 0x00, 0x00},
+        // Get response Case4 61FF+61XX with answer length (P1P2) of 0x0800, 2048 bytes
+        {0x00, 0xC4, 0x08, 0x00, 0x02, 0x12, 0x34, 0x00},
+        // Get response Case2 6100+61XX with answer length (P1P2) of 0x0800, 2048 bytes
+        {0x00, 0xC6, 0x08, 0x00, 0x00},
+        // Get response Case4 6100+61XX with answer length (P1P2) of 0x0800, 2048 bytes
+        {0x00, 0xC8, 0x08, 0x00, 0x02, 0x12, 0x34, 0x00},
+        // Test device buffer capacity 7FFF data
+        {0x00, 0xC2, 0x7F, 0xFF, 0x00},
+        // Get response 6CFF+61XX with answer length (P1P2) of 0x0800, 2048 bytes
+        {0x00, 0xCF, 0x08, 0x00, 0x00},
+        // Get response with another CLA  with answer length (P1P2) of 0x0800, 2048 bytes
+        {0x94, 0xC2, 0x08, 0x00, 0x00}};
+    long SERVICE_CONNECTION_TIME_OUT = 3000;
+
+    std::shared_ptr<aidl::android::se::omapi::ISecureElementService> mOmapiSeService;
+
+    std::map<std::string, std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>>
+        mVSReaders = {};
+};
+
+/** Tests getReaders API */
+TEST_P(OMAPISEServiceHalTest, TestGetReaders) {
+    std::vector<std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>> eseReaders =
+        {};
+
+    for (const auto& [name, reader] : mVSReaders) {
+        bool status = false;
+        LOG(INFO) << "Name of the reader: " << name;
+
+        if (reader) {
+            auto res = reader->isSecureElementPresent(&status);
+            ASSERT_TRUE(res.isOk()) << res.getMessage();
+        }
+        ASSERT_TRUE(status);
+
+        if (name.find(ESE_READER_PREFIX) == std::string::npos) {
+            LOG(ERROR) << "Incorrect Reader name";
+            FAIL();
+        }
+
+        if (name.find(ESE_READER_PREFIX, 0) != std::string::npos) {
+            eseReaders.push_back(reader);
+        } else {
+            LOG(INFO) << "Reader not supported: " << name;
+            FAIL();
+        }
+    }
+
+    if (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str())) {
+        ASSERT_GE(eseReaders.size(), 1);
+    } else {
+        ASSERT_TRUE(eseReaders.size() == 0);
+    }
+}
+
+/** Tests OpenBasicChannel API when aid is null */
+TEST_P(OMAPISEServiceHalTest, TestOpenBasicChannelNullAid) {
+    ASSERT_TRUE(supportOMAPIReaders() == true);
+    std::vector<uint8_t> aid = {};
+    auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>();
+
+    if (mVSReaders.size() > 0) {
+        for (const auto& [name, reader] : mVSReaders) {
+            std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+            std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+            bool result = false;
+
+            auto status = reader->openSession(&session);
+            ASSERT_TRUE(status.isOk()) << status.getMessage();
+            if (!session) {
+                LOG(ERROR) << "Could not open session";
+                FAIL();
+            }
+
+            status = session->openBasicChannel(aid, 0x00, seListener, &channel);
+            ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+            if (channel != nullptr) channel->close();
+            if (session != nullptr) session->close();
+
+            if (channel != nullptr) {
+                status = channel->isBasicChannel(&result);
+                ASSERT_TRUE(status.isOk()) << "Basic Channel cannot be opened";
+            }
+        }
+    }
+}
+
+/** Tests OpenBasicChannel API when aid is provided */
+TEST_P(OMAPISEServiceHalTest, TestOpenBasicChannelNonNullAid) {
+    ASSERT_TRUE(supportOMAPIReaders() == true);
+    auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>();
+
+    if (mVSReaders.size() > 0) {
+        for (const auto& [name, reader] : mVSReaders) {
+            std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+            std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+            bool result = false;
+
+            auto status = reader->openSession(&session);
+            ASSERT_TRUE(status.isOk()) << status.getMessage();
+            if (!session) {
+                LOG(ERROR) << "Could not open session";
+                FAIL();
+            }
+
+            status = session->openBasicChannel(SELECTABLE_AID, 0x00, seListener, &channel);
+            ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+            if (channel != nullptr) channel->close();
+            if (session != nullptr) session->close();
+
+            if (channel != nullptr) {
+                status = channel->isBasicChannel(&result);
+                ASSERT_TRUE(status.isOk()) << "Basic Channel cannot be opened";
+            }
+        }
+    }
+}
+
+/** Tests Select API */
+TEST_P(OMAPISEServiceHalTest, TestSelectableAid) {
+    ASSERT_TRUE(supportOMAPIReaders() == true);
+    if (mVSReaders.size() > 0) {
+        for (const auto& [name, reader] : mVSReaders) {
+            std::vector<uint8_t> selectResponse = {};
+            testSelectableAid(reader, SELECTABLE_AID, selectResponse);
+        }
+    }
+}
+
+/** Tests Select API */
+TEST_P(OMAPISEServiceHalTest, TestLongSelectResponse) {
+    ASSERT_TRUE(supportOMAPIReaders() == true);
+    if (mVSReaders.size() > 0) {
+        for (const auto& [name, reader] : mVSReaders) {
+            std::vector<uint8_t> selectResponse = {};
+            testSelectableAid(reader, LONG_SELECT_RESPONSE_AID, selectResponse);
+            ASSERT_TRUE(verifyBerTlvData(selectResponse)) << "Select Response is not complete";
+        }
+    }
+}
+
+/** Test to fail open channel with wrong aid */
+TEST_P(OMAPISEServiceHalTest, TestWrongAid) {
+    ASSERT_TRUE(supportOMAPIReaders() == true);
+    if (mVSReaders.size() > 0) {
+        for (const auto& [name, reader] : mVSReaders) {
+            testNonSelectableAid(reader, NON_SELECTABLE_AID);
+        }
+    }
+}
+
+/** Tests with invalid cmds in Transmit */
+TEST_P(OMAPISEServiceHalTest, TestSecurityExceptionInTransmit) {
+    ASSERT_TRUE(supportOMAPIReaders() == true);
+    if (mVSReaders.size() > 0) {
+        for (const auto& [name, reader] : mVSReaders) {
+            std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+            std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+            auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>();
+            std::vector<uint8_t> selectResponse = {};
+
+            ASSERT_NE(reader, nullptr) << "reader is null";
+
+            bool status = false;
+            auto res = reader->isSecureElementPresent(&status);
+            ASSERT_TRUE(res.isOk()) << res.getMessage();
+            ASSERT_TRUE(status);
+
+            res = reader->openSession(&session);
+            ASSERT_TRUE(res.isOk()) << res.getMessage();
+            ASSERT_NE(session, nullptr) << "Could not open session";
+
+            res = session->openLogicalChannel(SELECTABLE_AID, 0x00, seListener, &channel);
+            ASSERT_TRUE(res.isOk()) << res.getMessage();
+            ASSERT_NE(channel, nullptr) << "Could not open channel";
+
+            res = channel->getSelectResponse(&selectResponse);
+            ASSERT_TRUE(res.isOk()) << "failed to get Select Response";
+            ASSERT_GE(selectResponse.size(), 2);
+
+            ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00));
+            ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90));
+
+            for (auto cmd : ILLEGAL_COMMANDS_TRANSMIT) {
+                std::vector<uint8_t> response = {};
+                res = channel->transmit(cmd, &response);
+                ASSERT_EQ(res.getExceptionCode(), EX_SECURITY);
+                ASSERT_FALSE(res.isOk()) << "expected failed status for this test";
+            }
+            if (channel != nullptr) channel->close();
+            if (session != nullptr) session->close();
+        }
+    }
+}
+
+/**
+ * Tests Transmit API for all readers.
+ *
+ * Checks the return status and verifies the size of the
+ * response.
+ */
+TEST_P(OMAPISEServiceHalTest, TestTransmitApdu) {
+    ASSERT_TRUE(supportOMAPIReaders() == true);
+    if (mVSReaders.size() > 0) {
+        for (const auto& [name, reader] : mVSReaders) {
+            for (auto apdu : NO_DATA_APDU) {
+                std::vector<uint8_t> response = {};
+                internalTransmitApdu(reader, apdu, response);
+                ASSERT_GE(response.size(), 2);
+                ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00));
+                ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90));
+            }
+
+            for (auto apdu : DATA_APDU) {
+                std::vector<uint8_t> response = {};
+                internalTransmitApdu(reader, apdu, response);
+                /* 256 byte data and 2 bytes of status word */
+                ASSERT_GE(response.size(), 258);
+                ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00));
+                ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90));
+            }
+        }
+    }
+}
+
+/**
+ * Tests if underlying implementations returns the correct Status Word
+ *
+ * TO verify that :
+ * - the device does not modify the APDU sent to the Secure Element
+ * - the warning code is properly received by the application layer as SW answer
+ * - the verify that the application layer can fetch the additionnal data (when present)
+ */
+TEST_P(OMAPISEServiceHalTest, testStatusWordTransmit) {
+    ASSERT_TRUE(supportOMAPIReaders() == true);
+    if (mVSReaders.size() > 0) {
+        for (const auto& [name, reader] : mVSReaders) {
+            for (auto apdu : SW_62xx_NO_DATA_APDU) {
+                for (uint8_t i = 0x00; i < SW_62xx.size(); i++) {
+                    apdu[2] = i + 1;
+                    std::vector<uint8_t> response = {};
+                    internalTransmitApdu(reader, apdu, response);
+                    std::vector<uint8_t> SW = SW_62xx[i];
+                    ASSERT_GE(response.size(), 2);
+                    ASSERT_EQ(response[response.size() - 1], SW[1]);
+                    ASSERT_EQ(response[response.size() - 2], SW[0]);
+                }
+            }
+
+            for (uint8_t i = 0x00; i < SW_62xx.size(); i++) {
+                std::vector<uint8_t> apdu = SW_62xx_DATA_APDU;
+                apdu[2] = i + 1;
+                std::vector<uint8_t> response = {};
+                internalTransmitApdu(reader, apdu, response);
+                std::vector<uint8_t> SW = SW_62xx[i];
+                ASSERT_GE(response.size(), 3);
+                ASSERT_EQ(response[response.size() - 1], SW[1]);
+                ASSERT_EQ(response[response.size() - 2], SW[0]);
+            }
+
+            for (uint8_t i = 0x00; i < SW_62xx.size(); i++) {
+                std::vector<uint8_t> apdu = SW_62xx_VALIDATE_DATA_APDU;
+                apdu[2] = i + 1;
+                std::vector<uint8_t> response = {};
+                internalTransmitApdu(reader, apdu, response);
+                ASSERT_GE(response.size(), apdu.size() + 2);
+                std::vector<uint8_t> responseSubstring((response.begin() + 0),
+                                                       (response.begin() + apdu.size()));
+                // We should not care about which channel number is actually assigned.
+                responseSubstring[0] = apdu[0];
+                ASSERT_TRUE((responseSubstring == apdu));
+                std::vector<uint8_t> SW = SW_62xx[i];
+                ASSERT_EQ(response[response.size() - 1], SW[1]);
+                ASSERT_EQ(response[response.size() - 2], SW[0]);
+            }
+        }
+    }
+}
+
+/** Test if the responses are segmented by the underlying implementation */
+TEST_P(OMAPISEServiceHalTest, TestSegmentedResponseTransmit) {
+    ASSERT_TRUE(supportOMAPIReaders() == true);
+    if (mVSReaders.size() > 0) {
+        for (const auto& [name, reader] : mVSReaders) {
+            for (auto apdu : SEGMENTED_RESP_APDU) {
+                std::vector<uint8_t> response = {};
+                internalTransmitApdu(reader, apdu, response);
+                int expectedLength = (0x00 << 24) | (0x00 << 16) | (apdu[2] << 8) | apdu[3];
+                ASSERT_EQ(response.size(), (expectedLength + 2));
+                ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00));
+                ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90));
+                ASSERT_EQ((response[response.size() - 3] & 0xFF), (0xFF));
+            }
+        }
+    }
+}
+
+/**
+ * Tests the P2 value of the select command.
+ *
+ * Verifies that the default P2 value (0x00) is not modified by the underlying implementation.
+ */
+TEST_P(OMAPISEServiceHalTest, TestP2Value) {
+    ASSERT_TRUE(supportOMAPIReaders() == true);
+    if (mVSReaders.size() > 0) {
+        for (const auto& [name, reader] : mVSReaders) {
+            std::vector<uint8_t> response = {};
+            internalTransmitApdu(reader, CHECK_SELECT_P2_APDU, response);
+            ASSERT_GE(response.size(), 3);
+            ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00));
+            ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90));
+            ASSERT_EQ((response[response.size() - 3] & 0xFF), (0x00));
+        }
+    }
+}
+
+INSTANTIATE_TEST_SUITE_P(PerInstance, OMAPISEServiceHalTest,
+                         testing::ValuesIn(::android::getAidlHalInstanceNames(
+                             aidl::android::se::omapi::ISecureElementService::descriptor)),
+                         android::hardware::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OMAPISEServiceHalTest);
+
+}  // namespace
diff --git a/omapi/java/Android.bp b/omapi/java/Android.bp
new file mode 100644
index 0000000..8d38da0
--- /dev/null
+++ b/omapi/java/Android.bp
@@ -0,0 +1,17 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+    name: "framework-omapi-sources",
+    srcs: [
+        "**/*.java",
+        "**/*.aidl",
+    ],
+    visibility: ["//frameworks/base"],
+}
diff --git a/core/java/android/se/OWNERS b/omapi/java/android/se/OWNERS
similarity index 100%
rename from core/java/android/se/OWNERS
rename to omapi/java/android/se/OWNERS
diff --git a/core/java/android/se/omapi/Channel.java b/omapi/java/android/se/omapi/Channel.java
similarity index 100%
rename from core/java/android/se/omapi/Channel.java
rename to omapi/java/android/se/omapi/Channel.java
diff --git a/core/java/android/se/omapi/OWNERS b/omapi/java/android/se/omapi/OWNERS
similarity index 100%
rename from core/java/android/se/omapi/OWNERS
rename to omapi/java/android/se/omapi/OWNERS
diff --git a/core/java/android/se/omapi/Reader.java b/omapi/java/android/se/omapi/Reader.java
similarity index 98%
rename from core/java/android/se/omapi/Reader.java
rename to omapi/java/android/se/omapi/Reader.java
index 90c934d..3c2135d9 100644
--- a/core/java/android/se/omapi/Reader.java
+++ b/omapi/java/android/se/omapi/Reader.java
@@ -170,7 +170,9 @@
             try {
                 closeSessions();
                 return mReader.reset();
-            } catch (RemoteException ignore) {return false;}
+            } catch (RemoteException ignore) {
+                return false;
+            }
         }
     }
 }
diff --git a/core/java/android/se/omapi/SEService.java b/omapi/java/android/se/omapi/SEService.java
similarity index 96%
rename from core/java/android/se/omapi/SEService.java
rename to omapi/java/android/se/omapi/SEService.java
index 333af91..f42ca36 100644
--- a/core/java/android/se/omapi/SEService.java
+++ b/omapi/java/android/se/omapi/SEService.java
@@ -230,20 +230,20 @@
       *         is not exist.
       * @return A Reader object for this uicc slot.
       */
-     public @NonNull Reader getUiccReader(int slotNumber) {
-         if (slotNumber < 1) {
-             throw new IllegalArgumentException("slotNumber should be larger than 0");
-         }
-         loadReaders();
+    public @NonNull Reader getUiccReader(int slotNumber) {
+        if (slotNumber < 1) {
+            throw new IllegalArgumentException("slotNumber should be larger than 0");
+        }
+        loadReaders();
 
-         String readerName = UICC_TERMINAL + slotNumber;
-         Reader reader = mReaders.get(readerName);
+        String readerName = UICC_TERMINAL + slotNumber;
+        Reader reader = mReaders.get(readerName);
 
-         if (reader == null) {
+        if (reader == null) {
             throw new IllegalArgumentException("Reader:" + readerName + " doesn't exist");
-         }
+        }
 
-         return reader;
+        return reader;
     }
 
     /**
diff --git a/core/java/android/se/omapi/Session.java b/omapi/java/android/se/omapi/Session.java
similarity index 100%
rename from core/java/android/se/omapi/Session.java
rename to omapi/java/android/se/omapi/Session.java
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 801b490..a3eb0ecc 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -251,7 +251,7 @@
             if (volume.getType() == VolumeInfo.TYPE_PUBLIC) {
                 root.flags |= Root.FLAG_HAS_SETTINGS;
             }
-            if (volume.isVisibleForRead(userId)) {
+            if (volume.isVisibleForUser(userId)) {
                 root.visiblePath = volume.getPathForUser(userId);
             } else {
                 root.visiblePath = null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 1df1bce..389892e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -307,7 +307,8 @@
             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
             if (cachedDevice == null) {
                 cachedDevice = mDeviceManager.addDevice(device);
-                Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice");
+                Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice "
+                        + cachedDevice.getDevice().getAnonymizedAddress());
             } else if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
                     && !cachedDevice.getDevice().isConnected()) {
                 // Dispatch device add callback to show bonded but
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index a210e90..8b17be1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -19,7 +19,6 @@
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AlertDialog;
-import android.app.Dialog;
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -85,6 +84,7 @@
 
     @VisibleForTesting
     protected Context mContext;
+    private final int mThemeResId;
     @VisibleForTesting
     protected TextView mZenAlarmWarning;
     @VisibleForTesting
@@ -97,10 +97,15 @@
     protected LayoutInflater mLayoutInflater;
 
     public EnableZenModeDialog(Context context) {
-        mContext = context;
+        this(context, 0);
     }
 
-    public Dialog createDialog() {
+    public EnableZenModeDialog(Context context, int themeResId) {
+        mContext = context;
+        mThemeResId = themeResId;
+    }
+
+    public AlertDialog createDialog() {
         mNotificationManager = (NotificationManager) mContext.
                 getSystemService(Context.NOTIFICATION_SERVICE);
         mForeverId =  Condition.newId(mContext).appendPath("forever").build();
@@ -108,7 +113,7 @@
         mUserId = mContext.getUserId();
         mAttached = false;
 
-        final AlertDialog.Builder builder = new AlertDialog.Builder(mContext)
+        final AlertDialog.Builder builder = new AlertDialog.Builder(mContext, mThemeResId)
                 .setTitle(R.string.zen_mode_settings_turn_on_dialog_title)
                 .setNegativeButton(R.string.cancel, null)
                 .setPositiveButton(R.string.zen_mode_enable_dialog_turn_on,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 0fe4efe..a944bf5 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -80,6 +80,7 @@
         Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
         Settings.System.RING_VIBRATION_INTENSITY,
         Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+        Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY,
         Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT, // must precede DISPLAY_COLOR_MODE
         Settings.System.DISPLAY_COLOR_MODE,
         Settings.System.ALARM_ALERT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 462c3a5..63acffb 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -120,6 +120,7 @@
         VALIDATORS.put(System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
         VALIDATORS.put(System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
         VALIDATORS.put(System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
+        VALIDATORS.put(System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
         VALIDATORS.put(System.RINGTONE, URI_VALIDATOR);
         VALIDATORS.put(System.NOTIFICATION_SOUND, URI_VALIDATOR);
         VALIDATORS.put(System.ALARM_ALERT, URI_VALIDATOR);
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 3ccf5e4..5fec4cc 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -53,8 +53,12 @@
     private val ghostedView: View,
 
     /** The [InteractionJankMonitor.CujType] associated to this animation. */
-    private val cujType: Int? = null
+    private val cujType: Int? = null,
+    private var interactionJankMonitor: InteractionJankMonitor? = null
 ) : ActivityLaunchAnimator.Controller {
+
+    constructor(view: View, type: Int) : this(view, type, null)
+
     /** The container to which we will add the ghost view and expanding background. */
     override var launchContainer = ghostedView.rootView as ViewGroup
     private val launchContainerOverlay: ViewGroupOverlay
@@ -170,7 +174,7 @@
         val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
         matrix.getValues(initialGhostViewMatrixValues)
 
-        cujType?.let { InteractionJankMonitor.getInstance().begin(ghostedView, it) }
+        cujType?.let { interactionJankMonitor?.begin(ghostedView, it) }
     }
 
     override fun onLaunchAnimationProgress(
@@ -251,7 +255,7 @@
             return
         }
 
-        cujType?.let { InteractionJankMonitor.getInstance().end(it) }
+        cujType?.let { interactionJankMonitor?.end(it) }
 
         backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
 
diff --git a/packages/SystemUI/res/color/prv_text_color_on_accent.xml b/packages/SystemUI/res/color/prv_text_color_on_accent.xml
deleted file mode 100644
index 9f44aca..0000000
--- a/packages/SystemUI/res/color/prv_text_color_on_accent.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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.
-  -->
-<selector xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-          xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="?androidprv:attr/textColorOnAccent" />
-</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
index 1a128df..14cb1de9 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
@@ -16,8 +16,8 @@
   -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-       android:insetTop="@dimen/qs_dialog_button_vertical_inset"
-       android:insetBottom="@dimen/qs_dialog_button_vertical_inset">
+       android:insetTop="@dimen/dialog_button_vertical_inset"
+       android:insetBottom="@dimen/dialog_button_vertical_inset">
     <ripple android:color="?android:attr/colorControlHighlight">
         <item android:id="@android:id/mask">
             <shape android:shape="rectangle">
@@ -29,10 +29,10 @@
             <shape android:shape="rectangle">
                 <corners android:radius="?android:attr/buttonCornerRadius"/>
                 <solid android:color="?androidprv:attr/colorAccentPrimary"/>
-                <padding android:left="@dimen/qs_dialog_button_horizontal_padding"
-                         android:top="@dimen/qs_dialog_button_vertical_padding"
-                         android:right="@dimen/qs_dialog_button_horizontal_padding"
-                         android:bottom="@dimen/qs_dialog_button_vertical_padding"/>
+                <padding android:left="@dimen/dialog_button_horizontal_padding"
+                         android:top="@dimen/dialog_button_vertical_padding"
+                         android:right="@dimen/dialog_button_horizontal_padding"
+                         android:bottom="@dimen/dialog_button_vertical_padding"/>
             </shape>
         </item>
     </ripple>
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
index 467c20f..665b456 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
@@ -16,8 +16,8 @@
   -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-       android:insetTop="@dimen/qs_dialog_button_vertical_inset"
-       android:insetBottom="@dimen/qs_dialog_button_vertical_inset">
+       android:insetTop="@dimen/dialog_button_vertical_inset"
+       android:insetBottom="@dimen/dialog_button_vertical_inset">
     <ripple android:color="?android:attr/colorControlHighlight">
         <item android:id="@android:id/mask">
             <shape android:shape="rectangle">
@@ -32,10 +32,10 @@
                 <stroke android:color="?androidprv:attr/colorAccentPrimary"
                         android:width="1dp"
                 />
-                <padding android:left="@dimen/qs_dialog_button_horizontal_padding"
-                         android:top="@dimen/qs_dialog_button_vertical_padding"
-                         android:right="@dimen/qs_dialog_button_horizontal_padding"
-                         android:bottom="@dimen/qs_dialog_button_vertical_padding"/>
+                <padding android:left="@dimen/dialog_button_horizontal_padding"
+                         android:top="@dimen/dialog_button_vertical_padding"
+                         android:right="@dimen/dialog_button_horizontal_padding"
+                         android:bottom="@dimen/dialog_button_vertical_padding"/>
             </shape>
         </item>
     </ripple>
diff --git a/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
new file mode 100644
index 0000000..a3e289a
--- /dev/null
+++ b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
@@ -0,0 +1,63 @@
+<?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.
+  -->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@*android:id/buttonPanel"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:scrollbarAlwaysDrawVerticalTrack="true"
+    android:scrollIndicators="top|bottom"
+    android:fillViewport="true"
+    android:paddingTop="@dimen/dialog_button_bar_top_padding"
+    android:paddingStart="@dimen/dialog_side_padding"
+    android:paddingEnd="@dimen/dialog_side_padding"
+    android:paddingBottom="@dimen/dialog_bottom_padding"
+    style="?android:attr/buttonBarStyle">
+    <com.android.internal.widget.ButtonBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layoutDirection="locale"
+        android:orientation="horizontal"
+        android:gravity="bottom">
+
+        <Button
+            android:id="@android:id/button3"
+            style="?android:attr/buttonBarNeutralButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <Space
+            android:id="@*android:id/spacer"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:visibility="invisible" />
+
+        <Button
+            android:id="@android:id/button2"
+            style="?android:attr/buttonBarNegativeButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <Button
+            android:id="@android:id/button1"
+            style="?android:attr/buttonBarPositiveButtonStyle"
+            android:layout_marginStart="8dp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+    </com.android.internal.widget.ButtonBarLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/alert_dialog_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
new file mode 100644
index 0000000..f280cbd
--- /dev/null
+++ b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
@@ -0,0 +1,91 @@
+<?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.
+  -->
+<com.android.internal.widget.AlertDialogLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@*android:id/parentPanel"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_horizontal|top"
+    android:orientation="vertical"
+    android:paddingTop="@dimen/dialog_top_padding"
+    >
+
+    <include layout="@layout/alert_dialog_title_systemui" />
+
+    <FrameLayout
+        android:id="@*android:id/contentPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="48dp"
+        android:paddingStart="@dimen/dialog_side_padding"
+        android:paddingEnd="@dimen/dialog_side_padding"
+        >
+
+        <ScrollView
+            android:id="@*android:id/scrollView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:clipToPadding="false">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <Space
+                    android:id="@*android:id/textSpacerNoTitle"
+                    android:visibility="gone"
+                    android:layout_width="match_parent"
+                    android:layout_height="0dp" />
+
+                <TextView
+                    android:id="@*android:id/message"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    style="@style/TextAppearance.Dialog.Body.Message" />
+
+                <Space
+                    android:id="@*android:id/textSpacerNoButtons"
+                    android:visibility="gone"
+                    android:layout_width="match_parent"
+                    android:layout_height="6dp" />
+            </LinearLayout>
+        </ScrollView>
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@*android:id/customPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="48dp"
+        android:paddingStart="@dimen/dialog_side_padding"
+        android:paddingEnd="@dimen/dialog_side_padding"
+        >
+
+        <FrameLayout
+            android:id="@*android:id/custom"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </FrameLayout>
+
+    <include
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        layout="@layout/alert_dialog_button_bar_systemui" />
+
+</com.android.internal.widget.AlertDialogLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml
new file mode 100644
index 0000000..480ba00
--- /dev/null
+++ b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml
@@ -0,0 +1,63 @@
+<?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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:id="@*android:id/topPanel"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingStart="@dimen/dialog_side_padding"
+    android:paddingEnd="@dimen/dialog_side_padding"
+>
+
+    <!-- If the client uses a customTitle, it will be added here. -->
+
+    <LinearLayout
+        android:id="@*android:id/title_template"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:gravity="center_horizontal|top">
+
+        <ImageView
+            android:id="@*android:id/icon"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:layout_marginBottom="16dp"
+            android:scaleType="fitCenter"
+            android:src="@null"
+            android:tint="?androidprv:attr/colorAccentPrimaryVariant"
+            />
+
+        <com.android.internal.widget.DialogTitle
+            android:id="@*android:id/alertTitle"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="16dp"
+            style="@style/TextAppearance.Dialog.Title" />
+    </LinearLayout>
+
+    <Space
+        android:id="@*android:id/titleDividerNoCustom"
+        android:visibility="gone"
+        android:layout_width="match_parent"
+        android:layout_height="0dp" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 86e2661..98518c2 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -156,6 +156,14 @@
                             style="@style/InternetDialog.NetworkSummary"/>
                     </LinearLayout>
 
+                    <View
+                        android:id="@+id/mobile_toggle_divider"
+                        android:layout_width="1dp"
+                        android:layout_height="28dp"
+                        android:layout_marginEnd="16dp"
+                        android:layout_gravity="center_vertical"
+                        android:background="?android:attr/textColorSecondary"/>
+
                     <FrameLayout
                         android:layout_width="@dimen/settingslib_switch_track_width"
                         android:layout_height="48dp"
@@ -367,8 +375,9 @@
                 android:id="@+id/done_layout"
                 android:layout_width="67dp"
                 android:layout_height="48dp"
+                android:layout_marginTop="8dp"
                 android:layout_marginEnd="24dp"
-                android:layout_marginBottom="40dp"
+                android:layout_marginBottom="34dp"
                 android:layout_gravity="end|center_vertical"
                 android:clickable="true"
                 android:focusable="true">
diff --git a/packages/SystemUI/res/layout/privacy_dialog.xml b/packages/SystemUI/res/layout/privacy_dialog.xml
index ee4530c..9368a6a 100644
--- a/packages/SystemUI/res/layout/privacy_dialog.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog.xml
@@ -22,10 +22,9 @@
     android:layout_height="wrap_content"
     android:layout_marginStart="@dimen/ongoing_appops_dialog_side_margins"
     android:layout_marginEnd="@dimen/ongoing_appops_dialog_side_margins"
-    android:layout_marginTop="8dp"
     android:orientation="vertical"
     android:paddingBottom="8dp"
-    android:paddingTop="12dp"
+    android:paddingTop="8dp"
     android:paddingHorizontal="@dimen/ongoing_appops_dialog_side_padding"
     android:background="@drawable/qs_dialog_bg"
 />
diff --git a/packages/SystemUI/res/layout/qs_user_detail.xml b/packages/SystemUI/res/layout/qs_user_detail.xml
index 91d3a53..1aec296 100644
--- a/packages/SystemUI/res/layout/qs_user_detail.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail.xml
@@ -22,6 +22,6 @@
         xmlns:sysui="http://schemas.android.com/apk/res-auto"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        sysui:verticalSpacing="4dp"
+        sysui:verticalSpacing="20dp"
         sysui:horizontalSpacing="4dp"
         style="@style/UserDetailView" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml
index cc6c5d3..91b11fc 100644
--- a/packages/SystemUI/res/layout/qs_user_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml
@@ -24,8 +24,6 @@
         android:layout_height="wrap_content"
         android:orientation="vertical"
         android:gravity="top|center_horizontal"
-        android:paddingTop="16dp"
-        android:minHeight="112dp"
         android:clipChildren="false"
         android:clipToPadding="false"
         android:focusable="true"
diff --git a/packages/SystemUI/res/layout/qs_user_dialog_content.xml b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
index 9495ee6..355df2c 100644
--- a/packages/SystemUI/res/layout/qs_user_dialog_content.xml
+++ b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
@@ -15,75 +15,19 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<androidx.constraintlayout.widget.ConstraintLayout
+<FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:sysui="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:padding="24dp"
-    android:layout_marginStart="16dp"
-    android:layout_marginEnd="16dp"
->
-    <TextView
-        android:id="@+id/title"
-        android:layout_height="wrap_content"
-        android:layout_width="0dp"
-        android:textAlignment="center"
-        android:text="@string/qs_user_switch_dialog_title"
-        android:textAppearance="@style/TextAppearance.QSDialog.Title"
-        android:layout_marginBottom="32dp"
-        sysui:layout_constraintTop_toTopOf="parent"
-        sysui:layout_constraintStart_toStartOf="parent"
-        sysui:layout_constraintEnd_toEndOf="parent"
-        sysui:layout_constraintBottom_toTopOf="@id/grid"
-        />
-
+    >
     <com.android.systemui.qs.PseudoGridView
-        android:id="@+id/grid"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="28dp"
-        sysui:verticalSpacing="4dp"
-        sysui:horizontalSpacing="4dp"
-        sysui:fixedChildWidth="80dp"
-        sysui:layout_constraintTop_toBottomOf="@id/title"
-        sysui:layout_constraintStart_toStartOf="parent"
-        sysui:layout_constraintEnd_toEndOf="parent"
-        sysui:layout_constraintBottom_toTopOf="@id/barrier"
-    />
-
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/barrier"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        sysui:barrierDirection="top"
-        sysui:constraint_referenced_ids="settings,done"
+            android:id="@+id/grid"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            sysui:verticalSpacing="20dp"
+            sysui:horizontalSpacing="4dp"
+            sysui:fixedChildWidth="80dp"
         />
-
-    <Button
-        android:id="@+id/settings"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:text="@string/quick_settings_more_user_settings"
-        sysui:layout_constraintTop_toBottomOf="@id/barrier"
-        sysui:layout_constraintBottom_toBottomOf="parent"
-        sysui:layout_constraintStart_toStartOf="parent"
-        sysui:layout_constraintEnd_toStartOf="@id/done"
-        sysui:layout_constraintHorizontal_chainStyle="spread_inside"
-        style="@style/Widget.QSDialog.Button.BorderButton"
-        />
-
-    <Button
-        android:id="@+id/done"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:text="@string/quick_settings_done"
-        sysui:layout_constraintTop_toBottomOf="@id/barrier"
-        sysui:layout_constraintBottom_toBottomOf="parent"
-        sysui:layout_constraintStart_toEndOf="@id/settings"
-        sysui:layout_constraintEnd_toEndOf="parent"
-        style="@style/Widget.QSDialog.Button"
-        />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+</FrameLayout>
\ 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 07e28b6..cb963e6 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -16,9 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog">
-        <item name="android:buttonCornerRadius">28dp</item>
-    </style>
+    <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Dialog"/>
 
     <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Dialog.Alert" />
 
@@ -53,13 +51,17 @@
     <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:textColor">?android:attr/textColorPrimaryInverse</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>
+        <item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
+    </style>
+
+    <style name="InternetDialog.Divider.Active">
+        <item name="android:background">?android:attr/textColorSecondaryInverse</item>
     </style>
 
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 56464e4..1bf2cd8 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -597,6 +597,17 @@
         280
     </integer>
 
+    <!-- Haptic feedback intensity for ticks used for the udfps dwell time -->
+    <item name="config_udfpsTickIntensity" translatable="false" format="float"
+          type="dimen">.5</item>
+
+    <!-- Haptic feedback delay between ticks used for udfps dwell time -->
+    <integer name="config_udfpsTickDelay" translatable="false">25</integer>
+
+    <!-- Haptic feedback tick type - if true, uses VibrationEffect.Composition.PRIMITIVE_LOW_TICK
+         else uses VibrationEffect.Composition.PRIMITIVE_TICK -->
+    <bool name="config_udfpsUseLowTick">true</bool>
+
     <!-- package name of a built-in camera app to use to restrict implicit intent resolution
          when the double-press power gesture is used. Ignored if empty. -->
     <string translatable="false" name="config_cameraGesturePackage"></string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 059aad7..aee1f43 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1276,10 +1276,19 @@
     <dimen name="qs_tile_service_request_tile_width">192dp</dimen>
     <dimen name="qs_tile_service_request_content_space">24dp</dimen>
 
-    <dimen name="qs_dialog_button_horizontal_padding">16dp</dimen>
-    <dimen name="qs_dialog_button_vertical_padding">8dp</dimen>
+    <!-- Dimensions for unified SystemUI dialogs styling. Used by Theme.SystemUI.Dialog and
+         alert_dialog_systemui.xml
+      -->
+    <dimen name="dialog_button_horizontal_padding">16dp</dimen>
+    <dimen name="dialog_button_vertical_padding">8dp</dimen>
     <!-- The button will be 48dp tall, but the background needs to be 36dp tall -->
-    <dimen name="qs_dialog_button_vertical_inset">6dp</dimen>
+    <dimen name="dialog_button_vertical_inset">6dp</dimen>
+    <dimen name="dialog_top_padding">24dp</dimen>
+    <dimen name="dialog_bottom_padding">18dp</dimen>
+    <dimen name="dialog_side_padding">24dp</dimen>
+    <dimen name="dialog_button_bar_top_padding">32dp</dimen>
+
+    <!-- ************************************************************************* -->
 
     <dimen name="keyguard_unfold_translation_x">16dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index d972b7fc..b6cdd1b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -14,8 +14,8 @@
      limitations under the License.
 -->
 
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <!-- NOTE: Adding the androidprv: namespace to this file will break the studio build. -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
 
     <!-- HybridNotification themes and styles -->
 
@@ -351,11 +351,19 @@
         <item name="android:windowIsFloating">true</item>
     </style>
 
-    <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog">
+    <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/>
+
+    <style name="Theme.SystemUI.Dialog" parent="@style/Theme.SystemUI.DayNightDialog">
         <item name="android:buttonCornerRadius">28dp</item>
-        <item name="android:buttonBarPositiveButtonStyle">@style/Widget.QSDialog.Button</item>
-        <item name="android:buttonBarNegativeButtonStyle">@style/Widget.QSDialog.Button.BorderButton</item>
-        <item name="android:buttonBarNeutralButtonStyle">@style/Widget.QSDialog.Button.BorderButton</item>
+        <item name="android:buttonBarPositiveButtonStyle">@style/Widget.Dialog.Button</item>
+        <item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
+        <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
+        <item name="android:colorBackground">?androidprv:attr/colorSurface</item>
+        <item name="android:alertDialogStyle">@style/AlertDialogStyle</item>
+    </style>
+
+    <style name="AlertDialogStyle" parent="@androidprv:style/AlertDialog.DeviceDefault">
+        <item name="android:layout">@layout/alert_dialog_systemui</item>
     </style>
 
     <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
@@ -863,24 +871,37 @@
         <item name="actionDividerHeight">32dp</item>
     </style>
 
-    <style name="TextAppearance.QSDialog.Title" parent="Theme.SystemUI.Dialog">
+    <style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.DeviceDefault.Large">
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textSize">24sp</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:lineHeight">32sp</item>
+        <item name="android:gravity">center</item>
+        <item name="android:textAlignment">center</item>
     </style>
 
-    <style name="Widget.QSDialog.Button" parent = "Theme.SystemUI.Dialog">
+    <style name="TextAppearance.Dialog.Body" parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:lineHeight">20sp</item>
+    </style>
+
+    <style name="TextAppearance.Dialog.Body.Message">
+        <item name="android:gravity">center</item>
+        <item name="android:textAlignment">center</item>
+    </style>
+
+    <style name="Widget.Dialog.Button" parent = "Theme.SystemUI.Dialog">
         <item name="android:background">@drawable/qs_dialog_btn_filled</item>
-        <item name="android:textColor">@color/prv_text_color_on_accent</item>
+        <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
         <item name="android:textSize">14sp</item>
         <item name="android:lineHeight">20sp</item>
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
         <item name="android:stateListAnimator">@null</item>
-        <item name="android:layout_marginHorizontal">4dp</item>
     </style>
 
-    <style name="Widget.QSDialog.Button.BorderButton">
+    <style name="Widget.Dialog.Button.BorderButton">
         <item name="android:background">@drawable/qs_dialog_btn_outline</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
@@ -957,4 +978,10 @@
 
     <style name="TextAppearance.InternetDialog.Secondary.Active"/>
 
+    <style name="InternetDialog.Divider">
+        <item name="android:background">?android:attr/textColorSecondary</item>
+    </style>
+
+    <style name="InternetDialog.Divider.Active"/>
+
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 8bd0f91..0149751 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -95,4 +95,9 @@
      * Sent when screen turned on and ready to use (blocker scrim is hidden)
      */
     void onScreenTurnedOn() = 21;
+
+    /**
+     * Sent when the desired dark intensity of the nav buttons has changed
+     */
+    void onNavButtonsDarkIntensityChanged(float darkIntensity) = 22;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 3128ffd..675dc9b5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -28,6 +28,7 @@
 import android.os.Parcelable;
 import android.view.ViewDebug;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import java.io.PrintWriter;
@@ -50,6 +51,7 @@
         @ViewDebug.ExportedProperty(category="recents")
         public int windowingMode;
         @ViewDebug.ExportedProperty(category="recents")
+        @NonNull
         public final Intent baseIntent;
         @ViewDebug.ExportedProperty(category="recents")
         public final int userId;
@@ -83,7 +85,7 @@
             updateHashCode();
         }
 
-        public TaskKey(int id, int windowingMode, Intent intent,
+        public TaskKey(int id, int windowingMode, @NonNull Intent intent,
                 ComponentName sourceComponent, int userId, long lastActiveTime) {
             this.id = id;
             this.windowingMode = windowingMode;
@@ -95,7 +97,7 @@
             updateHashCode();
         }
 
-        public TaskKey(int id, int windowingMode, Intent intent,
+        public TaskKey(int id, int windowingMode, @NonNull Intent intent,
                 ComponentName sourceComponent, int userId, long lastActiveTime, int displayId) {
             this.id = id;
             this.windowingMode = windowingMode;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index e24f07c..b56d189 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -801,13 +801,16 @@
             mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
         }
 
+        boolean lockedOutStateChanged = false;
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
+            lockedOutStateChanged |= !mFingerprintLockedOutPermanent;
             mFingerprintLockedOutPermanent = true;
             requireStrongAuthIfAllLockedOut();
         }
 
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
                 || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
+            lockedOutStateChanged |= !mFingerprintLockedOut;
             mFingerprintLockedOut = true;
             if (isUdfpsEnrolled()) {
                 updateFingerprintListeningState();
@@ -820,9 +823,14 @@
                 cb.onBiometricError(msgId, errString, BiometricSourceType.FINGERPRINT);
             }
         }
+
+        if (lockedOutStateChanged) {
+            notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+        }
     }
 
     private void handleFingerprintLockoutReset() {
+        boolean changed = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
         mFingerprintLockedOut = false;
         mFingerprintLockedOutPermanent = false;
 
@@ -837,6 +845,10 @@
         } else {
             updateFingerprintListeningState();
         }
+
+        if (changed) {
+            notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+        }
     }
 
     private void setFingerprintRunningState(int fingerprintRunningState) {
@@ -999,7 +1011,9 @@
             }
         }
 
+        boolean lockedOutStateChanged = false;
         if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
+            lockedOutStateChanged = !mFaceLockedOutPermanent;
             mFaceLockedOutPermanent = true;
             requireStrongAuthIfAllLockedOut();
         }
@@ -1011,11 +1025,21 @@
                         BiometricSourceType.FACE);
             }
         }
+
+        if (lockedOutStateChanged) {
+            notifyLockedOutStateChanged(BiometricSourceType.FACE);
+        }
     }
 
     private void handleFaceLockoutReset() {
+        boolean changed = mFaceLockedOutPermanent;
         mFaceLockedOutPermanent = false;
+
         updateFaceListeningState();
+
+        if (changed) {
+            notifyLockedOutStateChanged(BiometricSourceType.FACE);
+        }
     }
 
     private void setFaceRunningState(int faceRunningState) {
@@ -1237,6 +1261,16 @@
         }
     }
 
+    private void notifyLockedOutStateChanged(BiometricSourceType type) {
+        Assert.isMainThread();
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onLockedOutStateChanged(type);
+            }
+        }
+    }
+
     public boolean isScreenOn() {
         return mScreenOn;
     }
@@ -2454,6 +2488,10 @@
         }
     }
 
+    public boolean isFingerprintLockedOut() {
+        return mFingerprintLockedOut || mFingerprintLockedOutPermanent;
+    }
+
     /**
      * If biometrics hardware is available, not disabled, and user has enrolled templates.
      * This does NOT check if the device is encrypted or in lockdown.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 12431984..8170a81 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -292,6 +292,11 @@
     public void onStrongAuthStateChanged(int userId) { }
 
     /**
+     * When the current user's locked out state changed.
+     */
+    public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) { }
+
+    /**
      * Called when the dream's window state is changed.
      * @param dreaming true if the dream's window has been created and is visible
      */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 0ff3dbc..3f077f5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -163,7 +163,10 @@
     private boolean mOnFingerDown;
     private boolean mAttemptedToDismissKeyguard;
     private Set<Callback> mCallbacks = new HashSet<>();
-    private final VibrationEffect mLowTick;
+
+    // by default, use low tick
+    private int mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
+    private final VibrationEffect mTick;
 
     @VisibleForTesting
     public static final VibrationAttributes VIBRATION_ATTRIBUTES =
@@ -571,7 +574,7 @@
         mConfigurationController = configurationController;
         mSystemClock = systemClock;
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
-        mLowTick = lowTick();
+        mTick = lowTick();
 
         mSensorProps = findFirstUdfps();
         // At least one UDFPS sensor exists
@@ -607,22 +610,31 @@
     }
 
     private VibrationEffect lowTick() {
+        boolean useLowTickDefault = mContext.getResources()
+                .getBoolean(R.bool.config_udfpsUseLowTick);
+        if (Settings.Global.getFloat(
+                mContext.getContentResolver(),
+                "tick-low", useLowTickDefault ? 1 : 0) == 0) {
+            mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_TICK;
+        }
         float tickIntensity = Settings.Global.getFloat(
-                mContext.getContentResolver(), "low-tick-intensity", .5f);
-        VibrationEffect.Composition composition = VibrationEffect.startComposition();
-        composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
-                tickIntensity, 0);
+                mContext.getContentResolver(),
+                "tick-intensity",
+                mContext.getResources().getFloat(R.dimen.config_udfpsTickIntensity));
         int tickDelay = Settings.Global.getInt(
-                mContext.getContentResolver(), "low-tick-delay", 25);
+                mContext.getContentResolver(),
+                "tick-delay",
+                mContext.getResources().getInteger(R.integer.config_udfpsTickDelay));
+
+        VibrationEffect.Composition composition = VibrationEffect.startComposition();
+        composition.addPrimitive(mPrimitiveTick, tickIntensity, 0);
         int primitives = 1000 / tickDelay;
         float[] rampUp = new float[]{.48f, .58f, .69f, .83f};
         for (int i = 0; i < rampUp.length; i++) {
-            composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
-                    tickIntensity * rampUp[i], tickDelay);
+            composition.addPrimitive(mPrimitiveTick, tickIntensity * rampUp[i], tickDelay);
         }
         for (int i = rampUp.length; i < primitives; i++) {
-            composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
-                    tickIntensity, tickDelay);
+            composition.addPrimitive(mPrimitiveTick, tickIntensity, tickDelay);
         }
         return composition.compose();
     }
@@ -636,7 +648,7 @@
             mVibrator.vibrate(
                     Process.myUid(),
                     mContext.getOpPackageName(),
-                    mLowTick,
+                    mTick,
                     "udfps-onStart-tick",
                     VIBRATION_ATTRIBUTES);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 223eb78..8f4d6f6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -255,7 +255,6 @@
     private void maybeShowInputBouncer() {
         if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
             mKeyguardViewManager.showBouncer(true);
-            mKeyguardViewManager.resetAlternateAuth(false);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
new file mode 100644
index 0000000..b5d81f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.concurrency.ThreadFactory
+import dagger.Module
+import dagger.Provides
+import java.util.concurrent.Executor
+import javax.inject.Qualifier
+
+/**
+ * Dagger module for all things biometric.
+ */
+@Module
+object BiometricsModule {
+
+    /** Background [Executor] for HAL related operations. */
+    @Provides
+    @SysUISingleton
+    @JvmStatic
+    @BiometricsBackground
+    fun providesPluginExecutor(threadFactory: ThreadFactory): Executor =
+        threadFactory.buildExecutorOnNewThread("biometrics")
+}
+
+/**
+ * Background executor for HAL operations that are latency sensitive but too
+ * slow to run on the main thread. Prefer the shared executors, such as
+ * [com.android.systemui.dagger.qualifiers.Background] when a HAL is not directly involved.
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class BiometricsBackground
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index a64351f..1d17fd8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -30,6 +30,7 @@
 import com.android.systemui.appops.dagger.AppOpsModule;
 import com.android.systemui.assist.AssistModule;
 import com.android.systemui.biometrics.UdfpsHbmProvider;
+import com.android.systemui.biometrics.dagger.BiometricsModule;
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.communal.dagger.CommunalModule;
 import com.android.systemui.controls.dagger.ControlsModule;
@@ -66,7 +67,6 @@
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -101,6 +101,7 @@
 @Module(includes = {
             AppOpsModule.class,
             AssistModule.class,
+            BiometricsModule.class,
             ClockModule.class,
             CommunalModule.class,
             DreamModule.class,
@@ -127,7 +128,6 @@
         },
         subcomponents = {
             StatusBarComponent.class,
-            StatusBarFragmentComponent.class,
             NotificationRowComponent.class,
             DozeComponent.class,
             ExpandableNotificationRowComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 2f7c8ba..fa3ad4e 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -2371,7 +2371,8 @@
                             message.animate()
                                     .alpha(0f)
                                     .setDuration(TOAST_FADE_TIME)
-                                    .setStartDelay(visibleTime);
+                                    .setStartDelay(visibleTime)
+                                    .setListener(null);
                         }
                     });
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e97e762..fbc9ba6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -793,7 +793,8 @@
                 return KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
             } else if (trust && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
                 return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
-            } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0) {
+            } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0
+                    || mUpdateMonitor.isFingerprintLockedOut())) {
                 return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
             } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) {
                 return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
@@ -820,6 +821,7 @@
 
     private final KeyguardStateController mKeyguardStateController;
     private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
+    private final InteractionJankMonitor mInteractionJankMonitor;
     private boolean mWallpaperSupportsAmbientMode;
 
     /**
@@ -845,7 +847,8 @@
             KeyguardStateController keyguardStateController,
             Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
-            Lazy<NotificationShadeDepthController> notificationShadeDepthController) {
+            Lazy<NotificationShadeDepthController> notificationShadeDepthController,
+            InteractionJankMonitor interactionJankMonitor) {
         super(context);
         mFalsingCollector = falsingCollector;
         mLockPatternUtils = lockPatternUtils;
@@ -882,6 +885,7 @@
         mKeyguardStateController = keyguardStateController;
         mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy;
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+        mInteractionJankMonitor = interactionJankMonitor;
     }
 
     public void userActivity() {
@@ -2245,8 +2249,7 @@
                                 onKeyguardExitFinished();
                                 mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
                                         0 /* fadeoutDuration */);
-                                InteractionJankMonitor.getInstance()
-                                        .end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
+                                mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
                             }
 
                             @Override
@@ -2255,7 +2258,7 @@
                             }
                         };
                 try {
-                    InteractionJankMonitor.getInstance().begin(
+                    mInteractionJankMonitor.begin(
                             createInteractionJankMonitorConf("RunRemoteAnimation"));
                     runner.onAnimationStart(WindowManager.TRANSIT_KEYGUARD_GOING_AWAY, apps,
                             wallpapers, nonApps, callback);
@@ -2271,14 +2274,14 @@
                 mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
                 mSurfaceBehindRemoteAnimationRunning = true;
 
-                InteractionJankMonitor.getInstance().begin(
+                mInteractionJankMonitor.begin(
                         createInteractionJankMonitorConf("DismissPanel"));
 
                 // Pass the surface and metadata to the unlock animation controller.
                 mKeyguardUnlockAnimationControllerLazy.get().notifyStartKeyguardExitAnimation(
                         apps[0], startTime, mSurfaceBehindRemoteAnimationRequested);
             } else {
-                InteractionJankMonitor.getInstance().begin(
+                mInteractionJankMonitor.begin(
                         createInteractionJankMonitorConf("RemoteAnimationDisabled"));
 
                 mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
@@ -2288,7 +2291,7 @@
                 // supported, so it's always null.
                 mContext.getMainExecutor().execute(() -> {
                     if (finishedCallback == null) {
-                        InteractionJankMonitor.getInstance().end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
+                        mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
                         return;
                     }
 
@@ -2316,8 +2319,7 @@
                             } catch (RemoteException e) {
                                 Slog.e(TAG, "RemoteException");
                             } finally {
-                                InteractionJankMonitor.getInstance()
-                                        .end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
+                                mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
                             }
                         }
 
@@ -2328,8 +2330,7 @@
                             } catch (RemoteException e) {
                                 Slog.e(TAG, "RemoteException");
                             } finally {
-                                InteractionJankMonitor.getInstance()
-                                        .cancel(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
+                                mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
                             }
                         }
                     });
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index cae9fee..8d23e9f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -22,6 +22,7 @@
 import android.content.pm.PackageManager;
 import android.os.PowerManager;
 
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardDisplayManager;
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -97,7 +98,8 @@
             KeyguardStateController keyguardStateController,
             Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
-            Lazy<NotificationShadeDepthController> notificationShadeDepthController) {
+            Lazy<NotificationShadeDepthController> notificationShadeDepthController,
+            InteractionJankMonitor interactionJankMonitor) {
         return new KeyguardViewMediator(
                 context,
                 falsingCollector,
@@ -120,7 +122,8 @@
                 keyguardStateController,
                 keyguardUnlockAnimationController,
                 unlockedScreenOffAnimationController,
-                notificationShadeDepthController
+                notificationShadeDepthController,
+                interactionJankMonitor
         );
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 47ef5e4..d54b151 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -400,7 +400,7 @@
         }
         updatePageIndicator()
         mediaCarouselScrollHandler.onPlayersChanged()
-        mediaCarousel.requiresRemeasuring = true
+        mediaFrame.requiresRemeasuring = true
         // Check postcondition: mediaContent should have the same number of children as there are
         // elements in mediaPlayers.
         if (MediaPlayerData.players().size != mediaContent.childCount) {
@@ -439,7 +439,7 @@
         updatePlayerToState(newRecs, noAnimation = true)
         reorderAllPlayers(curVisibleMediaKey)
         updatePageIndicator()
-        mediaCarousel.requiresRemeasuring = true
+        mediaFrame.requiresRemeasuring = true
         // Check postcondition: mediaContent should have the same number of children as there are
         // elements in mediaPlayers.
         if (MediaPlayerData.players().size != mediaContent.childCount) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 868193b..54e40f1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -289,6 +289,9 @@
                         public void onAnimationEnd(Animator animation) {
                             to.requireViewById(R.id.volume_indeterminate_progress).setVisibility(
                                     View.VISIBLE);
+                            // Unset the listener, otherwise this may persist for another view
+                            // property animation
+                            toTitleText.animate().setListener(null);
                         }
                     });
             // Animation for seek bar
@@ -312,8 +315,14 @@
                                         public void onAnimationEnd(Animator animation) {
                                             mIsAnimating = false;
                                             notifyDataSetChanged();
+                                            // Unset the listener, otherwise this may persist for
+                                            // another view property animation
+                                            fromTitleText.animate().setListener(null);
                                         }
                                     });
+                            // Unset the listener, otherwise this may persist for another view
+                            // property animation
+                            fromSeekBar.animate().setListener(null);
                         }
                     });
         }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 52103d3..23062d8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -72,7 +72,7 @@
     private final UserTracker mUserTracker;
     private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
     private final List<NavbarTaskbarStateUpdater> mA11yEventListeners = new ArrayList<>();
-    private Context mContext;
+    private final Context mContext;
     private ContentResolver mContentResolver;
     private boolean mAssistantAvailable;
     private boolean mLongPressHomeEnabled;
@@ -87,8 +87,13 @@
         }
     };
 
+    /**
+     * @param context This is not display specific, then again neither is any of the code in
+     *                this class. Once there's display specific code, we may want to create an
+     *                instance of this class per navbar vs having it be a singleton.
+     */
     @Inject
-    public NavBarHelper(AccessibilityManager accessibilityManager,
+    public NavBarHelper(Context context, AccessibilityManager accessibilityManager,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             AccessibilityButtonModeObserver accessibilityButtonModeObserver,
             OverviewProxyService overviewProxyService,
@@ -96,6 +101,7 @@
             NavigationModeController navigationModeController,
             UserTracker userTracker,
             DumpManager dumpManager) {
+        mContext = context;
         mAccessibilityManager = accessibilityManager;
         mAssistManagerLazy = assistManagerLazy;
         mUserTracker = userTracker;
@@ -109,8 +115,7 @@
         dumpManager.registerDumpable(this);
     }
 
-    public void init(Context context) {
-        mContext = context;
+    public void init() {
         mContentResolver = mContext.getContentResolver();
         mContentResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index e0da9a0..75ef8de 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -563,7 +563,7 @@
         mCommandQueue.addCallback(this);
         mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled();
         mContentResolver = mContext.getContentResolver();
-        mNavBarHelper.init(mContext);
+        mNavBarHelper.init();
         mAllowForceNavBarHandleOpaque = mContext.getResources().getBoolean(
                 R.bool.allow_force_nav_bar_handle_opaque);
         mForceNavBarHandleOpaque = DeviceConfig.getBoolean(
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 3398778..0429c02 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -57,6 +57,7 @@
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
+import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.FileDescriptor;
@@ -104,7 +105,8 @@
             TaskbarDelegate taskbarDelegate,
             NavigationBar.Factory navigationBarFactory,
             DumpManager dumpManager,
-            AutoHideController autoHideController) {
+            AutoHideController autoHideController,
+            LightBarController lightBarController) {
         mContext = context;
         mHandler = mainHandler;
         mNavigationBarFactory = navigationBarFactory;
@@ -116,7 +118,7 @@
         mTaskbarDelegate = taskbarDelegate;
         mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService,
                 navBarHelper, navigationModeController, sysUiFlagsContainer,
-                dumpManager, autoHideController);
+                dumpManager, autoHideController, lightBarController);
         mIsTablet = isTablet(mContext);
         dumpManager.registerDumpable(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 680cc5c..364a8ae 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -110,6 +110,7 @@
     private final int mNavColorSampleMargin;
     private final SysUiState mSysUiFlagContainer;
 
+    // The current view is one of mHorizontal or mVertical depending on the current configuration
     View mCurrentView = null;
     private View mVertical;
     private View mHorizontal;
@@ -397,12 +398,6 @@
         }
     }
 
-    @Override
-    protected boolean onSetAlpha(int alpha) {
-        Log.e(TAG, "onSetAlpha", new Throwable());
-        return super.onSetAlpha(alpha);
-    }
-
     public void setAutoHideController(AutoHideController autoHideController) {
         mAutoHideController = autoHideController;
     }
@@ -505,6 +500,18 @@
         return mCurrentView;
     }
 
+    /**
+     * Applies {@param consumer} to each of the nav bar views.
+     */
+    public void forEachView(Consumer<View> consumer) {
+        if (mVertical != null) {
+            consumer.accept(mVertical);
+        }
+        if (mHorizontal != null) {
+            consumer.accept(mHorizontal);
+        }
+    }
+
     public RotationButtonController getRotationButtonController() {
         return mRotationButtonController;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 428d9d6..fb9b8eb 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -64,6 +64,9 @@
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.BarTransitions;
+import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.LightBarTransitionsController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -85,6 +88,8 @@
     private NavigationModeController mNavigationModeController;
     private SysUiState mSysUiState;
     private AutoHideController mAutoHideController;
+    private LightBarController mLightBarController;
+    private LightBarTransitionsController mLightBarTransitionsController;
     private int mDisplayId;
     private int mNavigationIconHints;
     private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater =
@@ -141,7 +146,8 @@
             NavBarHelper navBarHelper,
             NavigationModeController navigationModeController,
             SysUiState sysUiState, DumpManager dumpManager,
-            AutoHideController autoHideController) {
+            AutoHideController autoHideController,
+            LightBarController lightBarController) {
         // TODO: adding this in the ctor results in a dagger dependency cycle :(
         mCommandQueue = commandQueue;
         mOverviewProxyService = overviewProxyService;
@@ -150,6 +156,30 @@
         mSysUiState = sysUiState;
         dumpManager.registerDumpable(this);
         mAutoHideController = autoHideController;
+        mLightBarController = lightBarController;
+        mLightBarTransitionsController = createLightBarTransitionsController();
+    }
+
+    // Separated into a method to keep setDependencies() clean/readable.
+    private LightBarTransitionsController createLightBarTransitionsController() {
+        return new LightBarTransitionsController(mContext,
+                new LightBarTransitionsController.DarkIntensityApplier() {
+                    @Override
+                    public void applyDarkIntensity(float darkIntensity) {
+                        mOverviewProxyService.onNavButtonsDarkIntensityChanged(darkIntensity);
+                    }
+
+                    @Override
+                    public int getTintAnimationDuration() {
+                        return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION;
+                    }
+                }, mCommandQueue) {
+            @Override
+            public boolean supportsIconTintForNavMode(int navigationMode) {
+                // Always tint taskbar nav buttons (region sampling handles gesture bar separately).
+                return true;
+            }
+        };
     }
 
     public void init(int displayId) {
@@ -162,7 +192,7 @@
         mEdgeBackGestureHandler.onNavigationModeChanged(
                 mNavigationModeController.addListener(this));
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
-        mNavBarHelper.init(mContext);
+        mNavBarHelper.init();
         mEdgeBackGestureHandler.onNavBarAttached();
         // Initialize component callback
         Display display = mDisplayManager.getDisplay(displayId);
@@ -171,6 +201,7 @@
         // Set initial state for any listeners
         updateSysuiFlags();
         mAutoHideController.setNavigationBar(mAutoHideUiElement);
+        mLightBarController.setNavigationBar(mLightBarTransitionsController);
         mInitialized = true;
     }
 
@@ -189,6 +220,8 @@
             mWindowContext = null;
         }
         mAutoHideController.setNavigationBar(null);
+        mLightBarTransitionsController.destroy(mContext);
+        mLightBarController.setNavigationBar(null);
         mInitialized = false;
     }
 
@@ -268,6 +301,10 @@
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior,
             InsetsVisibilities requestedVisibilities, String packageName) {
         mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior);
+        if (mLightBarController != null && displayId == mDisplayId) {
+            mLightBarController.onNavigationBarAppearanceChanged(appearance, false/*nbModeChanged*/,
+                    BarTransitions.MODE_TRANSPARENT /*navigationBarMode*/, navbarColorManagedByIme);
+        }
         if (mBehavior != behavior) {
             mBehavior = behavior;
             updateSysuiFlags();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 98b9146..fce0c0c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -17,8 +17,10 @@
 package com.android.systemui.qs
 
 import android.content.Intent
+import android.os.Handler
 import android.os.UserManager
 import android.provider.Settings
+import android.provider.Settings.Global.USER_SWITCHER_ENABLED
 import android.view.View
 import android.widget.Toast
 import androidx.annotation.VisibleForTesting
@@ -35,6 +37,7 @@
 import com.android.systemui.qs.FooterActionsController.ExpansionState.COLLAPSED
 import com.android.systemui.qs.FooterActionsController.ExpansionState.EXPANDED
 import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.MultiUserSwitchController
 import com.android.systemui.statusbar.phone.SettingsButton
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -42,6 +45,7 @@
 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.ViewController
+import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 import javax.inject.Named
 
@@ -55,6 +59,7 @@
     private val qsPanelController: QSPanelController,
     private val activityStarter: ActivityStarter,
     private val userManager: UserManager,
+    private val userTracker: UserTracker,
     private val userInfoController: UserInfoController,
     private val multiUserSwitchController: MultiUserSwitchController,
     private val deviceProvisionedController: DeviceProvisionedController,
@@ -64,7 +69,9 @@
     private val globalActionsDialog: GlobalActionsDialogLite,
     private val uiEventLogger: UiEventLogger,
     @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
-    private val buttonsVisibleState: ExpansionState
+    private val buttonsVisibleState: ExpansionState,
+    private val globalSetting: GlobalSettings,
+    private val handler: Handler
 ) : ViewController<FooterActionsView>(view) {
 
     enum class ExpansionState { COLLAPSED, EXPANDED }
@@ -83,6 +90,16 @@
         mView.onUserInfoChanged(picture, isGuestUser)
     }
 
+    private val multiUserSetting =
+            object : SettingObserver(
+                    globalSetting, handler, USER_SWITCHER_ENABLED, userTracker.userId) {
+                override fun handleValueChanged(value: Int, observedChange: Boolean) {
+                    if (observedChange) {
+                        updateView()
+                    }
+                }
+            }
+
     private val onClickListener = View.OnClickListener { v ->
         // Don't do anything until views are unhidden. Don't do anything if the tap looks
         // suspicious.
@@ -182,6 +199,7 @@
             return
         }
         this.listening = listening
+        multiUserSetting.isListening = listening
         if (this.listening) {
             userInfoController.addCallback(onUserInfoChangedListener)
             updateView()
@@ -215,4 +233,4 @@
     }
 
     private fun isTunerEnabled() = tunerService.isTunerEnabled
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
index f6c89a9..dd4dc87 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
@@ -16,18 +16,22 @@
 
 package com.android.systemui.qs
 
+import android.os.Handler
 import android.os.UserManager
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.globalactions.GlobalActionsDialogLite
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.FooterActionsController.ExpansionState
 import com.android.systemui.qs.dagger.QSFlagsModule
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.MultiUserSwitchController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 import javax.inject.Named
 
@@ -35,6 +39,7 @@
     private val qsPanelController: QSPanelController,
     private val activityStarter: ActivityStarter,
     private val userManager: UserManager,
+    private val userTracker: UserTracker,
     private val userInfoController: UserInfoController,
     private val multiUserSwitchControllerFactory: MultiUserSwitchController.Factory,
     private val deviceProvisionedController: DeviceProvisionedController,
@@ -43,7 +48,9 @@
     private val tunerService: TunerService,
     private val globalActionsDialog: GlobalActionsDialogLite,
     private val uiEventLogger: UiEventLogger,
-    @Named(QSFlagsModule.PM_LITE_ENABLED) private val showPMLiteButton: Boolean
+    @Named(QSFlagsModule.PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
+    private val globalSettings: GlobalSettings,
+    @Main private val handler: Handler
 ) {
     private lateinit var view: FooterActionsView
     private lateinit var buttonsVisibleState: ExpansionState
@@ -60,8 +67,9 @@
 
     fun build(): FooterActionsController {
         return FooterActionsController(view, qsPanelController, activityStarter, userManager,
-                userInfoController, multiUserSwitchControllerFactory.create(view),
+                userTracker, userInfoController, multiUserSwitchControllerFactory.create(view),
                 deviceProvisionedController, falsingManager, metricsLogger, tunerService,
-                globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState)
+                globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState,
+                globalSettings, handler)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
index 2f189be..768598a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
@@ -133,10 +133,7 @@
                     x += width + mHorizontalSpacing;
                 }
             }
-            y += maxHeight;
-            if (row > 0) {
-                y += mVerticalSpacing;
-            }
+            y += maxHeight + mVerticalSpacing;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index e42c47b..ee59ae6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -770,6 +770,8 @@
         public void onAnimationEnd(Animator animation) {
             mHeaderAnimating = false;
             updateQsState();
+            // Unset the listener, otherwise this may persist for another view property animation
+            getView().animate().setListener(null);
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 99cb700..18b401f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -19,6 +19,7 @@
 import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
 
+import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.Intent;
@@ -41,7 +42,6 @@
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
 import android.view.ViewGroup;
-import android.view.WindowManager;
 import android.widget.Switch;
 import android.widget.Toast;
 
@@ -53,6 +53,7 @@
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.SysUIToast;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -84,6 +85,7 @@
     private final DndDetailAdapter mDetailAdapter;
     private final SharedPreferences mSharedPreferences;
     private final SettingObserver mSettingZenDuration;
+    private final DialogLaunchAnimator mDialogLaunchAnimator;
 
     private boolean mListening;
     private boolean mShowingDetail;
@@ -100,7 +102,8 @@
             QSLogger qsLogger,
             ZenModeController zenModeController,
             @Main SharedPreferences sharedPreferences,
-            SecureSettings secureSettings
+            SecureSettings secureSettings,
+            DialogLaunchAnimator dialogLaunchAnimator
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
@@ -108,6 +111,7 @@
         mSharedPreferences = sharedPreferences;
         mDetailAdapter = new DndDetailAdapter();
         mController.observe(getLifecycle(), mZenCallback);
+        mDialogLaunchAnimator = dialogLaunchAnimator;
         mSettingZenDuration = new SettingObserver(secureSettings, mUiHandler,
                 Settings.Secure.ZEN_DURATION, getHost().getUserId()) {
             @Override
@@ -117,8 +121,6 @@
         };
     }
 
-
-
     public static void setVisible(Context context, boolean visible) {
         Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible);
     }
@@ -187,14 +189,17 @@
             switch (zenDuration) {
                 case Settings.Secure.ZEN_DURATION_PROMPT:
                     mUiHandler.post(() -> {
-                        Dialog mDialog = new EnableZenModeDialog(mContext).createDialog();
-                        mDialog.getWindow().setType(
-                                WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
-                        SystemUIDialog.setShowForAllUsers(mDialog, true);
-                        SystemUIDialog.registerDismissListener(mDialog);
-                        SystemUIDialog.setWindowOnTop(mDialog);
-                        mUiHandler.post(() -> mDialog.show());
-                        mHost.collapsePanels();
+                        Dialog dialog = makeZenModeDialog();
+                        if (view != null) {
+                            final Dialog hostDialog =
+                                    mDialogLaunchAnimator.showFromView(dialog, view, false);
+                            setDialogListeners(dialog, hostDialog);
+                        } else {
+                            // If we are not launching with animator, register default
+                            // dismiss listener
+                            SystemUIDialog.registerDismissListener(dialog);
+                            dialog.show();
+                        }
                     });
                     break;
                 case Settings.Secure.ZEN_DURATION_FOREVER:
@@ -209,6 +214,20 @@
         }
     }
 
+    private Dialog makeZenModeDialog() {
+        AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog)
+                .createDialog();
+        SystemUIDialog.applyFlags(dialog);
+        SystemUIDialog.setShowForAllUsers(dialog, true);
+        return dialog;
+    }
+
+    private void setDialogListeners(Dialog zenModeDialog, Dialog hostDialog) {
+        // Zen mode dialog is never hidden.
+        SystemUIDialog.registerDismissListener(zenModeDialog, hostDialog::dismiss);
+        zenModeDialog.setOnCancelListener(dialog -> hostDialog.cancel());
+    }
+
     @Override
     protected void handleSecondaryClick(@Nullable View view) {
         if (mController.isVolumeRestricted()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
index 521dbe71..7e17124 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
@@ -105,8 +105,7 @@
 
     @Override
     public Intent getLongClickIntent() {
-        // TODO(b/201743873) define new intent action ACTION_ONE_HANDED_SETTINGS in Settings.
-        return null;
+        return new Intent(Settings.ACTION_ONE_HANDED_SETTINGS);
     }
 
     @Override
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 77b9cc1..883552a 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
@@ -56,6 +56,7 @@
 
 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.accessibility.floatingmenu.AnnotationLinkSpan;
@@ -120,6 +121,7 @@
     private TextView mMobileTitleText;
     private TextView mMobileSummaryText;
     private Switch mMobileDataToggle;
+    private View mMobileToggleDivider;
     private Switch mWiFiToggle;
     private FrameLayout mDoneLayout;
     private Drawable mBackgroundOn;
@@ -207,6 +209,7 @@
         mSignalIcon = mDialogView.requireViewById(R.id.signal_icon);
         mMobileTitleText = mDialogView.requireViewById(R.id.mobile_title);
         mMobileSummaryText = mDialogView.requireViewById(R.id.mobile_summary);
+        mMobileToggleDivider = mDialogView.requireViewById(R.id.mobile_toggle_divider);
         mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle);
         mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle);
         mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on);
@@ -378,6 +381,14 @@
             mMobileNetworkLayout.setBackground(
                     isCarrierNetworkConnected ? mBackgroundOn : mBackgroundOff);
 
+            TypedArray array = mContext.obtainStyledAttributes(
+                    R.style.InternetDialog_Divider_Active, new int[]{android.R.attr.background});
+            int dividerColor = Utils.getColorAttrDefaultColor(mContext,
+                    android.R.attr.textColorSecondary);
+            mMobileToggleDivider.setBackgroundColor(isCarrierNetworkConnected
+                    ? array.getColor(0, dividerColor) : dividerColor);
+            array.recycle();
+
             mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
deleted file mode 100644
index 26d1bbd..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
+++ /dev/null
@@ -1,82 +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.qs.user
-
-import android.content.Context
-import android.os.Bundle
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowInsets
-import android.view.WindowManager
-import com.android.systemui.qs.PseudoGridView
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.R
-
-/**
- * Dialog for switching users or creating new ones.
- */
-class UserDialog(
-    context: Context
-) : SystemUIDialog(context) {
-
-    // create() is no-op after creation
-    private lateinit var _doneButton: View
-    /**
-     * Button with text "Done" in dialog.
-     */
-    val doneButton: View
-        get() {
-            create()
-            return _doneButton
-        }
-
-    private lateinit var _settingsButton: View
-    /**
-     * Button with text "User Settings" in dialog.
-     */
-    val settingsButton: View
-        get() {
-            create()
-            return _settingsButton
-        }
-
-    private lateinit var _grid: PseudoGridView
-    /**
-     * Grid to populate with user avatar from adapter
-     */
-    val grid: ViewGroup
-        get() {
-            create()
-            return _grid
-        }
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        window?.apply {
-            setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
-            attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
-            attributes.receiveInsetsIgnoringZOrder = true
-            setGravity(Gravity.CENTER)
-        }
-        setContentView(R.layout.qs_user_dialog_content)
-
-        _doneButton = requireViewById(R.id.done)
-        _settingsButton = requireViewById(R.id.settings)
-        _grid = requireViewById(R.id.grid)
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index d74a50e..00e0454 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -21,13 +21,16 @@
 import android.content.DialogInterface
 import android.content.Intent
 import android.provider.Settings
+import android.view.LayoutInflater
 import android.view.View
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.R
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.statusbar.phone.SystemUIDialog
 import javax.inject.Inject
 import javax.inject.Provider
 
@@ -40,7 +43,7 @@
     private val activityStarter: ActivityStarter,
     private val falsingManager: FalsingManager,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
-    private val dialogFactory: (Context) -> UserDialog
+    private val dialogFactory: (Context) -> SystemUIDialog
 ) {
 
     @Inject
@@ -54,7 +57,7 @@
         activityStarter,
         falsingManager,
         dialogLaunchAnimator,
-        { UserDialog(it) }
+        { SystemUIDialog(it) }
     )
 
     companion object {
@@ -71,9 +74,10 @@
         with(dialogFactory(view.context)) {
             setShowForAllUsers(true)
             setCanceledOnTouchOutside(true)
-            create() // Needs to be called before we can retrieve views
 
-            settingsButton.setOnClickListener {
+            setTitle(R.string.qs_user_switch_dialog_title)
+            setPositiveButton(R.string.quick_settings_done, null)
+            setNeutralButton(R.string.quick_settings_more_user_settings) { _, _ ->
                 if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
                     dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
                     activityStarter.postStartActivityDismissingKeyguard(
@@ -81,12 +85,14 @@
                         0
                     )
                 }
-                dismiss()
             }
-            doneButton.setOnClickListener { dismiss() }
+            val gridFrame = LayoutInflater.from(this.context)
+                .inflate(R.layout.qs_user_dialog_content, null)
+            setView(gridFrame)
 
             val adapter = userDetailViewAdapterProvider.get()
-            adapter.linkToViewGroup(grid)
+
+            adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
 
             val hostDialog = dialogLaunchAnimator.showFromView(this, view)
             adapter.injectDialogShower(DialogShowerImpl(hostDialog, dialogLaunchAnimator))
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index fa874b1..3ed7e84 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -986,6 +986,18 @@
         }
     }
 
+    public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
+        try {
+            if (mOverviewProxy != null) {
+                mOverviewProxy.onNavButtonsDarkIntensityChanged(darkIntensity);
+            } else {
+                Log.e(TAG_OPS, "Failed to get overview proxy to update nav buttons dark intensity");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG_OPS, "Failed to call onNavButtonsDarkIntensityChanged()", e);
+        }
+    }
+
     private void updateEnabledState() {
         final int currentUser = ActivityManagerWrapper.getInstance().getCurrentUserId();
         mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 75b3592..3cecbb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -533,9 +533,13 @@
      * @param animate {@code true} to show animations.
      */
     public void recomputeDisableFlags(int displayId, boolean animate) {
-        int disabled1 = getDisabled1(displayId);
-        int disabled2 = getDisabled2(displayId);
-        disable(displayId, disabled1, disabled2, animate);
+        // This must update holding the lock otherwise it can clobber the disabled flags set on the
+        // binder thread from the disable() call
+        synchronized (mLock) {
+            int disabled1 = getDisabled1(displayId);
+            int disabled2 = getDisabled2(displayId);
+            disable(displayId, disabled1, disabled2, animate);
+        }
     }
 
     private void setDisabled(int displayId, int disabled1, int disabled2) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 190c773e..f23a7ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -717,6 +717,9 @@
                                         textView.setTranslationY(BOUNCE_ANIMATION_FINAL_Y);
                                         ViewClippingUtil.setClippingDeactivated(textView, false,
                                                 mClippingParams);
+                                        // Unset the listener, otherwise this may persist for
+                                        // another view property animation
+                                        textView.animate().setListener(null);
                                     }
                                 });
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index ecde001..83ef41e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.Handler;
@@ -39,6 +41,8 @@
 import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
+import com.android.systemui.statusbar.notification.collection.render.NotifStats;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -97,6 +101,7 @@
     private final Context mContext;
 
     private NotificationPresenter mPresenter;
+    private NotifStackController mStackController;
     private NotificationListContainer mListContainer;
 
     // Used to help track down re-entrant calls to our update methods, which will cause bugs.
@@ -147,8 +152,10 @@
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
+            NotifStackController stackController,
             NotificationListContainer listContainer) {
         mPresenter = presenter;
+        mStackController = stackController;
         mListContainer = listContainer;
         if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
             mDynamicPrivacyController.addListener(this);
@@ -328,6 +335,7 @@
         mTmpChildOrderMap.clear();
 
         updateRowStatesInternal();
+        updateNotifStats();
 
         mListContainer.onNotificationViewUpdateFinished();
 
@@ -335,6 +343,55 @@
     }
 
     /**
+     * In the spirit of unidirectional data flow, calculate this information when the notification
+     * views are updated, and set it once, speeding up lookups later.
+     * This is analogous to logic in the
+     * {@link com.android.systemui.statusbar.notification.collection.coordinator.StackCoordinator}
+     */
+    private void updateNotifStats() {
+        boolean hasNonClearableAlertingNotifs = false;
+        boolean hasClearableAlertingNotifs = false;
+        boolean hasNonClearableSilentNotifs = false;
+        boolean hasClearableSilentNotifs = false;
+        final int childCount = mListContainer.getContainerChildCount();
+        int visibleTopLevelEntries = 0;
+        for (int i = 0; i < childCount; i++) {
+            View child = mListContainer.getContainerChildAt(i);
+            if (child == null || child.getVisibility() == View.GONE) {
+                continue;
+            }
+            if (!(child instanceof ExpandableNotificationRow)) {
+                continue;
+            }
+            final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            boolean isSilent = row.getEntry().getBucket() == BUCKET_SILENT;
+            // NOTE: NotificationEntry.isClearable() will internally check group children to ensure
+            //  the group itself definitively clearable.
+            boolean isClearable = row.getEntry().isClearable();
+            if (isSilent) {
+                if (isClearable) {
+                    hasClearableSilentNotifs = true;
+                } else {  // !isClearable
+                    hasNonClearableSilentNotifs = true;
+                }
+            } else {  // !isSilent
+                if (isClearable) {
+                    hasClearableAlertingNotifs = true;
+                } else {  // !isClearable
+                    hasNonClearableAlertingNotifs = true;
+                }
+            }
+        }
+        mStackController.setNotifStats(new NotifStats(
+                visibleTopLevelEntries /* numActiveNotifs */,
+                hasNonClearableAlertingNotifs /* hasNonClearableAlertingNotifs */,
+                hasClearableAlertingNotifs /* hasClearableAlertingNotifs */,
+                hasNonClearableSilentNotifs /* hasNonClearableSilentNotifs */,
+                hasClearableSilentNotifs /* hasClearableSilentNotifs */
+        ));
+    }
+
+    /**
      * Should a notification entry from the active list be suppressed and not show?
      */
     private boolean shouldSuppressActiveNotification(NotificationEntry ent) {
@@ -528,9 +585,7 @@
 
     @Override
     public void onDynamicPrivacyChanged() {
-        if (mNotifPipelineFlags.isNewPipelineEnabled()) {
-            throw new IllegalStateException("Old pipeline code running w/ new pipeline enabled");
-        }
+        mNotifPipelineFlags.assertLegacyPipelineEnabled();
         if (mPerformingUpdate) {
             Log.w(TAG, "onDynamicPrivacyChanged made a re-entrant call");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 0fb9fc8..9bf21d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -24,11 +24,11 @@
 import javax.inject.Inject
 
 class NotifPipelineFlags @Inject constructor(
-        val context: Context,
-        val featureFlags: FeatureFlags
+    val context: Context,
+    val featureFlags: FeatureFlags
 ) {
     fun checkLegacyPipelineEnabled(): Boolean {
-        if (!featureFlags.isEnabled(Flags.NEW_NOTIFICATION_PIPELINE_RENDERING)) {
+        if (!isNewPipelineEnabled()) {
             return true
         }
         Log.d("NotifPipeline", "Old pipeline code running w/ new pipeline enabled", Exception())
@@ -36,10 +36,13 @@
         return false
     }
 
-    fun isNewPipelineEnabled(): Boolean = featureFlags.isEnabled(
-            Flags.NEW_NOTIFICATION_PIPELINE_RENDERING)
+    fun assertLegacyPipelineEnabled(): Nothing =
+        error("Old pipeline code running w/ new pipeline enabled")
+
+    fun isNewPipelineEnabled(): Boolean =
+        featureFlags.isEnabled(Flags.NEW_NOTIFICATION_PIPELINE_RENDERING)
 
     fun isSmartspaceDedupingEnabled(): Boolean =
-            featureFlags.isEnabled(Flags.SMARTSPACE)
-                    && featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING)
+            featureFlags.isEnabled(Flags.SMARTSPACE) &&
+                    featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING)
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index e78b4f4..0389a7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -791,6 +791,7 @@
      * these don't exist, although there are a couple exceptions.
      */
     public Iterable<NotificationEntry> getPendingNotificationsIterator() {
+        mNotifPipelineFlags.checkLegacyPipelineEnabled();
         return mPendingNotifications.values();
     }
 
@@ -803,6 +804,7 @@
      * @return a {@link NotificationEntry} if it has been prepared, else null
      */
     public NotificationEntry getActiveNotificationUnfiltered(String key) {
+        mNotifPipelineFlags.checkLegacyPipelineEnabled();
         return mActiveNotifications.get(key);
     }
 
@@ -811,6 +813,7 @@
      * notification doesn't exist.
      */
     public NotificationEntry getPendingOrActiveNotif(String key) {
+        mNotifPipelineFlags.checkLegacyPipelineEnabled();
         NotificationEntry entry = mPendingNotifications.get(key);
         if (entry != null) {
             return entry;
@@ -945,6 +948,7 @@
      * @return A read-only list of the currently active notifications
      */
     public List<NotificationEntry> getVisibleNotifications() {
+        mNotifPipelineFlags.checkLegacyPipelineEnabled();
         return mReadOnlyNotifications;
     }
 
@@ -954,17 +958,20 @@
      */
     @Override
     public Collection<NotificationEntry> getAllNotifs() {
+        mNotifPipelineFlags.checkLegacyPipelineEnabled();
         return mReadOnlyAllNotifications;
     }
 
     @Nullable
     @Override
     public NotificationEntry getEntry(String key) {
+        mNotifPipelineFlags.checkLegacyPipelineEnabled();
         return getPendingOrActiveNotif(key);
     }
 
     /** @return A count of the active notifications */
     public int getActiveNotificationsCount() {
+        mNotifPipelineFlags.checkLegacyPipelineEnabled();
         return mReadOnlyNotifications.size();
     }
 
@@ -972,6 +979,7 @@
      * @return {@code true} if there is at least one notification that should be visible right now
      */
     public boolean hasActiveNotifications() {
+        mNotifPipelineFlags.checkLegacyPipelineEnabled();
         return mReadOnlyNotifications.size() != 0;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFadeAware.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFadeAware.java
new file mode 100644
index 0000000..8d2e3c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFadeAware.java
@@ -0,0 +1,70 @@
+/*
+ * 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.notification;
+
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Used to let views that have an alpha not apply the HARDWARE layer type directly, and instead
+ * delegate that to specific children.  This is useful if we want to fake not having overlapping
+ * rendering to avoid layer trashing, when fading out a view that is also changing.
+ */
+public interface NotificationFadeAware {
+    /**
+     * Calls {@link View#setLayerType} with {@link View#LAYER_TYPE_HARDWARE} if faded and
+     * {@link View#LAYER_TYPE_NONE} otherwise.
+     */
+    static void setLayerTypeForFaded(@Nullable View view, boolean faded) {
+        if (view != null) {
+            int newLayerType = faded ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE;
+            view.setLayerType(newLayerType, null);
+        }
+    }
+
+    /**
+     * Used like {@link View#setLayerType} with {@link View#LAYER_TYPE_HARDWARE} or
+     * {@link View#LAYER_TYPE_NONE} except that instead of necessarily affecting this view
+     * specifically, this may delegate the call to child views.
+     *
+     * When set to <code>true</code>, the view has two possible paths:
+     *  1. If a hardware layer is required to ensure correct appearance of this view, then
+     *    set that layer type.
+     *  2. Otherwise, delegate this call to children, who might make that call for themselves.
+     *
+     * When set to <code>false</code>, the view should undo the above, typically by calling
+     *  {@link View#setLayerType} with {@link View#LAYER_TYPE_NONE} on itself and children, and
+     *  delegating to this method on children where implemented.
+     *
+     * When this delegates to {@link View#setLayerType} on this view or a subview, `null` will be
+     * passed for the `paint` argument of that call.
+     */
+    void setNotificationFaded(boolean faded);
+
+    /**
+     * Interface for the top level notification view that fades and optimizes that through deep
+     * awareness of individual components.
+     */
+    interface FadeOptimizedNotification extends NotificationFadeAware {
+        /** Top-level feature switch */
+        boolean FADE_LAYER_OPTIMIZATION_ENABLED = true;
+
+        /** Determine if the notification is currently faded. */
+        boolean isNotificationFaded();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionClassifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionClassifier.kt
new file mode 100644
index 0000000..1f2d0fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionClassifier.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.notification
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import javax.inject.Inject
+
+/**
+ * A class which is used to classify the sections.
+ * NOTE: This class exists to avoid putting metadata like "isMinimized" on the NotifSection
+ */
+@SysUISingleton
+class SectionClassifier @Inject constructor() {
+    private lateinit var lowPrioritySections: Set<NotifSectioner>
+
+    /**
+     * Feed the provider the information it needs about which sections should have minimized top
+     * level views, so that it can calculate the correct minimized state.
+     */
+    fun setMinimizedSections(sections: Collection<NotifSectioner>) {
+        lowPrioritySections = sections.toSet()
+    }
+
+    /**
+     * Determine if the given section is minimized
+     */
+    fun isMinimizedSection(section: NotifSection): Boolean {
+        return lowPrioritySections.contains(section.sectioner)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
index 0ea6857..918843c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
@@ -19,8 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
-import com.android.systemui.statusbar.notification.collection.coordinator.PreparationCoordinator;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -61,24 +59,6 @@
         mSummary = summary;
     }
 
-    /**
-     * @see #getUntruncatedChildCount()
-     */
-    public void setUntruncatedChildCount(int childCount) {
-        mUntruncatedChildCount = childCount;
-    }
-
-    /**
-     * Get the untruncated number of children from the data model, including those that will not
-     * have views bound. This includes children that {@link PreparationCoordinator} will filter out
-     * entirely when they are beyond the last visible child.
-     *
-     * TODO: This should move to some shared class between the model and view hierarchy
-     */
-    public int getUntruncatedChildCount() {
-        return mUntruncatedChildCount;
-    }
-
     void clearChildren() {
         mChildren.clear();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 2d6522e..f8f1279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -245,9 +245,15 @@
             DismissedByUserStats stats = entriesToDismiss.get(i).second;
 
             requireNonNull(stats);
-            if (entry != mNotificationSet.get(entry.getKey())) {
+            NotificationEntry storedEntry = mNotificationSet.get(entry.getKey());
+            if (storedEntry == null) {
+                mLogger.logNonExistentNotifDismissed(entry.getKey());
+                continue;
+            }
+            if (entry != storedEntry) {
                 throw mEulogizer.record(
-                        new IllegalStateException("Invalid entry: " + entry.getKey()));
+                        new IllegalStateException("Invalid entry: "
+                                + "different stored and dismissed entries for " + entry.getKey()));
             }
 
             if (entry.getDismissState() == DISMISSED) {
@@ -489,6 +495,37 @@
         }
     }
 
+    /**
+     * Get the group summary entry
+     * @param group
+     * @return
+     */
+    @Nullable
+    public NotificationEntry getGroupSummary(String group) {
+        return mNotificationSet
+                .values()
+                .stream()
+                .filter(it -> Objects.equals(it.getSbn().getGroup(), group))
+                .filter(it -> it.getSbn().getNotification().isGroupSummary())
+                .findFirst().orElse(null);
+    }
+
+    /**
+     * Checks if the entry is the only child in the logical group
+     * @param entry
+     * @return
+     */
+    public boolean isOnlyChildInGroup(NotificationEntry entry) {
+        String group = entry.getSbn().getGroup();
+        return mNotificationSet.get(entry.getKey()) == entry
+                && mNotificationSet
+                .values()
+                .stream()
+                .filter(it -> Objects.equals(it.getSbn().getGroup(), group))
+                .filter(it -> !it.getSbn().getNotification().isGroupSummary())
+                .count() == 1;
+    }
+
     private void applyRanking(@NonNull RankingMap rankingMap) {
         for (NotificationEntry entry : mNotificationSet.values()) {
             if (!isCanceled(entry)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index 4f3c287..4daed77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -93,7 +93,7 @@
             public void onAsyncInflationFinished(NotificationEntry entry) {
                 mNotifErrorManager.clearInflationError(entry);
                 if (callback != null) {
-                    callback.onInflationFinished(entry);
+                    callback.onInflationFinished(entry, entry.getRowController());
                 }
             }
         };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
index 9ae9fe5..6fbed9a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
@@ -16,6 +16,9 @@
 package com.android.systemui.statusbar.notification.collection
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener
@@ -31,6 +34,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
+import com.android.systemui.statusbar.notification.collection.render.RenderStageManager
 import javax.inject.Inject
 
 /**
@@ -65,11 +69,15 @@
  *  9. Finalize filters are fired on each notification ([.addFinalizeFilter])
  *  10. OnBeforeRenderListListeners are fired ([.addOnBeforeRenderListListener])
  *  11. The list is handed off to the view layer to be rendered
+ *  12. OnAfterRenderListListeners are fired ([.addOnAfterRenderListListener])
+ *  13. OnAfterRenderGroupListeners are fired ([.addOnAfterRenderGroupListener])
+ *  13. OnAfterRenderEntryListeners are fired ([.addOnAfterRenderEntryListener])
  */
 @SysUISingleton
 class NotifPipeline @Inject constructor(
     private val mNotifCollection: NotifCollection,
-    private val mShadeListBuilder: ShadeListBuilder
+    private val mShadeListBuilder: ShadeListBuilder,
+    private val mRenderStageManager: RenderStageManager
 ) : CommonNotifCollection {
     /**
      * Returns the list of all known notifications, i.e. the notifications that are currently posted
@@ -206,6 +214,28 @@
     }
 
     /**
+     * Called at the end of the pipeline after the notif list has been handed off to the view layer.
+     */
+    fun addOnAfterRenderListListener(listener: OnAfterRenderListListener) {
+        mRenderStageManager.addOnAfterRenderListListener(listener)
+    }
+
+    /**
+     * Called at the end of the pipeline after a group has been handed off to the view layer.
+     */
+    fun addOnAfterRenderGroupListener(listener: OnAfterRenderGroupListener) {
+        mRenderStageManager.addOnAfterRenderGroupListener(listener)
+    }
+
+    /**
+     * Called at the end of the pipeline after an entry has been handed off to the view layer.
+     * This will be called for every top level entry, every group summary, and every group child.
+     */
+    fun addOnAfterRenderEntryListener(listener: OnAfterRenderEntryListener) {
+        mRenderStageManager.addOnAfterRenderEntryListener(listener)
+    }
+
+    /**
      * Get an object which can be used to update a notification (internally to the pipeline)
      * in response to a user action.
      *
@@ -218,8 +248,8 @@
 
     /**
      * Returns a read-only view in to the current shade list, i.e. the list of notifications that
-     * are currently present in the shade. If this method is called during pipeline execution it
-     * will return the current state of the list, which will likely be only partially-generated.
+     * are currently present in the shade.
+     * @throws IllegalStateException if called during pipeline execution.
      */
     val shadeList: List<ListEntry>
         get() = mShadeListBuilder.shadeList
@@ -227,21 +257,20 @@
     /**
      * Constructs a flattened representation of the notification tree, where each group will have
      * the summary (if present) followed by the children.
+     * @throws IllegalStateException if called during pipeline execution.
      */
     fun getFlatShadeList(): List<NotificationEntry> = shadeList.flatMap { entry ->
         when (entry) {
             is NotificationEntry -> sequenceOf(entry)
-            is GroupEntry -> (entry.summary?.let { sequenceOf(it) }.orEmpty() +
-                    entry.children)
+            is GroupEntry -> sequenceOf(entry.summary).filterNotNull() + entry.children
             else -> throw RuntimeException("Unexpected entry $entry")
         }
     }
 
     /**
      * Returns the number of notifications currently shown in the shade. This includes all
-     * children and summary notifications. If this method is called during pipeline execution it
-     * will return the number of notifications in its current state, which will likely be only
-     * partially-generated.
+     * children and summary notifications.
+     * @throws IllegalStateException if called during pipeline execution.
      */
     fun getShadeListCount(): Int = shadeList.sumOf { entry ->
         // include the summary in the count
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 72cd951..2787975 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -253,6 +253,9 @@
 
     List<ListEntry> getShadeList() {
         Assert.isMainThread();
+        // NOTE: Accessing this method when the pipeline is running is generally going to provide
+        //  incorrect results, and indicates a poorly behaved component of the pipeline.
+        mPipelineState.requireState(STATE_IDLE);
         return mReadOnlyNotifList;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt
new file mode 100644
index 0000000..82b1268
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt
@@ -0,0 +1,35 @@
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.util.ArrayMap
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.render.NotifGroupController
+import javax.inject.Inject
+
+/** A small coordinator which calculates, stores, and applies the untruncated child count. */
+@CoordinatorScope
+class GroupCountCoordinator @Inject constructor() : Coordinator {
+    private val untruncatedChildCounts = ArrayMap<GroupEntry, Int>()
+
+    override fun attach(pipeline: NotifPipeline) {
+        pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilter)
+        pipeline.addOnAfterRenderGroupListener(::onAfterRenderGroup)
+    }
+
+    private fun onBeforeFinalizeFilter(entries: List<ListEntry>) {
+        // save untruncated child counts to our internal map
+        untruncatedChildCounts.clear()
+        entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
+            untruncatedChildCounts[groupEntry] = groupEntry.children.size
+        }
+    }
+
+    private fun onAfterRenderGroup(group: GroupEntry, controller: NotifGroupController) {
+        // find the untruncated child count for a group and apply it to the controller
+        val count = untruncatedChildCounts[group]
+        checkNotNull(count) { "No untruncated child count for group: ${group.key}" }
+        controller.setUntruncatedChildCount(count)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index dae76f8..a16b565 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -46,8 +46,11 @@
     gutsCoordinator: GutsCoordinator,
     conversationCoordinator: ConversationCoordinator,
     preparationCoordinator: PreparationCoordinator,
+    groupCountCoordinator: GroupCountCoordinator,
     mediaCoordinator: MediaCoordinator,
     remoteInputCoordinator: RemoteInputCoordinator,
+    rowAppearanceCoordinator: RowAppearanceCoordinator,
+    stackCoordinator: StackCoordinator,
     shadeEventCoordinator: ShadeEventCoordinator,
     smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
     viewConfigCoordinator: ViewConfigCoordinator,
@@ -72,8 +75,11 @@
         mCoordinators.add(deviceProvisionedCoordinator)
         mCoordinators.add(bubbleCoordinator)
         mCoordinators.add(conversationCoordinator)
+        mCoordinators.add(groupCountCoordinator)
         mCoordinators.add(mediaCoordinator)
         mCoordinators.add(remoteInputCoordinator)
+        mCoordinators.add(rowAppearanceCoordinator)
+        mCoordinators.add(stackCoordinator)
         mCoordinators.add(shadeEventCoordinator)
         mCoordinators.add(viewConfigCoordinator)
         mCoordinators.add(visualStabilityCoordinator)
@@ -89,7 +95,7 @@
         }
 
         // Manually add Ordered Sections
-        // HeadsUp > FGS > People > Alerting > Silent > Unknown/Default
+        // HeadsUp > FGS > People > Alerting > Silent > Minimized > Unknown/Default
         if (notifPipelineFlags.isNewPipelineEnabled()) {
             mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index b5d4635..bbb97d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -31,19 +31,19 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustment;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider;
-import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.NotifViewBarn;
+import com.android.systemui.statusbar.notification.collection.render.NotifViewController;
 import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
 import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager.NotifInflationErrorListener;
 
@@ -61,8 +61,7 @@
  * If a notification was uninflated, this coordinator will filter the notification out from the
  * {@link ShadeListBuilder} until it is inflated.
  */
-// TODO(b/204468557): Move to @CoordinatorScope
-@SysUISingleton
+@CoordinatorScope
 public class PreparationCoordinator implements Coordinator {
     private static final String TAG = "PreparationCoordinator";
 
@@ -145,7 +144,7 @@
 
         pipeline.addCollectionListener(mNotifCollectionListener);
         // Inflate after grouping/sorting since that affects what views to inflate.
-        pipeline.addOnBeforeFinalizeFilterListener(mOnBeforeFinalizeFilterListener);
+        pipeline.addOnBeforeFinalizeFilterListener(this::inflateAllRequiredViews);
         pipeline.addFinalizeFilter(mNotifInflationErrorFilter);
         pipeline.addFinalizeFilter(mNotifInflatingFilter);
     }
@@ -182,9 +181,6 @@
         }
     };
 
-    private final OnBeforeFinalizeFilterListener mOnBeforeFinalizeFilterListener =
-            entries -> inflateAllRequiredViews(entries);
-
     private final NotifFilter mNotifInflationErrorFilter = new NotifFilter(
             TAG + "InflationError") {
         /**
@@ -256,7 +252,6 @@
             ListEntry entry = entries.get(i);
             if (entry instanceof GroupEntry) {
                 GroupEntry groupEntry = (GroupEntry) entry;
-                groupEntry.setUntruncatedChildCount(groupEntry.getChildren().size());
                 inflateRequiredGroupViews(groupEntry);
             } else {
                 NotificationEntry notifEntry = (NotificationEntry) entry;
@@ -363,10 +358,10 @@
         mInflatingNotifs.remove(entry);
     }
 
-    private void onInflationFinished(NotificationEntry entry) {
+    private void onInflationFinished(NotificationEntry entry, NotifViewController controller) {
         mLogger.logNotifInflated(entry.getKey());
         mInflatingNotifs.remove(entry);
-        mViewBarn.registerViewForEntry(entry, entry.getRowController());
+        mViewBarn.registerViewForEntry(entry, controller);
         mInflationStates.put(entry, STATE_INFLATED);
         mNotifInflatingFilter.invalidateList();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index c60ebcd..57fd197 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -20,11 +20,11 @@
 import android.annotation.Nullable;
 
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.SectionClassifier;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
-import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
@@ -51,7 +51,7 @@
     public static final boolean SHOW_ALL_SECTIONS = false;
     private final StatusBarStateController mStatusBarStateController;
     private final HighPriorityProvider mHighPriorityProvider;
-    private final NotifUiAdjustmentProvider mAdjustmentProvider;
+    private final SectionClassifier mSectionClassifier;
     private final NodeController mSilentNodeController;
     private final SectionHeaderController mSilentHeaderController;
     private final NodeController mAlertingHeaderController;
@@ -62,13 +62,13 @@
     public RankingCoordinator(
             StatusBarStateController statusBarStateController,
             HighPriorityProvider highPriorityProvider,
-            NotifUiAdjustmentProvider adjustmentProvider,
+            SectionClassifier sectionClassifier,
             @AlertingHeader NodeController alertingHeaderController,
             @SilentHeader SectionHeaderController silentHeaderController,
             @SilentHeader NodeController silentNodeController) {
         mStatusBarStateController = statusBarStateController;
         mHighPriorityProvider = highPriorityProvider;
-        mAdjustmentProvider = adjustmentProvider;
+        mSectionClassifier = sectionClassifier;
         mAlertingHeaderController = alertingHeaderController;
         mSilentNodeController = silentNodeController;
         mSilentHeaderController = silentHeaderController;
@@ -77,7 +77,7 @@
     @Override
     public void attach(NotifPipeline pipeline) {
         mStatusBarStateController.addCallback(mStatusBarStateCallback);
-        mAdjustmentProvider.setLowPrioritySections(Collections.singleton(mMinimizedNotifSectioner));
+        mSectionClassifier.setMinimizedSections(Collections.singleton(mMinimizedNotifSectioner));
 
         pipeline.addPreGroupFilter(mSuspendedFilter);
         pipeline.addPreGroupFilter(mDndVisualEffectsFilter);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
index 3397815..2608c30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
@@ -22,7 +22,6 @@
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.NotificationRemoteInputManager
@@ -32,6 +31,7 @@
 import com.android.systemui.statusbar.SmartReplyController
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.notifcollection.SelfTrackingLifetimeExtender
 import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
@@ -61,7 +61,7 @@
 /** Whether this class should print spammy debug logs */
 private val DEBUG: Boolean by lazy { Log.isLoggable(TAG, Log.DEBUG) }
 
-@SysUISingleton
+@CoordinatorScope
 class RemoteInputCoordinator @Inject constructor(
     dumpManager: DumpManager,
     private val mRebuilder: RemoteInputNotificationRebuilder,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
new file mode 100644
index 0000000..c8f7360
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.notification.collection.coordinator
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.statusbar.notification.AssistantFeedbackController
+import com.android.systemui.statusbar.notification.SectionClassifier
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.render.NotifRowController
+import javax.inject.Inject
+
+/**
+ * A small coordinator which updates the notif rows with data related to the current shade after
+ * they are fully attached.
+ */
+@CoordinatorScope
+class RowAppearanceCoordinator @Inject internal constructor(
+    context: Context,
+    private var mAssistantFeedbackController: AssistantFeedbackController,
+    private var mSectionClassifier: SectionClassifier
+) : Coordinator {
+
+    private var entryToExpand: NotificationEntry? = null
+
+    /**
+     * `true` if notifications not part of a group should by default be rendered in their
+     * expanded state. If `false`, then only the first notification will be expanded if
+     * possible.
+     */
+    private val mAlwaysExpandNonGroupedNotification =
+        context.resources.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications)
+
+    override fun attach(pipeline: NotifPipeline) {
+        pipeline.addOnBeforeRenderListListener(::onBeforeRenderList)
+        pipeline.addOnAfterRenderEntryListener(::onAfterRenderEntry)
+    }
+
+    private fun onBeforeRenderList(list: List<ListEntry>) {
+        entryToExpand = list.firstOrNull()?.representativeEntry?.takeIf { entry ->
+            !mSectionClassifier.isMinimizedSection(entry.section!!)
+        }
+    }
+
+    private fun onAfterRenderEntry(entry: NotificationEntry, controller: NotifRowController) {
+        // If mAlwaysExpandNonGroupedNotification is false, then only expand the
+        // very first notification and if it's not a child of grouped notifications.
+        controller.setSystemExpanded(mAlwaysExpandNonGroupedNotification || entry == entryToExpand)
+        // Show/hide the feedback icon
+        controller.showFeedbackIcon(
+            mAssistantFeedbackController.showFeedbackIndicator(entry),
+            mAssistantFeedbackController.getFeedbackResources(entry)
+        )
+        // Show the "alerted" bell icon
+        controller.setLastAudiblyAlertedMs(entry.lastAudiblyAlertedMs)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
new file mode 100644
index 0000000..38f11fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.notification.collection.coordinator
+
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.collection.render.NotifStackController
+import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
+import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import javax.inject.Inject
+
+/**
+ * A small coordinator which updates the notif stack (the view layer which holds notifications)
+ * with high-level data after the stack is populated with the final entries.
+ */
+@CoordinatorScope
+class StackCoordinator @Inject internal constructor(
+    private val notificationIconAreaController: NotificationIconAreaController
+) : Coordinator {
+
+    override fun attach(pipeline: NotifPipeline) {
+        pipeline.addOnAfterRenderListListener(::onAfterRenderList)
+    }
+
+    fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) {
+        controller.setNotifStats(calculateNotifStats(entries))
+        notificationIconAreaController.updateNotificationIcons(entries)
+    }
+
+    private fun calculateNotifStats(entries: List<ListEntry>): NotifStats {
+        var hasNonClearableAlertingNotifs = false
+        var hasClearableAlertingNotifs = false
+        var hasNonClearableSilentNotifs = false
+        var hasClearableSilentNotifs = false
+        entries.forEach {
+            val isSilent = it.section!!.bucket == BUCKET_SILENT
+            // NOTE: NotificationEntry.isClearable will internally check group children to ensure
+            //  the group itself definitively clearable.
+            val isClearable = it.representativeEntry!!.isClearable
+            when {
+                isSilent && isClearable -> hasClearableSilentNotifs = true
+                isSilent && !isClearable -> hasNonClearableSilentNotifs = true
+                !isSilent && isClearable -> hasClearableAlertingNotifs = true
+                !isSilent && !isClearable -> hasNonClearableAlertingNotifs = true
+            }
+        }
+        val stats = NotifStats(
+            numActiveNotifs = entries.size,
+            hasNonClearableAlertingNotifs = hasNonClearableAlertingNotifs,
+            hasClearableAlertingNotifs = hasClearableAlertingNotifs,
+            hasNonClearableSilentNotifs = hasNonClearableSilentNotifs,
+            hasClearableSilentNotifs = hasClearableSilentNotifs
+        )
+        return stats
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
index c59f184..d98e7f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.inflation
 
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.render.NotifViewController
 
 /**
  * Used by the [PreparationCoordinator].  When notifications are added or updated, the
@@ -49,7 +50,7 @@
      * Callback once all the views are inflated and bound for a given NotificationEntry.
      */
     interface InflationCallback {
-        fun onInflationFinished(entry: NotificationEntry)
+        fun onInflationFinished(entry: NotificationEntry, controller: NotifViewController)
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
index 3290cdf..497691d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.statusbar.notification.collection.inflation
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.SectionClassifier
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import javax.inject.Inject
 
 /**
@@ -27,25 +27,17 @@
  * to ensure that notifications are reinflated when ranking-derived information changes.
  */
 @SysUISingleton
-open class NotifUiAdjustmentProvider @Inject constructor() {
-
-    private lateinit var lowPrioritySections: Set<NotifSectioner>
-
-    /**
-     * Feed the provider the information it needs about which sections should have minimized top
-     * level views, so that it can calculate the correct minimized value in the adjustment.
-     */
-    fun setLowPrioritySections(sections: Collection<NotifSectioner>) {
-        lowPrioritySections = sections.toSet()
-    }
+open class NotifUiAdjustmentProvider @Inject constructor(
+    private val sectionClassifier: SectionClassifier
+) {
 
     private fun isEntryMinimized(entry: NotificationEntry): Boolean {
         val section = entry.section ?: error("Entry must have a section to determine if minimized")
         val parent = entry.parent ?: error("Entry must have a parent to determine if minimized")
-        val isLowPrioritySection = lowPrioritySections.contains(section.sectioner)
+        val isMinimizedSection = sectionClassifier.isMinimizedSection(section)
         val isTopLevelEntry = parent == GroupEntry.ROOT_ENTRY
         val isGroupSummary = parent.summary == entry
-        return isLowPrioritySection && (isTopLevelEntry || isGroupSummary)
+        return isMinimizedSection && (isTopLevelEntry || isGroupSummary)
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
index 66ec30d..f50038c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
@@ -108,11 +108,10 @@
     @Override
     @Nullable
     public NotificationEntry getGroupSummaryToDismiss(NotificationEntry entry) {
-        if (entry.getParent() != null
-                && entry.getParent().getSummary() != null
-                && mGroupMembershipManager.isOnlyChildInGroup(entry)) {
-            NotificationEntry groupSummary = entry.getParent().getSummary();
-            return groupSummary.isClearable() ? groupSummary : null;
+        String group = entry.getSbn().getGroup();
+        if (mNotifCollection.isOnlyChildInGroup(entry)) {
+            NotificationEntry summary = mNotifCollection.getGroupSummary(group);
+            if (summary != null && summary.isClearable()) return summary;
         }
         return null;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index a8f3730..f9358eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -30,6 +30,8 @@
 import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
 import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
+import com.android.systemui.statusbar.notification.collection.render.RenderStageManager;
 import com.android.systemui.statusbar.notification.collection.render.ShadeViewManagerFactory;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 
@@ -47,6 +49,7 @@
     private final GroupCoalescer mGroupCoalescer;
     private final NotifCollection mNotifCollection;
     private final ShadeListBuilder mListBuilder;
+    private final RenderStageManager mRenderStageManager;
     private final NotifCoordinators mNotifPluggableCoordinators;
     private final NotifInflaterImpl mNotifInflater;
     private final DumpManager mDumpManager;
@@ -60,15 +63,18 @@
             GroupCoalescer groupCoalescer,
             NotifCollection notifCollection,
             ShadeListBuilder listBuilder,
+            RenderStageManager renderStageManager,
             NotifCoordinators notifCoordinators,
             NotifInflaterImpl notifInflater,
             DumpManager dumpManager,
             ShadeViewManagerFactory shadeViewManagerFactory,
-            NotifPipelineFlags notifPipelineFlags) {
+            NotifPipelineFlags notifPipelineFlags
+    ) {
         mPipelineWrapper = pipelineWrapper;
         mGroupCoalescer = groupCoalescer;
         mNotifCollection = notifCollection;
         mListBuilder = listBuilder;
+        mRenderStageManager = renderStageManager;
         mNotifPluggableCoordinators = notifCoordinators;
         mDumpManager = dumpManager;
         mNotifInflater = notifInflater;
@@ -80,7 +86,8 @@
     public void initialize(
             NotificationListener notificationService,
             NotificationRowBinderImpl rowBinder,
-            NotificationListContainer listContainer) {
+            NotificationListContainer listContainer,
+            NotifStackController stackController) {
 
         mDumpManager.registerDumpable("NotifPipeline", this);
 
@@ -94,8 +101,11 @@
 
         // Wire up pipeline
         if (mNotifPipelineFlags.isNewPipelineEnabled()) {
-            mShadeViewManagerFactory.create(listContainer).attach(mListBuilder);
+            mShadeViewManagerFactory
+                    .create(listContainer, stackController)
+                    .attach(mRenderStageManager);
         }
+        mRenderStageManager.attach(mListBuilder);
         mListBuilder.attach(mNotifCollection);
         mNotifCollection.attach(mGroupCoalescer);
         mGroupCoalescer.attach(notificationService);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderEntryListener.java
new file mode 100644
index 0000000..20cd6dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderEntryListener.java
@@ -0,0 +1,37 @@
+/*
+ * 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.statusbar.notification.collection.listbuilder;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.render.NotifRowController;
+
+/** See {@link NotifPipeline#addOnAfterRenderEntryListener(OnAfterRenderEntryListener)} */
+public interface OnAfterRenderEntryListener {
+    /**
+     * Called at the end of the pipeline after an entry has been handed off to the view layer.
+     * This will be called for every top level entry, every group summary, and every group child.
+     *
+     * @param entry the entry to read from.
+     * @param controller the object to which data can be pushed.
+     */
+    void onAfterRenderEntry(
+            @NonNull NotificationEntry entry,
+            @NonNull NotifRowController controller);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderGroupListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderGroupListener.java
new file mode 100644
index 0000000..b1a4b65
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderGroupListener.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.statusbar.notification.collection.listbuilder;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.render.NotifGroupController;
+
+/** See {@link NotifPipeline#addOnAfterRenderGroupListener(OnAfterRenderGroupListener)} */
+public interface OnAfterRenderGroupListener {
+    /**
+     * Called at the end of the pipeline after a group has been handed off to the view layer.
+     *
+     * @param groupEntry the entry for the group itself.
+     * @param controller the object to which data can be pushed.
+     */
+    void onAfterRenderGroup(
+            @NonNull GroupEntry groupEntry,
+            @NonNull NotifGroupController controller);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java
new file mode 100644
index 0000000..b5a0f7a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java
@@ -0,0 +1,39 @@
+/*
+ * 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.statusbar.notification.collection.listbuilder;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
+
+import java.util.List;
+
+/** See {@link NotifPipeline#addOnAfterRenderListListener(OnAfterRenderListListener)} */
+public interface OnAfterRenderListListener {
+    /**
+     * Called at the end of the pipeline after the notif list has been handed off to the view layer.
+     *
+     * @param entries The current list of top-level entries. Note that this is a live view into the
+     * current list and will change whenever the pipeline is rerun.
+     * @param controller An object for setting state on the shade.
+     */
+    void onAfterRenderList(
+            @NonNull List<ListEntry> entries,
+            @NonNull NotifStackController controller);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index b4389b9..7302de5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -109,6 +109,14 @@
         })
     }
 
+    fun logNonExistentNotifDismissed(key: String) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+        }, {
+            "DISMISSED Non Existent $str1"
+        })
+    }
+
     fun logChildDismissed(entry: NotificationEntry) {
         buffer.log(TAG, DEBUG, {
             str1 = entry.key
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
index 8182e73..f59e4ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
@@ -64,12 +64,13 @@
             root.children.add(buildNotifNode(root, entry))
         }
 
-        return root
+        return@traceSection root
     }
 
     private fun buildNotifNode(parent: NodeSpec, entry: ListEntry): NodeSpec = when (entry) {
-        is NotificationEntry -> NodeSpecImpl(parent, viewBarn.requireView(entry))
-        is GroupEntry -> NodeSpecImpl(parent, viewBarn.requireView(checkNotNull(entry.summary)))
+        is NotificationEntry -> NodeSpecImpl(parent, viewBarn.requireNodeController(entry))
+        is GroupEntry ->
+            NodeSpecImpl(parent, viewBarn.requireNodeController(checkNotNull(entry.summary)))
                 .apply { entry.children.forEach { children.add(buildNotifNode(this, it)) } }
         else -> throw RuntimeException("Unexpected entry: $entry")
     }
diff --git a/core/java/android/window/TaskFragmentAppearedInfo.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
similarity index 65%
copy from core/java/android/window/TaskFragmentAppearedInfo.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
index 3729c09..e2edc01 100644
--- a/core/java/android/window/TaskFragmentAppearedInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package android.window;
+package com.android.systemui.statusbar.notification.collection.render
 
-/**
- * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
- * @hide
- */
-parcelable TaskFragmentAppearedInfo;
+/** A view controller for a notification group row */
+interface NotifGroupController {
+    /** Set the number of children that this group would have if not for the 8-child max */
+    fun setUntruncatedChildCount(untruncatedChildCount: Int)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifRowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifRowController.kt
new file mode 100644
index 0000000..c10e401
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifRowController.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.notification.collection.render
+
+import android.util.Pair
+
+/** A view controller for a notification row */
+interface NotifRowController {
+    /**
+     * This tells the row what the 'default expanded' state should be.  Once a user expands or
+     * contracts a row, that will set the user expanded state, which takes precedence, but
+     * collapsing the shade and re-opening it will clear the user expanded state.  This allows for
+     * nice auto expansion of the next notification as users dismiss the top notification.
+     */
+    fun setSystemExpanded(systemExpanded: Boolean)
+
+    /**
+     * Sets the timestamp that the notification was last audibly alerted, which the row uses to
+     * show a bell icon in the header which indicates to the user which notification made a noise.
+     */
+    fun setLastAudiblyAlertedMs(lastAudiblyAlertedMs: Long)
+
+    /**
+     * Sets both whether to show a feedback indicator and which resources to use for the drawable
+     * and content description.
+     */
+    fun showFeedbackIcon(showFeedbackIndicator: Boolean, feedbackResources: Pair<Int, Int>?)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
new file mode 100644
index 0000000..b6278d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.notification.collection.render
+
+/** An interface by which the pipeline can make updates to the notification root view. */
+interface NotifStackController {
+    /** Provides stats about the list of notifications attached to the shade */
+    fun setNotifStats(stats: NotifStats)
+}
+
+/** Data provided to the NotificationRootController whenever the pipeline runs */
+data class NotifStats(
+    val numActiveNotifs: Int,
+    val hasNonClearableAlertingNotifs: Boolean,
+    val hasClearableAlertingNotifs: Boolean,
+    val hasNonClearableSilentNotifs: Boolean,
+    val hasClearableSilentNotifs: Boolean
+) {
+    companion object {
+        @JvmStatic
+        val empty = NotifStats(0, false, false, false, false)
+    }
+}
+
+/**
+ * An implementation of NotifStackController which provides default, no-op implementations of each
+ * method.  This is used by ArcSystemUI so that that implementation can opt-in to overriding
+ * methods, rather than forcing us to add no-op implementations in their implementation every time
+ * a method is added.
+ */
+open class DefaultNotifStackController : NotifStackController {
+    override fun setNotifStats(stats: NotifStats) {}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
index c79f59b..fd91d5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
@@ -19,6 +19,7 @@
 import android.view.textclassifier.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import javax.inject.Inject
 
 /**
@@ -26,30 +27,39 @@
  */
 @SysUISingleton
 class NotifViewBarn @Inject constructor() {
-    private val rowMap = mutableMapOf<String, NodeController>()
+    private val rowMap = mutableMapOf<String, NotifViewController>()
 
-    fun requireView(forEntry: ListEntry): NodeController {
+    fun requireNodeController(entry: ListEntry): NodeController {
         if (DEBUG) {
-            Log.d(TAG, "requireView: $forEntry.key")
+            Log.d(TAG, "requireNodeController: ${entry.key}")
         }
-        val li = rowMap[forEntry.key]
-        if (li == null) {
-            throw IllegalStateException("No view has been registered for entry: $forEntry")
-        }
-
-        return li
+        return rowMap[entry.key] ?: error("No view has been registered for entry: ${entry.key}")
     }
 
-    fun registerViewForEntry(entry: ListEntry, controller: NodeController) {
+    fun requireGroupController(entry: NotificationEntry): NotifGroupController {
         if (DEBUG) {
-            Log.d(TAG, "registerViewForEntry: $entry.key")
+            Log.d(TAG, "requireGroupController: ${entry.key}")
+        }
+        return rowMap[entry.key] ?: error("No view has been registered for entry: ${entry.key}")
+    }
+
+    fun requireRowController(entry: NotificationEntry): NotifRowController {
+        if (DEBUG) {
+            Log.d(TAG, "requireRowController: ${entry.key}")
+        }
+        return rowMap[entry.key] ?: error("No view has been registered for entry: ${entry.key}")
+    }
+
+    fun registerViewForEntry(entry: ListEntry, controller: NotifViewController) {
+        if (DEBUG) {
+            Log.d(TAG, "registerViewForEntry: ${entry.key}")
         }
         rowMap[entry.key] = controller
     }
 
     fun removeViewForEntry(entry: ListEntry) {
         if (DEBUG) {
-            Log.d(TAG, "removeViewForEntry: $entry.key")
+            Log.d(TAG, "removeViewForEntry: ${entry.key}")
         }
         rowMap.remove(entry.key)
     }
diff --git a/core/java/android/window/TaskFragmentAppearedInfo.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewController.kt
similarity index 76%
copy from core/java/android/window/TaskFragmentAppearedInfo.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewController.kt
index 3729c09..11d4e83 100644
--- a/core/java/android/window/TaskFragmentAppearedInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewController.kt
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package android.window;
+package com.android.systemui.statusbar.notification.collection.render
 
-/**
- * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
- * @hide
- */
-parcelable TaskFragmentAppearedInfo;
+interface NotifViewController : NotifGroupController, NotifRowController, NodeController
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt
new file mode 100644
index 0000000..1ea574b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.notification.collection.render
+
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/**
+ * This interface and the interfaces it returns define the main API surface that must be
+ * implemented by the view implementation.  The term "render" is used to indicate a handoff
+ * to the view system, whether that be to attach views to the hierarchy or to update independent
+ * view models, data stores, or adapters.
+ */
+interface NotifViewRenderer {
+
+    /**
+     * Hand off the list of notifications to the view implementation.  This may attach views to the
+     * hierarchy or simply update an independent datastore, but once called, the implementer myst
+     * also ensure that future calls to [getStackController], [getGroupController], and
+     * [getRowController] will provide valid results.
+     */
+    fun onRenderList(notifList: List<ListEntry>)
+
+    /**
+     * Provides an interface for the pipeline to update the overall shade.
+     * This will be called at most once for each time [onRenderList] is called.
+     */
+    fun getStackController(): NotifStackController
+
+    /**
+     * Provides an interface for the pipeline to update individual groups.
+     * This will be called at most once for each group in the most recent call to [onRenderList].
+     */
+    fun getGroupController(group: GroupEntry): NotifGroupController
+
+    /**
+     * Provides an interface for the pipeline to update individual entries.
+     * This will be called at most once for each entry in the most recent call to [onRenderList].
+     * This includes top level entries, group summaries, and group children.
+     */
+    fun getRowController(entry: NotificationEntry): NotifRowController
+
+    /**
+     * Invoked after the render stage manager has finished dispatching to all of the listeners.
+     *
+     * This is an opportunity for the view system to do any cleanup or trigger any finalization
+     * logic now that all data from the pipeline is known to have been set for this execution.
+     *
+     * When this is called, the view system can expect that no more calls will be made to the
+     * getters on this interface until after the next call to [onRenderList].  Additionally, there
+     * should be no further calls made on the objects previously returned by those getters.
+     */
+    fun onDispatchComplete() {}
+}
\ No newline at end of file
diff --git a/core/java/android/window/TaskFragmentAppearedInfo.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderExtensions.kt
similarity index 63%
copy from core/java/android/window/TaskFragmentAppearedInfo.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderExtensions.kt
index 3729c09..6e7f415 100644
--- a/core/java/android/window/TaskFragmentAppearedInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderExtensions.kt
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-package android.window;
+package com.android.systemui.statusbar.notification.collection.render
+
+import com.android.systemui.statusbar.notification.collection.GroupEntry
 
 /**
- * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
- * @hide
+ * Extension used during the render stage which assumes the summary exists, and throws a more
+ * helpful error if not.
  */
-parcelable TaskFragmentAppearedInfo;
+inline val GroupEntry.requireSummary get() = checkNotNull(summary) { "No Summary: $this" }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
new file mode 100644
index 0000000..a9c3987
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.notification.collection.render
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
+import com.android.systemui.util.traceSection
+import javax.inject.Inject
+
+/**
+ * The class which is part of the pipeline which guarantees a consistent that coordinators get a
+ * consistent interface to the view system regardless of the [NotifViewRenderer] implementation
+ * provided to [setViewRenderer].
+ */
+@SysUISingleton
+class RenderStageManager @Inject constructor() {
+    private val onAfterRenderListListeners = mutableListOf<OnAfterRenderListListener>()
+    private val onAfterRenderGroupListeners = mutableListOf<OnAfterRenderGroupListener>()
+    private val onAfterRenderEntryListeners = mutableListOf<OnAfterRenderEntryListener>()
+    private var viewRenderer: NotifViewRenderer? = null
+
+    /** Attach this stage to the rest of the pipeline */
+    fun attach(listBuilder: ShadeListBuilder) {
+        listBuilder.setOnRenderListListener(::onRenderList)
+    }
+
+    private fun onRenderList(notifList: List<ListEntry>) {
+        traceSection("RenderStageManager.onRenderList") {
+            val viewRenderer = viewRenderer ?: return
+            viewRenderer.onRenderList(notifList)
+            dispatchOnAfterRenderList(viewRenderer, notifList)
+            dispatchOnAfterRenderGroups(viewRenderer, notifList)
+            dispatchOnAfterRenderEntries(viewRenderer, notifList)
+            viewRenderer.onDispatchComplete()
+        }
+    }
+
+    /** Provides this class with the view rendering implementation. */
+    fun setViewRenderer(renderer: NotifViewRenderer) {
+        viewRenderer = renderer
+    }
+
+    /** Adds a listener that will get a single callback after rendering the list. */
+    fun addOnAfterRenderListListener(listener: OnAfterRenderListListener) {
+        onAfterRenderListListeners.add(listener)
+    }
+
+    /** Adds a listener that will get a callback for each group rendered. */
+    fun addOnAfterRenderGroupListener(listener: OnAfterRenderGroupListener) {
+        onAfterRenderGroupListeners.add(listener)
+    }
+
+    /** Adds a listener that will get a callback for each entry rendered. */
+    fun addOnAfterRenderEntryListener(listener: OnAfterRenderEntryListener) {
+        onAfterRenderEntryListeners.add(listener)
+    }
+
+    private fun dispatchOnAfterRenderList(
+        viewRenderer: NotifViewRenderer,
+        entries: List<ListEntry>
+    ) {
+        traceSection("RenderStageManager.dispatchOnAfterRenderList") {
+            val stackController = viewRenderer.getStackController()
+            onAfterRenderListListeners.forEach { listener ->
+                listener.onAfterRenderList(entries, stackController)
+            }
+        }
+    }
+
+    private fun dispatchOnAfterRenderGroups(
+        viewRenderer: NotifViewRenderer,
+        entries: List<ListEntry>
+    ) {
+        traceSection("RenderStageManager.dispatchOnAfterRenderGroups") {
+            if (onAfterRenderGroupListeners.isEmpty()) {
+                return
+            }
+            entries.asSequence().filterIsInstance<GroupEntry>().forEach { group ->
+                val controller = viewRenderer.getGroupController(group)
+                onAfterRenderGroupListeners.forEach { listener ->
+                    listener.onAfterRenderGroup(group, controller)
+                }
+            }
+        }
+    }
+
+    private fun dispatchOnAfterRenderEntries(
+        viewRenderer: NotifViewRenderer,
+        entries: List<ListEntry>
+    ) {
+        traceSection("RenderStageManager.dispatchOnAfterRenderEntries") {
+            if (onAfterRenderEntryListeners.isEmpty()) {
+                return
+            }
+            entries.forEachNotificationEntry { entry ->
+                val controller = viewRenderer.getRowController(entry)
+                onAfterRenderEntryListeners.forEach { listener ->
+                    listener.onAfterRenderEntry(entry, controller)
+                }
+            }
+        }
+    }
+
+    /**
+     * Performs a forward, depth-first traversal of the list where the group's summary
+     * immediately precedes the group's children.
+     */
+    private inline fun List<ListEntry>.forEachNotificationEntry(
+        action: (NotificationEntry) -> Unit
+    ) {
+        forEach { entry ->
+            when (entry) {
+                is NotificationEntry -> action(entry)
+                is GroupEntry -> {
+                    action(entry.requireSummary)
+                    entry.children.forEach(action)
+                }
+                else -> error("Unhandled entry: $entry")
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index b582a24..1a8d720 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -20,10 +20,8 @@
 import android.view.View
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.util.traceSection
 import javax.inject.Inject
 
@@ -34,9 +32,9 @@
 class ShadeViewManager constructor(
     context: Context,
     listContainer: NotificationListContainer,
+    private val stackController: NotifStackController,
     logger: ShadeViewDifferLogger,
-    private val viewBarn: NotifViewBarn,
-    private val notificationIconAreaController: NotificationIconAreaController
+    private val viewBarn: NotifViewBarn
 ) {
     // We pass a shim view here because the listContainer may not actually have a view associated
     // with it and the differ never actually cares about the root node's view.
@@ -44,39 +42,39 @@
     private val specBuilder = NodeSpecBuilder(viewBarn)
     private val viewDiffer = ShadeViewDiffer(rootController, logger)
 
-    fun attach(listBuilder: ShadeListBuilder) =
-            listBuilder.setOnRenderListListener(::onNewNotifTree)
-
-    private fun onNewNotifTree(notifList: List<ListEntry>) {
-        traceSection("ShadeViewManager.onNewNotifTree") {
-            viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
-            updateGroupCounts(notifList)
-            notificationIconAreaController.updateNotificationIcons(notifList)
-        }
+    /** Method for attaching this manager to the pipeline. */
+    fun attach(renderStageManager: RenderStageManager) {
+        renderStageManager.setViewRenderer(viewRenderer)
     }
 
-    private fun updateGroupCounts(notifList: List<ListEntry>) {
-        traceSection("ShadeViewManager.updateGroupCounts") {
-            notifList.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
-                val controller = viewBarn.requireView(checkNotNull(groupEntry.summary))
-                val row = controller.view as ExpandableNotificationRow
-                row.setUntruncatedChildCount(groupEntry.untruncatedChildCount)
+    private val viewRenderer = object : NotifViewRenderer {
+
+        override fun onRenderList(notifList: List<ListEntry>) {
+            traceSection("ShadeViewManager.onRenderList") {
+                viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
             }
         }
+
+        override fun getStackController(): NotifStackController = stackController
+
+        override fun getGroupController(group: GroupEntry): NotifGroupController =
+            viewBarn.requireGroupController(group.requireSummary)
+
+        override fun getRowController(entry: NotificationEntry): NotifRowController =
+            viewBarn.requireRowController(entry)
     }
 }
 
 class ShadeViewManagerFactory @Inject constructor(
     private val context: Context,
     private val logger: ShadeViewDifferLogger,
-    private val viewBarn: NotifViewBarn,
-    private val notificationIconAreaController: NotificationIconAreaController
+    private val viewBarn: NotifViewBarn
 ) {
-    fun create(listContainer: NotificationListContainer) =
-            ShadeViewManager(
-                    context,
-                    listContainer,
-                    logger,
-                    viewBarn,
-                    notificationIconAreaController)
+    fun create(listContainer: NotificationListContainer, stackController: NotifStackController) =
+        ShadeViewManager(
+            context,
+            listContainer,
+            stackController,
+            logger,
+            viewBarn)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
index 2f496dd..a59d421 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
+import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.StatusBar
 import com.android.wm.shell.bubbles.Bubbles
@@ -40,6 +41,7 @@
         bubblesOptional: Optional<Bubbles>,
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
+        stackController: NotifStackController,
         notificationActivityStarter: NotificationActivityStarter,
         bindRowCallback: NotificationRowBinderImpl.BindRowCallback
     )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 9411de7..212c342e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
 import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
+import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
@@ -47,7 +48,7 @@
 import dagger.Lazy
 import java.io.FileDescriptor
 import java.io.PrintWriter
-import java.util.*
+import java.util.Optional
 import javax.inject.Inject
 
 /**
@@ -85,6 +86,7 @@
         bubblesOptional: Optional<Bubbles>,
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
+        stackController: NotifStackController,
         notificationActivityStarter: NotificationActivityStarter,
         bindRowCallback: NotificationRowBinderImpl.BindRowCallback
     ) {
@@ -112,7 +114,8 @@
             newNotifPipeline.get().initialize(
                     notificationListener,
                     notificationRowBinder,
-                    listContainer)
+                    listContainer,
+                    stackController)
         }
 
         if (notifPipelineFlags.isNewPipelineEnabled()) {
@@ -152,8 +155,14 @@
     }
 
     override fun resetUserExpandedStates() {
-        for (entry in entryManager.visibleNotifications) {
-            entry.resetUserExpansion()
+        if (notifPipelineFlags.isNewPipelineEnabled()) {
+            for (entry in notifPipeline.get().allNotifs) {
+                entry.resetUserExpansion()
+            }
+        } else {
+            for (entry in entryManager.visibleNotifications) {
+                entry.resetUserExpansion()
+            }
         }
     }
 
@@ -167,9 +176,12 @@
         }
     }
 
-    override fun getActiveNotificationsCount(): Int {
-        return entryManager.activeNotificationsCount
-    }
+    override fun getActiveNotificationsCount(): Int =
+        if (notifPipelineFlags.isNewPipelineEnabled()) {
+            notifPipeline.get().getShadeListCount()
+        } else {
+            entryManager.activeNotificationsCount
+        }
 
     companion object {
         // NOTE: The new pipeline is always active, even if the old pipeline is *rendering*.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
index eadf5ac..1c9af11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
+import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.StatusBar
 import com.android.wm.shell.bubbles.Bubbles
@@ -42,6 +43,7 @@
         bubblesOptional: Optional<Bubbles>,
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
+        stackController: NotifStackController,
         notificationActivityStarter: NotificationActivityStarter,
         bindRowCallback: NotificationRowBinderImpl.BindRowCallback
     ) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9f10322..1c2b938 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -92,6 +92,7 @@
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
 import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
+import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -131,7 +132,8 @@
  * the group summary (which contains 1 or more child notifications).
  */
 public class ExpandableNotificationRow extends ActivatableNotificationView
-        implements PluginListener<NotificationMenuRowPlugin>, SwipeableView {
+        implements PluginListener<NotificationMenuRowPlugin>, SwipeableView,
+        NotificationFadeAware.FadeOptimizedNotification {
 
     private static final boolean DEBUG = false;
     private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
@@ -144,6 +146,7 @@
     private boolean mUpdateBackgroundOnUpdate;
     private boolean mNotificationTranslationFinished = false;
     private boolean mIsSnoozed;
+    private boolean mIsFaded;
 
     /**
      * Listener for when {@link ExpandableNotificationRow} is laid out.
@@ -812,6 +815,13 @@
         mChildrenContainer.setUntruncatedChildCount(childCount);
     }
 
+    /** Called after children have been attached to set the expansion states */
+    public void resetChildSystemExpandedStates() {
+        if (isSummaryWithChildren()) {
+            mChildrenContainer.updateExpansionStates();
+        }
+    }
+
     /**
      * Add a child notification to this view.
      *
@@ -2339,6 +2349,7 @@
             onExpansionChanged(false /* userAction */, wasExpanded);
             if (mIsSummaryWithChildren) {
                 mChildrenContainer.updateGroupOverflow();
+                resetChildSystemExpandedStates();
             }
         }
     }
@@ -2774,17 +2785,112 @@
             // alphas are reset
             if (mChildrenContainer != null) {
                 mChildrenContainer.setAlpha(1.0f);
-                mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null);
             }
             for (NotificationContentView l : mLayouts) {
                 l.setAlpha(1.0f);
-                l.setLayerType(LAYER_TYPE_NONE, null);
+            }
+            if (FADE_LAYER_OPTIMIZATION_ENABLED) {
+                setNotificationFaded(false);
+            } else {
+                setNotificationFadedOnChildren(false);
             }
         } else {
             setHeadsUpAnimatingAway(false);
         }
     }
 
+    /** Gets the last value set with {@link #setNotificationFaded(boolean)} */
+    @Override
+    public boolean isNotificationFaded() {
+        return mIsFaded;
+    }
+
+    /**
+     * This class needs to delegate the faded state set on it by
+     * {@link com.android.systemui.statusbar.notification.stack.ViewState} to its children.
+     * Having each notification use layerType of HARDWARE anytime it fades in/out can result in
+     * extremely large layers (in the case of groups, it can even exceed the device height).
+     * Because these large renders can cause serious jank when rendering, we instead have
+     * notifications return false from {@link #hasOverlappingRendering()} and delegate the
+     * layerType to child views which really need it in order to render correctly, such as icon
+     * views or the conversation face pile.
+     *
+     * Another compounding factor for notifications is that we change clipping on each frame of the
+     * animation, so the hardware layer isn't able to do any caching at the top level, but the
+     * individual elements we render with hardware layers (e.g. icons) cache wonderfully because we
+     * never invalidate them.
+     */
+    @Override
+    public void setNotificationFaded(boolean faded) {
+        mIsFaded = faded;
+        if (childrenRequireOverlappingRendering()) {
+            // == Simple Scenario ==
+            // If a child (like remote input) needs this to have overlapping rendering, then set
+            // the layerType of this view and reset the children to render as if the notification is
+            // not fading.
+            NotificationFadeAware.setLayerTypeForFaded(this, faded);
+            setNotificationFadedOnChildren(false);
+        } else {
+            // == Delegating Scenario ==
+            // This is the new normal for alpha: Explicitly reset this view's layer type to NONE,
+            // and require that all children use their own hardware layer if they have bad
+            // overlapping rendering.
+            NotificationFadeAware.setLayerTypeForFaded(this, false);
+            setNotificationFadedOnChildren(faded);
+        }
+    }
+
+    /** Private helper for iterating over the layouts and children containers to set faded state */
+    private void setNotificationFadedOnChildren(boolean faded) {
+        delegateNotificationFaded(mChildrenContainer, faded);
+        for (NotificationContentView layout : mLayouts) {
+            delegateNotificationFaded(layout, faded);
+        }
+    }
+
+    private static void delegateNotificationFaded(@Nullable View view, boolean faded) {
+        if (FADE_LAYER_OPTIMIZATION_ENABLED && view instanceof NotificationFadeAware) {
+            ((NotificationFadeAware) view).setNotificationFaded(faded);
+        } else {
+            NotificationFadeAware.setLayerTypeForFaded(view, faded);
+        }
+    }
+
+    /**
+     * Only declare overlapping rendering if independent children of the view require it.
+     */
+    @Override
+    public boolean hasOverlappingRendering() {
+        return super.hasOverlappingRendering() && childrenRequireOverlappingRendering();
+    }
+
+    /**
+     * Because RemoteInputView is designed to be an opaque view that overlaps the Actions row, the
+     * row should require overlapping rendering to ensure that the overlapped view doesn't bleed
+     * through when alpha fading.
+     *
+     * Note that this currently works for top-level notifications which squish their height down
+     * while collapsing the shade, but does not work for children inside groups, because the
+     * accordion affect does not apply to those views, so super.hasOverlappingRendering() will
+     * always return false to avoid the clipping caused when the view's measured height is less than
+     * the 'actual height'.
+     */
+    private boolean childrenRequireOverlappingRendering() {
+        if (!FADE_LAYER_OPTIMIZATION_ENABLED) {
+            return true;
+        }
+        // The colorized background is another layer with which all other elements overlap
+        if (getEntry().getSbn().getNotification().isColorized()) {
+            return true;
+        }
+        // Check if the showing layout has a need for overlapping rendering.
+        // NOTE: We could check both public and private layouts here, but becuause these states
+        //  don't animate well, there are bigger visual artifacts if we start changing the shown
+        //  layout during shade expansion.
+        NotificationContentView showingLayout = getShowingLayout();
+        return showingLayout != null && showingLayout.requireRowToHaveOverlappingRendering();
+    }
+
     @Override
     public int getExtraBottomPadding() {
         if (mIsSummaryWithChildren && isGroupExpanded()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 59ea0ea..3d35d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -20,6 +20,8 @@
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 
+import android.util.Log;
+import android.util.Pair;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -36,6 +38,7 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.collection.render.NodeController;
+import com.android.systemui.statusbar.notification.collection.render.NotifViewController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.dagger.AppName;
@@ -58,7 +61,8 @@
  * Controller for {@link ExpandableNotificationRow}.
  */
 @NotificationRowScope
-public class ExpandableNotificationRowController implements NodeController {
+public class ExpandableNotificationRowController implements NotifViewController {
+    private static final String TAG = "NotifRowController";
     private final ExpandableNotificationRow mView;
     private final NotificationListContainer mListContainer;
     private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory;
@@ -267,4 +271,28 @@
         final List<ExpandableNotificationRow> mChildren = mView.getAttachedChildren();
         return mChildren != null ? mChildren.size() : 0;
     }
+
+    @Override
+    public void setUntruncatedChildCount(int childCount) {
+        if (mView.isSummaryWithChildren()) {
+            mView.setUntruncatedChildCount(childCount);
+        } else {
+            Log.w(TAG, "Called setUntruncatedChildCount(" + childCount + ") on a leaf row");
+        }
+    }
+
+    @Override
+    public void setSystemExpanded(boolean systemExpanded) {
+        mView.setSystemExpanded(systemExpanded);
+    }
+
+    @Override
+    public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
+        mView.setLastAudiblyAlertedMs(lastAudiblyAlertedMs);
+    }
+
+    @Override
+    public void showFeedbackIcon(boolean show, Pair<Integer, Integer> feedbackResources) {
+        mView.showFeedbackIcon(show, feedbackResources);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
index caba3ac..c661408 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.widget.ConversationLayout;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.NotificationFadeAware;
 
 /**
  * A hybrid view which may contain information about one ore more conversations.
@@ -138,4 +139,14 @@
         lp.height = size;
         view.setLayoutParams(lp);
     }
+
+    /**
+     * Apply the faded state as a layer type change to the face pile view which needs to have
+     * overlapping contents render precisely.
+     */
+    @Override
+    public void setNotificationFaded(boolean faded) {
+        super.setNotificationFaded(faded);
+        NotificationFadeAware.setLayerTypeForFaded(mConversationFacePile, faded);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
index bc2adac3..c0d85a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
@@ -28,13 +28,14 @@
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.ViewTransformationHelper;
+import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.TransformState;
 
 /**
  * A hybrid view which may contain information about one ore more notifications.
  */
 public class HybridNotificationView extends AlphaOptimizedLinearLayout
-        implements TransformableView {
+        implements TransformableView, NotificationFadeAware {
 
     protected final ViewTransformationHelper mTransformationHelper = new ViewTransformationHelper();
     protected TextView mTitleView;
@@ -148,4 +149,7 @@
         setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
         mTransformationHelper.setVisible(visible);
     }
+
+    @Override
+    public void setNotificationFaded(boolean faded) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 727f0e5..438992e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -46,6 +46,7 @@
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -73,7 +74,7 @@
  * expanded and heads up layout. This class is responsible for clipping the content and and
  * switching between the expanded, contracted and the heads up view depending on its clipped size.
  */
-public class NotificationContentView extends FrameLayout {
+public class NotificationContentView extends FrameLayout implements NotificationFadeAware {
 
     private static final String TAG = "NotificationContentView";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -2045,6 +2046,41 @@
         return Notification.COLOR_INVALID;
     }
 
+    /**
+     * Delegate the faded state to the notification content views which actually
+     * need to have overlapping contents render precisely.
+     */
+    @Override
+    public void setNotificationFaded(boolean faded) {
+        if (mContractedWrapper != null) {
+            mContractedWrapper.setNotificationFaded(faded);
+        }
+        if (mHeadsUpWrapper != null) {
+            mHeadsUpWrapper.setNotificationFaded(faded);
+        }
+        if (mExpandedWrapper != null) {
+            mExpandedWrapper.setNotificationFaded(faded);
+        }
+        if (mSingleLineView != null) {
+            mSingleLineView.setNotificationFaded(faded);
+        }
+    }
+
+    /**
+     * @return true if a visible view has a remote input active, as this requires that the entire
+     * row report that it has overlapping rendering.
+     */
+    public boolean requireRowToHaveOverlappingRendering() {
+        // This inexpensive check is done on both states to avoid state invalidating the result.
+        if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive()) {
+            return true;
+        }
+        if (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive()) {
+            return true;
+        }
+        return false;
+    }
+
     public void setRemoteInputViewSubcomponentFactory(RemoteInputViewSubcomponent.Factory factory) {
         mRemoteInputSubcomponentFactory = factory;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
index 12e94cb..bb43b95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
@@ -21,6 +21,7 @@
 import com.android.internal.widget.CachingIconView
 import com.android.internal.widget.CallLayout
 import com.android.systemui.R
+import com.android.systemui.statusbar.notification.NotificationFadeAware
 import com.android.systemui.statusbar.notification.NotificationUtils
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 
@@ -37,6 +38,7 @@
             NotificationUtils.getFontScaledHeight(ctx, R.dimen.notification_max_height)
     private val callLayout: CallLayout = view as CallLayout
 
+    private lateinit var conversationIconContainer: View
     private lateinit var conversationIconView: CachingIconView
     private lateinit var conversationBadgeBg: View
     private lateinit var expandBtn: View
@@ -45,6 +47,8 @@
 
     private fun resolveViews() {
         with(callLayout) {
+            conversationIconContainer =
+                    requireViewById(com.android.internal.R.id.conversation_icon_container)
             conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon)
             conversationBadgeBg =
                     requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
@@ -82,4 +86,14 @@
     }
 
     override fun getMinLayoutHeight(): Int = minHeightWithActions
+
+    /**
+     * Apply the faded state as a layer type change to the face pile view which needs to have
+     * overlapping contents render precisely.
+     */
+    override fun setNotificationFaded(faded: Boolean) {
+        // Do not call super
+        NotificationFadeAware.setLayerTypeForFaded(expandBtn, faded)
+        NotificationFadeAware.setLayerTypeForFaded(conversationIconContainer, faded)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index 3ef5b665..e136055 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -23,6 +23,7 @@
 import com.android.internal.widget.ConversationLayout
 import com.android.internal.widget.MessagingLinearLayout
 import com.android.systemui.R
+import com.android.systemui.statusbar.notification.NotificationFadeAware
 import com.android.systemui.statusbar.notification.NotificationUtils
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationMessagingTemplateViewWrapper.setCustomImageMessageTransform
@@ -42,6 +43,7 @@
     )
     private val conversationLayout: ConversationLayout = view as ConversationLayout
 
+    private lateinit var conversationIconContainer: View
     private lateinit var conversationIconView: CachingIconView
     private lateinit var conversationBadgeBg: View
     private lateinit var expandBtn: View
@@ -59,6 +61,8 @@
         messagingLinearLayout = conversationLayout.messagingLinearLayout
         imageMessageContainer = conversationLayout.imageMessageContainer
         with(conversationLayout) {
+            conversationIconContainer =
+                    requireViewById(com.android.internal.R.id.conversation_icon_container)
             conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon)
             conversationBadgeBg =
                     requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
@@ -136,4 +140,10 @@
                 minHeightWithActions
             else
                 super.getMinLayoutHeight()
+
+    override fun setNotificationFaded(faded: Boolean) {
+        // Do not call super
+        NotificationFadeAware.setLayerTypeForFaded(expandBtn, faded)
+        NotificationFadeAware.setLayerTypeForFaded(conversationIconContainer, faded)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java
index 4c9c2f9..fdff12d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java
@@ -22,6 +22,7 @@
 
 import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
 /**
@@ -86,4 +87,14 @@
     public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
         return true;
     }
+
+    /**
+     * Apply the faded state as a layer type change to the custom view which needs to have
+     * overlapping contents render precisely.
+     */
+    @Override
+    public void setNotificationFaded(boolean faded) {
+        super.setNotificationFaded(faded);
+        NotificationFadeAware.setLayerTypeForFaded(mView, faded);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java
index 8c6fa02..3159539 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java
@@ -20,6 +20,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
 /**
@@ -67,4 +68,14 @@
         }
         super.onContentUpdated(row);
     }
+
+    /**
+     * Apply the faded state as a layer type change to the custom view which needs to have
+     * overlapping contents render precisely.
+     */
+    @Override
+    public void setNotificationFaded(boolean faded) {
+        super.setNotificationFaded(faded);
+        NotificationFadeAware.setLayerTypeForFaded(mWrappedView, faded);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 7630191..6c3e0d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -42,6 +42,7 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.TransformState;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
@@ -395,4 +396,13 @@
      */
     public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
     }
+
+    /**
+     * Apply the faded state as a layer type change to the views which need to have overlapping
+     * contents render precisely.
+     */
+    public void setNotificationFaded(boolean faded) {
+        NotificationFadeAware.setLayerTypeForFaded(getIcon(), faded);
+        NotificationFadeAware.setLayerTypeForFaded(getExpandButton(), faded);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index a472710..1875124 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -38,6 +38,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.NotificationGroupingUtil;
+import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,7 +52,8 @@
 /**
  * A container containing child notifications
  */
-public class NotificationChildrenContainer extends ViewGroup {
+public class NotificationChildrenContainer extends ViewGroup
+        implements NotificationFadeAware {
 
     @VisibleForTesting
     static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2;
@@ -111,6 +113,7 @@
     private int mCurrentHeaderTranslation = 0;
     private float mHeaderVisibleAmount = 1.0f;
     private int mUntruncatedChildCount;
+    private boolean mContainingNotificationIsFaded = false;
 
     public NotificationChildrenContainer(Context context) {
         this(context, null);
@@ -277,6 +280,7 @@
         mDividers.add(newIndex, divider);
 
         row.setContentTransformationAmount(0, false /* isLastChild */);
+        row.setNotificationFaded(mContainingNotificationIsFaded);
         // It doesn't make sense to keep old animations around, lets cancel them!
         ExpandableViewState viewState = row.getViewState();
         if (viewState != null) {
@@ -301,6 +305,7 @@
         });
 
         row.setSystemChildExpanded(false);
+        row.setNotificationFaded(false);
         row.setUserLocked(false);
         if (!row.isRemoved()) {
             mGroupingUtil.restoreChildNotification(row);
@@ -473,7 +478,8 @@
         return result;
     }
 
-    private void updateExpansionStates() {
+    /** To be called any time the rows have been updated */
+    public void updateExpansionStates() {
         if (mChildrenExpanded || mUserLocked) {
             // we don't modify it the group is expanded or if we are expanding it
             return;
@@ -1308,4 +1314,18 @@
             mNotificationHeaderWrapperLowPriority.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
         }
     }
+
+    @Override
+    public void setNotificationFaded(boolean faded) {
+        mContainingNotificationIsFaded = faded;
+        if (mNotificationHeaderWrapper != null) {
+            mNotificationHeaderWrapper.setNotificationFaded(faded);
+        }
+        if (mNotificationHeaderWrapperLowPriority != null) {
+            mNotificationHeaderWrapperLowPriority.setNotificationFaded(faded);
+        }
+        for (ExpandableNotificationRow child : mAttachedChildren) {
+            child.setNotificationFaded(faded);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index a10d309..b9f0241 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -678,7 +678,7 @@
         // TODO: move this logic to controller, which will invoke updateFooterView directly
         boolean showDismissView = mClearAllEnabled &&
                 mController.hasActiveClearableNotifications(ROWS_ALL);
-        boolean showFooterView = (showDismissView || getVisibleNotificationCount() > 0)
+        boolean showFooterView = (showDismissView || mController.getVisibleNotificationCount() > 0)
                 && mIsCurrentUserSetup  // see: b/193149550
                 && mStatusBarState != StatusBarState.KEYGUARD
                 && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
@@ -1173,20 +1173,6 @@
         }
     }
 
-    /**
-     * Returns best effort count of visible notifications.
-     */
-    public int getVisibleNotificationCount() {
-        int count = 0;
-        for (int i = 0; i < getChildCount(); i++) {
-            final View child = getChildAt(i);
-            if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) {
-                count++;
-            }
-        }
-        return count;
-    }
-
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private boolean isCurrentlyAnimating() {
         return mStateAnimator.isRunning();
@@ -1458,7 +1444,7 @@
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private float getAppearEndPosition() {
         int appearPosition = 0;
-        int visibleNotifCount = getVisibleNotificationCount();
+        int visibleNotifCount = mController.getVisibleNotificationCount();
         if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) {
             if (isHeadsUpTransition()
                     || (mInHeadsUpPinnedMode && !mAmbientState.isDozing())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index f7a3e3c..dd68f94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -48,6 +48,7 @@
 import android.view.ViewGroup;
 import android.view.WindowInsets;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -98,6 +99,8 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
+import com.android.systemui.statusbar.notification.collection.render.NotifStats;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 import com.android.systemui.statusbar.notification.dagger.SilentHeader;
@@ -192,6 +195,8 @@
 
     private final NotificationListContainerImpl mNotificationListContainer =
             new NotificationListContainerImpl();
+    private final NotifStackController mNotifStackController =
+            new NotifStackControllerImpl();
 
     @Nullable
     private NotificationActivityStarter mNotificationActivityStarter;
@@ -294,6 +299,8 @@
         }
     };
 
+    private NotifStats mNotifStats = NotifStats.getEmpty();
+
     private void updateResources() {
         mNotificationDragDownMovement = mResources.getDimensionPixelSize(
                 R.dimen.lockscreen_shade_notification_movement);
@@ -988,7 +995,7 @@
     }
 
     public int getVisibleNotificationCount() {
-        return mView.getVisibleNotificationCount();
+        return mNotifStats.getNumActiveNotifs();
     }
 
     public int getIntrinsicContentHeight() {
@@ -1183,7 +1190,7 @@
     public void updateShowEmptyShadeView() {
         mShowEmptyShadeView = mBarState != KEYGUARD
                 && (!mView.isQsExpanded() || mView.isUsingSplitNotificationShade())
-                && mView.getVisibleNotificationCount() == 0;
+                && getVisibleNotificationCount() == 0;
 
         mView.updateEmptyShadeView(
                 mShowEmptyShadeView,
@@ -1246,29 +1253,22 @@
     }
 
     public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
-        if (mDynamicPrivacyController.isInLockedDownShade()) {
-            return false;
+        boolean hasAlertingMatchingClearable = isClearable
+                ? mNotifStats.getHasClearableAlertingNotifs()
+                : mNotifStats.getHasNonClearableAlertingNotifs();
+        boolean hasSilentMatchingClearable = isClearable
+                ? mNotifStats.getHasClearableSilentNotifs()
+                : mNotifStats.getHasNonClearableSilentNotifs();
+        switch (selection) {
+            case ROWS_GENTLE:
+                return hasSilentMatchingClearable;
+            case ROWS_HIGH_PRIORITY:
+                return hasAlertingMatchingClearable;
+            case ROWS_ALL:
+                return hasSilentMatchingClearable || hasAlertingMatchingClearable;
+            default:
+                throw new IllegalStateException("Bad selection: " + selection);
         }
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = getChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
-                continue;
-            }
-            final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            final boolean matchClearable =
-                    isClearable ? row.canViewBeDismissed() : !row.canViewBeDismissed();
-            final boolean inSection =
-                    NotificationStackScrollLayout.matchesSelection(row, selection);
-            if (matchClearable && inSection) {
-                if (mLegacyGroupManager == null
-                        || !mLegacyGroupManager.isSummaryOfSuppressedGroup(
-                        row.getEntry().getSbn())) {
-                    return true;
-                }
-            }
-        }
-        return false;
     }
 
     /**
@@ -1383,6 +1383,10 @@
         return mNotificationListContainer;
     }
 
+    public NotifStackController getNotifStackController() {
+        return mNotifStackController;
+    }
+
     public void resetCheckSnoozeLeavebehind() {
         mView.resetCheckSnoozeLeavebehind();
     }
@@ -1394,17 +1398,6 @@
                 mVisibilityProvider.obtain(entry, true));
     }
 
-    /**
-     * @return if the shade has currently any active notifications.
-     */
-    public boolean hasActiveNotifications() {
-        if (mNotifPipelineFlags.isNewPipelineEnabled()) {
-            return !mNotifPipeline.getShadeList().isEmpty();
-        } else {
-            return mNotificationEntryManager.hasActiveNotifications();
-        }
-    }
-
     public void closeControlsIfOutsideTouch(MotionEvent ev) {
         NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
         NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
@@ -1876,4 +1869,13 @@
             }
         }
     }
+
+    private class NotifStackControllerImpl implements NotifStackController {
+        @Override
+        public void setNotifStats(@NonNull NotifStats notifStats) {
+            mNotifStats = notifStats;
+            updateFooter();
+            updateShowEmptyShadeView();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
index 6d82a45..83bea84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
@@ -29,6 +29,7 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.NotificationFadeAware.FadeOptimizedNotification;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
@@ -206,14 +207,26 @@
         } else if (view.getAlpha() != this.alpha) {
             // apply layer type
             boolean becomesFullyVisible = this.alpha == 1.0f;
-            boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
-                    && view.hasOverlappingRendering();
-            int layerType = view.getLayerType();
-            int newLayerType = newLayerTypeIsHardware
-                    ? View.LAYER_TYPE_HARDWARE
-                    : View.LAYER_TYPE_NONE;
-            if (layerType != newLayerType) {
-                view.setLayerType(newLayerType, null);
+            boolean becomesFaded = !becomesInvisible && !becomesFullyVisible;
+            if (FadeOptimizedNotification.FADE_LAYER_OPTIMIZATION_ENABLED
+                    && view instanceof FadeOptimizedNotification) {
+                // NOTE: A view that's going to utilize this interface to avoid having a hardware
+                //  layer will have to return false from hasOverlappingRendering(), so we
+                //  intentionally do not check that value in this if, even though we do in the else.
+                FadeOptimizedNotification fadeOptimizedView = (FadeOptimizedNotification) view;
+                boolean isFaded = fadeOptimizedView.isNotificationFaded();
+                if (isFaded != becomesFaded) {
+                    fadeOptimizedView.setNotificationFaded(becomesFaded);
+                }
+            } else {
+                boolean newLayerTypeIsHardware = becomesFaded && view.hasOverlappingRendering();
+                int layerType = view.getLayerType();
+                int newLayerType = newLayerTypeIsHardware
+                        ? View.LAYER_TYPE_HARDWARE
+                        : View.LAYER_TYPE_NONE;
+                if (layerType != newLayerType) {
+                    view.setLayerType(newLayerType, null);
+                }
             }
 
             // apply alpha
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 67f51cb..aa3b3e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.fingerprint.FingerprintManager;
 import android.metrics.LogMaker;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -46,9 +47,11 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.FileDescriptor;
@@ -71,6 +74,7 @@
     private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
     private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
     private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
+    private static final int FP_ATTEMPTS_BEFORE_SHOW_BOUNCER = 3;
 
     @IntDef(prefix = { "MODE_" }, value = {
             MODE_NONE,
@@ -167,6 +171,10 @@
 
     private final MetricsLogger mMetricsLogger;
     private final AuthController mAuthController;
+    private final StatusBarStateController mStatusBarStateController;
+
+    private long mLastFpFailureUptimeMillis;
+    private int mNumConsecutiveFpFailures;
 
     private static final class PendingAuthenticated {
         public final int userId;
@@ -209,7 +217,10 @@
         BIOMETRIC_IRIS_FAILURE(403),
 
         @UiEvent(doc = "A biometric event of type iris errored.")
-        BIOMETRIC_IRIS_ERROR(404);
+        BIOMETRIC_IRIS_ERROR(404),
+
+        @UiEvent(doc = "Bouncer was shown as a result of consecutive failed UDFPS attempts.")
+        BIOMETRIC_BOUNCER_SHOWN(916);
 
         private final int mId;
 
@@ -257,7 +268,8 @@
             NotificationMediaManager notificationMediaManager,
             WakefulnessLifecycle wakefulnessLifecycle,
             ScreenLifecycle screenLifecycle,
-            AuthController authController) {
+            AuthController authController,
+            StatusBarStateController statusBarStateController) {
         mContext = context;
         mPowerManager = powerManager;
         mShadeController = shadeController;
@@ -279,6 +291,7 @@
         mKeyguardBypassController.setUnlockController(this);
         mMetricsLogger = metricsLogger;
         mAuthController = authController;
+        mStatusBarStateController = statusBarStateController;
         dumpManager.registerDumpable(getClass().getName(), this);
     }
 
@@ -620,6 +633,22 @@
                 .setType(MetricsEvent.TYPE_FAILURE).setSubtype(toSubtype(biometricSourceType)));
         Optional.ofNullable(BiometricUiEvent.FAILURE_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
                 .ifPresent(UI_EVENT_LOGGER::log);
+
+        long currUptimeMillis = SystemClock.uptimeMillis();
+        if (currUptimeMillis - mLastFpFailureUptimeMillis < 2000) { // attempt within 2 seconds
+            mNumConsecutiveFpFailures += 1;
+        } else {
+            mNumConsecutiveFpFailures = 1;
+        }
+        mLastFpFailureUptimeMillis = currUptimeMillis;
+
+        if (biometricSourceType.equals(BiometricSourceType.FINGERPRINT)
+                && mUpdateMonitor.isUdfpsSupported()
+                && mNumConsecutiveFpFailures >= FP_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
+            mKeyguardViewController.showBouncer(true);
+            UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN);
+            mNumConsecutiveFpFailures = 0;
+        }
         cleanup();
     }
 
@@ -631,6 +660,16 @@
                 .addTaggedData(MetricsEvent.FIELD_BIOMETRIC_AUTH_ERROR, msgId));
         Optional.ofNullable(BiometricUiEvent.ERROR_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
                 .ifPresent(UI_EVENT_LOGGER::log);
+
+        // if we're on the shade and we're locked out, immediately show the bouncer
+        if (biometricSourceType == BiometricSourceType.FINGERPRINT
+                && (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
+                || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
+                && mUpdateMonitor.isUdfpsSupported()
+                && (mStatusBarStateController.getState() == StatusBarState.SHADE
+                    || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED)) {
+            mKeyguardViewController.showBouncer(true);
+        }
         cleanup();
     }
 
@@ -664,6 +703,8 @@
             mBiometricModeListener.onResetMode();
             mBiometricModeListener.notifyBiometricAuthModeChanged();
         }
+        mNumConsecutiveFpFailures = 0;
+        mLastFpFailureUptimeMillis = 0;
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 927b4c8..0664900 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -24,6 +24,7 @@
 import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.RootView;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
@@ -35,15 +36,19 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
+import javax.inject.Inject;
+
 /**
  * Controls the appearance of heads up notifications in the icon area and the header itself.
  */
+@StatusBarFragmentScope
 public class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
         DarkIconDispatcher.DarkReceiver, NotificationWakeUpCoordinator.WakeUpListener {
     public static final int CONTENT_FADE_DURATION = 110;
@@ -83,7 +88,7 @@
     Point mPoint;
     private KeyguardStateController mKeyguardStateController;
 
-
+    @Inject
     public HeadsUpAppearanceController(
             NotificationIconAreaController notificationIconAreaController,
             HeadsUpManagerPhone headsUpManager,
@@ -92,11 +97,15 @@
             KeyguardBypassController keyguardBypassController,
             KeyguardStateController keyguardStateController,
             NotificationWakeUpCoordinator wakeUpCoordinator, CommandQueue commandQueue,
-            NotificationPanelViewController notificationPanelViewController, View statusBarView) {
+            NotificationPanelViewController notificationPanelViewController,
+            @RootView PhoneStatusBarView statusBarView) {
         this(notificationIconAreaController, headsUpManager, statusBarStateController,
                 keyguardBypassController, wakeUpCoordinator, keyguardStateController,
                 commandQueue, notificationStackScrollLayoutController,
                 notificationPanelViewController,
+                // TODO(b/205609837): We should have the StatusBarFragmentComponent provide these
+                //  four views, and then we can delete this constructor and just use the one below
+                //  (which also removes the undesirable @VisibleForTesting).
                 statusBarView.findViewById(R.id.heads_up_status_bar_view),
                 statusBarView.findViewById(R.id.clock),
                 statusBarView.findViewById(R.id.operator_name_frame),
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 353868b..9647486 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -21,6 +21,7 @@
 
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.hardware.biometrics.BiometricSourceType;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -81,6 +82,13 @@
                 public void onStrongAuthStateChanged(int userId) {
                     mBouncerPromptReason = mCallback.getBouncerPromptReason();
                 }
+
+                @Override
+                public void onLockedOutStateChanged(BiometricSourceType type) {
+                    if (type == BiometricSourceType.FINGERPRINT) {
+                        mBouncerPromptReason = mCallback.getBouncerPromptReason();
+                    }
+                }
             };
     private final Runnable mRemoveViewRunnable = this::removeView;
     private final KeyguardBypassController mKeyguardBypassController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 570b0ca..88ae0db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -37,7 +37,6 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.policy.BatteryController;
 
 import java.io.FileDescriptor;
@@ -251,7 +250,7 @@
 
     private void updateNavigation() {
         if (mNavigationBarController != null
-                && !QuickStepContract.isGesturalMode(mNavigationMode)) {
+                && mNavigationBarController.supportsIconTintForNavMode(mNavigationMode)) {
             mNavigationBarController.setIconsDark(mNavigationLight, animateChange());
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index 9021b74..415fb92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -28,6 +28,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -230,6 +231,14 @@
     }
 
     /**
+     * Return whether to use the tint calculated in this class for nav icons.
+     */
+    public boolean supportsIconTintForNavMode(int navigationMode) {
+        // In gesture mode, we already do region sampling to update tint based on content beneath.
+        return !QuickStepContract.isGesturalMode(navigationMode);
+    }
+
+    /**
      * Interface to apply a specific dark intensity.
      */
     public interface DarkIntensityApplier {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index 3f33281..68ab077 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
@@ -124,6 +124,9 @@
                         public void onAnimationEnd(Animator a) {
                             mLightsOutNotifView.setAlpha(showDot ? 1 : 0);
                             mLightsOutNotifView.setVisibility(showDot ? View.VISIBLE : View.GONE);
+                            // Unset the listener, otherwise this may persist for
+                            // another view property animation
+                            mLightsOutNotifView.animate().setListener(null);
                         }
                     })
                     .start();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
index 9cefded..bf54677 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
@@ -120,6 +120,9 @@
                         setAlpha(1f);
                         setTranslationX(0);
                         cancelLongClick();
+                        // Unset the listener, otherwise this may persist for
+                        // another view property animation
+                        animate().setListener(null);
                     }
 
                     @Override
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 94b010d..d26ac29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1172,16 +1172,16 @@
                     HeadsUpAppearanceController oldController = mHeadsUpAppearanceController;
                     if (mHeadsUpAppearanceController != null) {
                         // This view is being recreated, let's destroy the old one
+                        // TODO(b/205609837): Automatically destroy the old controller so that this
+                        //  class doesn't need to hold a reference to the old one.
                         mHeadsUpAppearanceController.destroy();
                     }
                     // TODO (b/136993073) Separate notification shade and status bar
                     // TODO(b/205609837): Migrate this to StatusBarFragmentComponent.
-                    mHeadsUpAppearanceController = new HeadsUpAppearanceController(
-                            mNotificationIconAreaController, mHeadsUpManager,
-                            mStackScrollerController,
-                            mStatusBarStateController, mKeyguardBypassController,
-                            mKeyguardStateController, mWakeUpCoordinator, mCommandQueue,
-                            mNotificationPanelViewController, mStatusBarView);
+                    mHeadsUpAppearanceController =
+                            statusBarFragmentComponent.getHeadsUpAppearanceController();
+                    // TODO(b/205609837): Delete this readFrom method so that this class doesn't
+                    //  need to hold a reference to the old controller.
                     mHeadsUpAppearanceController.readFrom(oldController);
 
                     mLightsOutNotifController.setLightsOutNotifView(
@@ -1488,6 +1488,7 @@
                 mBubblesOptional,
                 mPresenter,
                 mStackScrollerController.getNotificationListContainer(),
+                mStackScrollerController.getNotifStackController(),
                 mNotificationActivityStarter,
                 mPresenter);
     }
@@ -1909,10 +1910,6 @@
         mScrimController.setKeyguardOccluded(occluded);
     }
 
-    public boolean headsUpShouldBeVisible() {
-        return mHeadsUpAppearanceController.shouldBeVisible();
-    }
-
     /** A launch animation was cancelled. */
     //TODO: These can / should probably be moved to NotificationPresenter or ShadeController
     public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
@@ -1992,7 +1989,7 @@
         }
     }
 
-    public void maybeEscalateHeadsUp() {
+    private void maybeEscalateHeadsUp() {
         mHeadsUpManager.getAllEntries().forEach(entry -> {
             final StatusBarNotification sbn = entry.getSbn();
             final Notification notification = sbn.getNotification();
@@ -2003,6 +2000,7 @@
                 try {
                     EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
                             sbn.getKey());
+                    wakeUpForFullScreenIntent();
                     notification.fullScreenIntent.send();
                     entry.notifyFullScreenIntentLaunched();
                 } catch (PendingIntent.CanceledException e) {
@@ -2012,6 +2010,17 @@
         mHeadsUpManager.releaseAllImmediately();
     }
 
+    void wakeUpForFullScreenIntent() {
+        if (isGoingToSleep() || mDozing) {
+            mPowerManager.wakeUp(
+                    SystemClock.uptimeMillis(),
+                    PowerManager.WAKE_REASON_APPLICATION,
+                    "com.android.systemui:full_screen_intent");
+            mWakeUpComingFromTouch = false;
+            mWakeUpTouchLocation = null;
+        }
+    }
+
     void makeExpandedVisible(boolean force) {
         if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
         if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
@@ -3541,7 +3550,7 @@
             DejankUtils.startDetectingBlockingIpcs(tag);
             updateRevealEffect(false /* wakingUp */);
             updateNotificationPanelTouchState();
-            notifyHeadsUpGoingToSleep();
+            maybeEscalateHeadsUp();
             dismissVolumeDialog();
             mWakeUpCoordinator.setFullyAwake(false);
             mBypassHeadsUpNotifier.setFullyAwake(false);
@@ -4086,10 +4095,6 @@
         }
     }
 
-    protected void notifyHeadsUpGoingToSleep() {
-        maybeEscalateHeadsUp();
-    }
-
     /**
      * @return Whether the security bouncer from Keyguard is showing.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
index a77a097..ae3b7ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
@@ -30,11 +30,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.media.AudioAttributes;
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.util.Log;
@@ -106,10 +106,8 @@
     private final VibrationEffect mCameraLaunchGestureVibrationEffect;
 
 
-    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
-            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
-            .build();
+    private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
+            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
 
     @Inject
     StatusBarCommandQueueCallbacks(
@@ -611,9 +609,9 @@
     }
 
     private void vibrateForCameraGesture() {
-        // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
         mVibratorOptional.ifPresent(
-                v -> v.vibrate(mCameraLaunchGestureVibrationEffect, VIBRATION_ATTRIBUTES));
+                v -> v.vibrate(mCameraLaunchGestureVibrationEffect,
+                        HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES));
     }
 
     private static VibrationEffect getCameraGestureVibrationEffect(
@@ -627,6 +625,8 @@
                     .compose();
         }
         if (vibratorOptional.isPresent() && vibratorOptional.get().hasAmplitudeControl()) {
+            // Make sure to pass -1 for repeat so VibratorManagerService doesn't stop us when going
+            // to sleep.
             return VibrationEffect.createWaveform(
                     StatusBar.CAMERA_LAUNCH_GESTURE_VIBRATION_TIMINGS,
                     StatusBar.CAMERA_LAUNCH_GESTURE_VIBRATION_AMPLITUDES,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 7ab4a1e..e2bf0db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -441,6 +441,8 @@
      * dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
      */
     public void showBouncer(boolean scrimmed) {
+        resetAlternateAuth(false);
+
         if (mShowing && !mBouncer.isShowing()) {
             mBouncer.show(false /* resetSecuritySelection */, scrimmed);
         }
@@ -553,12 +555,13 @@
     public void onStartedWakingUp() {
         mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
                 .setAnimationsDisabled(false);
-        View currentView = getCurrentNavBarView();
-        if (currentView != null) {
-            currentView.animate()
-                    .alpha(1f)
-                    .setDuration(NAV_BAR_CONTENT_FADE_DURATION)
-                    .start();
+        NavigationBarView navBarView = mStatusBar.getNavigationBarView();
+        if (navBarView != null) {
+            navBarView.forEachView(view ->
+                    view.animate()
+                            .alpha(1f)
+                            .setDuration(NAV_BAR_CONTENT_FADE_DURATION)
+                            .start());
         }
     }
 
@@ -566,12 +569,13 @@
     public void onStartedGoingToSleep() {
         mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
                 .setAnimationsDisabled(true);
-        View currentView = getCurrentNavBarView();
-        if (currentView != null) {
-            currentView.animate()
-                    .alpha(0f)
-                    .setDuration(NAV_BAR_CONTENT_FADE_DURATION)
-                    .start();
+        NavigationBarView navBarView = mStatusBar.getNavigationBarView();
+        if (navBarView != null) {
+            navBarView.forEachView(view ->
+                    view.animate()
+                            .alpha(0f)
+                            .setDuration(NAV_BAR_CONTENT_FADE_DURATION)
+                            .start());
         }
     }
 
@@ -1013,17 +1017,6 @@
         mStatusBar.onKeyguardViewManagerStatesUpdated();
     }
 
-    /**
-     * Updates the visibility of the nav bar content views.
-     */
-    private void updateNavigationBarContentVisibility(boolean navBarContentVisible) {
-        final NavigationBarView navBarView = mStatusBar.getNavigationBarView();
-        if (navBarView != null && navBarView.getCurrentView() != null) {
-            final View currentView = navBarView.getCurrentView();
-            currentView.setVisibility(navBarContentVisible ? View.VISIBLE : View.INVISIBLE);
-        }
-    }
-
     private View getCurrentNavBarView() {
         final NavigationBarView navBarView = mStatusBar.getNavigationBarView();
         return navBarView != null ? navBarView.getCurrentView() : null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 11ed8cd..863ce57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -41,6 +41,8 @@
 import android.util.EventLog;
 import android.view.View;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.NotificationVisibility;
@@ -588,7 +590,8 @@
         }
     }
 
-    private void handleFullScreenIntent(NotificationEntry entry) {
+    @VisibleForTesting
+    void handleFullScreenIntent(NotificationEntry entry) {
         if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
             if (shouldSuppressFullScreenIntent(entry)) {
                 mLogger.logFullScreenIntentSuppressedByDnD(entry.getKey());
@@ -612,6 +615,7 @@
                 try {
                     EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
                             entry.getKey());
+                    mStatusBar.wakeUpForFullScreenIntent();
                     fullscreenIntent.send();
                     entry.notifyFullScreenIntentLaunched();
                     mMetricsLogger.count("note_fullscreen", 1);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 9682c60..c8e1cdc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -192,6 +192,7 @@
         initController.addPostInitTask(() -> {
             mKeyguardIndicationController.init();
             mViewHierarchyManager.setUpWithPresenter(this,
+                    stackScrollerController.getNotifStackController(),
                     stackScrollerController.getNotificationListContainer());
             mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
             mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 1130ec2..ed52a81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -36,6 +36,8 @@
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.animation.DialogListener;
@@ -303,13 +305,32 @@
      * the screen off / close system dialogs broadcast.
      * <p>
      * <strong>Note:</strong> Don't call dialog.setOnDismissListener() after
-     * calling this because it causes a leak of BroadcastReceiver.
+     * calling this because it causes a leak of BroadcastReceiver. Instead, call the version that
+     * takes an extra Runnable as a parameter.
      *
      * @param dialog The dialog to be associated with the listener.
      */
     public static void registerDismissListener(Dialog dialog) {
+        registerDismissListener(dialog, null);
+    }
+
+
+    /**
+     * Registers a listener that dismisses the given dialog when it receives
+     * the screen off / close system dialogs broadcast.
+     * <p>
+     * <strong>Note:</strong> Don't call dialog.setOnDismissListener() after
+     * calling this because it causes a leak of BroadcastReceiver.
+     *
+     * @param dialog The dialog to be associated with the listener.
+     * @param dismissAction An action to run when the dialog is dismissed.
+     */
+    public static void registerDismissListener(Dialog dialog, @Nullable Runnable dismissAction) {
         DismissReceiver dismissReceiver = new DismissReceiver(dialog);
-        dialog.setOnDismissListener(d -> dismissReceiver.unregister());
+        dialog.setOnDismissListener(d -> {
+            dismissReceiver.unregister();
+            if (dismissAction != null) dismissAction.run();
+        });
         dismissReceiver.register();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 8f11819..beed60b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -69,7 +69,7 @@
 import dagger.Module;
 import dagger.Provides;
 
-@Module
+@Module(subcomponents = StatusBarFragmentComponent.class)
 public abstract class StatusBarViewModule {
 
     public static final String SPLIT_SHADE_HEADER = "split_shade_header";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 2e3893a..2762b4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -329,8 +329,8 @@
     }
 
     protected int adjustDisableFlags(int state) {
-        boolean headsUpVisible = mStatusBarOptionalLazy.get()
-                .map(StatusBar::headsUpShouldBeVisible).orElse(false);
+        boolean headsUpVisible =
+                mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible();
         if (headsUpVisible) {
             state |= DISABLE_CLOCK;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
index 47c1875..6e31192 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
@@ -18,6 +18,7 @@
 
 import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.dagger.qualifiers.RootView;
+import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 
@@ -66,4 +67,8 @@
     @StatusBarFragmentScope
     @RootView
     PhoneStatusBarView getPhoneStatusBarView();
+
+    /** */
+    @StatusBarFragmentScope
+    HeadsUpAppearanceController getHeadsUpAppearanceController();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 364c931..cdbdb23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -351,7 +351,7 @@
                 boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers)
                         && guestRecord == null;
                 boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers)
-                        && mUserManager.canAddMoreUsers();
+                        && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
                 boolean createIsRestricted = !addUsersWhenLocked;
 
                 if (guestRecord == null) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
index 58e0cb2..3696ec5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
@@ -16,22 +16,53 @@
 
 package com.android.systemui.animation
 
+import android.graphics.drawable.Drawable
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import android.widget.LinearLayout
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewParent
 import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.SysuiTestCase
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() {
+    @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
+    @Mock lateinit var view: View
+    @Mock lateinit var rootView: ViewGroup
+    @Mock lateinit var viewParent: ViewParent
+    @Mock lateinit var drawable: Drawable
+    lateinit var controller: GhostedViewLaunchAnimatorController
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        whenever(view.rootView).thenReturn(rootView)
+        whenever(view.background).thenReturn(drawable)
+        whenever(view.height).thenReturn(0)
+        whenever(view.width).thenReturn(0)
+        whenever(view.parent).thenReturn(viewParent)
+        whenever(view.visibility).thenReturn(View.VISIBLE)
+        whenever(view.invalidate()).then { /* NO-OP */ }
+        whenever(view.getLocationOnScreen(any())).then { /* NO-OP */ }
+        whenever(interactionJankMonitor.begin(any(), anyInt())).thenReturn(true)
+        whenever(interactionJankMonitor.end(anyInt())).thenReturn(true)
+        controller = GhostedViewLaunchAnimatorController(view, 0, interactionJankMonitor)
+    }
+
     @Test
     fun animatingOrphanViewDoesNotCrash() {
-        val ghostedView = LinearLayout(mContext)
-        val controller = GhostedViewLaunchAnimatorController(ghostedView)
         val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
 
         controller.onIntentStarted(willAnimate = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 0e86964..1cf21ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -351,9 +351,8 @@
         mSystemClock.advanceTime(205);
         mController.onTouchOutsideView();
 
-        // THEN show the bouncer and reset alt auth
+        // THEN show the bouncer
         verify(mStatusBarKeyguardViewManager).showBouncer(eq(true));
-        verify(mStatusBarKeyguardViewManager).resetAlternateAuth(anyBoolean());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 6d8645e..b774daf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -41,6 +41,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.policy.IKeyguardDrawnCallback;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardDisplayManager;
@@ -64,9 +65,6 @@
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
-import java.util.Optional;
-import java.util.function.Function;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -75,6 +73,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Optional;
+import java.util.function.Function;
+
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
@@ -103,6 +104,7 @@
     private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     private @Mock IKeyguardDrawnCallback mKeyguardDrawnCallback;
+    private @Mock InteractionJankMonitor mInteractionJankMonitor;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -121,6 +123,8 @@
             .thenReturn(mUnfoldAnimationOptional);
         when(mUnfoldAnimationOptional.isPresent()).thenReturn(true);
         when(mUnfoldAnimationOptional.get()).thenReturn(mUnfoldAnimation);
+        when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
+        when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
 
         mViewMediator = new KeyguardViewMediator(
                 mContext,
@@ -144,7 +148,8 @@
                 mKeyguardStateController,
                 () -> mKeyguardUnlockAnimationController,
                 mUnlockedScreenOffAnimationController,
-                () -> mNotificationShadeDepthController);
+                () -> mNotificationShadeDepthController,
+                mInteractionJankMonitor);
         mViewMediator.start();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 734faec..ed5b8cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -80,7 +80,7 @@
         when(mAssistManager.getAssistInfoForUser(anyInt())).thenReturn(mAssistantComponent);
         when(mUserTracker.getUserId()).thenReturn(1);
 
-        mNavBarHelper = new NavBarHelper(mAccessibilityManager,
+        mNavBarHelper = new NavBarHelper(mContext, mAccessibilityManager,
                 mAccessibilityManagerWrapper, mAccessibilityButtonModeObserver,
                 mOverviewProxyService, mAssistManagerLazy, mNavigationModeController,
                 mUserTracker, mDumpManager);
@@ -96,13 +96,13 @@
 
     @Test
     public void registerAssistantContentObserver() {
-        mNavBarHelper.init(mContext);
+        mNavBarHelper.init();
         verify(mAssistManager, times(1)).getAssistInfoForUser(anyInt());
     }
 
     @Test
     public void callbacksFiredWhenRegistering() {
-        mNavBarHelper.init(mContext);
+        mNavBarHelper.init();
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
         verify(mNavbarTaskbarStateUpdater, times(1))
                 .updateAccessibilityServicesState();
@@ -112,7 +112,7 @@
 
     @Test
     public void assistantCallbacksFiredAfterConnecting() {
-        mNavBarHelper.init(mContext);
+        mNavBarHelper.init();
         // 1st set of callbacks get called when registering
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
@@ -133,7 +133,7 @@
 
     @Test
     public void a11yCallbacksFiredAfterModeChange() {
-        mNavBarHelper.init(mContext);
+        mNavBarHelper.init();
         // 1st set of callbacks get called when registering
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
@@ -146,7 +146,7 @@
 
     @Test
     public void assistantCallbacksFiredAfterNavModeChange() {
-        mNavBarHelper.init(mContext);
+        mNavBarHelper.init();
         // 1st set of callbacks get called when registering
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
@@ -159,7 +159,7 @@
 
     @Test
     public void removeListenerNoCallbacksFired() {
-        mNavBarHelper.init(mContext);
+        mNavBarHelper.init();
         // 1st set of callbacks get called when registering
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 31d8830..9d2541c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import org.junit.After;
@@ -86,7 +87,8 @@
                         mock(TaskbarDelegate.class),
                         mNavigationBarFactory,
                         mock(DumpManager.class),
-                        mock(AutoHideController.class)));
+                        mock(AutoHideController.class),
+                        mock(LightBarController.class)));
         initializeNavigationBars();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
index d2bba36..26f04fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -1,6 +1,8 @@
 package com.android.systemui.qs
 
+import android.os.Handler
 import android.os.UserManager
+import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.ViewUtils
@@ -16,10 +18,12 @@
 import com.android.systemui.globalactions.GlobalActionsDialogLite
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.FooterActionsController.ExpansionState
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.MultiUserSwitchController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.utils.leaks.FakeTunerService
 import com.android.systemui.utils.leaks.LeakCheckedTest
 import com.google.common.truth.Truth.assertThat
@@ -42,6 +46,8 @@
     @Mock
     private lateinit var userManager: UserManager
     @Mock
+    private lateinit var userTracker: UserTracker
+    @Mock
     private lateinit var activityStarter: ActivityStarter
     @Mock
     private lateinit var deviceProvisionedController: DeviceProvisionedController
@@ -62,11 +68,13 @@
     private lateinit var view: FooterActionsView
     private val falsingManager: FalsingManagerFake = FalsingManagerFake()
     private lateinit var testableLooper: TestableLooper
+    private lateinit var fakeSettings: FakeSettings
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
+        fakeSettings = FakeSettings()
         injectLeakCheckedDependencies(*LeakCheckedTest.ALL_SUPPORTED_CLASSES)
         val fakeTunerService = Dependency.get(TunerService::class.java) as FakeTunerService
 
@@ -74,10 +82,11 @@
                 .inflate(R.layout.footer_actions, null) as FooterActionsView
 
         controller = FooterActionsController(view, qsPanelController, activityStarter,
-                userManager, userInfoController, multiUserSwitchController,
+                userManager, userTracker, userInfoController, multiUserSwitchController,
                 deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService,
                 globalActionsDialog, uiEventLogger, showPMLiteButton = true,
-                buttonsVisibleState = ExpansionState.EXPANDED)
+                buttonsVisibleState = ExpansionState.EXPANDED, fakeSettings,
+                Handler(testableLooper.looper))
         controller.init()
         ViewUtils.attachView(view)
         // View looper is the testable looper associated with the test
@@ -122,4 +131,24 @@
 
         assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE)
     }
+
+    @Test
+    fun testMultiUserSwitchUpdatedWhenSettingChanged() {
+        // When expanded, listening is true
+        controller.setListening(true)
+        testableLooper.processAllMessages()
+
+        val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch)
+        assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE)
+
+        // The setting is only used as an indicator for whether the view should refresh. The actual
+        // value of the setting is ignored; isMultiUserEnabled is the source of truth
+        whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true)
+
+        // Changing the value of USER_SWITCHER_ENABLED should cause the view to update
+        fakeSettings.putIntForUser(Settings.Global.USER_SWITCHER_ENABLED, 1, userTracker.userId)
+        testableLooper.processAllMessages()
+
+        assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE)
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
index f99703e..3ea2cc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
@@ -16,22 +16,28 @@
 
 package com.android.systemui.qs.tiles
 
+import android.app.Dialog
 import android.content.ContextWrapper
 import android.content.SharedPreferences
 import android.os.Handler
 import android.provider.Settings
+import android.provider.Settings.Global.ZEN_MODE_OFF
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
@@ -40,9 +46,12 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import java.io.File
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -70,6 +79,10 @@
     private lateinit var zenModeController: ZenModeController
     @Mock
     private lateinit var sharedPreferences: SharedPreferences
+    @Mock
+    private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+    @Mock
+    private lateinit var hostDialog: Dialog
 
     private lateinit var secureSettings: SecureSettings
     private lateinit var testableLooper: TestableLooper
@@ -81,15 +94,17 @@
         testableLooper = TestableLooper.get(this)
         secureSettings = FakeSettings()
 
-        Mockito.`when`(qsHost.userId).thenReturn(DEFAULT_USER)
-        Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+        whenever(qsHost.userId).thenReturn(DEFAULT_USER)
+        whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+        whenever(dialogLaunchAnimator.showFromView(any(), any(), anyBoolean()))
+                .thenReturn(hostDialog)
 
         val wrappedContext = object : ContextWrapper(context) {
             override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences {
                 return sharedPreferences
             }
         }
-        Mockito.`when`(qsHost.context).thenReturn(wrappedContext)
+        whenever(qsHost.context).thenReturn(wrappedContext)
 
         tile = DndTile(
             qsHost,
@@ -102,7 +117,8 @@
             qsLogger,
             zenModeController,
             sharedPreferences,
-            secureSettings
+            secureSettings,
+            dialogLaunchAnimator
         )
     }
 
@@ -147,4 +163,32 @@
 
         assertThat(tile.state.forceExpandIcon).isTrue()
     }
+
+    @Test
+    fun testLaunchDialogFromViewWhenPrompt() {
+        whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF)
+
+        secureSettings.putIntForUser(KEY, Settings.Secure.ZEN_DURATION_PROMPT, DEFAULT_USER)
+        testableLooper.processAllMessages()
+
+        val view = View(context)
+        tile.handleClick(view)
+        testableLooper.processAllMessages()
+
+        verify(dialogLaunchAnimator).showFromView(any(), eq(view), anyBoolean())
+    }
+
+    @Test
+    fun testNoLaunchDialogWhenNotPrompt() {
+        whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF)
+
+        secureSettings.putIntForUser(KEY, 60, DEFAULT_USER)
+        testableLooper.processAllMessages()
+
+        val view = View(context)
+        tile.handleClick(view)
+        testableLooper.processAllMessages()
+
+        verify(dialogLaunchAnimator, never()).showFromView(any(), any(), anyBoolean())
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt
deleted file mode 100644
index d5fe588..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt
+++ /dev/null
@@ -1,62 +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.qs.user
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import android.view.ViewGroup
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.google.common.truth.Truth.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class UserDialogTest : SysuiTestCase() {
-
-    private lateinit var dialog: UserDialog
-
-    @Before
-    fun setUp() {
-        dialog = UserDialog(mContext)
-    }
-
-    @After
-    fun tearDown() {
-        dialog.dismiss()
-    }
-
-    @Test
-    fun doneButtonExists() {
-        assertThat(dialog.doneButton).isInstanceOf(View::class.java)
-    }
-
-    @Test
-    fun settingsButtonExists() {
-        assertThat(dialog.settingsButton).isInstanceOf(View::class.java)
-    }
-
-    @Test
-    fun gridExistsAndIsViewGroup() {
-        assertThat(dialog.grid).isInstanceOf(ViewGroup::class.java)
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index ea3a42c..3c4a557 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.user
 
 import android.app.Dialog
+import android.content.DialogInterface
 import android.content.Intent
 import android.provider.Settings
 import android.testing.AndroidTestingRunner
@@ -28,6 +29,7 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PseudoGridView
 import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
@@ -43,7 +45,6 @@
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.argThat
-import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -53,27 +54,21 @@
 class UserSwitchDialogControllerTest : SysuiTestCase() {
 
     @Mock
-    private lateinit var dialog: UserDialog
+    private lateinit var dialog: SystemUIDialog
     @Mock
     private lateinit var falsingManager: FalsingManager
     @Mock
-    private lateinit var settingsView: View
-    @Mock
-    private lateinit var doneView: View
-    @Mock
     private lateinit var activityStarter: ActivityStarter
     @Mock
     private lateinit var userDetailViewAdapter: UserDetailView.Adapter
     @Mock
     private lateinit var launchView: View
     @Mock
-    private lateinit var gridView: PseudoGridView
-    @Mock
     private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
     @Mock
     private lateinit var hostDialog: Dialog
     @Captor
-    private lateinit var clickCaptor: ArgumentCaptor<View.OnClickListener>
+    private lateinit var clickCaptor: ArgumentCaptor<DialogInterface.OnClickListener>
 
     private lateinit var controller: UserSwitchDialogController
 
@@ -81,11 +76,8 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        `when`(dialog.settingsButton).thenReturn(settingsView)
-        `when`(dialog.doneButton).thenReturn(doneView)
-        `when`(dialog.grid).thenReturn(gridView)
-
         `when`(launchView.context).thenReturn(mContext)
+        `when`(dialog.context).thenReturn(mContext)
         `when`(dialogLaunchAnimator.showFromView(any(), any(), anyBoolean()))
                 .thenReturn(hostDialog)
 
@@ -105,30 +97,6 @@
     }
 
     @Test
-    fun createCalledBeforeDoneButton() {
-        controller.showDialog(launchView)
-        val inOrder = inOrder(dialog)
-        inOrder.verify(dialog).create()
-        inOrder.verify(dialog).doneButton
-    }
-
-    @Test
-    fun createCalledBeforeSettingsButton() {
-        controller.showDialog(launchView)
-        val inOrder = inOrder(dialog)
-        inOrder.verify(dialog).create()
-        inOrder.verify(dialog).settingsButton
-    }
-
-    @Test
-    fun createCalledBeforeGrid() {
-        controller.showDialog(launchView)
-        val inOrder = inOrder(dialog)
-        inOrder.verify(dialog).create()
-        inOrder.verify(dialog).grid
-    }
-
-    @Test
     fun dialog_showForAllUsers() {
         controller.showDialog(launchView)
         verify(dialog).setShowForAllUsers(true)
@@ -143,51 +111,44 @@
     @Test
     fun adapterAndGridLinked() {
         controller.showDialog(launchView)
-        verify(userDetailViewAdapter).linkToViewGroup(gridView)
+        verify(userDetailViewAdapter).linkToViewGroup(any<PseudoGridView>())
     }
 
     @Test
-    fun clickDoneButton_dismiss() {
+    fun doneButtonSetWithNullHandler() {
         controller.showDialog(launchView)
 
-        verify(doneView).setOnClickListener(capture(clickCaptor))
-
-        clickCaptor.value.onClick(doneView)
-
-        verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt())
-        verify(dialog).dismiss()
+        verify(dialog).setPositiveButton(anyInt(), eq(null))
     }
 
     @Test
-    fun clickSettingsButton_noFalsing_opensSettingsAndDismisses() {
+    fun clickSettingsButton_noFalsing_opensSettings() {
         `when`(falsingManager.isFalseTap(anyInt())).thenReturn(false)
 
         controller.showDialog(launchView)
 
-        verify(settingsView).setOnClickListener(capture(clickCaptor))
+        verify(dialog).setNeutralButton(anyInt(), capture(clickCaptor))
 
-        clickCaptor.value.onClick(settingsView)
+        clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL)
 
         verify(activityStarter)
                 .postStartActivityDismissingKeyguard(
                         argThat(IntentMatcher(Settings.ACTION_USER_SETTINGS)),
                         eq(0)
                 )
-        verify(dialog).dismiss()
     }
 
     @Test
-    fun clickSettingsButton_Falsing_notOpensSettingsAndDismisses() {
+    fun clickSettingsButton_Falsing_notOpensSettings() {
         `when`(falsingManager.isFalseTap(anyInt())).thenReturn(true)
 
         controller.showDialog(launchView)
 
-        verify(settingsView).setOnClickListener(capture(clickCaptor))
+        verify(dialog).setNeutralButton(anyInt(), capture(clickCaptor))
 
-        clickCaptor.value.onClick(settingsView)
+        clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL)
 
         verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt())
-        verify(dialog).dismiss()
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index b0f2a89..4213b07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -29,6 +29,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
@@ -54,6 +55,7 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class NonPhoneDependencyTest extends SysuiTestCase {
     @Mock private NotificationPresenter mPresenter;
+    @Mock private NotifStackController mStackController;
     @Mock private NotificationListContainer mListContainer;
     @Mock
     private NotificationEntryListener mEntryListener;
@@ -95,7 +97,7 @@
         remoteInputManager.setUpWithCallback(mRemoteInputManagerCallback,
                 mDelegate);
         lockscreenUserManager.setUpWithPresenter(mPresenter);
-        viewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
+        viewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
 
         TestableLooper.get(this).processAllMessages();
         assertFalse(mDependency.hasInstantiatedDependency(NotificationShadeWindowController.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 3972f14..83f1d87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -74,6 +75,7 @@
 @TestableLooper.RunWithLooper
 public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
     @Mock private NotificationPresenter mPresenter;
+    @Mock private NotifStackController mStackController;
     @Spy private FakeListContainer mListContainer = new FakeListContainer();
 
     // Dependency mocks:
@@ -122,7 +124,7 @@
                 mock(LowPriorityInflationHelper.class),
                 mock(AssistantFeedbackController.class),
                 mNotifPipelineFlags);
-        mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
+        mViewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
     }
 
     private NotificationEntry createEntry() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 8e6bcb01..41163bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -174,6 +174,41 @@
     }
 
     @Test
+    public void testGetGroupSummary() {
+        assertEquals(null, mCollection.getGroupSummary("group"));
+        NotifEvent summary = mNoMan.postNotif(
+                buildNotif(TEST_PACKAGE, 0)
+                        .setGroup(mContext, "group")
+                        .setGroupSummary(mContext, true));
+
+        final NotificationEntry entry = mCollection.getGroupSummary("group");
+        assertEquals(summary.key, entry.getKey());
+        assertEquals(summary.sbn, entry.getSbn());
+        assertEquals(summary.ranking, entry.getRanking());
+    }
+
+    @Test
+    public void testIsOnlyChildInGroup() {
+        NotifEvent notif1 = mNoMan.postNotif(
+                buildNotif(TEST_PACKAGE, 1)
+                        .setGroup(mContext, "group"));
+        final NotificationEntry entry = mCollection.getEntry(notif1.key);
+        assertTrue(mCollection.isOnlyChildInGroup(entry));
+
+        // summaries are not counted
+        mNoMan.postNotif(
+                buildNotif(TEST_PACKAGE, 0)
+                        .setGroup(mContext, "group")
+                        .setGroupSummary(mContext, true));
+        assertTrue(mCollection.isOnlyChildInGroup(entry));
+
+        mNoMan.postNotif(
+                buildNotif(TEST_PACKAGE, 2)
+                        .setGroup(mContext, "group"));
+        assertFalse(mCollection.isOnlyChildInGroup(entry));
+    }
+
+    @Test
     public void testEventDispatchedWhenNotifPosted() {
         // WHEN a notification is posted
         NotifEvent notif1 = mNoMan.postNotif(
@@ -193,6 +228,15 @@
     }
 
     @Test
+    public void testCancelNonExistingNotification() {
+        NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+        NotificationEntry entry = mCollectionListener.getEntry(notif.key);
+        mCollection.dismissNotification(entry, defaultStats(entry));
+        mCollection.dismissNotification(entry, defaultStats(entry));
+        mCollection.dismissNotification(entry, defaultStats(entry));
+    }
+
+    @Test
     public void testEventDispatchedWhenNotifBatchPosted() {
         // GIVEN a NotifCollection with one notif already posted
         mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2)
@@ -649,21 +693,6 @@
         // THEN an exception is thrown
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testDismissingNonExistentNotificationThrows() {
-        // GIVEN a collection that originally had three notifs, but where one was dismissed
-        NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
-        NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
-        NotifEvent notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 99));
-        NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
-        mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
-
-        // WHEN we try to dismiss a notification that isn't present
-        mCollection.dismissNotification(entry2, defaultStats(entry2));
-
-        // THEN an exception is thrown
-    }
-
     @Test
     public void testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed() {
         // GIVEN a collection with two grouped notifs in it
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt
index cf7174e..287f50c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt
@@ -19,6 +19,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.render.RenderStageManager
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -33,12 +34,13 @@
 
     @Mock private lateinit var notifCollection: NotifCollection
     @Mock private lateinit var shadeListBuilder: ShadeListBuilder
+    @Mock private lateinit var renderStageManager: RenderStageManager
     private lateinit var notifPipeline: NotifPipeline
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        notifPipeline = NotifPipeline(notifCollection, shadeListBuilder)
+        notifPipeline = NotifPipeline(notifCollection, shadeListBuilder, renderStageManager)
         whenever(shadeListBuilder.shadeList).thenReturn(listOf(
                 NotificationEntryBuilder().setPkg("foo").setId(1).build(),
                 NotificationEntryBuilder().setPkg("foo").setId(2).build(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt
new file mode 100644
index 0000000..929c3d4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener
+import com.android.systemui.statusbar.notification.collection.render.NotifGroupController
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.withArgCaptor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class GroupCountCoordinatorTest : SysuiTestCase() {
+    private lateinit var coordinator: GroupCountCoordinator
+    private lateinit var beforeFinalizeFilterListener: OnBeforeFinalizeFilterListener
+    private lateinit var afterRenderGroupListener: OnAfterRenderGroupListener
+
+    private lateinit var summaryEntry: NotificationEntry
+    private lateinit var childEntry1: NotificationEntry
+    private lateinit var childEntry2: NotificationEntry
+
+    @Mock private lateinit var pipeline: NotifPipeline
+    @Mock private lateinit var groupController: NotifGroupController
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+        coordinator = GroupCountCoordinator()
+        coordinator.attach(pipeline)
+        beforeFinalizeFilterListener = withArgCaptor {
+            verify(pipeline).addOnBeforeFinalizeFilterListener(capture())
+        }
+        afterRenderGroupListener = withArgCaptor {
+            verify(pipeline).addOnAfterRenderGroupListener(capture())
+        }
+        summaryEntry = NotificationEntryBuilder().setId(0).build()
+        childEntry1 = NotificationEntryBuilder().setId(1).build()
+        childEntry2 = NotificationEntryBuilder().setId(2).build()
+    }
+
+    @Test
+    fun testSetUntruncatedChildCount() {
+        val groupEntry = GroupEntryBuilder()
+            .setSummary(summaryEntry)
+            .setChildren(listOf(childEntry1, childEntry2))
+            .build()
+        beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+        afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+        verify(groupController).setUntruncatedChildCount(eq(2))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index c3e10aa..f70330d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -40,6 +40,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.SectionClassifier;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
@@ -85,7 +86,6 @@
 
     @Captor private ArgumentCaptor<NotifCollectionListener> mCollectionListenerCaptor;
     @Captor private ArgumentCaptor<OnBeforeFinalizeFilterListener> mBeforeFilterListenerCaptor;
-    @Captor private ArgumentCaptor<NotifInflater.InflationCallback> mCallbackCaptor;
     @Captor private ArgumentCaptor<NotifInflater.Params> mParamsCaptor;
 
     @Mock private NotifSectioner mNotifSectioner;
@@ -93,7 +93,9 @@
     @Mock private NotifPipeline mNotifPipeline;
     @Mock private IStatusBarService mService;
     @Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
-    private final TestableAdjustmentProvider mAdjustmentProvider = new TestableAdjustmentProvider();
+    private final SectionClassifier mSectionClassifier = new SectionClassifier();
+    private final NotifUiAdjustmentProvider mAdjustmentProvider =
+            new NotifUiAdjustmentProvider(mSectionClassifier);
 
     @NonNull
     private NotificationEntryBuilder getNotificationEntryBuilder() {
@@ -108,7 +110,7 @@
         mInflationError = new Exception(TEST_MESSAGE);
         mErrorManager = new NotifInflationErrorManager();
         when(mNotifSection.getSectioner()).thenReturn(mNotifSectioner);
-        mAdjustmentProvider.setSectionIsLowPriority(false);
+        setSectionIsLowPriority(false);
 
         PreparationCoordinator coordinator = new PreparationCoordinator(
                 mock(PreparationCoordinatorLogger.class),
@@ -180,8 +182,8 @@
         // GIVEN an inflated notification
         mCollectionListener.onEntryAdded(mEntry);
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
-        verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture());
-        mCallbackCaptor.getValue().onInflationFinished(mEntry);
+        verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
+        mNotifInflater.invokeInflateCallbackForEntry(mEntry);
 
         // WHEN notification is updated
         mCollectionListener.onEntryUpdated(mEntry);
@@ -199,8 +201,8 @@
         // GIVEN an inflated notification
         mCollectionListener.onEntryAdded(mEntry);
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
-        verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture());
-        mCallbackCaptor.getValue().onInflationFinished(mEntry);
+        verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
+        mNotifInflater.invokeInflateCallbackForEntry(mEntry);
 
         // WHEN notification ranking now has smart replies
         mEntry.setRanking(new RankingBuilder(mEntry.getRanking()).setSmartReplies("yes").build());
@@ -218,13 +220,12 @@
         // GIVEN an inflated notification
         mCollectionListener.onEntryAdded(mEntry);
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
-        verify(mNotifInflater).inflateViews(eq(mEntry),
-                mParamsCaptor.capture(), mCallbackCaptor.capture());
+        verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());
         assertFalse(mParamsCaptor.getValue().isLowPriority());
-        mCallbackCaptor.getValue().onInflationFinished(mEntry);
+        mNotifInflater.invokeInflateCallbackForEntry(mEntry);
 
         // WHEN notification moves to a min priority section
-        mAdjustmentProvider.setSectionIsLowPriority(true);
+        setSectionIsLowPriority(true);
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
 
         // THEN we rebind it
@@ -238,13 +239,12 @@
     @Test
     public void testMinimizedEntryMovedIntoGroupWillRebindViews() {
         // GIVEN an inflated, minimized notification
-        mAdjustmentProvider.setSectionIsLowPriority(true);
+        setSectionIsLowPriority(true);
         mCollectionListener.onEntryAdded(mEntry);
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
-        verify(mNotifInflater).inflateViews(eq(mEntry),
-                mParamsCaptor.capture(), mCallbackCaptor.capture());
+        verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());
         assertTrue(mParamsCaptor.getValue().isLowPriority());
-        mCallbackCaptor.getValue().onInflationFinished(mEntry);
+        mNotifInflater.invokeInflateCallbackForEntry(mEntry);
 
         // WHEN notification is moved under a parent
         NotificationEntryBuilder.setNewParent(mEntry, mock(GroupEntry.class));
@@ -263,8 +263,8 @@
         // GIVEN an inflated notification
         mCollectionListener.onEntryAdded(mEntry);
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
-        verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture());
-        mCallbackCaptor.getValue().onInflationFinished(mEntry);
+        verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
+        mNotifInflater.invokeInflateCallbackForEntry(mEntry);
 
         // WHEN notification ranking changes rank, which does not affect views
         mEntry.setRanking(new RankingBuilder(mEntry.getRanking()).setRank(100).build());
@@ -282,8 +282,8 @@
         // GIVEN an inflated notification
         mCollectionListener.onEntryAdded(mEntry);
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
-        verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture());
-        mCallbackCaptor.getValue().onInflationFinished(mEntry);
+        verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
+        mNotifInflater.invokeInflateCallbackForEntry(mEntry);
 
         // THEN it isn't filtered from shade list
         assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0));
@@ -347,7 +347,7 @@
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
 
         // WHEN one of this children finishes inflating
-        mNotifInflater.getInflateCallback(child0).onInflationFinished(child0);
+        mNotifInflater.invokeInflateCallbackForEntry(child0);
 
         // THEN the inflated child is still filtered out
         assertTrue(mUninflatedFilter.shouldFilterOut(child0, 401));
@@ -369,8 +369,8 @@
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
 
         // WHEN all of the children (but not the summary) finish inflating
-        mNotifInflater.getInflateCallback(child0).onInflationFinished(child0);
-        mNotifInflater.getInflateCallback(child1).onInflationFinished(child1);
+        mNotifInflater.invokeInflateCallbackForEntry(child0);
+        mNotifInflater.invokeInflateCallbackForEntry(child1);
 
         // THEN the entire group is still filtered out
         assertTrue(mUninflatedFilter.shouldFilterOut(summary, 401));
@@ -394,9 +394,9 @@
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
 
         // WHEN all of the children (and the summary) finish inflating
-        mNotifInflater.getInflateCallback(child0).onInflationFinished(child0);
-        mNotifInflater.getInflateCallback(child1).onInflationFinished(child1);
-        mNotifInflater.getInflateCallback(summary).onInflationFinished(summary);
+        mNotifInflater.invokeInflateCallbackForEntry(child0);
+        mNotifInflater.invokeInflateCallbackForEntry(child1);
+        mNotifInflater.invokeInflateCallbackForEntry(summary);
 
         // THEN the entire group is still filtered out
         assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401));
@@ -418,7 +418,7 @@
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
 
         // WHEN one of this children finishes inflating and enough time passes
-        mNotifInflater.getInflateCallback(child0).onInflationFinished(child0);
+        mNotifInflater.invokeInflateCallbackForEntry(child0);
 
         // THEN the inflated child is not filtered out even though the rest of the group hasn't
         // finished inflating yet
@@ -446,6 +446,10 @@
         public InflationCallback getInflateCallback(NotificationEntry entry) {
             return requireNonNull(mInflateCallbacks.get(entry));
         }
+
+        public void invokeInflateCallbackForEntry(NotificationEntry entry) {
+            getInflateCallback(entry).onInflationFinished(entry, entry.getRowController());
+        }
     }
 
     private void fireAddEvents(List<? extends ListEntry> entries) {
@@ -470,11 +474,9 @@
     private static final int TEST_CHILD_BIND_CUTOFF = 9;
     private static final int TEST_MAX_GROUP_DELAY = 100;
 
-    private class TestableAdjustmentProvider extends NotifUiAdjustmentProvider {
-        private void setSectionIsLowPriority(boolean lowPriority) {
-            setLowPrioritySections(lowPriority
-                    ? Collections.singleton(mNotifSection.getSectioner())
-                    : Collections.emptyList());
-        }
+    private void setSectionIsLowPriority(boolean minimized) {
+        mSectionClassifier.setMinimizedSections(minimized
+                ? Collections.singleton(mNotifSection.getSectioner())
+                : Collections.emptyList());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index abe33aa..f4d8405 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -39,11 +40,11 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.statusbar.notification.SectionClassifier;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
@@ -67,7 +68,7 @@
 
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private HighPriorityProvider mHighPriorityProvider;
-    @Mock private NotifUiAdjustmentProvider mAdjustmentProvider;
+    @Mock private SectionClassifier mSectionClassifier;
     @Mock private NotifPipeline mNotifPipeline;
     @Mock private NodeController mAlertingHeaderController;
     @Mock private NodeController mSilentNodeController;
@@ -91,7 +92,7 @@
         mRankingCoordinator = new RankingCoordinator(
                 mStatusBarStateController,
                 mHighPriorityProvider,
-                mAdjustmentProvider,
+                mSectionClassifier,
                 mAlertingHeaderController,
                 mSilentHeaderController,
                 mSilentNodeController);
@@ -99,6 +100,7 @@
         mEntry.setRanking(getRankingForUnfilteredNotif().build());
 
         mRankingCoordinator.attach(mNotifPipeline);
+        verify(mSectionClassifier).setMinimizedSections(any());
         verify(mNotifPipeline, times(2)).addPreGroupFilter(mNotifFilterCaptor.capture());
         mCapturedSuspendedFilter = mNotifFilterCaptor.getAllValues().get(0);
         mCapturedDozingFilter = mNotifFilterCaptor.getAllValues().get(1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
new file mode 100644
index 0000000..52fce13
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.util.Pair
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.AssistantFeedbackController
+import com.android.systemui.statusbar.notification.SectionClassifier
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
+import com.android.systemui.statusbar.notification.collection.render.NotifRowController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.withArgCaptor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class RowAppearanceCoordinatorTest : SysuiTestCase() {
+    private lateinit var coordinator: RowAppearanceCoordinator
+    private lateinit var beforeRenderListListener: OnBeforeRenderListListener
+    private lateinit var afterRenderEntryListener: OnAfterRenderEntryListener
+
+    private lateinit var entry1: NotificationEntry
+    private lateinit var entry2: NotificationEntry
+
+    @Mock private lateinit var pipeline: NotifPipeline
+    @Mock private lateinit var assistantFeedbackController: AssistantFeedbackController
+    @Mock private lateinit var sectionClassifier: SectionClassifier
+
+    @Mock private lateinit var section1: NotifSection
+    @Mock private lateinit var section2: NotifSection
+    @Mock private lateinit var controller1: NotifRowController
+    @Mock private lateinit var controller2: NotifRowController
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+        coordinator = RowAppearanceCoordinator(
+            mContext,
+            assistantFeedbackController,
+            sectionClassifier
+        )
+        coordinator.attach(pipeline)
+        beforeRenderListListener = withArgCaptor {
+            verify(pipeline).addOnBeforeRenderListListener(capture())
+        }
+        afterRenderEntryListener = withArgCaptor {
+            verify(pipeline).addOnAfterRenderEntryListener(capture())
+        }
+        whenever(assistantFeedbackController.showFeedbackIndicator(any())).thenReturn(true)
+        whenever(assistantFeedbackController.getFeedbackResources(any())).thenReturn(Pair(1, 2))
+        entry1 = NotificationEntryBuilder().setSection(section1).setLastAudiblyAlertedMs(17).build()
+        entry2 = NotificationEntryBuilder().setSection(section2).build()
+    }
+
+    @Test
+    fun testSetSystemExpandedOnlyOnFirst() {
+        whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(false)
+        whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(false)
+        beforeRenderListListener.onBeforeRenderList(listOf(entry1, entry2))
+        afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)
+        verify(controller1).setSystemExpanded(eq(true))
+        afterRenderEntryListener.onAfterRenderEntry(entry2, controller2)
+        verify(controller2).setSystemExpanded(eq(false))
+    }
+
+    @Test
+    fun testSetSystemExpandedNeverIfMinimized() {
+        whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(true)
+        whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(true)
+        beforeRenderListListener.onBeforeRenderList(listOf(entry1, entry2))
+        afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)
+        verify(controller1).setSystemExpanded(eq(false))
+        afterRenderEntryListener.onAfterRenderEntry(entry2, controller2)
+        verify(controller2).setSystemExpanded(eq(false))
+    }
+
+    @Test
+    fun testSetLastAudiblyAlerted() {
+        afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)
+        verify(controller1).setLastAudiblyAlertedMs(eq(17.toLong()))
+    }
+
+    @Test
+    fun testShowFeedbackIcon() {
+        afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)
+        verify(controller1).showFeedbackIcon(eq(true), eq(Pair(1, 2)))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
new file mode 100644
index 0000000..70266e4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
+import com.android.systemui.statusbar.notification.collection.render.NotifStackController
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
+import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.withArgCaptor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class StackCoordinatorTest : SysuiTestCase() {
+    private lateinit var coordinator: StackCoordinator
+    private lateinit var afterRenderListListener: OnAfterRenderListListener
+
+    private lateinit var entry: NotificationEntry
+
+    @Mock private lateinit var pipeline: NotifPipeline
+    @Mock private lateinit var notificationIconAreaController: NotificationIconAreaController
+    @Mock private lateinit var stackController: NotifStackController
+    @Mock private lateinit var section: NotifSection
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+        coordinator = StackCoordinator(notificationIconAreaController)
+        coordinator.attach(pipeline)
+        afterRenderListListener = withArgCaptor {
+            verify(pipeline).addOnAfterRenderListListener(capture())
+        }
+        entry = NotificationEntryBuilder().setSection(section).build()
+    }
+
+    @Test
+    fun testUpdateNotificationIcons() {
+        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry)))
+    }
+
+    @Test
+    fun testSetNotificationStats_clearableAlerting() {
+        whenever(section.bucket).thenReturn(BUCKET_ALERTING)
+        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        verify(stackController).setNotifStats(NotifStats(1, false, true, false, false))
+    }
+
+    @Test
+    fun testSetNotificationStats_clearableSilent() {
+        whenever(section.bucket).thenReturn(BUCKET_SILENT)
+        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        verify(stackController).setNotifStats(NotifStats(1, false, false, false, true))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
index 6313d3a..5271745 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
@@ -68,7 +68,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        `when`(viewBarn.requireView(any())).thenAnswer {
+        `when`(viewBarn.requireNodeController(any())).thenAnswer {
             fakeViewBarn.getViewByEntry(it.getArgument(0))
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
new file mode 100644
index 0000000..70d309b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
@@ -0,0 +1,222 @@
+/*
+ * 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.notification.collection.render
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class RenderStageManagerTest : SysuiTestCase() {
+
+    @Mock private lateinit var shadeListBuilder: ShadeListBuilder
+    @Mock private lateinit var onAfterRenderListListener: OnAfterRenderListListener
+    @Mock private lateinit var onAfterRenderGroupListener: OnAfterRenderGroupListener
+    @Mock private lateinit var onAfterRenderEntryListener: OnAfterRenderEntryListener
+
+    private lateinit var onRenderListListener: ShadeListBuilder.OnRenderListListener
+    private lateinit var renderStageManager: RenderStageManager
+    private val spyViewRenderer = spy(FakeNotifViewRenderer())
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        renderStageManager = RenderStageManager()
+        renderStageManager.attach(shadeListBuilder)
+        onRenderListListener = withArgCaptor {
+            verify(shadeListBuilder).setOnRenderListListener(capture())
+        }
+    }
+
+    private fun setUpRenderer() {
+        renderStageManager.setViewRenderer(spyViewRenderer)
+    }
+
+    private fun setUpListeners() {
+        renderStageManager.addOnAfterRenderListListener(onAfterRenderListListener)
+        renderStageManager.addOnAfterRenderGroupListener(onAfterRenderGroupListener)
+        renderStageManager.addOnAfterRenderEntryListener(onAfterRenderEntryListener)
+    }
+
+    @Test
+    fun testNoCallbacksWithoutRenderer() {
+        // GIVEN listeners but no renderer
+        setUpListeners()
+
+        // WHEN a shade list is built
+        onRenderListListener.onRenderList(listWith2Groups8Entries())
+
+        // VERIFY that no listeners are called
+        verifyNoMoreInteractions(
+            onAfterRenderListListener,
+            onAfterRenderGroupListener,
+            onAfterRenderEntryListener
+        )
+    }
+
+    @Test
+    fun testDoesNotQueryControllerIfNoListeners() {
+        // GIVEN a renderer but no listeners
+        setUpRenderer()
+
+        // WHEN a shade list is built
+        onRenderListListener.onRenderList(listWith2Groups8Entries())
+
+        // VERIFY that the renderer is not queried for group or row controllers
+        inOrder(spyViewRenderer).apply {
+            verify(spyViewRenderer, times(1)).onRenderList(any())
+            verify(spyViewRenderer, times(1)).getStackController()
+            verify(spyViewRenderer, never()).getGroupController(any())
+            verify(spyViewRenderer, never()).getRowController(any())
+            verify(spyViewRenderer, times(1)).onDispatchComplete()
+            verifyNoMoreInteractions(spyViewRenderer)
+        }
+    }
+
+    @Test
+    fun testDoesQueryControllerIfListeners() {
+        // GIVEN a renderer and listeners
+        setUpRenderer()
+        setUpListeners()
+
+        // WHEN a shade list is built
+        onRenderListListener.onRenderList(listWith2Groups8Entries())
+
+        // VERIFY that the renderer is queried once per group/entry
+        inOrder(spyViewRenderer).apply {
+            verify(spyViewRenderer, times(1)).onRenderList(any())
+            verify(spyViewRenderer, times(1)).getStackController()
+            verify(spyViewRenderer, times(2)).getGroupController(any())
+            verify(spyViewRenderer, times(8)).getRowController(any())
+            verify(spyViewRenderer, times(1)).onDispatchComplete()
+            verifyNoMoreInteractions(spyViewRenderer)
+        }
+    }
+
+    @Test
+    fun testDoesNotQueryControllerTwice() {
+        // GIVEN a renderer and multiple distinct listeners
+        setUpRenderer()
+        setUpListeners()
+        renderStageManager.addOnAfterRenderListListener(mock())
+        renderStageManager.addOnAfterRenderGroupListener(mock())
+        renderStageManager.addOnAfterRenderEntryListener(mock())
+
+        // WHEN a shade list is built
+        onRenderListListener.onRenderList(listWith2Groups8Entries())
+
+        // VERIFY that the renderer is queried once per group/entry
+        inOrder(spyViewRenderer).apply {
+            verify(spyViewRenderer, times(1)).onRenderList(any())
+            verify(spyViewRenderer, times(1)).getStackController()
+            verify(spyViewRenderer, times(2)).getGroupController(any())
+            verify(spyViewRenderer, times(8)).getRowController(any())
+            verify(spyViewRenderer, times(1)).onDispatchComplete()
+            verifyNoMoreInteractions(spyViewRenderer)
+        }
+    }
+
+    @Test
+    fun testDoesCallListenerWithEachGroupAndEntry() {
+        // GIVEN a renderer and multiple distinct listeners
+        setUpRenderer()
+        setUpListeners()
+
+        // WHEN a shade list is built
+        onRenderListListener.onRenderList(listWith2Groups8Entries())
+
+        // VERIFY that the listeners are invoked once per group and once per entry
+        verify(onAfterRenderListListener, times(1)).onAfterRenderList(any(), any())
+        verify(onAfterRenderGroupListener, times(2)).onAfterRenderGroup(any(), any())
+        verify(onAfterRenderEntryListener, times(8)).onAfterRenderEntry(any(), any())
+        verifyNoMoreInteractions(
+            onAfterRenderListListener,
+            onAfterRenderGroupListener,
+            onAfterRenderEntryListener
+        )
+    }
+
+    @Test
+    fun testDoesNotCallGroupAndEntryListenersIfTheListIsEmpty() {
+        // GIVEN a renderer and multiple distinct listeners
+        setUpRenderer()
+        setUpListeners()
+
+        // WHEN a shade list is built empty
+        onRenderListListener.onRenderList(listOf())
+
+        // VERIFY that the stack listener is invoked once but other listeners are not
+        verify(onAfterRenderListListener, times(1)).onAfterRenderList(any(), any())
+        verify(onAfterRenderGroupListener, never()).onAfterRenderGroup(any(), any())
+        verify(onAfterRenderEntryListener, never()).onAfterRenderEntry(any(), any())
+        verifyNoMoreInteractions(
+            onAfterRenderListListener,
+            onAfterRenderGroupListener,
+            onAfterRenderEntryListener
+        )
+    }
+
+    private fun listWith2Groups8Entries() = listOf(
+        group(
+            notif(1),
+            notif(2),
+            notif(3)
+        ),
+        notif(4),
+        group(
+            notif(5),
+            notif(6),
+            notif(7)
+        ),
+        notif(8)
+    )
+
+    private class FakeNotifViewRenderer : NotifViewRenderer {
+        override fun onRenderList(notifList: List<ListEntry>) {}
+        override fun getStackController(): NotifStackController = mock()
+        override fun getGroupController(group: GroupEntry): NotifGroupController = mock()
+        override fun getRowController(entry: NotificationEntry): NotifRowController = mock()
+        override fun onDispatchComplete() {}
+    }
+
+    private fun notif(id: Int): NotificationEntry = NotificationEntryBuilder().setId(id).build()
+
+    private fun group(summary: NotificationEntry, vararg children: NotificationEntry): GroupEntry =
+        GroupEntryBuilder().setSummary(summary).setChildren(children.toList()).build()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 185d9cd..9be2837 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -30,7 +30,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
@@ -296,14 +295,10 @@
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
 
-        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
-        when(row.canViewBeDismissed()).thenReturn(true);
-        when(mStackScroller.getChildCount()).thenReturn(1);
-        when(mStackScroller.getChildAt(anyInt())).thenReturn(row);
         mStackScroller.setIsRemoteInputActive(true);
-        when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
+        when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
+        when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
                 .thenReturn(true);
-        when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true);
 
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
@@ -312,14 +307,28 @@
     }
 
     @Test
+    public void testUpdateFooter_withoutNotifications() {
+        setBarStateForTest(StatusBarState.SHADE);
+        mStackScroller.setCurrentUserSetup(true);
+
+        when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+        when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
+                .thenReturn(false);
+
+        FooterView view = mock(FooterView.class);
+        mStackScroller.setFooterView(view);
+        mStackScroller.updateFooter();
+        verify(mStackScroller).updateFooterView(false, false, true);
+    }
+
+    @Test
     public void testUpdateFooter_oneClearableNotification() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
 
-        when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
-        when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
+        when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
+        when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
                 .thenReturn(true);
-        when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true);
 
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
@@ -332,10 +341,9 @@
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(false);
 
-        when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
-        when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
+        when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
+        when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
                 .thenReturn(true);
-        when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true);
 
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
@@ -348,12 +356,8 @@
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
 
-        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
-        when(row.canViewBeDismissed()).thenReturn(false);
-        when(mStackScroller.getChildCount()).thenReturn(1);
-        when(mStackScroller.getChildAt(anyInt())).thenReturn(row);
-        when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true);
-        when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
+        when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
+        when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
                 .thenReturn(false);
         when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 25fd801..07debe6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -101,6 +102,8 @@
     private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
     private ScreenLifecycle mScreenLifecycle;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
     private BiometricUnlockController mBiometricUnlockController;
 
     @Before
@@ -123,7 +126,7 @@
                 mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
                 mMetricsLogger, mDumpManager, mPowerManager,
                 mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
-                mAuthController);
+                mAuthController, mStatusBarStateController);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
     }
@@ -378,6 +381,23 @@
     }
 
     @Test
+    public void onUdfpsConsecutivelyFailedThreeTimes_showBouncer() {
+        // GIVEN UDFPS is supported
+        when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true);
+
+        // WHEN udfps fails twice - then don't show the bouncer
+        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+
+        // WHEN udfps fails the third time
+        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+
+        // THEN show the bouncer
+        verify(mStatusBarKeyguardViewManager).showBouncer(true);
+    }
+
+    @Test
     public void onFinishedGoingToSleep_authenticatesWhenPending() {
         when(mUpdateMonitor.isGoingToSleep()).thenReturn(true);
         mBiometricUnlockController.onFinishedGoingToSleep(-1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 6f174cb..c5bdfed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -374,6 +374,21 @@
     }
 
     @Test
+    public void testHideAltAuth_onShowBouncer() {
+        // GIVEN alt auth is showing
+        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
+        when(mBouncer.isShowing()).thenReturn(false);
+        when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
+        reset(mAlternateAuthInterceptor);
+
+        // WHEN showBouncer is called
+        mStatusBarKeyguardViewManager.showBouncer(true);
+
+        // THEN alt bouncer should be hidden
+        verify(mAlternateAuthInterceptor).hideAlternateAuthBouncer();
+    }
+
+    @Test
     public void testUpdateResources_delegatesToBouncer() {
         mStatusBarKeyguardViewManager.updateResources();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 348c181..07ec0e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -35,6 +35,7 @@
 
 import android.app.KeyguardManager;
 import android.app.Notification;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.os.Handler;
@@ -116,6 +117,8 @@
     @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
+    private NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+    @Mock
     private Handler mHandler;
     @Mock
     private BubblesManager mBubblesManager;
@@ -137,7 +140,7 @@
     @Mock
     private OnUserInteractionCallback mOnUserInteractionCallback;
     @Mock
-    private NotificationActivityStarter mNotificationActivityStarter;
+    private StatusBarNotificationActivityStarter mNotificationActivityStarter;
     @Mock
     private ActivityLaunchAnimator mActivityLaunchAnimator;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -219,7 +222,7 @@
                         mock(NotificationLockscreenUserManager.class),
                         mShadeController,
                         mKeyguardStateController,
-                        mock(NotificationInterruptStateProvider.class),
+                        mNotificationInterruptStateProvider,
                         mock(LockPatternUtils.class),
                         mock(StatusBarRemoteInputCallback.class),
                         mActivityIntentHelper,
@@ -375,4 +378,27 @@
         // Notification should not be cancelled.
         verify(mEntryManager, never()).performRemoveNotification(eq(sbn), any(), anyInt());
     }
+
+    @Test
+    public void testOnFullScreenIntentWhenDozing_wakeUpDevice() {
+        // GIVEN entry that can has a full screen intent that can show
+        Notification.Builder nb = new Notification.Builder(mContext, "a")
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setFullScreenIntent(mock(PendingIntent.class), true);
+        StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0,
+                "tag" + System.currentTimeMillis(), 0, 0,
+                nb.build(), new UserHandle(0), null, 0);
+        NotificationEntry entry = mock(NotificationEntry.class);
+        when(entry.getImportance()).thenReturn(NotificationManager.IMPORTANCE_HIGH);
+        when(entry.getSbn()).thenReturn(sbn);
+        when(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(eq(entry)))
+                .thenReturn(true);
+
+        // WHEN
+        mNotificationActivityStarter.handleFullScreenIntent(entry);
+
+        // THEN display should try wake up for the full screen intent
+        verify(mStatusBar).wakeUpForFullScreenIntent();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 609d69c..d36c516 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -65,7 +66,9 @@
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
 
 import java.util.Optional;
 
@@ -90,8 +93,12 @@
     private OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
     private OperatorNameViewController mOperatorNameViewController;
 
+    @Mock
     private StatusBarFragmentComponent.Factory mStatusBarFragmentComponentFactory;
+    @Mock
     private StatusBarFragmentComponent mStatusBarFragmentComponent;
+    @Mock
+    private HeadsUpAppearanceController mHeadsUpAppearanceController;
 
     public CollapsedStatusBarFragmentTest() {
         super(CollapsedStatusBarFragment.class);
@@ -254,6 +261,32 @@
     }
 
     @Test
+    public void disable_headsUpShouldBeVisibleTrue_clockDisabled() {
+        mFragments.dispatchResume();
+        processAllMessages();
+        CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment;
+
+        when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
+
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+        assertEquals(View.GONE, mFragment.getView().findViewById(R.id.clock).getVisibility());
+    }
+
+    @Test
+    public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() {
+        mFragments.dispatchResume();
+        processAllMessages();
+        CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment;
+
+        when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(false);
+
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+        assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.clock).getVisibility());
+    }
+
+    @Test
     public void setUp_fragmentCreatesDaggerComponent() {
         mFragments.dispatchResume();
         processAllMessages();
@@ -264,11 +297,8 @@
 
     @Override
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
-        mStatusBarFragmentComponentFactory =
-                mock(StatusBarFragmentComponent.Factory.class);
-        mStatusBarFragmentComponent = mock(StatusBarFragmentComponent.class);
-        when(mStatusBarFragmentComponentFactory.create(any()))
-                .thenReturn(mStatusBarFragmentComponent);
+        MockitoAnnotations.initMocks(this);
+        setUpDaggerComponent();
         mOngoingCallController = mock(OngoingCallController.class);
         mAnimationScheduler = mock(SystemStatusAnimationScheduler.class);
         mLocationPublisher = mock(StatusBarLocationPublisher.class);
@@ -306,6 +336,13 @@
                 mOperatorNameViewControllerFactory);
     }
 
+    private void setUpDaggerComponent() {
+        when(mStatusBarFragmentComponentFactory.create(any()))
+                .thenReturn(mStatusBarFragmentComponent);
+        when(mStatusBarFragmentComponent.getHeadsUpAppearanceController())
+                .thenReturn(mHeadsUpAppearanceController);
+    }
+
     private void setUpNotificationIconAreaController() {
         mMockNotificationAreaController = mock(NotificationIconAreaController.class);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index de2012a..25fafa1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -124,7 +124,8 @@
         mContext.addMockSystemService(Context.FINGERPRINT_SERVICE,
                 mock(FingerprintManager::class.java))
 
-        `when`(userManager.canAddMoreUsers()).thenReturn(true)
+        `when`(userManager.canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_SECONDARY)))
+                .thenReturn(true)
         `when`(notificationShadeWindowView.context).thenReturn(context)
 
         userSwitcherController = UserSwitcherController(
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 67bb726..f1599e4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -39,6 +39,7 @@
 import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.accessibilityservice.MagnificationConfig;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
@@ -1101,8 +1102,12 @@
             try {
                 MagnificationProcessor magnificationProcessor =
                         mSystemSupport.getMagnificationProcessor();
-                return magnificationProcessor
-                        .setScaleAndCenter(displayId, scale, centerX, centerY, animate, mId);
+                final MagnificationConfig config = new MagnificationConfig.Builder()
+                        .setScale(scale)
+                        .setCenterX(centerX)
+                        .setCenterY(centerY).build();
+                return magnificationProcessor.setMagnificationConfig(displayId, config, animate,
+                        mId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 572cfdc..f3a5d35 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3528,9 +3528,8 @@
 
             mConnectionId = service.mId;
 
-            mClient = AccessibilityInteractionClient.getInstance(/* initializeCache= */false,
-                    mContext);
-            mClient.addConnection(mConnectionId, service);
+            mClient = AccessibilityInteractionClient.getInstance(mContext);
+            mClient.addConnection(mConnectionId, service, /*initializeCache=*/false);
 
             //TODO: (multi-display) We need to support multiple displays.
             DisplayManager displayManager = (DisplayManager)
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 6473bf5..327f087 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -84,6 +84,8 @@
 
     @GuardedBy("mLock")
     private int mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
+    @GuardedBy("mLock")
+    private int mLastActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
     // Track the active user to reset the magnification and get the associated user settings.
     private @UserIdInt int mUserId = UserHandle.USER_SYSTEM;
     @GuardedBy("mLock")
@@ -239,6 +241,7 @@
 
             synchronized (mLock) {
                 mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+                mLastActivatedMode = mActivatedMode;
             }
             logMagnificationModeWithImeOnIfNeeded();
             disableFullScreenMagnificationIfNeeded(displayId);
@@ -276,6 +279,7 @@
 
             synchronized (mLock) {
                 mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+                mLastActivatedMode = mActivatedMode;
             }
             logMagnificationModeWithImeOnIfNeeded();
         } else {
@@ -298,6 +302,16 @@
     }
 
     /**
+     * Returns the last activated magnification mode. If there is no activated magnifier before, it
+     * returns fullscreen mode by default.
+     */
+    public int getLastActivatedMode() {
+        synchronized (mLock) {
+            return mLastActivatedMode;
+        }
+    }
+
+    /**
      * Wrapper method of logging the magnification activated mode and its duration of the usage
      * when the magnification is disabled.
      *
@@ -336,6 +350,7 @@
         synchronized (mLock) {
             fullMagnificationController = mFullScreenMagnificationController;
             windowMagnificationManager = mWindowMagnificationMgr;
+            mLastActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
         }
 
         mScaleProvider.onUserChanged(userId);
@@ -462,7 +477,15 @@
         return mTempPoint;
     }
 
-    private boolean isActivated(int displayId, int mode) {
+    /**
+     * Return {@code true} if the specified magnification mode on the given display is activated
+     * or not.
+     *
+     * @param displayId The logical displayId.
+     * @param mode It's either ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN or
+     * ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW.
+     */
+    public boolean isActivated(int displayId, int mode) {
         boolean isActivated = false;
         if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
             synchronized (mLock) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index efc6d51..2324a5a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -16,6 +16,13 @@
 
 package com.android.server.accessibility.magnification;
 
+import static android.accessibilityservice.MagnificationConfig.DEFAULT_MODE;
+import static android.accessibilityservice.MagnificationConfig.FULLSCREEN_MODE;
+import static android.accessibilityservice.MagnificationConfig.WINDOW_MODE;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+
+import android.accessibilityservice.MagnificationConfig;
 import android.annotation.NonNull;
 import android.graphics.Region;
 
@@ -23,6 +30,22 @@
  * Processor class for AccessibilityService connection to control magnification on the specified
  * display. This wraps the function of magnification controller.
  *
+ * <p>
+ * If the magnification config uses {@link DEFAULT_MODE}. This processor will control the current
+ * activated magnifier on the display. If there is no magnifier activated, it controls
+ * full-screen magnifier by default.
+ * </p>
+ *
+ * <p>
+ * If the magnification config uses {@link FULLSCREEN_MODE}. This processor will control
+ * full-screen magnifier on the display.
+ * </p>
+ *
+ * <p>
+ * If the magnification config uses {@link WINDOW_MODE}. This processor will control
+ * the activated window magnifier on the display.
+ * </p>
+ *
  * @see MagnificationController
  * @see FullScreenMagnificationController
  */
@@ -35,53 +58,166 @@
     }
 
     /**
-     * {@link FullScreenMagnificationController#getScale(int)}
+     * Gets the magnification config of the display.
+     *
+     * @param displayId The logical display id
+     * @return the magnification config
+     */
+    public @NonNull MagnificationConfig getMagnificationConfig(int displayId) {
+        final int mode = getControllingMode(displayId);
+        MagnificationConfig.Builder builder = new MagnificationConfig.Builder();
+        if (mode == FULLSCREEN_MODE) {
+            final FullScreenMagnificationController fullScreenMagnificationController =
+                    mController.getFullScreenMagnificationController();
+            builder.setMode(mode)
+                    .setScale(fullScreenMagnificationController.getScale(displayId))
+                    .setCenterX(fullScreenMagnificationController.getCenterX(displayId))
+                    .setCenterY(fullScreenMagnificationController.getCenterY(displayId));
+        } else if (mode == WINDOW_MODE) {
+            final WindowMagnificationManager windowMagnificationManager =
+                    mController.getWindowMagnificationMgr();
+            builder.setMode(mode)
+                    .setScale(windowMagnificationManager.getScale(displayId))
+                    .setCenterX(windowMagnificationManager.getCenterX(displayId))
+                    .setCenterY(windowMagnificationManager.getCenterY(displayId));
+        }
+        return builder.build();
+    }
+
+    /**
+     * Sets the magnification config of the display. If animation is disabled, the transition
+     * is immediate.
+     *
+     * @param displayId The logical display id
+     * @param config    The magnification config
+     * @param animate   {@code true} to animate from the current config or
+     *                  {@code false} to set the config immediately
+     * @param id        The ID of the service requesting the change
+     * @return {@code true} if the magnification spec changed, {@code false} if the spec did not
+     * change
+     */
+    public boolean setMagnificationConfig(int displayId, @NonNull MagnificationConfig config,
+            boolean animate, int id) {
+        int configMode = config.getMode();
+        if (configMode == DEFAULT_MODE) {
+            configMode = getControllingMode(displayId);
+        }
+        if (configMode == FULLSCREEN_MODE) {
+            return setScaleAndCenterForFullScreenMagnification(displayId, config.getScale(),
+                    config.getCenterX(), config.getCenterY(),
+                    animate, id);
+        } else if (configMode == WINDOW_MODE) {
+            return mController.getWindowMagnificationMgr().enableWindowMagnification(displayId,
+                    config.getScale(), config.getCenterX(), config.getCenterY());
+        }
+        return false;
+    }
+
+    /**
+     * Returns the magnification scale. If an animation is in progress,
+     * this reflects the end state of the animation.
+     *
+     * @param displayId The logical display id.
+     * @return the scale
      */
     public float getScale(int displayId) {
-        return mController.getFullScreenMagnificationController().getScale(displayId);
+        int mode = getControllingMode(displayId);
+        if (mode == FULLSCREEN_MODE) {
+            return mController.getFullScreenMagnificationController().getScale(displayId);
+        } else if (mode == WINDOW_MODE) {
+            return mController.getWindowMagnificationMgr().getScale(displayId);
+        }
+        return 0;
     }
 
     /**
-     * {@link FullScreenMagnificationController#getCenterX(int)}
+     * Returns the magnification center in X coordinate of the controlling magnification mode.
+     * If the service can control magnification but fullscreen magnifier is not registered, it will
+     * register the magnifier for this call then unregister the magnifier finally to make the
+     * magnification center correct.
+     *
+     * @param displayId The logical display id
+     * @param canControlMagnification Whether the service can control magnification
+     * @return the X coordinate
      */
     public float getCenterX(int displayId, boolean canControlMagnification) {
-        boolean registeredJustForThisCall = registerMagnificationIfNeeded(displayId,
-                canControlMagnification);
-        try {
-            return mController.getFullScreenMagnificationController().getCenterX(displayId);
-        } finally {
-            if (registeredJustForThisCall) {
-                unregister(displayId);
+        int mode = getControllingMode(displayId);
+        if (mode == FULLSCREEN_MODE) {
+            boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
+                    canControlMagnification);
+            try {
+                return mController.getFullScreenMagnificationController().getCenterX(displayId);
+            } finally {
+                if (registeredJustForThisCall) {
+                    unregister(displayId);
+                }
             }
+        } else if (mode == WINDOW_MODE) {
+            return mController.getWindowMagnificationMgr().getCenterX(displayId);
         }
+        return 0;
     }
 
     /**
-     * {@link FullScreenMagnificationController#getCenterY(int)}
+     * Returns the magnification center in Y coordinate of the controlling magnification mode.
+     * If the service can control magnification but fullscreen magnifier is not registered, it will
+     * register the magnifier for this call then unregister the magnifier finally to make the
+     * magnification center correct.
+     *
+     * @param displayId The logical display id
+     * @param canControlMagnification Whether the service can control magnification
+     * @return the Y coordinate
      */
     public float getCenterY(int displayId, boolean canControlMagnification) {
-        boolean registeredJustForThisCall = registerMagnificationIfNeeded(displayId,
-                canControlMagnification);
-        try {
-            return mController.getFullScreenMagnificationController().getCenterY(displayId);
-        } finally {
-            if (registeredJustForThisCall) {
-                unregister(displayId);
+        int mode = getControllingMode(displayId);
+        if (mode == FULLSCREEN_MODE) {
+            boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
+                    canControlMagnification);
+            try {
+                return mController.getFullScreenMagnificationController().getCenterY(displayId);
+            } finally {
+                if (registeredJustForThisCall) {
+                    unregister(displayId);
+                }
             }
+        } else if (mode == WINDOW_MODE) {
+            return mController.getWindowMagnificationMgr().getCenterY(displayId);
         }
+        return 0;
     }
 
     /**
-     * {@link FullScreenMagnificationController#getMagnificationRegion(int, Region)}
+     * Return the magnification bounds of the current controlling magnification on the given
+     * display. If the magnifier is not enabled, it returns an empty region.
+     * If the service can control magnification but fullscreen magnifier is not registered, it will
+     * register the magnifier for this call then unregister the magnifier finally to make
+     * the magnification region correct.
+     *
+     * @param displayId The logical display id
+     * @param outRegion the region to populate
+     * @param canControlMagnification Whether the service can control magnification
+     * @return outRegion the magnification bounds of full-screen magnifier or the magnification
+     * source bounds of window magnifier
      */
     public Region getMagnificationRegion(int displayId, @NonNull Region outRegion,
             boolean canControlMagnification) {
-        boolean registeredJustForThisCall = registerMagnificationIfNeeded(displayId,
+        int mode = getControllingMode(displayId);
+        if (mode == FULLSCREEN_MODE) {
+            getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification);
+        } else if (mode == WINDOW_MODE) {
+            mController.getWindowMagnificationMgr().getMagnificationSourceBounds(displayId,
+                    outRegion);
+        }
+        return outRegion;
+    }
+
+    private void getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion,
+            boolean canControlMagnification) {
+        boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
                 canControlMagnification);
         try {
             mController.getFullScreenMagnificationController().getMagnificationRegion(displayId,
                     outRegion);
-            return outRegion;
         } finally {
             if (registeredJustForThisCall) {
                 unregister(displayId);
@@ -89,67 +225,105 @@
         }
     }
 
-    /**
-     * {@link FullScreenMagnificationController#setScaleAndCenter(int, float, float, float, boolean,
-     * int)}
-     */
-    public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
+    private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale,
+            float centerX, float centerY,
             boolean animate, int id) {
         if (!isRegistered(displayId)) {
             register(displayId);
         }
-        return mController.getFullScreenMagnificationController().setScaleAndCenter(displayId,
+        return mController.getFullScreenMagnificationController().setScaleAndCenter(
+                displayId,
                 scale,
                 centerX, centerY, animate, id);
     }
 
     /**
-     * {@link FullScreenMagnificationController#reset(int, boolean)}
+     * Resets the magnification on the given display. The reset mode could be full-screen or
+     * window if it is activated.
+     *
+     * @param displayId The logical display id.
+     * @param animate   {@code true} to animate the transition, {@code false}
+     *                  to transition immediately
+     * @return {@code true} if the magnification spec changed, {@code false} if
+     * the spec did not change
      */
     public boolean reset(int displayId, boolean animate) {
-        return mController.getFullScreenMagnificationController().reset(displayId, animate);
+        int mode = getControllingMode(displayId);
+        if (mode == FULLSCREEN_MODE) {
+            return mController.getFullScreenMagnificationController().reset(displayId, animate);
+        } else if (mode == WINDOW_MODE) {
+            return mController.getWindowMagnificationMgr().reset(displayId);
+        }
+        return false;
     }
 
     /**
      * {@link FullScreenMagnificationController#resetIfNeeded(int, boolean)}
      */
+    // TODO: support window magnification
     public void resetAllIfNeeded(int connectionId) {
         mController.getFullScreenMagnificationController().resetAllIfNeeded(connectionId);
     }
 
     /**
+     * {@link FullScreenMagnificationController#isMagnifying(int)}
+     * {@link WindowMagnificationManager#isWindowMagnifierEnabled(int)}
+     */
+    public boolean isMagnifying(int displayId) {
+        int mode = getControllingMode(displayId);
+        if (mode == FULLSCREEN_MODE) {
+            return mController.getFullScreenMagnificationController().isMagnifying(displayId);
+        } else if (mode == WINDOW_MODE) {
+            return mController.getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId);
+        }
+        return false;
+    }
+
+    /**
+     * Returns the current controlling magnification mode on the given display.
+     * If there is no magnifier activated, it fallbacks to the last activated mode.
+     * And the last activated mode is {@link FULLSCREEN_MODE} by default.
+     *
+     * @param displayId The logical display id
+     */
+    public int getControllingMode(int displayId) {
+        if (mController.isActivated(displayId,
+                ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) {
+            return WINDOW_MODE;
+        } else if (mController.isActivated(displayId,
+                ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)) {
+            return FULLSCREEN_MODE;
+        } else {
+            return (mController.getLastActivatedMode() == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)
+                    ? WINDOW_MODE
+                    : FULLSCREEN_MODE;
+        }
+    }
+
+    private boolean registerDisplayMagnificationIfNeeded(int displayId,
+            boolean canControlMagnification) {
+        if (!isRegistered(displayId) && canControlMagnification) {
+            register(displayId);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isRegistered(int displayId) {
+        return mController.getFullScreenMagnificationController().isRegistered(displayId);
+    }
+
+    /**
      * {@link FullScreenMagnificationController#register(int)}
      */
-    public void register(int displayId) {
+    private void register(int displayId) {
         mController.getFullScreenMagnificationController().register(displayId);
     }
 
     /**
      * {@link FullScreenMagnificationController#unregister(int)} (int)}
      */
-    public void unregister(int displayId) {
+    private void unregister(int displayId) {
         mController.getFullScreenMagnificationController().unregister(displayId);
     }
-
-    /**
-     * {@link FullScreenMagnificationController#isMagnifying(int)}
-     */
-    public boolean isMagnifying(int displayId) {
-        return mController.getFullScreenMagnificationController().isMagnifying(displayId);
-    }
-
-    /**
-     * {@link FullScreenMagnificationController#isRegistered(int)}
-     */
-    public boolean isRegistered(int displayId) {
-        return mController.getFullScreenMagnificationController().isRegistered(displayId);
-    }
-
-    private boolean registerMagnificationIfNeeded(int displayId, boolean canControlMagnification) {
-        if (!isRegistered(displayId) && canControlMagnification) {
-            register(displayId);
-            return true;
-        }
-        return false;
-    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index e1ec273..d34b4a9 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -27,6 +27,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -271,9 +272,12 @@
      *                or {@link Float#NaN} to leave unchanged.
      * @param centerY The screen-relative Y coordinate around which to center,
      *                or {@link Float#NaN} to leave unchanged.
+     * @return {@code true} if the magnification is enabled successfully.
      */
-    void enableWindowMagnification(int displayId, float scale, float centerX, float centerY) {
-        enableWindowMagnification(displayId, scale, centerX, centerY, STUB_ANIMATION_CALLBACK);
+    public boolean enableWindowMagnification(int displayId, float scale, float centerX,
+            float centerY) {
+        return enableWindowMagnification(displayId, scale, centerX, centerY,
+                STUB_ANIMATION_CALLBACK);
     }
 
     /**
@@ -287,25 +291,29 @@
      * @param centerY The screen-relative Y coordinate around which to center,
      *                or {@link Float#NaN} to leave unchanged.
      * @param animationCallback Called when the animation result is valid.
+     * @return {@code true} if the magnification is enabled successfully.
      */
-    void enableWindowMagnification(int displayId, float scale, float centerX, float centerY,
-            @Nullable MagnificationAnimationCallback animationCallback) {
+    public boolean enableWindowMagnification(int displayId, float scale, float centerX,
+            float centerY, @Nullable MagnificationAnimationCallback animationCallback) {
         final boolean enabled;
+        boolean previousEnabled;
         synchronized (mLock) {
             if (mConnectionWrapper == null) {
-                return;
+                return false;
             }
             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
             if (magnifier == null) {
                 magnifier = createWindowMagnifier(displayId);
             }
+            previousEnabled = magnifier.mEnabled;
             enabled = magnifier.enableWindowMagnificationInternal(scale, centerX, centerY,
                     animationCallback);
         }
 
-        if (enabled) {
+        if (enabled && !previousEnabled) {
             mCallback.onWindowMagnificationActivationState(displayId, true);
         }
+        return enabled;
     }
 
     /**
@@ -464,7 +472,7 @@
      * @param displayId The logical display id
      * @return the X coordinate. {@link Float#NaN} if the window magnification is not enabled.
      */
-    float getCenterX(int displayId) {
+    public float getCenterX(int displayId) {
         synchronized (mLock) {
             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
             if (magnifier == null) {
@@ -480,7 +488,7 @@
      * @param displayId The logical display id
      * @return the Y coordinate. {@link Float#NaN} if the window magnification is not enabled.
      */
-    float getCenterY(int displayId) {
+    public float getCenterY(int displayId) {
         synchronized (mLock) {
             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
             if (magnifier == null) {
@@ -491,6 +499,42 @@
     }
 
     /**
+     * Populates magnified bounds on the screen. And the populated magnified bounds would be
+     * empty If window magnifier is not activated.
+     *
+     * @param displayId The logical display id.
+     * @param outRegion the region to populate
+     */
+    public void getMagnificationSourceBounds(int displayId, @NonNull Region outRegion) {
+        synchronized (mLock) {
+            WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
+            if (magnifier == null) {
+                outRegion.setEmpty();
+            } else {
+                outRegion.set(magnifier.mSourceBounds);
+            }
+        }
+    }
+
+    /**
+     * Resets the magnification scale and center.
+     *
+     * @param displayId The logical display id.
+     * @return {@code true} if the magnification spec changed, {@code false} if
+     * the spec did not change
+     */
+    public boolean reset(int displayId) {
+        synchronized (mLock) {
+            WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
+            if (magnifier == null) {
+                return false;
+            }
+            magnifier.reset();
+            return true;
+        }
+    }
+
+    /**
      * Creates the windowMagnifier based on the specified display and stores it.
      *
      * @param displayId logical display id.
@@ -626,8 +670,9 @@
         @GuardedBy("mLock")
         boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY,
                 @Nullable MagnificationAnimationCallback animationCallback) {
-            if (mEnabled) {
-                return false;
+            // Handle defaults. The scale may be NAN when just updating magnification center.
+            if (Float.isNaN(scale)) {
+                scale = getScale();
             }
             final float normScale = MagnificationScaleProvider.constrainScale(scale);
             if (mWindowMagnificationManager.enableWindowMagnificationInternal(mDisplayId, normScale,
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 5e2449d..422749e 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -210,7 +210,7 @@
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         context.registerReceiver(mBroadcastReceiver, filter, null, FgThread.getHandler(),
-                Context.RECEIVER_NOT_EXPORTED);
+                Context.RECEIVER_EXPORTED);
 
         mAugmentedAutofillResolver = new FrameworkResourcesServiceNameResolver(getContext(),
                 com.android.internal.R.string.config_defaultAugmentedAutofillService);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9f22489..9351415 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -137,6 +137,7 @@
     ],
 
     required: [
+        "default_television.xml",
         "gps_debug.conf",
         "protolog.conf.json.gz",
     ],
diff --git a/services/core/java/com/android/server/AppFuseMountException.java b/services/core/java/com/android/server/AppFuseMountException.java
new file mode 100644
index 0000000..9a9379e
--- /dev/null
+++ b/services/core/java/com/android/server/AppFuseMountException.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import android.os.Parcel;
+
+/**
+ * An exception that indicates there was an error with a
+ * app fuse mount operation.
+ */
+public class AppFuseMountException extends Exception {
+    public AppFuseMountException(String detailMessage) {
+        super(detailMessage);
+    }
+
+    public AppFuseMountException(String detailMessage, Throwable throwable) {
+        super(detailMessage, throwable);
+    }
+
+    /**
+     * Rethrow as a {@link RuntimeException} subclass that is handled by
+     * {@link Parcel#writeException(Exception)}.
+     */
+    public IllegalArgumentException rethrowAsParcelableException() {
+        throw new IllegalStateException(getMessage(), this);
+    }
+}
diff --git a/services/core/java/com/android/server/BluetoothAirplaneModeListener.java b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
index 197321f..263ff18 100644
--- a/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
+++ b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
@@ -35,6 +35,7 @@
  * when Bluetooth is on and Bluetooth is in one of the following situations:
  *   1. Bluetooth A2DP is connected.
  *   2. Bluetooth Hearing Aid profile is connected.
+ *   3. Bluetooth LE Audio is connected
  */
 class BluetoothAirplaneModeListener {
     private static final String TAG = "BluetoothAirplaneModeListener";
@@ -132,7 +133,7 @@
             return false;
         }
         if (!mAirplaneHelper.isBluetoothOn() || !mAirplaneHelper.isAirplaneModeOn()
-                || !mAirplaneHelper.isA2dpOrHearingAidConnected()) {
+                || !mAirplaneHelper.isMediaProfileConnected()) {
             return false;
         }
         return true;
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index f62935a..8860a81 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -35,6 +35,7 @@
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothProtoEnums;
 import android.bluetooth.IBluetooth;
@@ -456,12 +457,13 @@
                     }
                 }
             } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)
-                    || BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
+                    || BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(action)
+                    || BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED.equals(action)) {
                 final int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
                         BluetoothProfile.STATE_CONNECTED);
                 if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED)
                         && state == BluetoothProfile.STATE_DISCONNECTED
-                        && !mBluetoothModeChangeHelper.isA2dpOrHearingAidConnected()) {
+                        && !mBluetoothModeChangeHelper.isMediaProfileConnected()) {
                     Slog.i(TAG, "Device disconnected, reactivating pending flag changes");
                     onInitFlagsChanged();
                 }
@@ -2291,7 +2293,7 @@
                         Slog.d(TAG, "MESSAGE_INIT_FLAGS_CHANGED");
                     }
                     mHandler.removeMessages(MESSAGE_INIT_FLAGS_CHANGED);
-                    if (mBluetoothModeChangeHelper.isA2dpOrHearingAidConnected()) {
+                    if (mBluetoothModeChangeHelper.isMediaProfileConnected()) {
                         Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by "
                                 + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS
                                 + " ms due to existing connections");
diff --git a/services/core/java/com/android/server/BluetoothModeChangeHelper.java b/services/core/java/com/android/server/BluetoothModeChangeHelper.java
index 3642e4d..e5854c9 100644
--- a/services/core/java/com/android/server/BluetoothModeChangeHelper.java
+++ b/services/core/java/com/android/server/BluetoothModeChangeHelper.java
@@ -20,6 +20,7 @@
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothProfile.ServiceListener;
 import android.content.Context;
@@ -37,6 +38,7 @@
 public class BluetoothModeChangeHelper {
     private volatile BluetoothA2dp mA2dp;
     private volatile BluetoothHearingAid mHearingAid;
+    private volatile BluetoothLeAudio mLeAudio;
     private final BluetoothAdapter mAdapter;
     private final Context mContext;
 
@@ -47,6 +49,7 @@
         mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.A2DP);
         mAdapter.getProfileProxy(mContext, mProfileServiceListener,
                 BluetoothProfile.HEARING_AID);
+        mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.LE_AUDIO);
     }
 
     private final ServiceListener mProfileServiceListener = new ServiceListener() {
@@ -60,6 +63,9 @@
                 case BluetoothProfile.HEARING_AID:
                     mHearingAid = (BluetoothHearingAid) proxy;
                     break;
+                case BluetoothProfile.LE_AUDIO:
+                    mLeAudio = (BluetoothLeAudio) proxy;
+                    break;
                 default:
                     break;
             }
@@ -75,6 +81,9 @@
                 case BluetoothProfile.HEARING_AID:
                     mHearingAid = null;
                     break;
+                case BluetoothProfile.LE_AUDIO:
+                    mLeAudio = null;
+                    break;
                 default:
                     break;
             }
@@ -82,8 +91,8 @@
     };
 
     @VisibleForTesting
-    public boolean isA2dpOrHearingAidConnected() {
-        return isA2dpConnected() || isHearingAidConnected();
+    public boolean isMediaProfileConnected() {
+        return isA2dpConnected() || isHearingAidConnected() || isLeAudioConnected();
     }
 
     @VisibleForTesting
@@ -142,4 +151,12 @@
         }
         return hearingAid.getConnectedDevices().size() > 0;
     }
+
+    private boolean isLeAudioConnected() {
+        final BluetoothLeAudio leAudio = mLeAudio;
+        if (leAudio == null) {
+            return false;
+        }
+        return leAudio.getConnectedDevices().size() > 0;
+    }
 }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 6f009b6..1929df8 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1223,7 +1223,7 @@
             }
             for (int i = 0; i < mVolumes.size(); i++) {
                 final VolumeInfo vol = mVolumes.valueAt(i);
-                if (vol.isVisibleForRead(userId) && vol.isMountedReadable()) {
+                if (vol.isVisibleForUser(userId) && vol.isMountedReadable()) {
                     final StorageVolume userVol = vol.buildStorageVolume(mContext, userId, false);
                     mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
 
@@ -1570,7 +1570,7 @@
                     || Objects.equals(privateVol.fsUuid, mPrimaryStorageUuid)) {
                 Slog.v(TAG, "Found primary storage at " + vol);
                 vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
-                vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
+                vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE;
                 mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
             }
 
@@ -1580,13 +1580,13 @@
                     && vol.disk.isDefaultPrimary()) {
                 Slog.v(TAG, "Found primary storage at " + vol);
                 vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
-                vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
+                vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE;
             }
 
             // Adoptable public disks are visible to apps, since they meet
             // public API requirement of being in a stable location.
             if (vol.disk.isAdoptable()) {
-                vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
+                vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE;
             }
 
             vol.mountUserId = mCurrentUserId;
@@ -1597,7 +1597,7 @@
 
         } else if (vol.type == VolumeInfo.TYPE_STUB) {
             if (vol.disk.isStubVisible()) {
-                vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
+                vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE;
             }
             vol.mountUserId = mCurrentUserId;
             mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
@@ -1744,7 +1744,7 @@
                 // started after this point will trigger additional
                 // user-specific broadcasts.
                 for (int userId : mSystemUnlockedUsers) {
-                    if (vol.isVisibleForRead(userId)) {
+                    if (vol.isVisibleForUser(userId)) {
                         final StorageVolume userVol = vol.buildStorageVolume(mContext, userId,
                                 false);
                         mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
@@ -3565,24 +3565,24 @@
         }
 
         @Override
-        public ParcelFileDescriptor open() throws NativeDaemonConnectorException {
+        public ParcelFileDescriptor open() throws AppFuseMountException {
             try {
                 final FileDescriptor fd = mVold.mountAppFuse(uid, mountId);
                 mMounted = true;
                 return new ParcelFileDescriptor(fd);
             } catch (Exception e) {
-                throw new NativeDaemonConnectorException("Failed to mount", e);
+                throw new AppFuseMountException("Failed to mount", e);
             }
         }
 
         @Override
         public ParcelFileDescriptor openFile(int mountId, int fileId, int flags)
-                throws NativeDaemonConnectorException {
+                throws AppFuseMountException {
             try {
                 return new ParcelFileDescriptor(
                         mVold.openAppFuseFile(uid, mountId, fileId, flags));
             } catch (Exception e) {
-                throw new NativeDaemonConnectorException("Failed to open", e);
+                throw new AppFuseMountException("Failed to open", e);
             }
         }
 
@@ -3622,7 +3622,7 @@
                         // It seems the thread of mAppFuseBridge has already been terminated.
                         mAppFuseBridge = null;
                     }
-                } catch (NativeDaemonConnectorException e) {
+                } catch (AppFuseMountException e) {
                     throw e.rethrowAsParcelableException();
                 }
             }
@@ -3783,7 +3783,7 @@
                 if (forWrite) {
                     match = vol.isVisibleForWrite(userId);
                 } else {
-                    match = vol.isVisibleForRead(userId)
+                    match = vol.isVisibleForUser(userId)
                             || (includeInvisible && vol.getPath() != null);
                 }
                 if (!match) continue;
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index b068f86..0c990ec 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -141,7 +141,7 @@
  *                      |                     or its properties
  *                      v                              |
  * +-----------------------------------------------------------------------+
- * |                       UnderlyingNetworkTracker                        |
+ * |                       UnderlyingNetworkController                     |
  * |                                                                       |
  * | Manages lifecycle of underlying physical networks, filing requests to |
  * | bring them up, and releasing them as they become no longer necessary  |
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index b019789..ac20a08 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -685,6 +685,7 @@
             final UUID errorId = mTraceErrorLogger.generateErrorId();
             if (mTraceErrorLogger.isAddErrorIdEnabled()) {
                 mTraceErrorLogger.addErrorIdToTrace("system_server", errorId);
+                mTraceErrorLogger.addSubjectToTrace(subject, errorId);
             }
 
             // Log the atom as early as possible since it is used as a mechanism to trigger
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 15871aa..02a16fc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15417,6 +15417,16 @@
     }
 
     @Override
+    public String getSwitchingFromUserMessage() {
+        return mUserController.getSwitchingFromSystemUserMessage();
+    }
+
+    @Override
+    public String getSwitchingToUserMessage() {
+        return mUserController.getSwitchingToSystemUserMessage();
+    }
+
+    @Override
     public void setStopUserOnSwitch(@StopUserOnSwitch int value) {
         mUserController.setStopUserOnSwitch(value);
     }
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 17daa75..592abbb 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -789,7 +789,7 @@
 
         // Ensure that broadcasts are only sent to other apps if they are explicitly marked as
         // exported, or are System level broadcasts
-        if (!skip && !filter.exported && Process.SYSTEM_UID != r.callingUid
+        if (!skip && !filter.exported && !Process.isCoreUid(r.callingUid)
                 && filter.receiverList.uid != r.callingUid) {
 
             Slog.w(TAG, "Exported Denial: sending "
@@ -800,7 +800,7 @@
                     + " due to receiver " + filter.receiverList.app
                     + " (uid " + filter.receiverList.uid + ")"
                     + " not specifying RECEIVER_EXPORTED");
-            skip = true;
+            // skip = true;
         }
 
         if (skip) {
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 4220506..18ad1f5 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -286,6 +286,7 @@
                     && mService.mTraceErrorLogger.isAddErrorIdEnabled()) {
                 errorId = mService.mTraceErrorLogger.generateErrorId();
                 mService.mTraceErrorLogger.addErrorIdToTrace(mApp.processName, errorId);
+                mService.mTraceErrorLogger.addSubjectToTrace(annotation, errorId);
             } else {
                 errorId = null;
             }
diff --git a/services/core/java/com/android/server/am/TraceErrorLogger.java b/services/core/java/com/android/server/am/TraceErrorLogger.java
index c658100..29a9b5c 100644
--- a/services/core/java/com/android/server/am/TraceErrorLogger.java
+++ b/services/core/java/com/android/server/am/TraceErrorLogger.java
@@ -54,4 +54,17 @@
                 COUNTER_PREFIX + processName + "#" + errorId.toString(),
                 PLACEHOLDER_VALUE);
     }
+
+    /**
+     * Pushes a counter containing an ANR/Watchdog subject and a unique id so that the subject
+     * can be uniquely identified.
+     *
+     * @param subject The subject to include in the trace.
+     * @param errorId The unique id with which to tag the trace.
+     */
+    public void addSubjectToTrace(String subject, UUID errorId) {
+        Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                String.format("Subject(for ErrorId %s):%s", errorId.toString(), subject),
+                PLACEHOLDER_VALUE);
+    }
 }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index d52f52e..e79cba1 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -67,10 +67,10 @@
 import android.content.PermissionChecker;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.PackagePartitions;
 import android.content.pm.UserInfo;
 import android.os.BatteryStats;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.Handler;
@@ -719,7 +719,7 @@
         // purposefully block sending BOOT_COMPLETED until after all
         // PRE_BOOT receivers are finished to avoid ANR'ing apps
         final UserInfo info = getUserInfo(userId);
-        if (!Objects.equals(info.lastLoggedInFingerprint, Build.FINGERPRINT)
+        if (!Objects.equals(info.lastLoggedInFingerprint, PackagePartitions.FINGERPRINT)
                 || SystemProperties.getBoolean("persist.pm.mock-upgrade", false)) {
             // Suppress double notifications for managed profiles that
             // were unlocked automatically as part of their parent user
@@ -1809,7 +1809,8 @@
     private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
         // The dialog will show and then initiate the user switch by calling startUserInForeground
         mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second,
-                getSwitchingFromSystemUserMessage(), getSwitchingToSystemUserMessage());
+                getSwitchingFromSystemUserMessageUnchecked(),
+                getSwitchingToSystemUserMessageUnchecked());
     }
 
     private void dispatchForegroundProfileChanged(@UserIdInt int userId) {
@@ -2622,18 +2623,40 @@
         }
     }
 
-    private String getSwitchingFromSystemUserMessage() {
+    // Called by AMS, must check permission
+    String getSwitchingFromSystemUserMessage() {
+        checkHasManageUsersPermission("getSwitchingFromSystemUserMessage()");
+
+        return getSwitchingFromSystemUserMessageUnchecked();
+    }
+
+    // Called by AMS, must check permission
+    String getSwitchingToSystemUserMessage() {
+        checkHasManageUsersPermission("getSwitchingToSystemUserMessage()");
+
+        return getSwitchingToSystemUserMessageUnchecked();
+    }
+
+    private String getSwitchingFromSystemUserMessageUnchecked() {
         synchronized (mLock) {
             return mSwitchingFromSystemUserMessage;
         }
     }
 
-    private String getSwitchingToSystemUserMessage() {
+    private String getSwitchingToSystemUserMessageUnchecked() {
         synchronized (mLock) {
             return mSwitchingToSystemUserMessage;
         }
     }
 
+    private void checkHasManageUsersPermission(String operation) {
+        if (mInjector.checkCallingPermission(
+                android.Manifest.permission.MANAGE_USERS) == PackageManager.PERMISSION_DENIED) {
+            throw new SecurityException(
+                    "You need MANAGE_USERS permission to call " + operation);
+        }
+    }
+
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
         synchronized (mLock) {
             long token = proto.start(fieldId);
@@ -2706,6 +2729,12 @@
             pw.println("  mMaxRunningUsers:" + mMaxRunningUsers);
             pw.println("  mUserSwitchUiEnabled:" + mUserSwitchUiEnabled);
             pw.println("  mInitialized:" + mInitialized);
+            if (mSwitchingFromSystemUserMessage != null) {
+                pw.println("  mSwitchingFromSystemUserMessage: " + mSwitchingFromSystemUserMessage);
+            }
+            if (mSwitchingToSystemUserMessage != null) {
+                pw.println("  mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/display/DensityMap.java b/services/core/java/com/android/server/display/DensityMap.java
new file mode 100644
index 0000000..4aafd14
--- /dev/null
+++ b/services/core/java/com/android/server/display/DensityMap.java
@@ -0,0 +1,137 @@
+/*
+ * 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.display;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Class which can compute the logical density for a display resolution. It holds a collection
+ * of pre-configured densities, which are used for look-up and interpolation.
+ */
+public class DensityMap {
+
+    // Instead of resolutions we store the squared diagonal size. Diagonals make the map
+    // keys invariant to rotations and are useful for interpolation because they're scalars.
+    // Squared diagonals have the same properties as diagonals (the square function is monotonic)
+    // but also allow us to use integer types and avoid floating point arithmetics.
+    private final Entry[] mSortedDensityMapEntries;
+
+    /**
+     * Creates a density map. The newly created object takes ownership of the passed array.
+     */
+    static DensityMap createByOwning(Entry[] densityMapEntries) {
+        return new DensityMap(densityMapEntries);
+    }
+
+    private DensityMap(Entry[] densityMapEntries) {
+        Arrays.sort(densityMapEntries, Comparator.comparingInt(entry -> entry.squaredDiagonal));
+        mSortedDensityMapEntries = densityMapEntries;
+        verifyDensityMap(mSortedDensityMapEntries);
+    }
+
+    /**
+     * Returns the logical density for the given resolution.
+     *
+     * If the resolution matches one of the entries in the map, the corresponding density is
+     * returned. Otherwise the return value is interpolated using the closest entries in the map.
+     */
+    public int getDensityForResolution(int width, int height) {
+        int squaredDiagonal = width * width + height * height;
+
+        // Search for two pre-configured entries "left" and "right" with the following criteria
+        //  * left <= squaredDiagonal
+        //  * squaredDiagonal - left is minimal
+        //  * right > squaredDiagonal
+        //  * right - squaredDiagonal is minimal
+        Entry left = Entry.ZEROES;
+        Entry right = null;
+
+        for (Entry entry : mSortedDensityMapEntries) {
+            if (entry.squaredDiagonal <= squaredDiagonal) {
+                left = entry;
+            } else {
+                right = entry;
+                break;
+            }
+        }
+
+        // Check if we found an exact match.
+        if (left.squaredDiagonal == squaredDiagonal) {
+            return left.density;
+        }
+
+        // If no configured resolution is higher than the specified resolution, interpolate
+        // between (0,0) and (maxConfiguredDiagonal, maxConfiguredDensity).
+        if (right == null) {
+            right = left;  // largest entry in the sorted array
+            left = Entry.ZEROES;
+        }
+
+        double leftDiagonal = Math.sqrt(left.squaredDiagonal);
+        double rightDiagonal = Math.sqrt(right.squaredDiagonal);
+        double diagonal = Math.sqrt(squaredDiagonal);
+
+        return (int) Math.round((diagonal - leftDiagonal) * (right.density - left.density)
+                / (rightDiagonal - leftDiagonal) + left.density);
+    }
+
+    private static void verifyDensityMap(Entry[] sortedEntries) {
+        for (int i = 1; i < sortedEntries.length; i++) {
+            Entry prev = sortedEntries[i - 1];
+            Entry curr = sortedEntries[i];
+
+            if (prev.squaredDiagonal == curr.squaredDiagonal) {
+                // This will most often happen because there are two entries with the same
+                // resolution (AxB and AxB) or rotated resolution (AxB and BxA), but it can also
+                // happen in the very rare cases when two different resolutions happen to have
+                // the same diagonal (e.g. 100x700 and 500x500).
+                throw new IllegalStateException("Found two entries in the density map with"
+                        + " the same diagonal: " + prev + ", " + curr);
+            } else if (prev.density > curr.density) {
+                throw new IllegalStateException("Found two entries in the density map with"
+                        + " increasing diagonal but decreasing density: " + prev + ", " + curr);
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "DensityMap{"
+                + "mDensityMapEntries=" + Arrays.toString(mSortedDensityMapEntries)
+                + '}';
+    }
+
+    static class Entry {
+        public static final Entry ZEROES = new Entry(0, 0, 0);
+
+        public final int squaredDiagonal;
+        public final int density;
+
+        Entry(int width, int height, int density) {
+            this.squaredDiagonal = width * width + height * height;
+            this.density = density;
+        }
+
+        @Override
+        public String toString() {
+            return "DensityMapEntry{"
+                    + "squaredDiagonal=" + squaredDiagonal
+                    + ", density=" + density + '}';
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 2ae5cbb..a9e1647 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
@@ -31,6 +32,7 @@
 
 import com.android.internal.R;
 import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.config.Density;
 import com.android.server.display.config.DisplayConfiguration;
 import com.android.server.display.config.DisplayQuirks;
 import com.android.server.display.config.HbmTiming;
@@ -52,6 +54,7 @@
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 
 import javax.xml.datatype.DatatypeConfigurationException;
@@ -70,6 +73,8 @@
     private static final String ETC_DIR = "etc";
     private static final String DISPLAY_CONFIG_DIR = "displayconfig";
     private static final String CONFIG_FILE_FORMAT = "display_%s.xml";
+    private static final String DEFAULT_CONFIG_FILE = "default.xml";
+    private static final String DEFAULT_CONFIG_FILE_WITH_UIMODE_FORMAT = "default_%s.xml";
     private static final String PORT_SUFFIX_FORMAT = "port_%d";
     private static final String STABLE_ID_SUFFIX_FORMAT = "id_%d";
     private static final String NO_SUFFIX_FORMAT = "%d";
@@ -121,6 +126,7 @@
     private List<String> mQuirks;
     private boolean mIsHighBrightnessModeEnabled = false;
     private HighBrightnessModeData mHbmData;
+    private DensityMap mDensityMap;
     private String mLoadedFrom = null;
 
     private DisplayDeviceConfig(Context context) {
@@ -141,6 +147,33 @@
      */
     public static DisplayDeviceConfig create(Context context, long physicalDisplayId,
             boolean isDefaultDisplay) {
+        final DisplayDeviceConfig config = createWithoutDefaultValues(context, physicalDisplayId,
+                isDefaultDisplay);
+
+        config.copyUninitializedValuesFromSecondaryConfig(loadDefaultConfigurationXml(context));
+        return config;
+    }
+
+    /**
+     * Creates an instance using global values since no display device config xml exists.
+     * Uses values from config or PowerManager.
+     *
+     * @param context
+     * @param useConfigXml
+     * @return A configuration instance.
+     */
+    public static DisplayDeviceConfig create(Context context, boolean useConfigXml) {
+        final DisplayDeviceConfig config;
+        if (useConfigXml) {
+            config = getConfigFromGlobalXml(context);
+        } else {
+            config = getConfigFromPmValues(context);
+        }
+        return config;
+    }
+
+    private static DisplayDeviceConfig createWithoutDefaultValues(Context context,
+            long physicalDisplayId, boolean isDefaultDisplay) {
         DisplayDeviceConfig config;
 
         config = loadConfigFromDirectory(context, Environment.getProductDirectory(),
@@ -161,22 +194,53 @@
         return create(context, isDefaultDisplay);
     }
 
-    /**
-     * Creates an instance using global values since no display device config xml exists.
-     * Uses values from config or PowerManager.
-     *
-     * @param context
-     * @param useConfigXml
-     * @return A configuration instance.
-     */
-    public static DisplayDeviceConfig create(Context context, boolean useConfigXml) {
-        DisplayDeviceConfig config;
-        if (useConfigXml) {
-            config = getConfigFromGlobalXml(context);
-        } else {
-            config = getConfigFromPmValues(context);
+    private static DisplayConfiguration loadDefaultConfigurationXml(Context context) {
+        List<File> defaultXmlLocations = new ArrayList<>();
+        defaultXmlLocations.add(Environment.buildPath(Environment.getProductDirectory(),
+                ETC_DIR, DISPLAY_CONFIG_DIR, DEFAULT_CONFIG_FILE));
+        defaultXmlLocations.add(Environment.buildPath(Environment.getVendorDirectory(),
+                ETC_DIR, DISPLAY_CONFIG_DIR, DEFAULT_CONFIG_FILE));
+
+        // Read config_defaultUiModeType directly because UiModeManager hasn't started yet.
+        final int uiModeType = context.getResources()
+                .getInteger(com.android.internal.R.integer.config_defaultUiModeType);
+        final String uiModeTypeStr = Configuration.getUiModeTypeString(uiModeType);
+        if (uiModeTypeStr != null) {
+            defaultXmlLocations.add(Environment.buildPath(Environment.getRootDirectory(),
+                    ETC_DIR, DISPLAY_CONFIG_DIR,
+                    String.format(DEFAULT_CONFIG_FILE_WITH_UIMODE_FORMAT, uiModeTypeStr)));
         }
-        return config;
+        defaultXmlLocations.add(Environment.buildPath(Environment.getRootDirectory(),
+                ETC_DIR, DISPLAY_CONFIG_DIR, DEFAULT_CONFIG_FILE));
+
+        final File configFile = getFirstExistingFile(defaultXmlLocations);
+        if (configFile == null) {
+            // Display configuration files aren't required to exist.
+            return null;
+        }
+
+        DisplayConfiguration defaultConfig = null;
+
+        try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
+            defaultConfig = XmlParser.read(in);
+            if (defaultConfig == null) {
+                Slog.i(TAG, "Default DisplayDeviceConfig file is null");
+            }
+        } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
+            Slog.e(TAG, "Encountered an error while reading/parsing display config file: "
+                    + configFile, e);
+        }
+
+        return defaultConfig;
+    }
+
+    private static File getFirstExistingFile(Collection<File> files) {
+        for (File file : files) {
+            if (file.exists() && file.isFile()) {
+                return file;
+            }
+        }
+        return null;
     }
 
     private static DisplayDeviceConfig loadConfigFromDirectory(Context context,
@@ -316,9 +380,13 @@
         return mRefreshRateLimitations;
     }
 
+    public DensityMap getDensityMap() {
+        return mDensityMap;
+    }
+
     @Override
     public String toString() {
-        String str = "DisplayDeviceConfig{"
+        return "DisplayDeviceConfig{"
                 + "mLoadedFrom=" + mLoadedFrom
                 + ", mBacklight=" + Arrays.toString(mBacklight)
                 + ", mNits=" + Arrays.toString(mNits)
@@ -340,8 +408,8 @@
                 + ", mAmbientLightSensor=" + mAmbientLightSensor
                 + ", mProximitySensor=" + mProximitySensor
                 + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
+                + ", mDensityMap= " + mDensityMap
                 + "}";
-        return str;
     }
 
     private static DisplayDeviceConfig getConfigFromSuffix(Context context, File baseDirectory,
@@ -384,6 +452,7 @@
         try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
             final DisplayConfiguration config = XmlParser.read(in);
             if (config != null) {
+                loadDensityMap(config);
                 loadBrightnessDefaultFromDdcXml(config);
                 loadBrightnessConstraintsFromConfigXml();
                 loadBrightnessMap(config);
@@ -429,6 +498,35 @@
         setProxSensorUnspecified();
     }
 
+    private void copyUninitializedValuesFromSecondaryConfig(DisplayConfiguration defaultConfig) {
+        if (defaultConfig == null) {
+            return;
+        }
+
+        if (mDensityMap == null) {
+            loadDensityMap(defaultConfig);
+        }
+    }
+
+    private void loadDensityMap(DisplayConfiguration config) {
+        if (config.getDensityMap() == null) {
+            return;
+        }
+
+        final List<Density> entriesFromXml = config.getDensityMap().getDensity();
+
+        final DensityMap.Entry[] entries =
+                new DensityMap.Entry[entriesFromXml.size()];
+        for (int i = 0; i < entriesFromXml.size(); i++) {
+            final Density density = entriesFromXml.get(i);
+            entries[i] = new DensityMap.Entry(
+                    density.getWidth().intValue(),
+                    density.getHeight().intValue(),
+                    density.getDensity().intValue());
+        }
+        mDensityMap = DensityMap.createByOwning(entries);
+    }
+
     private void loadBrightnessDefaultFromDdcXml(DisplayConfiguration config) {
         // Default brightness values are stored in the displayDeviceConfig file,
         // Or we fallback standard values if not.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e4bed3d..1c62699 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -110,6 +110,7 @@
 import android.view.DisplayInfo;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.window.DisplayWindowPolicyController;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -239,6 +240,10 @@
     public final SparseArray<CallbackRecord> mCallbacks =
             new SparseArray<CallbackRecord>();
 
+    /** All {@link DisplayWindowPolicyController}s indexed by {@link DisplayInfo#displayId}. */
+    final SparseArray<DisplayWindowPolicyController> mDisplayWindowPolicyController =
+            new SparseArray<>();
+
     // List of all currently registered display adapters.
     private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>();
 
@@ -1117,7 +1122,8 @@
 
     private int createVirtualDisplayInternal(IVirtualDisplayCallback callback,
             IMediaProjection projection, int callingUid, String packageName, Surface surface,
-            int flags, VirtualDisplayConfig virtualDisplayConfig) {
+            int flags, VirtualDisplayConfig virtualDisplayConfig,
+            DisplayWindowPolicyController controller) {
         synchronized (mSyncRoot) {
             if (mVirtualDisplayAdapter == null) {
                 Slog.w(TAG, "Rejecting request to create private virtual display "
@@ -1145,6 +1151,9 @@
 
             final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
             if (display != null) {
+                if (controller != null) {
+                    mDisplayWindowPolicyController.put(display.getDisplayIdLocked(), controller);
+                }
                 return display.getDisplayIdLocked();
             }
 
@@ -1188,6 +1197,10 @@
             DisplayDevice device =
                     mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
             if (device != null) {
+                final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
+                if (display != null) {
+                    mDisplayWindowPolicyController.delete(display.getDisplayIdLocked());
+                }
                 // TODO: multi-display - handle virtual displays the same as other display adapters.
                 mDisplayDeviceRepo.onDisplayDeviceEvent(device,
                         DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
@@ -2139,6 +2152,15 @@
             }
             pw.println();
             mPersistentDataStore.dump(pw);
+
+            final int displayWindowPolicyControllerCount = mDisplayWindowPolicyController.size();
+            pw.println();
+            pw.println("Display Window Policy Controllers: size="
+                    + displayWindowPolicyControllerCount);
+            for (int i = 0; i < displayWindowPolicyControllerCount; i++) {
+                pw.print("Display " + mDisplayWindowPolicyController.keyAt(i) + ":");
+                mDisplayWindowPolicyController.valueAt(i).dump("  ", pw);
+            }
         }
         pw.println();
         mDisplayModeDirector.dump(pw);
@@ -2704,6 +2726,13 @@
         @Override // Binder call
         public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig,
                 IVirtualDisplayCallback callback, IMediaProjection projection, String packageName) {
+            return createVirtualDisplay(virtualDisplayConfig, callback, projection, packageName,
+                    null /* controller */);
+        }
+
+        public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig,
+                IVirtualDisplayCallback callback, IMediaProjection projection, String packageName,
+                DisplayWindowPolicyController controller) {
             final int callingUid = Binder.getCallingUid();
             if (!validatePackageName(callingUid, packageName)) {
                 throw new SecurityException("packageName must match the calling uid");
@@ -2803,7 +2832,7 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 return createVirtualDisplayInternal(callback, projection, callingUid, packageName,
-                        surface, flags, virtualDisplayConfig);
+                        surface, flags, virtualDisplayConfig, controller);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -3624,6 +3653,13 @@
         public void onEarlyInteractivityChange(boolean interactive) {
             mLogicalDisplayMapper.onEarlyInteractivityChange(interactive);
         }
+
+        @Override
+        public DisplayWindowPolicyController getDisplayWindowPolicyController(int displayId) {
+            synchronized (mSyncRoot) {
+                return mDisplayWindowPolicyController.get(displayId);
+            }
+        }
     }
 
     class DesiredDisplayModeSpecsObserver
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index b6d13e0..300f59e 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -426,6 +426,15 @@
                     : mDefaultModeId;
         }
 
+        private int getLogicalDensity() {
+            DensityMap densityMap = getDisplayDeviceConfig().getDensityMap();
+            if (densityMap == null) {
+                return (int) (mStaticDisplayInfo.density * 160 + 0.5);
+            }
+
+            return densityMap.getDensityForResolution(mInfo.width, mInfo.height);
+        }
+
         private void loadDisplayDeviceConfig() {
             // Load display device config
             final Context context = getOverlayContext();
@@ -591,7 +600,7 @@
                 final DisplayAddress.Physical physicalAddress =
                         DisplayAddress.fromPhysicalDisplayId(mPhysicalDisplayId);
                 mInfo.address = physicalAddress;
-                mInfo.densityDpi = (int) (mStaticDisplayInfo.density * 160 + 0.5f);
+                mInfo.densityDpi = getLogicalDensity();
                 mInfo.xDpi = mActiveSfDisplayMode.xDpi;
                 mInfo.yDpi = mActiveSfDisplayMode.yDpi;
                 mInfo.deviceProductInfo = mStaticDisplayInfo.deviceProductInfo;
@@ -1029,7 +1038,7 @@
             for (int i = 0; i < mSupportedModes.size(); i++) {
                 pw.println("  " + mSupportedModes.valueAt(i));
             }
-            pw.println("mSupportedColorModes=" + mSupportedColorModes.toString());
+            pw.println("mSupportedColorModes=" + mSupportedColorModes);
             pw.println("mDisplayDeviceConfig=" + mDisplayDeviceConfig);
         }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index d6ac25a..b23395f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -21,6 +21,7 @@
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.SystemProperties;
@@ -47,6 +48,10 @@
     private static final boolean SET_MENU_LANGUAGE =
             HdmiProperties.set_menu_language_enabled().orElse(false);
 
+    // How long to wait after hotplug out before possibly going to Standby.
+    @VisibleForTesting
+    static final long STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS = 30_000;
+
     // Used to keep the device awake while it is the active source. For devices that
     // cannot wake up via CEC commands, this address the inconvenience of having to
     // turn them on. True by default, and can be disabled (i.e. device can go to sleep
@@ -55,6 +60,9 @@
     // Lazily initialized - should call getWakeLock() to get the instance.
     private ActiveWakeLock mWakeLock;
 
+    // Handler for queueing a delayed Standby runnable after hotplug out.
+    private Handler mDelayedStandbyHandler;
+
     // Determines what action should be taken upon receiving Routing Control messages.
     @VisibleForTesting
     protected HdmiProperties.playback_device_action_on_routing_control_values
@@ -64,6 +72,8 @@
 
     HdmiCecLocalDevicePlayback(HdmiControlService service) {
         super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
+
+        mDelayedStandbyHandler = new Handler(service.getServiceLooper());
     }
 
     @Override
@@ -195,9 +205,33 @@
     void onHotplug(int portId, boolean connected) {
         assertRunOnServiceThread();
         mCecMessageCache.flushAll();
-        // We'll not invalidate the active source on the hotplug event to pass CETC 11.2.2-2 ~ 3.
-        if (!connected) {
+
+        if (connected) {
+            mDelayedStandbyHandler.removeCallbacksAndMessages(null);
+        } else {
+            // We'll not invalidate the active source on the hotplug event to pass CETC 11.2.2-2 ~ 3
             getWakeLock().release();
+
+            mDelayedStandbyHandler.removeCallbacksAndMessages(null);
+            mDelayedStandbyHandler.postDelayed(new DelayedStandbyRunnable(),
+                    STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
+        }
+    }
+
+    /**
+     * Runnable for going to Standby if the device has been inactive for a certain amount of time.
+     * Posts a new instance of itself as a delayed message if the device was active.
+     */
+    private class DelayedStandbyRunnable implements Runnable {
+        @Override
+        public void run() {
+            if (mService.getPowerManagerInternal().wasDeviceIdleFor(
+                    STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS)) {
+                mService.standby();
+            } else {
+                mDelayedStandbyHandler.postDelayed(new DelayedStandbyRunnable(),
+                        STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 69f7af2..c08d343 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -408,6 +408,9 @@
     private PowerManagerWrapper mPowerManager;
 
     @Nullable
+    private PowerManagerInternalWrapper mPowerManagerInternal;
+
+    @Nullable
     private Looper mIoLooper;
 
     @HdmiControlManager.HdmiCecVersion
@@ -734,6 +737,7 @@
             mTvInputManager = (TvInputManager) getContext().getSystemService(
                     Context.TV_INPUT_SERVICE);
             mPowerManager = new PowerManagerWrapper(getContext());
+            mPowerManagerInternal = new PowerManagerInternalWrapper();
         } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
             runOnServiceThread(this::bootCompleted);
         }
@@ -758,10 +762,19 @@
         mPowerManager = powerManager;
     }
 
+    @VisibleForTesting
+    void setPowerManagerInternal(PowerManagerInternalWrapper powerManagerInternal) {
+        mPowerManagerInternal = powerManagerInternal;
+    }
+
     PowerManagerWrapper getPowerManager() {
         return mPowerManager;
     }
 
+    PowerManagerInternalWrapper getPowerManagerInternal() {
+        return mPowerManagerInternal;
+    }
+
     /**
      * Called when the initialization of local devices is complete.
      */
@@ -3239,18 +3252,19 @@
         assertRunOnServiceThread();
         Slog.v(TAG, "onPendingActionsCleared");
 
-        if (!mPowerStatusController.isPowerStatusTransientToStandby()) {
-            return;
+        if (mPowerStatusController.isPowerStatusTransientToStandby()) {
+            mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
+            for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
+                device.onStandby(mStandbyMessageReceived, standbyAction);
+            }
+            if (!isAudioSystemDevice()) {
+                mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
+                mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
+            }
         }
-        mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
-        for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
-            device.onStandby(mStandbyMessageReceived, standbyAction);
-        }
+
+        // Always reset this flag to set up for the next standby
         mStandbyMessageReceived = false;
-        if (!isAudioSystemDevice()) {
-            mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
-            mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
-        }
     }
 
     private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
diff --git a/services/core/java/com/android/server/hdmi/PowerManagerInternalWrapper.java b/services/core/java/com/android/server/hdmi/PowerManagerInternalWrapper.java
new file mode 100644
index 0000000..59671a8
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/PowerManagerInternalWrapper.java
@@ -0,0 +1,41 @@
+/*
+ * 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.hdmi;
+
+import android.os.PowerManagerInternal;
+
+import com.android.server.LocalServices;
+
+/**
+ * Abstraction around {@link PowerManagerInternal} to allow faking it in tests.
+ */
+public class PowerManagerInternalWrapper {
+    private static final String TAG = "PowerManagerInternalWrapper";
+
+    private PowerManagerInternal mPowerManagerInternal;
+
+    public PowerManagerInternalWrapper() {
+        mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+    }
+
+    /**
+     * Wraps {@link PowerManagerInternal#wasDeviceIdleFor(long)}
+     */
+    public boolean wasDeviceIdleFor(long ms) {
+        return mPowerManagerInternal.wasDeviceIdleFor(ms);
+    }
+}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index ffeaad1..457c2fd 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -154,10 +154,10 @@
         final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
                 mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName,
                         userId);
-        boolean isSuccess = updater.setLocales(locales).commit();
+        boolean isConfigChanged = updater.setLocales(locales).commit();
 
         //We want to send the broadcasts only if config was actually updated on commit.
-        if (isSuccess) {
+        if (isConfigChanged) {
             notifyAppWhoseLocaleChanged(appPackageName, userId, locales);
             notifyInstallerOfAppWhoseLocaleChanged(appPackageName, userId, locales);
             notifyRegisteredReceivers(appPackageName, userId, locales);
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 b676f28..efd3037 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -30,6 +30,7 @@
 import android.compat.annotation.EnabledAfter;
 import android.content.Context;
 import android.content.Intent;
+import android.hardware.contexthub.HostEndpointInfo;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubManager;
 import android.hardware.location.ContextHubTransaction;
@@ -42,6 +43,7 @@
 import android.os.Build;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
@@ -329,6 +331,15 @@
         mAppOpsManager = context.getSystemService(AppOpsManager.class);
 
         startMonitoringOpChanges();
+
+        HostEndpointInfo info = new HostEndpointInfo();
+        info.hostEndpointId = (char) mHostEndPointId;
+        info.packageName = mPackage;
+        info.attributionTag = mAttributionTag;
+        info.type = (mUid == Process.SYSTEM_UID)
+             ? HostEndpointInfo.Type.TYPE_FRAMEWORK
+             : HostEndpointInfo.Type.TYPE_APP;
+        mContextHubProxy.onHostEndpointConnected(info);
     }
 
     /* package */ ContextHubClientBroker(
@@ -862,6 +873,8 @@
             mRegistered = false;
         }
         mAppOpsManager.stopWatchingMode(this);
+
+        mContextHubProxy.onHostEndpointDisconnected(mHostEndPointId);
     }
 
     private String authStateToString(@ContextHubManager.AuthorizationState int state) {
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 74630d1..9078f3f 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.contexthub.HostEndpointInfo;
 import android.hardware.contexthub.V1_0.ContextHub;
 import android.hardware.contexthub.V1_0.ContextHubMsg;
 import android.hardware.contexthub.V1_0.TransactionResult;
@@ -239,6 +240,20 @@
     public abstract void onMicrophoneSettingChanged(boolean enabled);
 
     /**
+     * Invoked whenever a host client connects with the framework.
+     *
+     * @param info The host endpoint info.
+     */
+    public void onHostEndpointConnected(HostEndpointInfo info) {}
+
+    /**
+     * Invoked whenever a host client disconnects from the framework.
+     *
+     * @param hostEndpointId The ID of the host endpoint that disconnected.
+     */
+    public void onHostEndpointDisconnected(short hostEndpointId) {}
+
+    /**
      * Sends a message to the Context Hub.
      *
      * @param hostEndpointId The host endpoint ID of the sender.
@@ -407,6 +422,24 @@
             onSettingChanged(android.hardware.contexthub.Setting.WIFI_SCANNING, enabled);
         }
 
+        @Override
+        public void onHostEndpointConnected(HostEndpointInfo info) {
+            try {
+                mHub.onHostEndpointConnected(info);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException in onHostEndpointConnected");
+            }
+        }
+
+        @Override
+        public void onHostEndpointDisconnected(short hostEndpointId) {
+            try {
+                mHub.onHostEndpointDisconnected((char) hostEndpointId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException in onHostEndpointDisconnected");
+            }
+        }
+
         @ContextHubTransaction.Result
         public int sendMessageToContextHub(
                 short hostEndpointId, int contextHubId, NanoAppMessage message)
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 53c2802..6f54625 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -500,16 +500,10 @@
                 forcedQueryablePackageNames[i] = forcedQueryablePackageNames[i].intern();
             }
         }
-        final StateProvider stateProvider = new StateProvider() {
-            // TODO: This lock and its handling should be owned by AppsFilter
-            private final Object mLock = new Object();
-
-            @Override
-            public void runWithState(CurrentStateCallback command) {
-                synchronized (mLock) {
-                    command.currentState(pms.getPackageStates(),
-                            injector.getUserManagerInternal().getUserInfos());
-                }
+        final StateProvider stateProvider = command -> {
+            synchronized (injector.getLock()) {
+                command.currentState(injector.getSettings().getPackagesLocked().untrackedStorage(),
+                        injector.getUserManagerInternal().getUserInfos());
             }
         };
         AppsFilter appsFilter = new AppsFilter(stateProvider, featureConfig,
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 861fec7..5ca0618 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -649,12 +649,17 @@
             mPm.checkPackageFrozen(pkgName);
         }
 
-        // Also need to kill any apps that are dependent on the library.
+        final boolean isReplace =
+                reconciledPkg.mPrepareResult != null && reconciledPkg.mPrepareResult.mReplace;
+        // Also need to kill any apps that are dependent on the library, except the case of
+        // installation of new version static shared library.
         if (clientLibPkgs != null) {
-            for (int i = 0; i < clientLibPkgs.size(); i++) {
-                AndroidPackage clientPkg = clientLibPkgs.get(i);
-                mPm.killApplication(clientPkg.getPackageName(),
-                        clientPkg.getUid(), "update lib");
+            if (pkg.getStaticSharedLibName() == null || isReplace) {
+                for (int i = 0; i < clientLibPkgs.size(); i++) {
+                    AndroidPackage clientPkg = clientLibPkgs.get(i);
+                    mPm.killApplication(clientPkg.getPackageName(),
+                            clientPkg.getUid(), "update lib");
+                }
             }
         }
 
@@ -676,8 +681,6 @@
             ksms.addScannedPackageLPw(pkg);
 
             mPm.mComponentResolver.addAllComponents(pkg, chatty);
-            final boolean isReplace =
-                    reconciledPkg.mPrepareResult != null && reconciledPkg.mPrepareResult.mReplace;
             mPm.mAppsFilter.addPackage(pkgSetting, isReplace);
             mPm.addAllPackageProperties(pkg);
 
@@ -2050,6 +2053,7 @@
             }
         }
 
+        final String packageName = pkg.getPackageName();
         for (Map.Entry<String, String> entry : fsverityCandidates.entrySet()) {
             final String filePath = entry.getKey();
             final String signaturePath = entry.getValue();
@@ -2077,10 +2081,13 @@
                     try {
                         // A file may already have fs-verity, e.g. when reused during a split
                         // install. If the measurement succeeds, no need to attempt to set up.
-                        mPm.mInstaller.assertFsverityRootHashMatches(filePath, rootHash);
+                        mPm.mInstaller.assertFsverityRootHashMatches(packageName, filePath,
+                                rootHash);
                     } catch (Installer.InstallerException e) {
-                        mPm.mInstaller.installApkVerity(filePath, fd, result.getContentSize());
-                        mPm.mInstaller.assertFsverityRootHashMatches(filePath, rootHash);
+                        mPm.mInstaller.installApkVerity(packageName, filePath, fd,
+                                result.getContentSize());
+                        mPm.mInstaller.assertFsverityRootHashMatches(packageName, filePath,
+                                rootHash);
                     }
                 } finally {
                     IoUtils.closeQuietly(fd);
@@ -3125,10 +3132,12 @@
                             true, true, pkgList, uidArray, null);
                 }
             } else if (!ArrayUtils.isEmpty(res.mLibraryConsumers)) { // if static shared lib
+                // No need to kill consumers if it's installation of new version static shared lib.
+                final boolean dontKillApp = !update && res.mPkg.getStaticSharedLibName() != null;
                 for (int i = 0; i < res.mLibraryConsumers.size(); i++) {
                     AndroidPackage pkg = res.mLibraryConsumers.get(i);
                     // send broadcast that all consumers of the static shared library have changed
-                    mPm.sendPackageChangedBroadcast(pkg.getPackageName(), false /* dontKillApp */,
+                    mPm.sendPackageChangedBroadcast(pkg.getPackageName(), dontKillApp,
                             new ArrayList<>(Collections.singletonList(pkg.getPackageName())),
                             pkg.getUid(), null);
                 }
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index c8bd2c0..a380344 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -497,7 +497,7 @@
      *
      * @throws InstallerException if {@code dexopt} fails.
      */
-    public boolean dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
+    public boolean dexopt(String apkPath, int uid, String pkgName, String instructionSet,
             int dexoptNeeded, @Nullable String outputPath, int dexFlags,
             String compilerFilter, @Nullable String volumeUuid, @Nullable String classLoaderContext,
             @Nullable String seInfo, boolean downgrade, int targetSdkVersion,
@@ -585,11 +585,14 @@
         }
     }
 
-    public void rmPackageDir(String packageDir) throws InstallerException {
+    /**
+     * Remove a directory belonging to a package.
+     */
+    public void rmPackageDir(String packageName, String packageDir) throws InstallerException {
         if (!checkBeforeRemote()) return;
         BlockGuard.getVmPolicy().onPathAccess(packageDir);
         try {
-            mInstalld.rmPackageDir(packageDir);
+            mInstalld.rmPackageDir(packageName, packageDir);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
@@ -662,35 +665,44 @@
         }
     }
 
-    public void createOatDir(String oatDir, String dexInstructionSet)
+    /**
+     * Creates an oat dir for given package and instruction set.
+     */
+    public void createOatDir(String packageName, String oatDir, String dexInstructionSet)
             throws InstallerException {
         if (!checkBeforeRemote()) return;
         try {
-            mInstalld.createOatDir(oatDir, dexInstructionSet);
+            mInstalld.createOatDir(packageName, oatDir, dexInstructionSet);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
     }
 
-    public void linkFile(String relativePath, String fromBase, String toBase)
+    /**
+     * Creates a hardlink for a path.
+     */
+    public void linkFile(String packageName, String relativePath, String fromBase, String toBase)
             throws InstallerException {
         if (!checkBeforeRemote()) return;
         BlockGuard.getVmPolicy().onPathAccess(fromBase);
         BlockGuard.getVmPolicy().onPathAccess(toBase);
         try {
-            mInstalld.linkFile(relativePath, fromBase, toBase);
+            mInstalld.linkFile(packageName, relativePath, fromBase, toBase);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
     }
 
-    public void moveAb(String apkPath, String instructionSet, String outputPath)
+    /**
+     * Moves oat/vdex/art from "B" set defined by ro.boot.slot_suffix to the default set.
+     */
+    public void moveAb(String packageName, String apkPath, String instructionSet, String outputPath)
             throws InstallerException {
         if (!checkBeforeRemote()) return;
         BlockGuard.getVmPolicy().onPathAccess(apkPath);
         BlockGuard.getVmPolicy().onPathAccess(outputPath);
         try {
-            mInstalld.moveAb(apkPath, instructionSet, outputPath);
+            mInstalld.moveAb(packageName, apkPath, instructionSet, outputPath);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
@@ -700,35 +712,41 @@
      * Deletes the optimized artifacts generated by ART and returns the number
      * of freed bytes.
      */
-    public long deleteOdex(String apkPath, String instructionSet, String outputPath)
-            throws InstallerException {
+    public long deleteOdex(String packageName, String apkPath, String instructionSet,
+            String outputPath) throws InstallerException {
         if (!checkBeforeRemote()) return -1;
         BlockGuard.getVmPolicy().onPathAccess(apkPath);
         BlockGuard.getVmPolicy().onPathAccess(outputPath);
         try {
-            return mInstalld.deleteOdex(apkPath, instructionSet, outputPath);
+            return mInstalld.deleteOdex(packageName, apkPath, instructionSet, outputPath);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
     }
 
-    public void installApkVerity(String filePath, FileDescriptor verityInput, int contentSize)
-            throws InstallerException {
+    /**
+     * Enables legacy apk-verity for an apk.
+     */
+    public void installApkVerity(String packageName, String filePath, FileDescriptor verityInput,
+            int contentSize) throws InstallerException {
         if (!checkBeforeRemote()) return;
         BlockGuard.getVmPolicy().onPathAccess(filePath);
         try {
-            mInstalld.installApkVerity(filePath, verityInput, contentSize);
+            mInstalld.installApkVerity(packageName, filePath, verityInput, contentSize);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
     }
 
-    public void assertFsverityRootHashMatches(String filePath, @NonNull byte[] expectedHash)
-            throws InstallerException {
+    /**
+     * Checks if provided hash matches the file's fs-verity merkle tree root hash.
+     */
+    public void assertFsverityRootHashMatches(String packageName, String filePath,
+            @NonNull byte[] expectedHash) throws InstallerException {
         if (!checkBeforeRemote()) return;
         BlockGuard.getVmPolicy().onPathAccess(filePath);
         try {
-            mInstalld.assertFsverityRootHashMatches(filePath, expectedHash);
+            mInstalld.assertFsverityRootHashMatches(packageName, filePath, expectedHash);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 9e6f4f7..c125fe1 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -411,6 +411,7 @@
             final List<String> paths =
                     AndroidPackageUtils.getAllCodePathsExcludingResourceOnly(pkg);
             final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
+            final String packageName = pkg.getPackageName();
             for (String dexCodeInstructionSet : dexCodeInstructionSets) {
                 for (String path : paths) {
                     String oatDir = PackageDexOptimizer.getOatDir(
@@ -420,7 +421,7 @@
 
                     packagePaths++;
                     try {
-                        installer.moveAb(path, dexCodeInstructionSet, oatDir);
+                        installer.moveAb(packageName, path, dexCodeInstructionSet, oatDir);
                         pathsSuccessful++;
                     } catch (InstallerException e) {
                     }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index de1c2ad..7c8515b 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -123,6 +123,8 @@
     private static final String TAG = "PackageInstaller";
     private static final boolean LOGD = false;
 
+    private static final boolean DEBUG = Build.IS_DEBUGGABLE;
+
     // TODO: remove outstanding sessions when installer package goes away
     // TODO: notify listeners in other users when package has been installed there
     // TODO: purge expired sessions periodically in addition to at reboot
@@ -411,13 +413,6 @@
         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");
-        final boolean isLegacyContainer = name.startsWith("smdl2tmp");
-        return isFile || isContainer || isLegacyContainer;
-    }
-
     @Deprecated
     public File allocateStageDirLegacy(String volumeUuid, boolean isEphemeral) throws IOException {
         synchronized (mSessions) {
@@ -935,6 +930,23 @@
         throw new IllegalStateException("Failed to allocate session ID");
     }
 
+    static boolean isStageName(String name) {
+        final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp");
+        final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp");
+        final boolean isLegacyContainer = name.startsWith("smdl2tmp");
+        return isFile || isContainer || isLegacyContainer;
+    }
+
+    static int tryParseSessionId(@NonNull String tmpSessionDir)
+            throws IllegalArgumentException {
+        if (!tmpSessionDir.startsWith("vmdl") || !tmpSessionDir.endsWith(".tmp")) {
+            throw new IllegalArgumentException("Not a temporary session directory");
+        }
+        String sessionId = tmpSessionDir.substring("vmdl".length(),
+                tmpSessionDir.length() - ".tmp".length());
+        return Integer.parseInt(sessionId);
+    }
+
     private File getTmpSessionDir(String volumeUuid) {
         return Environment.getDataAppDirectory(volumeUuid);
     }
@@ -949,7 +961,12 @@
             final File sessionStagingDir = Environment.getDataStagingDirectory(params.volumeUuid);
             return new File(sessionStagingDir, "session_" + sessionId);
         }
-        return buildTmpSessionDir(sessionId, params.volumeUuid);
+        final File result = buildTmpSessionDir(sessionId, params.volumeUuid);
+        if (DEBUG && !Objects.equals(tryParseSessionId(result.getName()), sessionId)) {
+            throw new RuntimeException(
+                    "session folder format is off: " + result.getName() + " (" + sessionId + ")");
+        }
+        return result;
     }
 
     static void prepareStageDir(File stageDir) throws IOException {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 803a283..dbbc163 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -70,6 +70,7 @@
 import android.content.pm.FileSystemControlParcel;
 import android.content.pm.IDataLoader;
 import android.content.pm.IDataLoaderStatusListener;
+import android.content.pm.IOnChecksumsReadyListener;
 import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.IPackageInstallerSession;
 import android.content.pm.IPackageInstallerSessionFileSystemConnector;
@@ -166,6 +167,7 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileFilter;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.security.NoSuchAlgorithmException;
@@ -1397,6 +1399,20 @@
     }
 
     @Override
+    public void requestChecksums(@NonNull String name, @Checksum.TypeMask int optional,
+            @Checksum.TypeMask int required, @Nullable List trustedInstallers,
+            @NonNull IOnChecksumsReadyListener onChecksumsReadyListener) {
+        final File file = new File(stageDir, name);
+        final String installerPackageName = getInstallSource().initiatingPackageName;
+        try {
+            mPm.requestFileChecksums(file, installerPackageName, optional, required,
+                    trustedInstallers, onChecksumsReadyListener);
+        } catch (FileNotFoundException e) {
+            throw new ParcelableException(e);
+        }
+    }
+
+    @Override
     public void removeSplit(String splitName) {
         if (isDataLoaderInstallation()) {
             throw new IllegalStateException(
@@ -2402,6 +2418,7 @@
             try {
                 final List<File> fromFiles = mResolvedInheritedFiles;
                 final File toDir = stageDir;
+                final String tempPackageName = toDir.getName();
 
                 if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
                 if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
@@ -2411,7 +2428,7 @@
                 if (isLinkPossible(fromFiles, toDir)) {
                     if (!mResolvedInstructionSets.isEmpty()) {
                         final File oatDir = new File(toDir, "oat");
-                        createOatDirs(mResolvedInstructionSets, oatDir);
+                        createOatDirs(tempPackageName, mResolvedInstructionSets, oatDir);
                     }
                     // pre-create lib dirs for linking if necessary
                     if (!mResolvedNativeLibPaths.isEmpty()) {
@@ -2434,7 +2451,7 @@
                                     new File(libDir, archDirPath));
                         }
                     }
-                    linkFiles(fromFiles, toDir, mInheritedFilesBase);
+                    linkFiles(tempPackageName, fromFiles, toDir, mInheritedFilesBase);
                 } else {
                     // TODO: this should delegate to DCS so the system process
                     // avoids holding open FDs into containers.
@@ -3529,18 +3546,19 @@
         throw new IOException("File: " + pathStr + " outside base: " + baseStr);
     }
 
-    private void createOatDirs(List<String> instructionSets, File fromDir)
+    private void createOatDirs(String packageName, List<String> instructionSets, File fromDir)
             throws PackageManagerException {
         for (String instructionSet : instructionSets) {
             try {
-                mInstaller.createOatDir(fromDir.getAbsolutePath(), instructionSet);
+                mInstaller.createOatDir(packageName, fromDir.getAbsolutePath(), instructionSet);
             } catch (InstallerException e) {
                 throw PackageManagerException.from(e);
             }
         }
     }
 
-    private void linkFile(String relativePath, String fromBase, String toBase) throws IOException {
+    private void linkFile(String packageName, String relativePath, String fromBase, String toBase)
+            throws IOException {
         try {
             // Try
             final IncrementalFileStorages incrementalFileStorages = getIncrementalFileStorages();
@@ -3548,21 +3566,21 @@
                     fromBase, toBase)) {
                 return;
             }
-            mInstaller.linkFile(relativePath, fromBase, toBase);
+            mInstaller.linkFile(packageName, relativePath, fromBase, toBase);
         } catch (InstallerException | IOException e) {
             throw new IOException("failed linkOrCreateDir(" + relativePath + ", "
                     + fromBase + ", " + toBase + ")", e);
         }
     }
 
-    private void linkFiles(List<File> fromFiles, File toDir, File fromDir)
+    private void linkFiles(String packageName, List<File> fromFiles, File toDir, File fromDir)
             throws IOException {
         for (File fromFile : fromFiles) {
             final String relativePath = getRelativePath(fromFile, fromDir);
             final String fromBase = fromDir.getAbsolutePath();
             final String toBase = toDir.getAbsolutePath();
 
-            linkFile(relativePath, fromBase, toBase);
+            linkFile(packageName, relativePath, fromBase, toBase);
         }
 
         Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir);
@@ -4299,7 +4317,8 @@
                 incrementalFileStorages.cleanUpAndMarkComplete();
             }
             if (stageDir != null) {
-                mInstaller.rmPackageDir(stageDir.getAbsolutePath());
+                final String tempPackageName = stageDir.getName();
+                mInstaller.rmPackageDir(tempPackageName, stageDir.getAbsolutePath());
             }
         } catch (InstallerException ignored) {
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 747bbfa5..5526990 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -262,6 +262,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
@@ -554,6 +555,7 @@
     public static final int REASON_LAST = REASON_SHARED;
 
     static final String RANDOM_DIR_PREFIX = "~~";
+    static final char RANDOM_CODEPATH_PREFIX = '-';
 
     final Handler mHandler;
 
@@ -1261,15 +1263,50 @@
     }
 
     @Override
-    public void requestChecksums(@NonNull String packageName, boolean includeSplits,
-            @Checksum.TypeMask int optional,
-            @Checksum.TypeMask int required, @Nullable List trustedInstallers,
+    public void requestPackageChecksums(@NonNull String packageName, boolean includeSplits,
+            @Checksum.TypeMask int optional, @Checksum.TypeMask int required,
+            @Nullable List trustedInstallers,
             @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, int userId) {
         requestChecksumsInternal(packageName, includeSplits, optional, required, trustedInstallers,
                 onChecksumsReadyListener, userId, mInjector.getBackgroundExecutor(),
                 mInjector.getBackgroundHandler());
     }
 
+    /**
+     * Requests checksums for the APK file.
+     * See {@link PackageInstaller.Session#requestChecksums} for details.
+     */
+    public void requestFileChecksums(@NonNull File file,
+            @NonNull String installerPackageName, @Checksum.TypeMask int optional,
+            @Checksum.TypeMask int required, @Nullable List trustedInstallers,
+            @NonNull IOnChecksumsReadyListener onChecksumsReadyListener)
+            throws FileNotFoundException {
+        if (!file.exists()) {
+            throw new FileNotFoundException(file.getAbsolutePath());
+        }
+        if (TextUtils.isEmpty(installerPackageName)) {
+            throw new FileNotFoundException(file.getAbsolutePath());
+        }
+
+        final Executor executor = mInjector.getBackgroundExecutor();
+        final Handler handler = mInjector.getBackgroundHandler();
+        final Certificate[] trustedCerts = (trustedInstallers != null) ? decodeCertificates(
+                trustedInstallers) : null;
+
+        final List<Pair<String, File>> filesToChecksum = new ArrayList<>(1);
+        filesToChecksum.add(Pair.create(null, file));
+
+        executor.execute(() -> {
+            ApkChecksums.Injector injector = new ApkChecksums.Injector(
+                    () -> mContext,
+                    () -> handler,
+                    () -> mInjector.getIncrementalManager(),
+                    () -> mPmInternal);
+            ApkChecksums.getChecksums(filesToChecksum, optional, required, installerPackageName,
+                    trustedCerts, onChecksumsReadyListener, injector);
+        });
+    }
+
     private void requestChecksumsInternal(@NonNull String packageName, boolean includeSplits,
             @Checksum.TypeMask int optional, @Checksum.TypeMask int required,
             @Nullable List trustedInstallers,
@@ -1493,8 +1530,8 @@
         }
 
         PackageManagerService m = new PackageManagerService(injector, onlyCore, factoryTest,
-                Build.FINGERPRINT, Build.IS_ENG, Build.IS_USERDEBUG, Build.VERSION.SDK_INT,
-                Build.VERSION.INCREMENTAL, SNAPSHOT_ENABLED);
+                PackagePartitions.FINGERPRINT, Build.IS_ENG, Build.IS_USERDEBUG,
+                Build.VERSION.SDK_INT, Build.VERSION.INCREMENTAL, SNAPSHOT_ENABLED);
         t.traceEnd(); // "create package manager"
 
         final CompatChange.ChangeListener selinuxChangeListener = packageName -> {
@@ -1907,8 +1944,8 @@
             mIsUpgrade =
                     !buildFingerprint.equals(ver.fingerprint);
             if (mIsUpgrade) {
-                PackageManagerServiceUtils.logCriticalInfo(Log.INFO,
-                        "Upgrading from " + ver.fingerprint + " to " + Build.FINGERPRINT);
+                PackageManagerServiceUtils.logCriticalInfo(Log.INFO, "Upgrading from "
+                        + ver.fingerprint + " to " + PackagePartitions.FINGERPRINT);
             }
 
             // when upgrading from pre-M, promote system app permissions from install to runtime
@@ -2006,7 +2043,8 @@
             // this situation.
             if (mIsUpgrade) {
                 Slog.i(TAG, "Build fingerprint changed from " + ver.fingerprint + " to "
-                        + Build.FINGERPRINT + "; regranting permissions for internal storage");
+                        + PackagePartitions.FINGERPRINT
+                        + "; regranting permissions for internal storage");
             }
             mPermissionManager.onStorageVolumeMounted(
                     StorageManager.UUID_PRIVATE_INTERNAL, mIsUpgrade);
@@ -2040,7 +2078,7 @@
                                         | Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES);
                     }
                 }
-                ver.fingerprint = Build.FINGERPRINT;
+                ver.fingerprint = PackagePartitions.FINGERPRINT;
             }
 
             // Legacy existing (installed before Q) non-system apps to hide
@@ -9376,7 +9414,8 @@
                 mResolveActivity.processName = "system:ui";
                 mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
                 mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
-                mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
+                mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
+                        | ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
                 mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert;
                 mResolveActivity.exported = true;
                 mResolveActivity.enabled = true;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 6d031dd..1848ef5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -25,6 +25,7 @@
 import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
 import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED;
+import static com.android.server.pm.PackageManagerService.RANDOM_CODEPATH_PREFIX;
 import static com.android.server.pm.PackageManagerService.RANDOM_DIR_PREFIX;
 import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
 import static com.android.server.pm.PackageManagerService.TAG;
@@ -41,6 +42,7 @@
 import android.content.pm.ComponentInfo;
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageManager;
+import android.content.pm.PackagePartitions;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.Signature;
@@ -123,6 +125,8 @@
 public class PackageManagerServiceUtils {
     private static final long MAX_CRITICAL_INFO_DUMP_SIZE = 3 * 1000 * 1000; // 3MB
 
+    private static final boolean DEBUG = Build.IS_DEBUGGABLE;
+
     public final static Predicate<PackageStateInternal> REMOVE_IF_NULL_PKG =
             pkgSetting -> pkgSetting.getPkg() == null;
 
@@ -1122,13 +1126,28 @@
         File firstLevelDir;
         do {
             random.nextBytes(bytes);
-            String dirName = RANDOM_DIR_PREFIX
+            String firstLevelDirName = RANDOM_DIR_PREFIX
                     + Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
-            firstLevelDir = new File(targetDir, dirName);
+            firstLevelDir = new File(targetDir, firstLevelDirName);
         } while (firstLevelDir.exists());
+
         random.nextBytes(bytes);
-        String suffix = Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
-        return new File(firstLevelDir, packageName + "-" + suffix);
+        String dirName = packageName + RANDOM_CODEPATH_PREFIX + Base64.encodeToString(bytes,
+                Base64.URL_SAFE | Base64.NO_WRAP);
+        final File result = new File(firstLevelDir, dirName);
+        if (DEBUG && !Objects.equals(tryParsePackageName(result.getName()), packageName)) {
+            throw new RuntimeException(
+                    "codepath is off: " + result.getName() + " (" + packageName + ")");
+        }
+        return result;
+    }
+
+    static String tryParsePackageName(@NonNull String codePath) throws IllegalArgumentException {
+        int packageNameEnds = codePath.indexOf(RANDOM_CODEPATH_PREFIX);
+        if (packageNameEnds == -1) {
+            throw new IllegalArgumentException("Not a valid package folder name");
+        }
+        return codePath.substring(0, packageNameEnds);
     }
 
     /**
@@ -1215,7 +1234,7 @@
         // identify cached items. In particular, changing the value of certain
         // feature flags should cause us to invalidate any caches.
         final String cacheName = FORCE_PACKAGE_PARSED_CACHE_ENABLED ? "debug"
-                : SystemProperties.digestOf("ro.build.fingerprint");
+                : PackagePartitions.FINGERPRINT;
 
         // Reconcile cache directories, keeping only what we'd actually use.
         for (File cacheDir : FileUtils.listFilesOrEmpty(cacheBaseDir)) {
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 3a2ac1c..48b893b 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -94,9 +94,10 @@
                     }
                 }
 
-                mInstaller.rmPackageDir(codePath.getAbsolutePath());
+                final String packageName = codePath.getName();
+                mInstaller.rmPackageDir(packageName, codePath.getAbsolutePath());
                 if (needRemoveParent) {
-                    mInstaller.rmPackageDir(codePathParent.getAbsolutePath());
+                    mInstaller.rmPackageDir(packageName, codePathParent.getAbsolutePath());
                     removeCachedResult(codePathParent);
                 }
             } catch (Installer.InstallerException e) {
diff --git a/services/core/java/com/android/server/pm/ScanPackageHelper.java b/services/core/java/com/android/server/pm/ScanPackageHelper.java
index 6cc94ce..9b08ef9 100644
--- a/services/core/java/com/android/server/pm/ScanPackageHelper.java
+++ b/services/core/java/com/android/server/pm/ScanPackageHelper.java
@@ -894,14 +894,15 @@
      * Returns if forced apk verification can be skipped for the whole package, including splits.
      */
     private boolean canSkipForcedPackageVerification(AndroidPackage pkg) {
-        if (!canSkipForcedApkVerification(pkg.getBaseApkPath())) {
+        final String packageName = pkg.getPackageName();
+        if (!canSkipForcedApkVerification(packageName, pkg.getBaseApkPath())) {
             return false;
         }
         // TODO: Allow base and splits to be verified individually.
         String[] splitCodePaths = pkg.getSplitCodePaths();
         if (!ArrayUtils.isEmpty(splitCodePaths)) {
             for (int i = 0; i < splitCodePaths.length; i++) {
-                if (!canSkipForcedApkVerification(splitCodePaths[i])) {
+                if (!canSkipForcedApkVerification(packageName, splitCodePaths[i])) {
                     return false;
                 }
             }
@@ -914,7 +915,7 @@
      * whether the apk contains signed root hash.  Note that the signer's certificate still needs to
      * match one in a trusted source, and should be done separately.
      */
-    private boolean canSkipForcedApkVerification(String apkPath) {
+    private boolean canSkipForcedApkVerification(String packageName, String apkPath) {
         if (!PackageManagerServiceUtils.isLegacyApkVerityEnabled()) {
             return VerityUtils.hasFsverity(apkPath);
         }
@@ -926,7 +927,8 @@
             }
             synchronized (mPm.mInstallLock) {
                 // Returns whether the observed root hash matches what kernel has.
-                mPm.mInstaller.assertFsverityRootHashMatches(apkPath, rootHashObserved);
+                mPm.mInstaller.assertFsverityRootHashMatches(packageName, apkPath,
+                        rootHashObserved);
                 return true;
             }
         } catch (Installer.InstallerException | IOException | DigestException
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4a41047..1f5d79c 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -42,6 +42,7 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackagePartitions;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
@@ -437,7 +438,7 @@
         public void forceCurrent() {
             sdkVersion = Build.VERSION.SDK_INT;
             databaseVersion = CURRENT_DATABASE_VERSION;
-            fingerprint = Build.FINGERPRINT;
+            fingerprint = PackagePartitions.FINGERPRINT;
         }
     }
 
@@ -5357,7 +5358,7 @@
         }
 
         private String getExtendedFingerprint(long version) {
-            return Build.FINGERPRINT + "?pc_version=" + version;
+            return PackagePartitions.FINGERPRINT + "?pc_version=" + version;
         }
 
         public void writeStateForUserAsyncLPr(int userId) {
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 6b88081..1433abd 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -29,10 +29,10 @@
 import android.app.ResourcesManager;
 import android.content.IIntentReceiver;
 import android.content.pm.PackageManager;
+import android.content.pm.PackagePartitions;
 import android.content.pm.UserInfo;
 import android.content.pm.VersionedPackage;
 import android.content.pm.parsing.ParsingPackageUtils;
-import android.os.Build;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.UserHandle;
@@ -157,7 +157,7 @@
                     Slog.w(TAG, "Failed to scan " + ps.getPath() + ": " + e.getMessage());
                 }
 
-                if (!Build.FINGERPRINT.equals(ver.fingerprint)) {
+                if (!PackagePartitions.FINGERPRINT.equals(ver.fingerprint)) {
                     appDataHelper.clearAppDataLIF(
                             ps.getPkg(), UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE
                             | FLAG_STORAGE_EXTERNAL | Installer.FLAG_CLEAR_CODE_CACHE_ONLY
@@ -195,10 +195,10 @@
         }
 
         synchronized (mPm.mLock) {
-            final boolean isUpgrade = !Build.FINGERPRINT.equals(ver.fingerprint);
+            final boolean isUpgrade = !PackagePartitions.FINGERPRINT.equals(ver.fingerprint);
             if (isUpgrade) {
                 logCriticalInfo(Log.INFO, "Build fingerprint changed from " + ver.fingerprint
-                        + " to " + Build.FINGERPRINT + "; regranting permissions for "
+                        + " to " + PackagePartitions.FINGERPRINT + "; regranting permissions for "
                         + volumeUuid);
             }
             mPm.mPermissionManager.onStorageVolumeMounted(volumeUuid, isUpgrade);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 40d8845..74529c5 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -46,6 +46,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackagePartitions;
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
@@ -2396,6 +2397,17 @@
         return count;
     }
 
+    /**
+     * Returns whether more users of the given type can be added (based on how many users of that
+     * type already exist).
+     */
+    @Override
+    public boolean canAddMoreUsersOfType(String userType) {
+        checkManageOrCreateUsersPermission("check if more users can be added.");
+        final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
+        return userTypeDetails != null && canAddMoreUsersOfType(userTypeDetails);
+    }
+
     @Override
     public boolean canAddMoreManagedProfiles(@UserIdInt int userId, boolean allowedToRemoveOne) {
         return canAddMoreProfilesToUser(UserManager.USER_TYPE_PROFILE_MANAGED, userId,
@@ -3716,7 +3728,7 @@
                     userInfo.creationTime = getCreationTime();
                     userInfo.partial = true;
                     userInfo.preCreated = preCreate;
-                    userInfo.lastLoggedInFingerprint = Build.FINGERPRINT;
+                    userInfo.lastLoggedInFingerprint = PackagePartitions.FINGERPRINT;
                     if (userTypeDetails.hasBadge() && parentId != UserHandle.USER_NULL) {
                         userInfo.profileBadge = getFreeProfileBadgeLU(parentId, userType);
                     }
@@ -4847,7 +4859,8 @@
         t.traceBegin("onBeforeStartUser-" + userId);
         final int userSerial = userInfo.serialNumber;
         // Migrate only if build fingerprints mismatch
-        boolean migrateAppsData = !Build.FINGERPRINT.equals(userInfo.lastLoggedInFingerprint);
+        boolean migrateAppsData = !PackagePartitions.FINGERPRINT.equals(
+                userInfo.lastLoggedInFingerprint);
         t.traceBegin("prepareUserData");
         mUserDataPreparer.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_DE);
         t.traceEnd();
@@ -4877,7 +4890,8 @@
         }
         final int userSerial = userInfo.serialNumber;
         // Migrate only if build fingerprints mismatch
-        boolean migrateAppsData = !Build.FINGERPRINT.equals(userInfo.lastLoggedInFingerprint);
+        boolean migrateAppsData = !PackagePartitions.FINGERPRINT.equals(
+                userInfo.lastLoggedInFingerprint);
 
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
         t.traceBegin("prepareUserData-" + userId);
@@ -4921,7 +4935,7 @@
         if (now > EPOCH_PLUS_30_YEARS) {
             userData.info.lastLoggedInTime = now;
         }
-        userData.info.lastLoggedInFingerprint = Build.FINGERPRINT;
+        userData.info.lastLoggedInFingerprint = PackagePartitions.FINGERPRINT;
         scheduleWriteUser(userData);
     }
 
diff --git a/services/core/java/com/android/server/pm/VerificationParams.java b/services/core/java/com/android/server/pm/VerificationParams.java
index 4dbc5d5..6d68139 100644
--- a/services/core/java/com/android/server/pm/VerificationParams.java
+++ b/services/core/java/com/android/server/pm/VerificationParams.java
@@ -399,6 +399,8 @@
 
             verification.putExtra(PackageInstaller.EXTRA_DATA_LOADER_TYPE, mDataLoaderType);
 
+            verification.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
+
             populateInstallerExtras(verification);
 
             final List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite,
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 5820489..5371454 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -1043,10 +1043,12 @@
     public long deleteOptimizedFiles(ArtPackageInfo packageInfo) {
         long freedBytes = 0;
         boolean hadErrors = false;
+        final String packageName = packageInfo.getPackageName();
         for (String codePath : packageInfo.getCodePaths()) {
             for (String isa : packageInfo.getInstructionSets()) {
                 try {
-                    freedBytes += mInstaller.deleteOdex(codePath, isa, packageInfo.getOatDir());
+                    freedBytes += mInstaller.deleteOdex(packageName, codePath, isa,
+                            packageInfo.getOatDir());
                 } catch (InstallerException e) {
                     Log.e(TAG, "Failed deleting oat files for " + codePath, e);
                     hadErrors = true;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 2369c5e..131e587 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -332,6 +332,10 @@
     private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800;
     private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+    private static final VibrationAttributes PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES =
+            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_PHYSICAL_EMULATION);
+    private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
+            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
 
     /**
      * Keyguard stuff
@@ -1115,21 +1119,21 @@
                 break;
             case LONG_PRESS_POWER_GLOBAL_ACTIONS:
                 mPowerKeyHandled = true;
-                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
                         "Power - Long Press - Global Actions");
                 showGlobalActions();
                 break;
             case LONG_PRESS_POWER_SHUT_OFF:
             case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
                 mPowerKeyHandled = true;
-                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
                         "Power - Long Press - Shut Off");
                 sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
                 mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
                 break;
             case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST:
                 mPowerKeyHandled = true;
-                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
                         "Power - Long Press - Go To Voice Assist");
                 // Some devices allow the voice assistant intent during setup (and use that intent
                 // to launch something else, like Settings). So we explicitly allow that via the
@@ -1153,7 +1157,7 @@
                 break;
             case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
                 mPowerKeyHandled = true;
-                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
                         "Power - Very Long Press - Show Global Actions");
                 showGlobalActions();
                 break;
@@ -2098,7 +2102,8 @@
                                 mPowerKeyHandled = true;
                                 break;
                             case POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS:
-                                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+                                performHapticFeedback(
+                                        HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
                                         "Power + Volume Up - Global Actions");
                                 showGlobalActions();
                                 mPowerKeyHandled = true;
@@ -5291,7 +5296,7 @@
             return false;
         }
 
-        mVibrator.vibrate(uid, packageName, effect, reason, TOUCH_VIBRATION_ATTRIBUTES);
+        mVibrator.vibrate(uid, packageName, effect, reason, getVibrationAttributes(effectId));
         return true;
     }
 
@@ -5320,6 +5325,7 @@
             case HapticFeedbackConstants.GESTURE_START:
                 return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
             case HapticFeedbackConstants.LONG_PRESS:
+            case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
             case HapticFeedbackConstants.EDGE_SQUEEZE:
                 return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
             case HapticFeedbackConstants.REJECT:
@@ -5358,6 +5364,22 @@
         }
     }
 
+    private VibrationAttributes getVibrationAttributes(int effectId) {
+        switch (effectId) {
+            case HapticFeedbackConstants.EDGE_SQUEEZE:
+            case HapticFeedbackConstants.EDGE_RELEASE:
+                return PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES;
+            case HapticFeedbackConstants.ASSISTANT_BUTTON:
+            case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
+            case HapticFeedbackConstants.ROTARY_SCROLL_TICK:
+            case HapticFeedbackConstants.ROTARY_SCROLL_ITEM_FOCUS:
+            case HapticFeedbackConstants.ROTARY_SCROLL_LIMIT:
+                return HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
+            default:
+                return TOUCH_VIBRATION_ATTRIBUTES;
+        }
+    }
+
     @Override
     public void keepScreenOnStartedLw() {
     }
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 5ec2f83..4571cf3 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -27,7 +27,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
 import static android.view.WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
@@ -190,14 +189,6 @@
         String getOwningPackage();
 
         /**
-         * Retrieve the current LayoutParams of the window.
-         *
-         * @return WindowManager.LayoutParams The window's internal LayoutParams
-         *         instance.
-         */
-        public WindowManager.LayoutParams getAttrs();
-
-        /**
          * Retrieve the type of the top-level window.
          *
          * @return the base type of the parent window if attached or its own type otherwise
@@ -658,26 +649,6 @@
     public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs);
 
     /**
-     * @return whether {@param win} can be hidden by Keyguard
-     */
-    default boolean canBeHiddenByKeyguardLw(WindowState win) {
-        // Keyguard visibility of window from activities are determined over activity visibility.
-        if (win.getBaseType() == TYPE_BASE_APPLICATION) {
-            return false;
-        }
-        switch (win.getAttrs().type) {
-            case TYPE_NOTIFICATION_SHADE:
-            case TYPE_STATUS_BAR:
-            case TYPE_NAVIGATION_BAR:
-            case TYPE_WALLPAPER:
-                return false;
-            default:
-                // Hide only windows below the keyguard host window.
-                return getWindowLayerLw(win) < getWindowLayerFromTypeLw(TYPE_NOTIFICATION_SHADE);
-        }
-    }
-
-    /**
      * Create and return an animation to re-display a window that was force hidden by Keyguard.
      */
     public Animation createHiddenByKeyguardExit(boolean onWallpaper,
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 6d0f08d..70a804b 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -110,8 +110,8 @@
     private static final VibrationEffect CHARGING_VIBRATION_EFFECT =
             VibrationEffect.createWaveform(CHARGING_VIBRATION_TIME, CHARGING_VIBRATION_AMPLITUDE,
                     -1);
-    private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
-            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+    private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
+            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
 
     private final Object mLock = new Object();
 
@@ -807,7 +807,7 @@
         final boolean vibrate = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0;
         if (vibrate) {
-            mVibrator.vibrate(CHARGING_VIBRATION_EFFECT, TOUCH_VIBRATION_ATTRIBUTES);
+            mVibrator.vibrate(CHARGING_VIBRATION_EFFECT, HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
         }
 
         // play sound
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 7cdae58..3182290 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -56,6 +56,7 @@
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO;
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__MANUAL;
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY;
+import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
 import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
 import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
@@ -3407,7 +3408,7 @@
             pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag,
                     metricsState.isTelephonyDetectionSupported(),
                     metricsState.isGeoDetectionSupported(),
-                    metricsState.isUserLocationEnabled(),
+                    metricsState.getUserLocationEnabledSetting(),
                     metricsState.getAutoDetectionEnabledSetting(),
                     metricsState.getGeoDetectionEnabledSetting(),
                     convertToMetricsDetectionMode(metricsState.getDetectionMode()),
@@ -3434,7 +3435,7 @@
             case MetricsTimeZoneDetectorState.DETECTION_MODE_TELEPHONY:
                 return TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY;
             default:
-                throw new IllegalArgumentException("" + detectionMode);
+                return TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
         }
     }
 
diff --git a/services/core/java/com/android/server/storage/AppFuseBridge.java b/services/core/java/com/android/server/storage/AppFuseBridge.java
index b00540f..2923148 100644
--- a/services/core/java/com/android/server/storage/AppFuseBridge.java
+++ b/services/core/java/com/android/server/storage/AppFuseBridge.java
@@ -24,7 +24,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.FuseUnavailableMountException;
 import com.android.internal.util.Preconditions;
-import com.android.server.NativeDaemonConnectorException;
+import com.android.server.AppFuseMountException;
 import libcore.io.IoUtils;
 import java.util.concurrent.CountDownLatch;
 
@@ -55,7 +55,7 @@
     }
 
     public ParcelFileDescriptor addBridge(MountScope mountScope)
-            throws FuseUnavailableMountException, NativeDaemonConnectorException {
+            throws FuseUnavailableMountException, AppFuseMountException {
         /*
         ** Dead Lock between Java lock (AppFuseBridge.java) and Native lock (FuseBridgeLoop.cc)
         **
@@ -112,7 +112,7 @@
         try {
             int flags = FileUtils.translateModePfdToPosix(mode);
             return scope.openFile(mountId, fileId, flags);
-        } catch (NativeDaemonConnectorException error) {
+        } catch (AppFuseMountException error) {
             throw new FuseUnavailableMountException(mountId);
         }
     }
@@ -160,9 +160,9 @@
             return mMountResult;
         }
 
-        public abstract ParcelFileDescriptor open() throws NativeDaemonConnectorException;
+        public abstract ParcelFileDescriptor open() throws AppFuseMountException;
         public abstract ParcelFileDescriptor openFile(int mountId, int fileId, int flags)
-                throws NativeDaemonConnectorException;
+                throws AppFuseMountException;
     }
 
     private native long native_new();
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 22814b3..1a5945e 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -39,21 +39,21 @@
 
     private final boolean mTelephonyDetectionSupported;
     private final boolean mGeoDetectionSupported;
-    private final boolean mAutoDetectionEnabled;
+    private final boolean mAutoDetectionEnabledSetting;
     private final @UserIdInt int mUserId;
     private final boolean mUserConfigAllowed;
-    private final boolean mLocationEnabled;
-    private final boolean mGeoDetectionEnabled;
+    private final boolean mLocationEnabledSetting;
+    private final boolean mGeoDetectionEnabledSetting;
 
     private ConfigurationInternal(Builder builder) {
         mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported;
         mGeoDetectionSupported = builder.mGeoDetectionSupported;
-        mAutoDetectionEnabled = builder.mAutoDetectionEnabled;
+        mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting;
 
         mUserId = builder.mUserId;
         mUserConfigAllowed = builder.mUserConfigAllowed;
-        mLocationEnabled = builder.mLocationEnabled;
-        mGeoDetectionEnabled = builder.mGeoDetectionEnabled;
+        mLocationEnabledSetting = builder.mLocationEnabledSetting;
+        mGeoDetectionEnabledSetting = builder.mGeoDetectionEnabledSetting;
     }
 
     /** Returns true if the device supports any form of auto time zone detection. */
@@ -73,7 +73,7 @@
 
     /** Returns the value of the auto time zone detection enabled setting. */
     public boolean getAutoDetectionEnabledSetting() {
-        return mAutoDetectionEnabled;
+        return mAutoDetectionEnabledSetting;
     }
 
     /**
@@ -81,7 +81,7 @@
      * from the raw setting value.
      */
     public boolean getAutoDetectionEnabledBehavior() {
-        return isAutoDetectionSupported() && mAutoDetectionEnabled;
+        return isAutoDetectionSupported() && mAutoDetectionEnabledSetting;
     }
 
     /** Returns the ID of the user this configuration is associated with. */
@@ -101,13 +101,13 @@
     }
 
     /** Returns true if user's location can be used generally. */
-    public boolean isLocationEnabled() {
-        return mLocationEnabled;
+    public boolean getLocationEnabledSetting() {
+        return mLocationEnabledSetting;
     }
 
     /** Returns the value of the geolocation time zone detection enabled setting. */
     public boolean getGeoDetectionEnabledSetting() {
-        return mGeoDetectionEnabled;
+        return mGeoDetectionEnabledSetting;
     }
 
     /**
@@ -117,7 +117,7 @@
     public boolean getGeoDetectionEnabledBehavior() {
         return getAutoDetectionEnabledBehavior()
                 && isGeoDetectionSupported()
-                && isLocationEnabled()
+                && getLocationEnabledSetting()
                 && getGeoDetectionEnabledSetting();
     }
 
@@ -154,7 +154,7 @@
         final int configureGeolocationDetectionEnabledCapability;
         if (!deviceHasLocationTimeZoneDetection) {
             configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
-        } else if (!mAutoDetectionEnabled || !isLocationEnabled()) {
+        } else if (!mAutoDetectionEnabledSetting || !getLocationEnabledSetting()) {
             configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_APPLICABLE;
         } else {
             configureGeolocationDetectionEnabledCapability = CAPABILITY_POSSESSED;
@@ -195,10 +195,10 @@
     public ConfigurationInternal merge(TimeZoneConfiguration newConfiguration) {
         Builder builder = new Builder(this);
         if (newConfiguration.hasIsAutoDetectionEnabled()) {
-            builder.setAutoDetectionEnabled(newConfiguration.isAutoDetectionEnabled());
+            builder.setAutoDetectionEnabledSetting(newConfiguration.isAutoDetectionEnabled());
         }
         if (newConfiguration.hasIsGeoDetectionEnabled()) {
-            builder.setGeoDetectionEnabled(newConfiguration.isGeoDetectionEnabled());
+            builder.setGeoDetectionEnabledSetting(newConfiguration.isGeoDetectionEnabled());
         }
         return builder.build();
     }
@@ -216,16 +216,16 @@
                 && mUserConfigAllowed == that.mUserConfigAllowed
                 && mTelephonyDetectionSupported == that.mTelephonyDetectionSupported
                 && mGeoDetectionSupported == that.mGeoDetectionSupported
-                && mAutoDetectionEnabled == that.mAutoDetectionEnabled
-                && mLocationEnabled == that.mLocationEnabled
-                && mGeoDetectionEnabled == that.mGeoDetectionEnabled;
+                && mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting
+                && mLocationEnabledSetting == that.mLocationEnabledSetting
+                && mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported,
-                mGeoDetectionSupported, mAutoDetectionEnabled, mLocationEnabled,
-                mGeoDetectionEnabled);
+                mGeoDetectionSupported, mAutoDetectionEnabledSetting, mLocationEnabledSetting,
+                mGeoDetectionEnabledSetting);
     }
 
     @Override
@@ -235,9 +235,9 @@
                 + ", mUserConfigAllowed=" + mUserConfigAllowed
                 + ", mTelephonyDetectionSupported=" + mTelephonyDetectionSupported
                 + ", mGeoDetectionSupported=" + mGeoDetectionSupported
-                + ", mAutoDetectionEnabled=" + mAutoDetectionEnabled
-                + ", mLocationEnabled=" + mLocationEnabled
-                + ", mGeoDetectionEnabled=" + mGeoDetectionEnabled
+                + ", mAutoDetectionEnabledSetting=" + mAutoDetectionEnabledSetting
+                + ", mLocationEnabledSetting=" + mLocationEnabledSetting
+                + ", mGeoDetectionEnabledSetting=" + mGeoDetectionEnabledSetting
                 + '}';
     }
 
@@ -251,9 +251,9 @@
         private boolean mUserConfigAllowed;
         private boolean mTelephonyDetectionSupported;
         private boolean mGeoDetectionSupported;
-        private boolean mAutoDetectionEnabled;
-        private boolean mLocationEnabled;
-        private boolean mGeoDetectionEnabled;
+        private boolean mAutoDetectionEnabledSetting;
+        private boolean mLocationEnabledSetting;
+        private boolean mGeoDetectionEnabledSetting;
 
         /**
          * Creates a new Builder with only the userId set.
@@ -270,9 +270,9 @@
             this.mUserConfigAllowed = toCopy.mUserConfigAllowed;
             this.mTelephonyDetectionSupported = toCopy.mTelephonyDetectionSupported;
             this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported;
-            this.mAutoDetectionEnabled = toCopy.mAutoDetectionEnabled;
-            this.mLocationEnabled = toCopy.mLocationEnabled;
-            this.mGeoDetectionEnabled = toCopy.mGeoDetectionEnabled;
+            this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting;
+            this.mLocationEnabledSetting = toCopy.mLocationEnabledSetting;
+            this.mGeoDetectionEnabledSetting = toCopy.mGeoDetectionEnabledSetting;
         }
 
         /**
@@ -302,24 +302,24 @@
         /**
          * Sets the value of the automatic time zone detection enabled setting for this device.
          */
-        public Builder setAutoDetectionEnabled(boolean enabled) {
-            mAutoDetectionEnabled = enabled;
+        public Builder setAutoDetectionEnabledSetting(boolean enabled) {
+            mAutoDetectionEnabledSetting = enabled;
             return this;
         }
 
         /**
          * Sets the value of the location mode setting for this user.
          */
-        public Builder setLocationEnabled(boolean enabled) {
-            mLocationEnabled = enabled;
+        public Builder setLocationEnabledSetting(boolean enabled) {
+            mLocationEnabledSetting = enabled;
             return this;
         }
 
         /**
          * Sets the value of the geolocation time zone detection setting for this user.
          */
-        public Builder setGeoDetectionEnabled(boolean enabled) {
-            mGeoDetectionEnabled = enabled;
+        public Builder setGeoDetectionEnabledSetting(boolean enabled) {
+            mGeoDetectionEnabledSetting = enabled;
             return this;
         }
 
diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
index 9eb6a45..f8ba0d2 100644
--- a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
+++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
@@ -56,16 +56,11 @@
     public static final @DetectionMode int DETECTION_MODE_GEO = 1;
     public static final @DetectionMode int DETECTION_MODE_TELEPHONY = 2;
 
-    @NonNull
-    private final ConfigurationInternal mConfigurationInternal;
-    @NonNull
+    @NonNull private final ConfigurationInternal mConfigurationInternal;
     private final int mDeviceTimeZoneIdOrdinal;
-    @Nullable
-    private final MetricsTimeZoneSuggestion mLatestManualSuggestion;
-    @Nullable
-    private final MetricsTimeZoneSuggestion mLatestTelephonySuggestion;
-    @Nullable
-    private final MetricsTimeZoneSuggestion mLatestGeolocationSuggestion;
+    @Nullable private final MetricsTimeZoneSuggestion mLatestManualSuggestion;
+    @Nullable private final MetricsTimeZoneSuggestion mLatestTelephonySuggestion;
+    @Nullable private final MetricsTimeZoneSuggestion mLatestGeolocationSuggestion;
 
     private MetricsTimeZoneDetectorState(
             @NonNull ConfigurationInternal configurationInternal,
@@ -117,8 +112,8 @@
     }
 
     /** Returns true if user's location can be used generally. */
-    public boolean isUserLocationEnabled() {
-        return mConfigurationInternal.isLocationEnabled();
+    public boolean getUserLocationEnabledSetting() {
+        return mConfigurationInternal.getLocationEnabledSetting();
     }
 
     /** Returns the value of the geolocation time zone detection enabled setting. */
@@ -149,7 +144,6 @@
      * Returns the ordinal for the device's currently set time zone ID.
      * See {@link MetricsTimeZoneDetectorState} for information about ordinals.
      */
-    @NonNull
     public int getDeviceTimeZoneIdOrdinal() {
         return mDeviceTimeZoneIdOrdinal;
     }
@@ -281,6 +275,7 @@
             return new MetricsTimeZoneSuggestion(null);
         }
 
+        @NonNull
         public static MetricsTimeZoneSuggestion createCertain(
                 @NonNull int[] zoneIdOrdinals) {
             return new MetricsTimeZoneSuggestion(zoneIdOrdinals);
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index 6e63f59..be4b6bd 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -281,8 +281,8 @@
             // later releases that start to support geo detection on the same hardware.
             if (!getGeoDetectionSettingEnabledOverride().isPresent()
                     && isGeoTimeZoneDetectionFeatureSupported()) {
-                final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled();
-                setGeoDetectionEnabledIfRequired(userId, geoTzDetectionEnabled);
+                final boolean geoDetectionEnabledSetting = configuration.isGeoDetectionEnabled();
+                setGeoDetectionEnabledSettingIfRequired(userId, geoDetectionEnabledSetting);
             }
         }
     }
@@ -294,10 +294,10 @@
                 .setTelephonyDetectionFeatureSupported(
                         isTelephonyTimeZoneDetectionFeatureSupported())
                 .setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported())
-                .setAutoDetectionEnabled(isAutoDetectionEnabled())
+                .setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting())
                 .setUserConfigAllowed(isUserConfigAllowed(userId))
-                .setLocationEnabled(isLocationEnabled(userId))
-                .setGeoDetectionEnabled(isGeoDetectionEnabled(userId))
+                .setLocationEnabledSetting(getLocationEnabledSetting(userId))
+                .setGeoDetectionEnabledSetting(getGeoDetectionEnabledSetting(userId))
                 .build();
     }
 
@@ -306,12 +306,12 @@
         // a ConfigurationChangeListener callback triggering due to ContentObserver's still
         // triggering *sometimes* for no-op updates. Because callbacks are async this is necessary
         // for stable behavior during tests.
-        if (isAutoDetectionEnabled() != enabled) {
+        if (getAutoDetectionEnabledSetting() != enabled) {
             Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, enabled ? 1 : 0);
         }
     }
 
-    private boolean isLocationEnabled(@UserIdInt int userId) {
+    private boolean getLocationEnabledSetting(@UserIdInt int userId) {
         return mLocationManager.isLocationEnabledForUser(UserHandle.of(userId));
     }
 
@@ -320,11 +320,11 @@
         return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle);
     }
 
-    private boolean isAutoDetectionEnabled() {
+    private boolean getAutoDetectionEnabledSetting() {
         return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0;
     }
 
-    private boolean isGeoDetectionEnabled(@UserIdInt int userId) {
+    private boolean getGeoDetectionEnabledSetting(@UserIdInt int userId) {
         // We may never use this, but it gives us a way to force location-based time zone detection
         // on/off for testers (but only where their other settings would allow them to turn it on
         // for themselves).
@@ -339,9 +339,9 @@
                 (geoDetectionEnabledByDefault ? 1 : 0) /* defaultValue */, userId) != 0;
     }
 
-    private void setGeoDetectionEnabledIfRequired(@UserIdInt int userId, boolean enabled) {
+    private void setGeoDetectionEnabledSettingIfRequired(@UserIdInt int userId, boolean enabled) {
         // See comment in setAutoDetectionEnabledIfRequired. http://b/171953500
-        if (isGeoDetectionEnabled(userId) != enabled) {
+        if (getGeoDetectionEnabledSetting(userId) != enabled) {
             Settings.Secure.putIntForUser(mCr, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
                     enabled ? 1 : 0, userId);
         }
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e69acc3..59b6a08 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -3012,32 +3012,47 @@
         public void addHardwareInput(int deviceId, TvInputInfo inputInfo) {
             ensureHardwarePermission();
             ensureValidInput(inputInfo);
-            synchronized (mLock) {
-                mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
-                addHardwareInputLocked(inputInfo);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
+                    addHardwareInputLocked(inputInfo);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
             }
         }
 
         public void addHdmiInput(int id, TvInputInfo inputInfo) {
             ensureHardwarePermission();
             ensureValidInput(inputInfo);
-            synchronized (mLock) {
-                mTvInputHardwareManager.addHdmiInput(id, inputInfo);
-                addHardwareInputLocked(inputInfo);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    mTvInputHardwareManager.addHdmiInput(id, inputInfo);
+                    addHardwareInputLocked(inputInfo);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
             }
         }
 
         public void removeHardwareInput(String inputId) {
             ensureHardwarePermission();
-            synchronized (mLock) {
-                ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
-                boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
-                if (removed) {
-                    buildTvInputListLocked(mUserId, null);
-                    mTvInputHardwareManager.removeHardwareInput(inputId);
-                } else {
-                    Slog.e(TAG, "failed to remove input " + inputId);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
+                    boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
+                    if (removed) {
+                        buildTvInputListLocked(mUserId, null);
+                        mTvInputHardwareManager.removeHardwareInput(inputId);
+                    } else {
+                        Slog.e(TAG, "failed to remove input " + inputId);
+                    }
                 }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
             }
         }
     }
diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
index dfb0752..d0c6d13 100644
--- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
@@ -47,6 +47,7 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.InputChannel;
 import android.view.Surface;
 
 import com.android.internal.annotations.GuardedBy;
@@ -611,14 +612,14 @@
                     if (userId != mCurrentUserId && !mRunningProfiles.contains(userId)) {
                         // Only current user and its running profiles can create sessions.
                         // Let the client get onConnectionFailed callback for this case.
-                        sendSessionTokenToClientLocked(client, iAppServiceId, null, seq);
+                        sendSessionTokenToClientLocked(client, iAppServiceId, null, null, seq);
                         return;
                     }
                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
                     TvIAppState iAppState = userState.mIAppMap.get(iAppServiceId);
                     if (iAppState == null) {
                         Slogf.w(TAG, "Failed to find state for iAppServiceId=" + iAppServiceId);
-                        sendSessionTokenToClientLocked(client, iAppServiceId, null, seq);
+                        sendSessionTokenToClientLocked(client, iAppServiceId, null, null, seq);
                         return;
                     }
                     ServiceState serviceState =
@@ -631,7 +632,7 @@
                     }
                     // Send a null token immediately while reconnecting.
                     if (serviceState.mReconnecting) {
-                        sendSessionTokenToClientLocked(client, iAppServiceId, null, seq);
+                        sendSessionTokenToClientLocked(client, iAppServiceId, null, null, seq);
                         return;
                     }
 
@@ -780,9 +781,9 @@
 
     @GuardedBy("mLock")
     private void sendSessionTokenToClientLocked(ITvIAppClient client, String iAppServiceId,
-            IBinder sessionToken, int seq) {
+            IBinder sessionToken, InputChannel channel, int seq) {
         try {
-            client.onSessionCreated(iAppServiceId, sessionToken, seq);
+            client.onSessionCreated(iAppServiceId, sessionToken, channel, seq);
         } catch (RemoteException e) {
             Slogf.e(TAG, "error in onSessionCreated", e);
         }
@@ -797,20 +798,23 @@
             Slogf.d(TAG, "createSessionInternalLocked(iAppServiceId="
                     + sessionState.mIAppServiceId + ")");
         }
+        InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
 
         // Set up a callback to send the session token.
-        ITvIAppSessionCallback callback = new SessionCallback(sessionState);
+        ITvIAppSessionCallback callback = new SessionCallback(sessionState, channels);
 
         boolean created = true;
         // Create a session. When failed, send a null token immediately.
         try {
-            service.createSession(callback, sessionState.mIAppServiceId, sessionState.mType);
+            service.createSession(
+                    channels[1], callback, sessionState.mIAppServiceId, sessionState.mType);
         } catch (RemoteException e) {
             Slogf.e(TAG, "error in createSession", e);
             sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mIAppServiceId, null,
-                    sessionState.mSeq);
+                    null, sessionState.mSeq);
             created = false;
         }
+        channels[1].dispose();
         return created;
     }
 
@@ -883,7 +887,7 @@
         for (SessionState sessionState : sessionsToAbort) {
             removeSessionStateLocked(sessionState.mSessionToken, sessionState.mUserId);
             sendSessionTokenToClientLocked(sessionState.mClient,
-                    sessionState.mIAppServiceId, null, sessionState.mSeq);
+                    sessionState.mIAppServiceId, null, null, sessionState.mSeq);
         }
         updateServiceConnectionLocked(serviceState.mComponent, userId);
     }
@@ -1136,9 +1140,11 @@
 
     private final class SessionCallback extends ITvIAppSessionCallback.Stub {
         private final SessionState mSessionState;
+        private final InputChannel[] mInputChannels;
 
-        SessionCallback(SessionState sessionState) {
+        SessionCallback(SessionState sessionState, InputChannel[] channels) {
             mSessionState = sessionState;
+            mInputChannels = channels;
         }
 
         @Override
@@ -1154,12 +1160,14 @@
                             mSessionState.mClient,
                             mSessionState.mIAppServiceId,
                             mSessionState.mSessionToken,
+                            mInputChannels[0],
                             mSessionState.mSeq);
                 } else {
                     removeSessionStateLocked(mSessionState.mSessionToken, mSessionState.mUserId);
                     sendSessionTokenToClientLocked(mSessionState.mClient,
-                            mSessionState.mIAppServiceId, null, mSessionState.mSeq);
+                            mSessionState.mIAppServiceId, null, null, mSessionState.mSeq);
                 }
+                mInputChannels[0].dispose();
             }
         }
 
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 1c46ac8..be13168 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -87,9 +87,10 @@
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.WakeupMessage;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
-import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
 import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
+import com.android.server.vcn.routeselection.UnderlyingNetworkController;
+import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback;
+import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
 import com.android.server.vcn.util.LogUtils;
 import com.android.server.vcn.util.MtuUtils;
 import com.android.server.vcn.util.OneWayBoolean;
@@ -201,7 +202,7 @@
     private interface EventInfo {}
 
     /**
-     * Sent when there are changes to the underlying network (per the UnderlyingNetworkTracker).
+     * Sent when there are changes to the underlying network (per the UnderlyingNetworkController).
      *
      * <p>May indicate an entirely new underlying network, OR a change in network properties.
      *
@@ -522,11 +523,14 @@
 
     @NonNull private final VcnContext mVcnContext;
     @NonNull private final ParcelUuid mSubscriptionGroup;
-    @NonNull private final UnderlyingNetworkTracker mUnderlyingNetworkTracker;
+    @NonNull private final UnderlyingNetworkController mUnderlyingNetworkController;
     @NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
     @NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback;
     @NonNull private final Dependencies mDeps;
-    @NonNull private final VcnUnderlyingNetworkTrackerCallback mUnderlyingNetworkTrackerCallback;
+
+    @NonNull
+    private final VcnUnderlyingNetworkControllerCallback mUnderlyingNetworkControllerCallback;
+
     private final boolean mIsMobileDataEnabled;
 
     @NonNull private final IpSecManager mIpSecManager;
@@ -674,17 +678,17 @@
 
         mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
 
-        mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback();
+        mUnderlyingNetworkControllerCallback = new VcnUnderlyingNetworkControllerCallback();
 
         mWakeLock =
                 mDeps.newWakeLock(mVcnContext.getContext(), PowerManager.PARTIAL_WAKE_LOCK, TAG);
 
-        mUnderlyingNetworkTracker =
-                mDeps.newUnderlyingNetworkTracker(
+        mUnderlyingNetworkController =
+                mDeps.newUnderlyingNetworkController(
                         mVcnContext,
                         subscriptionGroup,
                         mLastSnapshot,
-                        mUnderlyingNetworkTrackerCallback);
+                        mUnderlyingNetworkControllerCallback);
         mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
 
         addState(mDisconnectedState);
@@ -748,7 +752,7 @@
         cancelRetryTimeoutAlarm();
         cancelSafeModeAlarm();
 
-        mUnderlyingNetworkTracker.teardown();
+        mUnderlyingNetworkController.teardown();
 
         mGatewayStatusCallback.onQuit();
     }
@@ -764,12 +768,13 @@
         mVcnContext.ensureRunningOnLooperThread();
 
         mLastSnapshot = snapshot;
-        mUnderlyingNetworkTracker.updateSubscriptionSnapshot(mLastSnapshot);
+        mUnderlyingNetworkController.updateSubscriptionSnapshot(mLastSnapshot);
 
         sendMessageAndAcquireWakeLock(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
     }
 
-    private class VcnUnderlyingNetworkTrackerCallback implements UnderlyingNetworkTrackerCallback {
+    private class VcnUnderlyingNetworkControllerCallback
+            implements UnderlyingNetworkControllerCallback {
         @Override
         public void onSelectedUnderlyingNetworkChanged(
                 @Nullable UnderlyingNetworkRecord underlying) {
@@ -2264,7 +2269,7 @@
                         + (mNetworkAgent == null ? null : mNetworkAgent.getNetwork()));
         pw.println();
 
-        mUnderlyingNetworkTracker.dump(pw);
+        mUnderlyingNetworkController.dump(pw);
         pw.println();
 
         pw.decreaseIndent();
@@ -2276,8 +2281,8 @@
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
-    UnderlyingNetworkTrackerCallback getUnderlyingNetworkTrackerCallback() {
-        return mUnderlyingNetworkTrackerCallback;
+    UnderlyingNetworkControllerCallback getUnderlyingNetworkControllerCallback() {
+        return mUnderlyingNetworkControllerCallback;
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -2356,17 +2361,14 @@
     /** External dependencies used by VcnGatewayConnection, for injection in tests */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public static class Dependencies {
-        /** Builds a new UnderlyingNetworkTracker. */
-        public UnderlyingNetworkTracker newUnderlyingNetworkTracker(
+        /** Builds a new UnderlyingNetworkController. */
+        public UnderlyingNetworkController newUnderlyingNetworkController(
                 VcnContext vcnContext,
                 ParcelUuid subscriptionGroup,
                 TelephonySubscriptionSnapshot snapshot,
-                UnderlyingNetworkTrackerCallback callback) {
-            return new UnderlyingNetworkTracker(
-                    vcnContext,
-                    subscriptionGroup,
-                    snapshot,
-                    callback);
+                UnderlyingNetworkControllerCallback callback) {
+            return new UnderlyingNetworkController(
+                    vcnContext, subscriptionGroup, snapshot, callback);
         }
 
         /** Builds a new IkeSession. */
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
new file mode 100644
index 0000000..bea8ae9
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -0,0 +1,196 @@
+/*
+ * 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.vcn.routeselection;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+import static com.android.server.VcnManagementService.LOCAL_LOG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.NetworkCapabilities;
+import android.net.vcn.VcnManager;
+import android.os.ParcelUuid;
+import android.os.PersistableBundle;
+import android.telephony.SubscriptionManager;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+
+import java.util.Set;
+
+/** @hide */
+class NetworkPriorityClassifier {
+    @NonNull private static final String TAG = NetworkPriorityClassifier.class.getSimpleName();
+    /**
+     * Minimum signal strength for a WiFi network to be eligible for switching to
+     *
+     * <p>A network that satisfies this is eligible to become the selected underlying network with
+     * no additional conditions
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT = -70;
+    /**
+     * Minimum signal strength to continue using a WiFi network
+     *
+     * <p>A network that satisfies the conditions may ONLY continue to be used if it is already
+     * selected as the underlying network. A WiFi network satisfying this condition, but NOT the
+     * prospective-network RSSI threshold CANNOT be switched to.
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74;
+    /** Priority for any cellular network for which the subscription is listed as opportunistic */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int PRIORITY_OPPORTUNISTIC_CELLULAR = 0;
+    /** Priority for any WiFi network which is in use, and satisfies the in-use RSSI threshold */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int PRIORITY_WIFI_IN_USE = 1;
+    /** Priority for any WiFi network which satisfies the prospective-network RSSI threshold */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int PRIORITY_WIFI_PROSPECTIVE = 2;
+    /** Priority for any standard macro cellular network */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int PRIORITY_MACRO_CELLULAR = 3;
+    /** Priority for any other networks (including unvalidated, etc) */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int PRIORITY_ANY = Integer.MAX_VALUE;
+
+    private static final SparseArray<String> PRIORITY_TO_STRING_MAP = new SparseArray<>();
+
+    static {
+        PRIORITY_TO_STRING_MAP.put(
+                PRIORITY_OPPORTUNISTIC_CELLULAR, "PRIORITY_OPPORTUNISTIC_CELLULAR");
+        PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_IN_USE, "PRIORITY_WIFI_IN_USE");
+        PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_PROSPECTIVE, "PRIORITY_WIFI_PROSPECTIVE");
+        PRIORITY_TO_STRING_MAP.put(PRIORITY_MACRO_CELLULAR, "PRIORITY_MACRO_CELLULAR");
+        PRIORITY_TO_STRING_MAP.put(PRIORITY_ANY, "PRIORITY_ANY");
+    }
+
+    /**
+     * Gives networks a priority class, based on the following priorities:
+     *
+     * <ol>
+     *   <li>Opportunistic cellular
+     *   <li>Carrier WiFi, signal strength >= WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT
+     *   <li>Carrier WiFi, active network + signal strength >= WIFI_EXIT_RSSI_THRESHOLD_DEFAULT
+     *   <li>Macro cellular
+     *   <li>Any others
+     * </ol>
+     */
+    static int calculatePriorityClass(
+            UnderlyingNetworkRecord networkRecord,
+            ParcelUuid subscriptionGroup,
+            TelephonySubscriptionSnapshot snapshot,
+            UnderlyingNetworkRecord currentlySelected,
+            PersistableBundle carrierConfig) {
+        final NetworkCapabilities caps = networkRecord.networkCapabilities;
+
+        // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED
+
+        if (networkRecord.isBlocked) {
+            logWtf("Network blocked for System Server: " + networkRecord.network);
+            return PRIORITY_ANY;
+        }
+
+        if (caps.hasTransport(TRANSPORT_CELLULAR)
+                && isOpportunistic(snapshot, caps.getSubscriptionIds())) {
+            // If this carrier is the active data provider, ensure that opportunistic is only
+            // ever prioritized if it is also the active data subscription. This ensures that
+            // if an opportunistic subscription is still in the process of being switched to,
+            // or switched away from, the VCN does not attempt to continue using it against the
+            // decision made at the telephony layer. Failure to do so may result in the modem
+            // switching back and forth.
+            //
+            // Allow the following two cases:
+            // 1. Active subId is NOT in the group that this VCN is supporting
+            // 2. This opportunistic subscription is for the active subId
+            if (!snapshot.getAllSubIdsInGroup(subscriptionGroup)
+                            .contains(SubscriptionManager.getActiveDataSubscriptionId())
+                    || caps.getSubscriptionIds()
+                            .contains(SubscriptionManager.getActiveDataSubscriptionId())) {
+                return PRIORITY_OPPORTUNISTIC_CELLULAR;
+            }
+        }
+
+        if (caps.hasTransport(TRANSPORT_WIFI)) {
+            if (caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)
+                    && currentlySelected != null
+                    && networkRecord.network.equals(currentlySelected.network)) {
+                return PRIORITY_WIFI_IN_USE;
+            }
+
+            if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) {
+                return PRIORITY_WIFI_PROSPECTIVE;
+            }
+        }
+
+        // Disallow opportunistic subscriptions from matching PRIORITY_MACRO_CELLULAR, as might
+        // be the case when Default Data SubId (CBRS) != Active Data SubId (MACRO), as might be
+        // the case if the Default Data SubId does not support certain services (eg voice
+        // calling)
+        if (caps.hasTransport(TRANSPORT_CELLULAR)
+                && !isOpportunistic(snapshot, caps.getSubscriptionIds())) {
+            return PRIORITY_MACRO_CELLULAR;
+        }
+
+        return PRIORITY_ANY;
+    }
+
+    static boolean isOpportunistic(
+            @NonNull TelephonySubscriptionSnapshot snapshot, Set<Integer> subIds) {
+        if (snapshot == null) {
+            logWtf("Got null snapshot");
+            return false;
+        }
+        for (int subId : subIds) {
+            if (snapshot.isOpportunistic(subId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static int getWifiEntryRssiThreshold(@Nullable PersistableBundle carrierConfig) {
+        if (carrierConfig != null) {
+            return carrierConfig.getInt(
+                    VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
+                    WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT);
+        }
+        return WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT;
+    }
+
+    static int getWifiExitRssiThreshold(@Nullable PersistableBundle carrierConfig) {
+        if (carrierConfig != null) {
+            return carrierConfig.getInt(
+                    VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
+                    WIFI_EXIT_RSSI_THRESHOLD_DEFAULT);
+        }
+        return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
+    }
+
+    static String priorityClassToString(int priorityClass) {
+        return PRIORITY_TO_STRING_MAP.get(priorityClass, "unknown");
+    }
+
+    private static void logWtf(String msg) {
+        Slog.wtf(TAG, msg);
+        LOCAL_LOG.log(TAG + " WTF: " + msg);
+    }
+}
diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
similarity index 60%
rename from services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
rename to services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index 7ddd135..071c7a6 100644
--- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.server.vcn;
+package com.android.server.vcn.routeselection;
 
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
 
 import static com.android.server.VcnManagementService.LOCAL_LOG;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiEntryRssiThreshold;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiExitRssiThreshold;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.isOpportunistic;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -31,27 +32,23 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.TelephonyNetworkSpecifier;
-import android.net.vcn.VcnManager;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.Slog;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -61,68 +58,18 @@
 /**
  * Tracks a set of Networks underpinning a VcnGatewayConnection.
  *
- * <p>A single UnderlyingNetworkTracker is built to serve a SINGLE VCN Gateway Connection, and MUST
- * be torn down with the VcnGatewayConnection in order to ensure underlying networks are allowed to
- * be reaped.
+ * <p>A single UnderlyingNetworkController is built to serve a SINGLE VCN Gateway Connection, and
+ * MUST be torn down with the VcnGatewayConnection in order to ensure underlying networks are
+ * allowed to be reaped.
  *
  * @hide
  */
-public class UnderlyingNetworkTracker {
-    @NonNull private static final String TAG = UnderlyingNetworkTracker.class.getSimpleName();
-
-    /**
-     * Minimum signal strength for a WiFi network to be eligible for switching to
-     *
-     * <p>A network that satisfies this is eligible to become the selected underlying network with
-     * no additional conditions
-     */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT = -70;
-
-    /**
-     * Minimum signal strength to continue using a WiFi network
-     *
-     * <p>A network that satisfies the conditions may ONLY continue to be used if it is already
-     * selected as the underlying network. A WiFi network satisfying this condition, but NOT the
-     * prospective-network RSSI threshold CANNOT be switched to.
-     */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74;
-
-    /** Priority for any cellular network for which the subscription is listed as opportunistic */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PRIORITY_OPPORTUNISTIC_CELLULAR = 0;
-
-    /** Priority for any WiFi network which is in use, and satisfies the in-use RSSI threshold */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PRIORITY_WIFI_IN_USE = 1;
-
-    /** Priority for any WiFi network which satisfies the prospective-network RSSI threshold */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PRIORITY_WIFI_PROSPECTIVE = 2;
-
-    /** Priority for any standard macro cellular network */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PRIORITY_MACRO_CELLULAR = 3;
-
-    /** Priority for any other networks (including unvalidated, etc) */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PRIORITY_ANY = Integer.MAX_VALUE;
-
-    private static final SparseArray<String> PRIORITY_TO_STRING_MAP = new SparseArray<>();
-
-    static {
-        PRIORITY_TO_STRING_MAP.put(
-                PRIORITY_OPPORTUNISTIC_CELLULAR, "PRIORITY_OPPORTUNISTIC_CELLULAR");
-        PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_IN_USE, "PRIORITY_WIFI_IN_USE");
-        PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_PROSPECTIVE, "PRIORITY_WIFI_PROSPECTIVE");
-        PRIORITY_TO_STRING_MAP.put(PRIORITY_MACRO_CELLULAR, "PRIORITY_MACRO_CELLULAR");
-        PRIORITY_TO_STRING_MAP.put(PRIORITY_ANY, "PRIORITY_ANY");
-    }
+public class UnderlyingNetworkController {
+    @NonNull private static final String TAG = UnderlyingNetworkController.class.getSimpleName();
 
     @NonNull private final VcnContext mVcnContext;
     @NonNull private final ParcelUuid mSubscriptionGroup;
-    @NonNull private final UnderlyingNetworkTrackerCallback mCb;
+    @NonNull private final UnderlyingNetworkControllerCallback mCb;
     @NonNull private final Dependencies mDeps;
     @NonNull private final Handler mHandler;
     @NonNull private final ConnectivityManager mConnectivityManager;
@@ -142,11 +89,11 @@
     @Nullable private UnderlyingNetworkRecord mCurrentRecord;
     @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress;
 
-    public UnderlyingNetworkTracker(
+    public UnderlyingNetworkController(
             @NonNull VcnContext vcnContext,
             @NonNull ParcelUuid subscriptionGroup,
             @NonNull TelephonySubscriptionSnapshot snapshot,
-            @NonNull UnderlyingNetworkTrackerCallback cb) {
+            @NonNull UnderlyingNetworkControllerCallback cb) {
         this(
                 vcnContext,
                 subscriptionGroup,
@@ -155,11 +102,11 @@
                 new Dependencies());
     }
 
-    private UnderlyingNetworkTracker(
+    private UnderlyingNetworkController(
             @NonNull VcnContext vcnContext,
             @NonNull ParcelUuid subscriptionGroup,
             @NonNull TelephonySubscriptionSnapshot snapshot,
-            @NonNull UnderlyingNetworkTrackerCallback cb,
+            @NonNull UnderlyingNetworkControllerCallback cb,
             @NonNull Dependencies deps) {
         mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
         mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
@@ -271,8 +218,8 @@
      * subscription group, while the VCN networks are excluded by virtue of not having subIds set on
      * the VCN-exposed networks.
      *
-     * <p>If the VCN that this UnderlyingNetworkTracker belongs to is in test-mode, this will return
-     * a NetworkRequest that only matches Test Networks.
+     * <p>If the VCN that this UnderlyingNetworkController belongs to is in test-mode, this will
+     * return a NetworkRequest that only matches Test Networks.
      */
     private NetworkRequest getRouteSelectionRequest() {
         if (mVcnContext.isInTestMode()) {
@@ -373,9 +320,9 @@
     }
 
     /**
-     * Update this UnderlyingNetworkTracker's TelephonySubscriptionSnapshot.
+     * Update this UnderlyingNetworkController's TelephonySubscriptionSnapshot.
      *
-     * <p>Updating the TelephonySubscriptionSnapshot will cause this UnderlyingNetworkTracker to
+     * <p>Updating the TelephonySubscriptionSnapshot will cause this UnderlyingNetworkController to
      * reevaluate its NetworkBringupCallbacks. This may result in NetworkRequests being registered
      * or unregistered if the subIds mapped to the this Tracker's SubscriptionGroup change.
      */
@@ -410,7 +357,7 @@
 
     private void reevaluateNetworks() {
         if (mIsQuitting || mRouteSelectionCallback == null) {
-            return; // UnderlyingNetworkTracker has quit.
+            return; // UnderlyingNetworkController has quit.
         }
 
         TreeSet<UnderlyingNetworkRecord> sorted =
@@ -424,22 +371,6 @@
         mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord);
     }
 
-    private static boolean isOpportunistic(
-            @NonNull TelephonySubscriptionSnapshot snapshot, Set<Integer> subIds) {
-        if (snapshot == null) {
-            logWtf("Got null snapshot");
-            return false;
-        }
-
-        for (int subId : subIds) {
-            if (snapshot.isOpportunistic(subId)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
     /**
      * NetworkBringupCallback is used to keep background, VCN-managed Networks from being reaped.
      *
@@ -544,230 +475,6 @@
         }
     }
 
-    private static int getWifiEntryRssiThreshold(@Nullable PersistableBundle carrierConfig) {
-        if (carrierConfig != null) {
-            return carrierConfig.getInt(
-                    VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
-                    WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT);
-        }
-
-        return WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT;
-    }
-
-    private static int getWifiExitRssiThreshold(@Nullable PersistableBundle carrierConfig) {
-        if (carrierConfig != null) {
-            return carrierConfig.getInt(
-                    VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
-                    WIFI_EXIT_RSSI_THRESHOLD_DEFAULT);
-        }
-
-        return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
-    }
-
-    /** A record of a single underlying network, caching relevant fields. */
-    public static class UnderlyingNetworkRecord {
-        @NonNull public final Network network;
-        @NonNull public final NetworkCapabilities networkCapabilities;
-        @NonNull public final LinkProperties linkProperties;
-        public final boolean isBlocked;
-
-        @VisibleForTesting(visibility = Visibility.PRIVATE)
-        UnderlyingNetworkRecord(
-                @NonNull Network network,
-                @NonNull NetworkCapabilities networkCapabilities,
-                @NonNull LinkProperties linkProperties,
-                boolean isBlocked) {
-            this.network = network;
-            this.networkCapabilities = networkCapabilities;
-            this.linkProperties = linkProperties;
-            this.isBlocked = isBlocked;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (!(o instanceof UnderlyingNetworkRecord)) return false;
-            final UnderlyingNetworkRecord that = (UnderlyingNetworkRecord) o;
-
-            return network.equals(that.network)
-                    && networkCapabilities.equals(that.networkCapabilities)
-                    && linkProperties.equals(that.linkProperties)
-                    && isBlocked == that.isBlocked;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(network, networkCapabilities, linkProperties, isBlocked);
-        }
-
-        /**
-         * Gives networks a priority class, based on the following priorities:
-         *
-         * <ol>
-         *   <li>Opportunistic cellular
-         *   <li>Carrier WiFi, signal strength >= WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT
-         *   <li>Carrier WiFi, active network + signal strength >= WIFI_EXIT_RSSI_THRESHOLD_DEFAULT
-         *   <li>Macro cellular
-         *   <li>Any others
-         * </ol>
-         */
-        private int calculatePriorityClass(
-                ParcelUuid subscriptionGroup,
-                TelephonySubscriptionSnapshot snapshot,
-                UnderlyingNetworkRecord currentlySelected,
-                PersistableBundle carrierConfig) {
-            final NetworkCapabilities caps = networkCapabilities;
-
-            // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED
-
-            if (isBlocked) {
-                logWtf("Network blocked for System Server: " + network);
-                return PRIORITY_ANY;
-            }
-
-            if (caps.hasTransport(TRANSPORT_CELLULAR)
-                    && isOpportunistic(snapshot, caps.getSubscriptionIds())) {
-                // If this carrier is the active data provider, ensure that opportunistic is only
-                // ever prioritized if it is also the active data subscription. This ensures that
-                // if an opportunistic subscription is still in the process of being switched to,
-                // or switched away from, the VCN does not attempt to continue using it against the
-                // decision made at the telephony layer. Failure to do so may result in the modem
-                // switching back and forth.
-                //
-                // Allow the following two cases:
-                // 1. Active subId is NOT in the group that this VCN is supporting
-                // 2. This opportunistic subscription is for the active subId
-                if (!snapshot.getAllSubIdsInGroup(subscriptionGroup)
-                                .contains(SubscriptionManager.getActiveDataSubscriptionId())
-                        || caps.getSubscriptionIds()
-                                .contains(SubscriptionManager.getActiveDataSubscriptionId())) {
-                    return PRIORITY_OPPORTUNISTIC_CELLULAR;
-                }
-            }
-
-            if (caps.hasTransport(TRANSPORT_WIFI)) {
-                if (caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)
-                        && currentlySelected != null
-                        && network.equals(currentlySelected.network)) {
-                    return PRIORITY_WIFI_IN_USE;
-                }
-
-                if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) {
-                    return PRIORITY_WIFI_PROSPECTIVE;
-                }
-            }
-
-            // Disallow opportunistic subscriptions from matching PRIORITY_MACRO_CELLULAR, as might
-            // be the case when Default Data SubId (CBRS) != Active Data SubId (MACRO), as might be
-            // the case if the Default Data SubId does not support certain services (eg voice
-            // calling)
-            if (caps.hasTransport(TRANSPORT_CELLULAR)
-                    && !isOpportunistic(snapshot, caps.getSubscriptionIds())) {
-                return PRIORITY_MACRO_CELLULAR;
-            }
-
-            return PRIORITY_ANY;
-        }
-
-        private static Comparator<UnderlyingNetworkRecord> getComparator(
-                ParcelUuid subscriptionGroup,
-                TelephonySubscriptionSnapshot snapshot,
-                UnderlyingNetworkRecord currentlySelected,
-                PersistableBundle carrierConfig) {
-            return (left, right) -> {
-                return Integer.compare(
-                        left.calculatePriorityClass(
-                                subscriptionGroup, snapshot, currentlySelected, carrierConfig),
-                        right.calculatePriorityClass(
-                                subscriptionGroup, snapshot, currentlySelected, carrierConfig));
-            };
-        }
-
-        /** Dumps the state of this record for logging and debugging purposes. */
-        private void dump(
-                IndentingPrintWriter pw,
-                ParcelUuid subscriptionGroup,
-                TelephonySubscriptionSnapshot snapshot,
-                UnderlyingNetworkRecord currentlySelected,
-                PersistableBundle carrierConfig) {
-            pw.println("UnderlyingNetworkRecord:");
-            pw.increaseIndent();
-
-            final int priorityClass =
-                    calculatePriorityClass(
-                            subscriptionGroup, snapshot, currentlySelected, carrierConfig);
-            pw.println(
-                    "Priority class: " + PRIORITY_TO_STRING_MAP.get(priorityClass) + " ("
-                            + priorityClass + ")");
-            pw.println("mNetwork: " + network);
-            pw.println("mNetworkCapabilities: " + networkCapabilities);
-            pw.println("mLinkProperties: " + linkProperties);
-
-            pw.decreaseIndent();
-        }
-
-        /** Builder to incrementally construct an UnderlyingNetworkRecord. */
-        private static class Builder {
-            @NonNull private final Network mNetwork;
-
-            @Nullable private NetworkCapabilities mNetworkCapabilities;
-            @Nullable private LinkProperties mLinkProperties;
-            boolean mIsBlocked;
-            boolean mWasIsBlockedSet;
-
-            @Nullable private UnderlyingNetworkRecord mCached;
-
-            private Builder(@NonNull Network network) {
-                mNetwork = network;
-            }
-
-            @NonNull
-            private Network getNetwork() {
-                return mNetwork;
-            }
-
-            private void setNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
-                mNetworkCapabilities = networkCapabilities;
-                mCached = null;
-            }
-
-            @Nullable
-            private NetworkCapabilities getNetworkCapabilities() {
-                return mNetworkCapabilities;
-            }
-
-            private void setLinkProperties(@NonNull LinkProperties linkProperties) {
-                mLinkProperties = linkProperties;
-                mCached = null;
-            }
-
-            private void setIsBlocked(boolean isBlocked) {
-                mIsBlocked = isBlocked;
-                mWasIsBlockedSet = true;
-                mCached = null;
-            }
-
-            private boolean isValid() {
-                return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet;
-            }
-
-            private UnderlyingNetworkRecord build() {
-                if (!isValid()) {
-                    throw new IllegalArgumentException(
-                            "Called build before UnderlyingNetworkRecord was valid");
-                }
-
-                if (mCached == null) {
-                    mCached =
-                            new UnderlyingNetworkRecord(
-                                    mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked);
-                }
-
-                return mCached;
-            }
-        }
-    }
-
     private static void logWtf(String msg) {
         Slog.wtf(TAG, msg);
         LOCAL_LOG.log(TAG + " WTF: " + msg);
@@ -780,7 +487,7 @@
 
     /** Dumps the state of this record for logging and debugging purposes. */
     public void dump(IndentingPrintWriter pw) {
-        pw.println("UnderlyingNetworkTracker:");
+        pw.println("UnderlyingNetworkController:");
         pw.increaseIndent();
 
         pw.println("Carrier WiFi Entry Threshold: " + getWifiEntryRssiThreshold(mCarrierConfig));
@@ -811,7 +518,7 @@
     }
 
     /** Callbacks for being notified of the changes in, or to the selected underlying network. */
-    public interface UnderlyingNetworkTrackerCallback {
+    public interface UnderlyingNetworkControllerCallback {
         /**
          * Fired when a new underlying network is selected, or properties have changed.
          *
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
new file mode 100644
index 0000000..65c69de
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
@@ -0,0 +1,175 @@
+/*
+ * 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.vcn.routeselection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.os.ParcelUuid;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+
+import java.util.Comparator;
+import java.util.Objects;
+
+/**
+ * A record of a single underlying network, caching relevant fields.
+ *
+ * @hide
+ */
+public class UnderlyingNetworkRecord {
+    @NonNull public final Network network;
+    @NonNull public final NetworkCapabilities networkCapabilities;
+    @NonNull public final LinkProperties linkProperties;
+    public final boolean isBlocked;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public UnderlyingNetworkRecord(
+            @NonNull Network network,
+            @NonNull NetworkCapabilities networkCapabilities,
+            @NonNull LinkProperties linkProperties,
+            boolean isBlocked) {
+        this.network = network;
+        this.networkCapabilities = networkCapabilities;
+        this.linkProperties = linkProperties;
+        this.isBlocked = isBlocked;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof UnderlyingNetworkRecord)) return false;
+        final UnderlyingNetworkRecord that = (UnderlyingNetworkRecord) o;
+
+        return network.equals(that.network)
+                && networkCapabilities.equals(that.networkCapabilities)
+                && linkProperties.equals(that.linkProperties)
+                && isBlocked == that.isBlocked;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(network, networkCapabilities, linkProperties, isBlocked);
+    }
+
+    static Comparator<UnderlyingNetworkRecord> getComparator(
+            ParcelUuid subscriptionGroup,
+            TelephonySubscriptionSnapshot snapshot,
+            UnderlyingNetworkRecord currentlySelected,
+            PersistableBundle carrierConfig) {
+        return (left, right) -> {
+            return Integer.compare(
+                    NetworkPriorityClassifier.calculatePriorityClass(
+                            left, subscriptionGroup, snapshot, currentlySelected, carrierConfig),
+                    NetworkPriorityClassifier.calculatePriorityClass(
+                            right, subscriptionGroup, snapshot, currentlySelected, carrierConfig));
+        };
+    }
+
+    /** Dumps the state of this record for logging and debugging purposes. */
+    void dump(
+            IndentingPrintWriter pw,
+            ParcelUuid subscriptionGroup,
+            TelephonySubscriptionSnapshot snapshot,
+            UnderlyingNetworkRecord currentlySelected,
+            PersistableBundle carrierConfig) {
+        pw.println("UnderlyingNetworkRecord:");
+        pw.increaseIndent();
+
+        final int priorityClass =
+                NetworkPriorityClassifier.calculatePriorityClass(
+                        this, subscriptionGroup, snapshot, currentlySelected, carrierConfig);
+        pw.println(
+                "Priority class: "
+                        + NetworkPriorityClassifier.priorityClassToString(priorityClass)
+                        + " ("
+                        + priorityClass
+                        + ")");
+        pw.println("mNetwork: " + network);
+        pw.println("mNetworkCapabilities: " + networkCapabilities);
+        pw.println("mLinkProperties: " + linkProperties);
+
+        pw.decreaseIndent();
+    }
+
+    /** Builder to incrementally construct an UnderlyingNetworkRecord. */
+    static class Builder {
+        @NonNull private final Network mNetwork;
+
+        @Nullable private NetworkCapabilities mNetworkCapabilities;
+        @Nullable private LinkProperties mLinkProperties;
+        boolean mIsBlocked;
+        boolean mWasIsBlockedSet;
+
+        @Nullable private UnderlyingNetworkRecord mCached;
+
+        Builder(@NonNull Network network) {
+            mNetwork = network;
+        }
+
+        @NonNull
+        Network getNetwork() {
+            return mNetwork;
+        }
+
+        void setNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
+            mNetworkCapabilities = networkCapabilities;
+            mCached = null;
+        }
+
+        @Nullable
+        NetworkCapabilities getNetworkCapabilities() {
+            return mNetworkCapabilities;
+        }
+
+        void setLinkProperties(@NonNull LinkProperties linkProperties) {
+            mLinkProperties = linkProperties;
+            mCached = null;
+        }
+
+        void setIsBlocked(boolean isBlocked) {
+            mIsBlocked = isBlocked;
+            mWasIsBlockedSet = true;
+            mCached = null;
+        }
+
+        boolean isValid() {
+            return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet;
+        }
+
+        UnderlyingNetworkRecord build() {
+            if (!isValid()) {
+                throw new IllegalArgumentException(
+                        "Called build before UnderlyingNetworkRecord was valid");
+            }
+
+            if (mCached == null) {
+                mCached =
+                        new UnderlyingNetworkRecord(
+                                mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked);
+            }
+
+            return mCached;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index bc21f1b..71a6b22 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -16,6 +16,14 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationAttributes.USAGE_ALARM;
+import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.IUidObserver;
@@ -90,6 +98,8 @@
     @GuardedBy("mLock")
     private int mHapticFeedbackIntensity;
     @GuardedBy("mLock")
+    private int mHardwareFeedbackIntensity;
+    @GuardedBy("mLock")
     private int mNotificationIntensity;
     @GuardedBy("mLock")
     private int mRingIntensity;
@@ -232,17 +242,20 @@
      * @return The vibration intensity, one of Vibrator.VIBRATION_INTENSITY_*
      */
     public int getDefaultIntensity(int usageHint) {
-        if (isAlarm(usageHint)) {
+        if (usageHint == USAGE_ALARM) {
             return Vibrator.VIBRATION_INTENSITY_HIGH;
         }
         synchronized (mLock) {
             if (mVibrator != null) {
-                if (isRingtone(usageHint)) {
-                    return mVibrator.getDefaultRingVibrationIntensity();
-                } else if (isNotification(usageHint)) {
-                    return mVibrator.getDefaultNotificationVibrationIntensity();
-                } else if (isHapticFeedback(usageHint)) {
-                    return mVibrator.getDefaultHapticFeedbackIntensity();
+                switch (usageHint) {
+                    case USAGE_RINGTONE:
+                        return mVibrator.getDefaultRingVibrationIntensity();
+                    case USAGE_NOTIFICATION:
+                        return mVibrator.getDefaultNotificationVibrationIntensity();
+                    case USAGE_TOUCH:
+                    case USAGE_HARDWARE_FEEDBACK:
+                    case USAGE_PHYSICAL_EMULATION:
+                        return mVibrator.getDefaultHapticFeedbackIntensity();
                 }
             }
         }
@@ -257,16 +270,20 @@
      */
     public int getCurrentIntensity(int usageHint) {
         synchronized (mLock) {
-            if (isRingtone(usageHint)) {
-                return mRingIntensity;
-            } else if (isNotification(usageHint)) {
-                return mNotificationIntensity;
-            } else if (isHapticFeedback(usageHint)) {
-                return mHapticFeedbackIntensity;
-            } else if (isAlarm(usageHint)) {
-                return Vibrator.VIBRATION_INTENSITY_HIGH;
-            } else {
-                return Vibrator.VIBRATION_INTENSITY_MEDIUM;
+            switch (usageHint) {
+                case USAGE_RINGTONE:
+                    return mRingIntensity;
+                case USAGE_NOTIFICATION:
+                    return mNotificationIntensity;
+                case USAGE_TOUCH:
+                    return mHapticFeedbackIntensity;
+                case USAGE_HARDWARE_FEEDBACK:
+                case USAGE_PHYSICAL_EMULATION:
+                    return mHardwareFeedbackIntensity;
+                case USAGE_ALARM:
+                    return Vibrator.VIBRATION_INTENSITY_HIGH;
+                default:
+                    return Vibrator.VIBRATION_INTENSITY_MEDIUM;
             }
         }
     }
@@ -289,7 +306,7 @@
      * for ringtone usage only. All other usages are allowed independently of ringer mode.
      */
     public boolean shouldVibrateForRingerMode(int usageHint) {
-        if (!isRingtone(usageHint)) {
+        if (usageHint != USAGE_RINGTONE) {
             return true;
         }
         synchronized (mLock) {
@@ -324,8 +341,10 @@
      * {@link VibrationAttributes#USAGE_COMMUNICATION_REQUEST} usages are allowed to vibrate.
      */
     public boolean shouldVibrateForPowerMode(int usageHint) {
-        return !mLowPowerMode || isRingtone(usageHint) || isAlarm(usageHint)
-                || usageHint == VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+        synchronized (mLock) {
+            return !mLowPowerMode || usageHint == USAGE_RINGTONE || usageHint == USAGE_ALARM
+                    || usageHint == USAGE_COMMUNICATION_REQUEST;
+        }
     }
 
     /** Return {@code true} if input devices should vibrate instead of this device. */
@@ -338,22 +357,6 @@
         return mZenMode != Settings.Global.ZEN_MODE_OFF;
     }
 
-    private static boolean isNotification(int usageHint) {
-        return usageHint == VibrationAttributes.USAGE_NOTIFICATION;
-    }
-
-    private static boolean isRingtone(int usageHint) {
-        return usageHint == VibrationAttributes.USAGE_RINGTONE;
-    }
-
-    private static boolean isHapticFeedback(int usageHint) {
-        return usageHint == VibrationAttributes.USAGE_TOUCH;
-    }
-
-    private static boolean isAlarm(int usageHint) {
-        return usageHint == VibrationAttributes.USAGE_ALARM;
-    }
-
     private static boolean isClassAlarm(int usageHint) {
         return (usageHint & VibrationAttributes.USAGE_CLASS_MASK)
                 == VibrationAttributes.USAGE_CLASS_ALARM;
@@ -365,18 +368,35 @@
             mVibrateWhenRinging = getSystemSetting(Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
             mApplyRampingRinger = getGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0) != 0;
             mHapticFeedbackIntensity = getSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                    getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
+                    getDefaultIntensity(USAGE_TOUCH));
+            mHardwareFeedbackIntensity = getSystemSetting(
+                    Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY,
+                    getHardwareFeedbackIntensityWhenSettingIsMissing(mHapticFeedbackIntensity));
             mNotificationIntensity = getSystemSetting(
                     Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                    getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
+                    getDefaultIntensity(USAGE_NOTIFICATION));
             mRingIntensity = getSystemSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                    getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+                    getDefaultIntensity(USAGE_RINGTONE));
             mVibrateInputDevices = getSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
             mZenMode = getGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
         }
         notifyListeners();
     }
 
+    /**
+     * Return the value to be used for {@link Settings.System#HARDWARE_HAPTIC_FEEDBACK_INTENSITY}
+     * when the value was not set by the user.
+     *
+     * <p>This should adapt the behavior preceding the introduction of this new setting key, which
+     * is to apply {@link Settings.System#HAPTIC_FEEDBACK_INTENSITY} unless it's disabled.
+     */
+    private int getHardwareFeedbackIntensityWhenSettingIsMissing(int hapticFeedbackIntensity) {
+        if (hapticFeedbackIntensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+            return getDefaultIntensity(USAGE_HARDWARE_FEEDBACK);
+        }
+        return hapticFeedbackIntensity;
+    }
+
     @Override
     public String toString() {
         return "VibrationSettings{"
@@ -389,18 +409,20 @@
                 + ", mHapticChannelMaxVibrationAmplitude=" + getHapticChannelMaxVibrationAmplitude()
                 + ", mRampStepDuration=" + mRampStepDuration
                 + ", mRampDownDuration=" + mRampDownDuration
+                + ", mHardwareHapticFeedbackIntensity="
+                + intensityToString(getCurrentIntensity(USAGE_HARDWARE_FEEDBACK))
                 + ", mHapticFeedbackIntensity="
-                + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_TOUCH))
+                + intensityToString(getCurrentIntensity(USAGE_TOUCH))
                 + ", mHapticFeedbackDefaultIntensity="
-                + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_TOUCH))
+                + intensityToString(getDefaultIntensity(USAGE_TOUCH))
                 + ", mNotificationIntensity="
-                + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION))
+                + intensityToString(getCurrentIntensity(USAGE_NOTIFICATION))
                 + ", mNotificationDefaultIntensity="
-                + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION))
+                + intensityToString(getDefaultIntensity(USAGE_NOTIFICATION))
                 + ", mRingIntensity="
-                + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE))
+                + intensityToString(getCurrentIntensity(USAGE_RINGTONE))
                 + ", mRingDefaultIntensity="
-                + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE))
+                + intensityToString(getDefaultIntensity(USAGE_RINGTONE))
                 + '}';
     }
 
@@ -410,15 +432,15 @@
             proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
                     mHapticFeedbackIntensity);
             proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
-                    getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
+                    getDefaultIntensity(USAGE_TOUCH));
             proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_INTENSITY,
                     mNotificationIntensity);
             proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
-                    getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
+                    getDefaultIntensity(USAGE_NOTIFICATION));
             proto.write(VibratorManagerServiceDumpProto.RING_INTENSITY,
                     mRingIntensity);
             proto.write(VibratorManagerServiceDumpProto.RING_DEFAULT_INTENSITY,
-                    getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+                    getDefaultIntensity(USAGE_RINGTONE));
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ea22d92..2a1bb0b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6224,15 +6224,15 @@
     public boolean inputDispatchingTimedOut(String reason, int windowPid) {
         ActivityRecord anrActivity;
         WindowProcessController anrApp;
-        boolean windowFromSameProcessAsActivity;
+        boolean blameActivityProcess;
         synchronized (mAtmService.mGlobalLock) {
             anrActivity = getWaitingHistoryRecordLocked();
             anrApp = app;
-            windowFromSameProcessAsActivity =
-                    !hasProcess() || app.getPid() == windowPid || windowPid == INVALID_PID;
+            blameActivityProcess =  hasProcess()
+                    && (app.getPid() == windowPid || windowPid == INVALID_PID);
         }
 
-        if (windowFromSameProcessAsActivity) {
+        if (blameActivityProcess) {
             return mAtmService.mAmInternal.inputDispatchingTimedOut(anrApp.mOwner,
                     anrActivity.shortComponentName, anrActivity.info.applicationInfo,
                     shortComponentName, app, false, reason);
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 8788225..721907c 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -176,6 +176,9 @@
         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
 
         ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO");
+        // TODO(b/205335975): Remove window which stuck in animatingExit status. Find actual cause.
+        mDisplayContent.forAllWindows(WindowState::cleanupAnimatingExitWindow,
+                true /* traverseTopToBottom */);
         // TODO(new-app-transition): Remove code using appTransition.getAppTransition()
         final AppTransition appTransition = mDisplayContent.mAppTransition;
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 42c8124..1f1fd34 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -218,6 +218,7 @@
 import android.view.WindowManager;
 import android.view.WindowManager.DisplayImePolicy;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.window.DisplayWindowPolicyController;
 import android.window.IDisplayAreaOrganizer;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -696,6 +697,14 @@
     // well and thus won't change the top resumed / focused record
     boolean mDontMoveToTop;
 
+    /**
+     * The policy controller of the windows that can be displayed on the virtual display.
+     *
+     * @see DisplayWindowPolicyController
+     */
+    @Nullable
+    DisplayWindowPolicyController mDisplayWindowPolicyController;
+
     private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
         WindowStateAnimator winAnimator = w.mWinAnimator;
         final ActivityRecord activity = w.mActivityRecord;
@@ -1631,11 +1640,6 @@
             // to cover the activity configuration change.
             return false;
         }
-        if (r.attachedToProcess() && mayImeShowOnLaunchingActivity(r)) {
-            // Currently it is unknown that when will IME window be ready. Reject the case to
-            // avoid flickering by showing IME in inconsistent orientation.
-            return false;
-        }
         if (checkOpening) {
             if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) {
                 // Apply normal rotation animation in case of the activity set different requested
@@ -2739,6 +2743,9 @@
             if (newDisplayInfo != null) {
                 mDisplayInfo.copyFrom(newDisplayInfo);
             }
+
+            mDisplayWindowPolicyController =
+                    displayManagerInternal.getDisplayWindowPolicyController(mDisplayId);
         }
 
         updateBaseDisplayMetrics(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight,
@@ -2821,8 +2828,14 @@
         mBaseDisplayDensity = baseDensity;
 
         if (mMaxUiWidth > 0 && mBaseDisplayWidth > mMaxUiWidth) {
-            mBaseDisplayHeight = (mMaxUiWidth * mBaseDisplayHeight) / mBaseDisplayWidth;
+            final float ratio = mMaxUiWidth / (float) mBaseDisplayWidth;
+            mBaseDisplayHeight = (int) (mBaseDisplayHeight * ratio);
             mBaseDisplayWidth = mMaxUiWidth;
+            if (!mIsDensityForced) {
+                // Update the density proportionally so the size of the UI elements won't change
+                // from the user's perspective.
+                mBaseDisplayDensity = (int) (mBaseDisplayDensity * ratio);
+            }
 
             if (DEBUG_DISPLAY) {
                 Slog.v(TAG_WM, "Applying config restraints:" + mBaseDisplayWidth + "x"
@@ -2879,6 +2892,13 @@
 
     /** If the given width and height equal to initial size, the setting will be cleared. */
     void setForcedSize(int width, int height) {
+        // Can't force size higher than the maximal allowed
+        if (mMaxUiWidth > 0 && width > mMaxUiWidth) {
+            final float ratio = mMaxUiWidth / (float) width;
+            height = (int) (height * ratio);
+            width = mMaxUiWidth;
+        }
+
         mIsSizeForced = mInitialDisplayWidth != width || mInitialDisplayHeight != height;
         if (mIsSizeForced) {
             // Set some sort of reasonable bounds on the size of the display that we will try
@@ -3420,6 +3440,10 @@
         mInputMonitor.dump(pw, "  ");
         pw.println();
         mInsetsStateController.dump(prefix, pw);
+        if (mDisplayWindowPolicyController != null) {
+            pw.println();
+            mDisplayWindowPolicyController.dump(prefix, pw);
+        }
     }
 
     @Override
@@ -4272,7 +4296,7 @@
             boolean subtle) {
         final WindowManagerPolicy policy = mWmService.mPolicy;
         forAllWindows(w -> {
-            if (w.mActivityRecord == null && policy.canBeHiddenByKeyguardLw(w)
+            if (w.mActivityRecord == null && w.canBeHiddenByKeyguard()
                     && w.wouldBeVisibleIfPolicyIgnored() && !w.isVisible()) {
                 w.startAnimation(policy.createHiddenByKeyguardExit(
                         onWallpaper, goingToShade, subtle));
@@ -4801,7 +4825,10 @@
             // WindowState#applyImeWindowsIfNeeded} in case of any state mismatch.
             return dc.mImeLayeringTarget != null
                     && (!dc.getDefaultTaskDisplayArea().isSplitScreenModeActivated()
-                             || dc.mImeLayeringTarget.getTask() == null);
+                             || dc.mImeLayeringTarget.getTask() == null)
+                    // Make sure that the IME window won't be skipped to report that it has
+                    // completed the orientation change.
+                    && !dc.mWmService.mDisplayFrozen;
         }
 
         /** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 09de6b3..6d3e8d5 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1738,7 +1738,7 @@
      * @param imeTarget The current IME target window.
      */
     private void applyKeyguardPolicy(WindowState win, WindowState imeTarget) {
-        if (mService.mPolicy.canBeHiddenByKeyguardLw(win)) {
+        if (win.canBeHiddenByKeyguard()) {
             if (shouldBeHiddenByKeyguard(win, imeTarget)) {
                 win.hide(false /* doAnimation */, true /* requestAnim */);
             } else {
@@ -1767,7 +1767,7 @@
         // Show IME over the keyguard if the target allows it.
         final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisible()
                 && win.mIsImWindow && (imeTarget.canShowWhenLocked()
-                        || !mService.mPolicy.canBeHiddenByKeyguardLw(imeTarget));
+                        || !imeTarget.canBeHiddenByKeyguard());
         if (showImeOverKeyguard) {
             return false;
         }
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index badb1f5..963f326 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -139,9 +139,6 @@
                 setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity);
             }
         }
-        if (mTaskFragment.mTransitionController.isShellTransitionsEnabled()) {
-            mTaskFragment.getDisplayContent().mWallpaperController.adjustWallpaperWindows();
-        }
     }
 
     private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting,
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 83fd3fa..4225f21 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -50,7 +50,6 @@
 import static java.lang.Integer.MAX_VALUE;
 
 import android.annotation.Nullable;
-import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Handler;
 import android.os.IBinder;
@@ -262,9 +261,6 @@
                 && !mDisableWallpaperTouchEvents;
         inputWindowHandle.setHasWallpaper(hasWallpaper);
 
-        final Rect frame = w.getFrame();
-        inputWindowHandle.setFrame(frame.left, frame.top, frame.right, frame.bottom);
-
         // Surface insets are hardcoded to be the same in all directions
         // and we could probably deprecate the "left/right/top/bottom" concept.
         // we avoid reintroducing this concept by just choosing one of them here.
@@ -274,11 +270,19 @@
         // what is on screen to what is actually being touched in the UI.
         inputWindowHandle.setScaleFactor(w.mGlobalScale != 1f ? (1f / w.mGlobalScale) : 1f);
 
-        final int flags = w.getSurfaceTouchableRegion(mTmpRegion, w.mAttrs.flags);
-        inputWindowHandle.setTouchableRegion(mTmpRegion);
+        // Update layout params flags to force the window to be not touch modal. We do this to
+        // restrict the window's touchable region to the task even if it request touches outside its
+        // window bounds. An example is a dialog in primary split should get touches outside its
+        // window within the primary task but should not get any touches going to the secondary
+        // task.
+        int flags = w.mAttrs.flags;
+        if (w.mAttrs.isModal()) {
+            flags = flags | FLAG_NOT_TOUCH_MODAL;
+        }
         inputWindowHandle.setLayoutParamsFlags(flags);
 
-        boolean useSurfaceCrop = false;
+        boolean useSurfaceBoundsAsTouchRegion = false;
+        SurfaceControl touchableRegionCrop = null;
         final Task task = w.getTask();
         if (task != null) {
             // TODO(b/165794636): Remove the special case for freeform window once drag resizing is
@@ -290,20 +294,22 @@
                 // we need to make sure that these changes in crop are reflected in the input
                 // windows, and so ensure this flag is set so that the input crop always reflects
                 // the surface hierarchy.
-                // TODO(b/168252846): we have some issues with modal-windows, so we need to cross
-                // that bridge now that we organize full-screen Tasks.
-                inputWindowHandle.setTouchableRegionCrop(null /* Use this surfaces crop */);
-                inputWindowHandle.setReplaceTouchableRegionWithCrop(true);
-                useSurfaceCrop = true;
+                useSurfaceBoundsAsTouchRegion = true;
+
+                if (w.mAttrs.isModal()) {
+                    TaskFragment parent = w.getTaskFragment();
+                    touchableRegionCrop = parent != null ? parent.getSurfaceControl() : null;
+                }
             } else if (task.cropWindowsToRootTaskBounds() && !w.inFreeformWindowingMode()) {
-                inputWindowHandle.setTouchableRegionCrop(task.getRootTask().getSurfaceControl());
-                inputWindowHandle.setReplaceTouchableRegionWithCrop(false);
-                useSurfaceCrop = true;
+                touchableRegionCrop = task.getRootTask().getSurfaceControl();
             }
         }
-        if (!useSurfaceCrop) {
-            inputWindowHandle.setReplaceTouchableRegionWithCrop(false);
-            inputWindowHandle.setTouchableRegionCrop(null);
+        inputWindowHandle.setReplaceTouchableRegionWithCrop(useSurfaceBoundsAsTouchRegion);
+        inputWindowHandle.setTouchableRegionCrop(touchableRegionCrop);
+
+        if (!useSurfaceBoundsAsTouchRegion) {
+            w.getSurfaceTouchableRegion(mTmpRegion, w.mAttrs);
+            inputWindowHandle.setTouchableRegion(mTmpRegion);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
index 7c35a21..49d30cd 100644
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -108,7 +108,7 @@
         final WindowManagerPolicy policy = service.mPolicy;
         service.mRoot.forAllWindows(nonAppWindow -> {
             // Animation on the IME window is controlled via Insets.
-            if (nonAppWindow.mActivityRecord == null && policy.canBeHiddenByKeyguardLw(nonAppWindow)
+            if (nonAppWindow.mActivityRecord == null && nonAppWindow.canBeHiddenByKeyguard()
                     && nonAppWindow.wouldBeVisibleIfPolicyIgnored() && !nonAppWindow.isVisible()
                     && nonAppWindow != service.mRoot.getCurrentInputMethodWindow()) {
                 final NonAppWindowAnimationAdapter nonAppAdapter = new NonAppWindowAnimationAdapter(
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 3de98f1..7a7fb65 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -169,21 +169,35 @@
         }
     }
 
+    /**
+     * Returns true when the app specific configuration is successfully stored or removed based on
+     * the current requested configuration. It will return false when the requested
+     * configuration is same as the pre-existing app-specific configuration.
+     */
     @GuardedBy("mLock")
     boolean updateFromImpl(String packageName, int userId,
             PackageConfigurationUpdaterImpl impl) {
         synchronized (mLock) {
-            PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId);
-            if (impl.getNightMode() != null) {
-                record.mNightMode = impl.getNightMode();
+            boolean isRecordPresent = false;
+            PackageConfigRecord record = findRecord(mModified, packageName, userId);
+            if (record != null) {
+                isRecordPresent = true;
+            } else {
+                record = findRecordOrCreate(mModified, packageName, userId);
             }
-            if (impl.getLocales() != null) {
-                record.mLocales = impl.getLocales();
-            }
+            boolean isNightModeChanged = updateNightMode(impl.getNightMode(), record);
+            boolean isLocalesChanged = updateLocales(impl.getLocales(), record);
+
             if ((record.mNightMode == null || record.isResetNightMode())
                     && (record.mLocales == null || record.mLocales.isEmpty())) {
                 // if all values default to system settings, we can remove the package.
                 removePackage(packageName, userId);
+                // if there was a pre-existing record for the package that was deleted,
+                // we return true (since it was successfully deleted), else false (since there was
+                // no change to the previous state).
+                return isRecordPresent;
+            } else if (!isNightModeChanged && !isLocalesChanged) {
+                return false;
             } else {
                 final PackageConfigRecord pendingRecord =
                         findRecord(mPendingWrite, record.mName, record.mUserId);
@@ -195,7 +209,8 @@
                     writeRecord = pendingRecord;
                 }
 
-                if (!updateNightMode(record, writeRecord) && !updateLocales(record, writeRecord)) {
+                if (!updateNightMode(record.mNightMode, writeRecord)
+                        && !updateLocales(record.mLocales, writeRecord)) {
                     return false;
                 }
 
@@ -203,24 +218,24 @@
                     Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord);
                 }
                 mPersisterQueue.addItem(new WriteProcessItem(writeRecord), false /* flush */);
+                return true;
             }
-            return true;
         }
     }
 
-    private boolean updateNightMode(PackageConfigRecord record, PackageConfigRecord writeRecord) {
-        if (record.mNightMode == null || record.mNightMode.equals(writeRecord.mNightMode)) {
+    private boolean updateNightMode(Integer requestedNightMode, PackageConfigRecord record) {
+        if (requestedNightMode == null || requestedNightMode.equals(record.mNightMode)) {
             return false;
         }
-        writeRecord.mNightMode = record.mNightMode;
+        record.mNightMode = requestedNightMode;
         return true;
     }
 
-    private boolean updateLocales(PackageConfigRecord record, PackageConfigRecord writeRecord) {
-        if (record.mLocales == null || record.mLocales.equals(writeRecord.mLocales)) {
+    private boolean updateLocales(LocaleList requestedLocaleList, PackageConfigRecord record) {
+        if (requestedLocaleList == null || requestedLocaleList.equals(record.mLocales)) {
             return false;
         }
-        writeRecord.mLocales = record.mLocales;
+        record.mLocales = requestedLocaleList;
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 600545e..7268610 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -808,6 +808,10 @@
         // Place root home tasks to the bottom.
         layer = adjustRootTaskLayer(t, mTmpHomeChildren, layer);
         layer = adjustRootTaskLayer(t, mTmpNormalChildren, layer);
+        // TODO(b/207185041): Remove this divider workaround after we full remove leagacy split and
+        //                    make app pair split only have single root then we can just attach the
+        //                    divider to the single root task in shell.
+        layer = Math.max(layer, SPLIT_DIVIDER_LAYER + 1);
         adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer);
         t.setLayer(mSplitScreenDividerAnchor, SPLIT_DIVIDER_LAYER);
     }
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 6d83fb6..29c27f9 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -31,10 +31,8 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.RemoteAnimationDefinition;
-import android.view.SurfaceControl;
 import android.window.ITaskFragmentOrganizer;
 import android.window.ITaskFragmentOrganizerController;
-import android.window.TaskFragmentAppearedInfo;
 import android.window.TaskFragmentInfo;
 
 import com.android.internal.protolog.common.ProtoLog;
@@ -135,11 +133,8 @@
         void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment tf) {
             ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName());
             final TaskFragmentInfo info = tf.getTaskFragmentInfo();
-            final SurfaceControl outSurfaceControl = new SurfaceControl(tf.getSurfaceControl(),
-                    "TaskFragmentOrganizerController.onTaskFragmentInfoAppeared");
             try {
-                organizer.onTaskFragmentAppeared(
-                        new TaskFragmentAppearedInfo(info, outSurfaceControl));
+                organizer.onTaskFragmentAppeared(info);
                 mLastSentTaskFragmentInfos.put(tf, info);
                 tf.mTaskFragmentAppearedSent = true;
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f175eec..7349594 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -244,11 +244,11 @@
         }
         mParticipants.add(wc);
         if (info.mShowWallpaper) {
-            // Collect the wallpaper so it is part of the sync set.
-            final WindowContainer wallpaper =
+            // Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set.
+            final WindowState wallpaper =
                     wc.getDisplayContent().mWallpaperController.getTopVisibleWallpaper();
             if (wallpaper != null) {
-                collect(wallpaper);
+                collect(wallpaper.mToken);
             }
         }
     }
@@ -495,25 +495,35 @@
             Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId);
             return;
         }
-        int displayId = DEFAULT_DISPLAY;
-        for (WindowContainer container : mParticipants) {
-            if (container.mDisplayContent == null) continue;
-            displayId = container.mDisplayContent.getDisplayId();
+        boolean hasWallpaper = false;
+        DisplayContent dc = null;
+        for (int i = mParticipants.size() - 1; i >= 0; --i) {
+            final WindowContainer<?> wc = mParticipants.valueAt(i);
+            if (dc == null && wc.mDisplayContent != null) {
+                dc = wc.mDisplayContent;
+            }
+            if (!hasWallpaper && isWallpaper(wc)) {
+                hasWallpaper = true;
+            }
         }
+        if (dc == null) dc = mController.mAtm.mRootWindowContainer.getDefaultDisplay();
 
         if (mState == STATE_ABORT) {
             mController.abort(this);
-            mController.mAtm.mRootWindowContainer.getDisplayContent(displayId)
-                    .getPendingTransaction().merge(transaction);
+            dc.getPendingTransaction().merge(transaction);
             mSyncId = -1;
             mOverrideOptions = null;
             return;
         }
+        // Ensure that wallpaper visibility is updated with the latest wallpaper target.
+        if (hasWallpaper) {
+            dc.mWallpaperController.adjustWallpaperWindows();
+        }
 
         mState = STATE_PLAYING;
         mController.moveToPlaying(this);
 
-        if (mController.mAtm.mTaskSupervisor.getKeyguardController().isKeyguardLocked(displayId)) {
+        if (dc.isKeyguardLocked()) {
             mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
         }
 
@@ -523,9 +533,9 @@
         info.setAnimationOptions(mOverrideOptions);
 
         // TODO(b/188669821): Move to animation impl in shell.
-        handleLegacyRecentsStartBehavior(displayId, info);
+        handleLegacyRecentsStartBehavior(dc, info);
 
-        handleNonAppWindowsInTransition(displayId, mType, mFlags);
+        handleNonAppWindowsInTransition(dc, mType, mFlags);
 
         reportStartReasonsToLogger();
 
@@ -627,14 +637,11 @@
     }
 
     /** @see RecentsAnimationController#attachNavigationBarToApp */
-    private void handleLegacyRecentsStartBehavior(int displayId, TransitionInfo info) {
+    private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) {
         if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
             return;
         }
-        final DisplayContent dc =
-                mController.mAtm.mRootWindowContainer.getDisplayContent(displayId);
-        if (dc == null) return;
-        mRecentsDisplayId = displayId;
+        mRecentsDisplayId = dc.mDisplayId;
 
         // Recents has an input-consumer to grab input from the "live tile" app. Set that up here
         final InputConsumerImpl recentsAnimationInputConsumer =
@@ -679,7 +686,7 @@
         // Find the top-most non-home, closing app.
         for (int i = 0; i < info.getChanges().size(); ++i) {
             final TransitionInfo.Change c = info.getChanges().get(i);
-            if (c.getTaskInfo() == null || c.getTaskInfo().displayId != displayId
+            if (c.getTaskInfo() == null || c.getTaskInfo().displayId != mRecentsDisplayId
                     || c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD
                     || !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) {
                 continue;
@@ -710,7 +717,7 @@
             t.setLayer(navSurfaceControl, Integer.MAX_VALUE);
         }
         if (mController.mStatusBar != null) {
-            mController.mStatusBar.setNavigationBarLumaSamplingEnabled(displayId, false);
+            mController.mStatusBar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, false);
         }
     }
 
@@ -760,13 +767,8 @@
         }
     }
 
-    private void handleNonAppWindowsInTransition(int displayId,
+    private void handleNonAppWindowsInTransition(@NonNull DisplayContent dc,
             @TransitionType int transit, @TransitionFlags int flags) {
-        final DisplayContent dc =
-                mController.mAtm.mRootWindowContainer.getDisplayContent(displayId);
-        if (dc == null) {
-            return;
-        }
         if ((transit == TRANSIT_KEYGUARD_GOING_AWAY
                 || (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0)
                 && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 08b1a2f..e24be37 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -611,8 +611,9 @@
     private void updateWallpaperTokens(boolean visible) {
         for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
             final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
-            token.updateWallpaperWindows(visible);
-            token.getDisplayContent().assignWindowLayers(false);
+            if (token.updateWallpaperWindows(visible)) {
+                token.mDisplayContent.assignWindowLayers(false /* setLayoutNeeded */);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 3a639f5..fe405e5 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -104,18 +104,21 @@
         }
     }
 
-    void updateWallpaperWindows(boolean visible) {
+    /** Returns {@code true} if visibility is changed. */
+    boolean updateWallpaperWindows(boolean visible) {
+        boolean changed = false;
         if (isVisible() != visible) {
             ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
                     token, visible);
             setVisibility(visible);
+            changed = true;
         }
-        final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
         if (mTransitionController.isShellTransitionsEnabled()) {
-            return;
+            return changed;
         }
 
-        final WindowState wallpaperTarget = wallpaperController.getWallpaperTarget();
+        final WindowState wallpaperTarget =
+                mDisplayContent.mWallpaperController.getWallpaperTarget();
 
         if (visible && wallpaperTarget != null) {
             final RecentsAnimationController recentsAnimationController =
@@ -137,6 +140,7 @@
         }
 
         setVisible(visible);
+        return changed;
     }
 
     private void setVisible(boolean visible) {
@@ -155,10 +159,12 @@
      * transition. In that situation, make sure to call {@link #commitVisibility} when done.
      */
     void setVisibility(boolean visible) {
-        // Before setting mVisibleRequested so we can track changes.
-        mTransitionController.collect(this);
+        if (mVisibleRequested != visible) {
+            // Before setting mVisibleRequested so we can track changes.
+            mTransitionController.collect(this);
 
-        setVisibleRequested(visible);
+            setVisibleRequested(visible);
+        }
 
         // If in a transition, defer commits for activities that are going invisible
         if (!visible && (mTransitionController.inTransition()
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 81b241e..44edaa2 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -48,7 +48,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
 import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
@@ -1338,8 +1337,7 @@
         outFrame.set(0, 0, mWindowFrames.mCompatFrame.width(), mWindowFrames.mCompatFrame.height());
     }
 
-    @Override
-    public WindowManager.LayoutParams getAttrs() {
+    WindowManager.LayoutParams getAttrs() {
         return mAttrs;
     }
 
@@ -2687,10 +2685,9 @@
         }
     }
 
-    int getSurfaceTouchableRegion(Region region, int flags) {
-        final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
+    void getSurfaceTouchableRegion(Region region, WindowManager.LayoutParams attrs) {
+        final boolean modal = attrs.isModal();
         if (modal) {
-            flags |= FLAG_NOT_TOUCH_MODAL;
             if (mActivityRecord != null) {
                 // Limit the outer touch to the activity root task region.
                 updateRegionForModalActivityWindow(region);
@@ -2722,8 +2719,6 @@
         if (mInvGlobalScale != 1.f) {
             region.scale(mInvGlobalScale);
         }
-
-        return flags;
     }
 
     /**
@@ -3559,10 +3554,9 @@
      * {@link WindowManager.LayoutParams#FLAG_NOT_TOUCH_MODAL touch modality.}
      */
     void getEffectiveTouchableRegion(Region outRegion) {
-        final boolean modal = (mAttrs.flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
         final DisplayContent dc = getDisplayContent();
 
-        if (modal && dc != null) {
+        if (mAttrs.isModal() && dc != null) {
             outRegion.set(dc.getBounds());
             cropRegionToRootTaskBoundsIfNeeded(outRegion);
             subtractTouchExcludeRegionIfNeeded(outRegion);
@@ -3835,6 +3829,24 @@
         return (mAttrs.insetsFlags.behavior & BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) != 0;
     }
 
+    boolean canBeHiddenByKeyguard() {
+        // Keyguard visibility of window from activities are determined over activity visibility.
+        if (mActivityRecord != null) {
+            return false;
+        }
+        switch (mAttrs.type) {
+            case TYPE_NOTIFICATION_SHADE:
+            case TYPE_STATUS_BAR:
+            case TYPE_NAVIGATION_BAR:
+            case TYPE_WALLPAPER:
+                return false;
+            default:
+                // Hide only windows below the keyguard host window.
+                return mPolicy.getWindowLayerLw(this)
+                        < mPolicy.getWindowLayerFromTypeLw(TYPE_NOTIFICATION_SHADE);
+        }
+    }
+
     private int getRootTaskId() {
         final Task rootTask = getRootTask();
         if (rootTask == null) {
@@ -4710,6 +4722,48 @@
         return false;
     }
 
+    private boolean shouldFinishAnimatingExit() {
+        // Exit animation might be applied soon.
+        if (inTransition()) {
+            ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "shouldWaitAnimatingExit: isTransition: %s",
+                    this);
+            return false;
+        }
+        if (!mDisplayContent.okToAnimate()) {
+            return true;
+        }
+        // Exit animation is running.
+        if (isAnimating(TRANSITION | PARENTS, EXIT_ANIMATING_TYPES)) {
+            ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "shouldWaitAnimatingExit: isAnimating: %s",
+                    this);
+            return false;
+        }
+        // If the wallpaper is currently behind this app window, we need to change both of
+        // them inside of a transaction to avoid artifacts.
+        if (mDisplayContent.mWallpaperController.isWallpaperTarget(this)) {
+            ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
+                    "shouldWaitAnimatingExit: isWallpaperTarget: %s", this);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * If this is window is stuck in the animatingExit status, resume clean up procedure blocked
+     * by the exit animation.
+     */
+    void cleanupAnimatingExitWindow() {
+        // TODO(b/205335975): WindowManagerService#tryStartExitingAnimation starts an exit animation
+        // and set #mAnimationExit. After the exit animation finishes, #onExitAnimationDone shall
+        // be called, but there seems to be a case that #onExitAnimationDone is not triggered, so
+        // a windows stuck in the animatingExit status.
+        if (mAnimatingExit && shouldFinishAnimatingExit()) {
+            ProtoLog.w(WM_DEBUG_APP_TRANSITIONS, "Clear window stuck on animatingExit status: %s",
+                    this);
+            onExitAnimationDone();
+        }
+    }
+
     void onExitAnimationDone() {
         if (DEBUG_ANIM) Slog.v(TAG, "onExitAnimationDone in " + this
                 + ": exiting=" + mAnimatingExit + " remove=" + mRemoveOnExit
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 0a55003..e4c8871 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -29,6 +29,8 @@
 #include <android/hardware/gnss/2.1/IGnssMeasurement.h>
 #include <android/hardware/gnss/BnGnss.h>
 #include <android/hardware/gnss/BnGnssCallback.h>
+#include <android/hardware/gnss/BnGnssGeofence.h>
+#include <android/hardware/gnss/BnGnssGeofenceCallback.h>
 #include <android/hardware/gnss/BnGnssMeasurementCallback.h>
 #include <android/hardware/gnss/BnGnssPowerIndicationCallback.h>
 #include <android/hardware/gnss/BnGnssPsdsCallback.h>
@@ -191,6 +193,9 @@
 using android::hardware::gnss::PsdsType;
 using IGnssAidl = android::hardware::gnss::IGnss;
 using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback;
+using IGnssBatchingAidl = android::hardware::gnss::IGnssBatching;
+using IGnssGeofenceAidl = android::hardware::gnss::IGnssGeofence;
+using IGnssGeofenceCallbackAidl = android::hardware::gnss::IGnssGeofenceCallback;
 using IGnssPsdsAidl = android::hardware::gnss::IGnssPsds;
 using IGnssPsdsCallbackAidl = android::hardware::gnss::IGnssPsdsCallback;
 using IGnssConfigurationAidl = android::hardware::gnss::IGnssConfiguration;
@@ -216,6 +221,8 @@
 sp<IGnss_V2_0> gnssHal_V2_0 = nullptr;
 sp<IGnss_V2_1> gnssHal_V2_1 = nullptr;
 sp<IGnssAidl> gnssHalAidl = nullptr;
+sp<IGnssBatchingAidl> gnssBatchingAidlIface = nullptr;
+sp<IGnssGeofenceAidl> gnssGeofenceAidlIface = nullptr;
 sp<IGnssPsdsAidl> gnssPsdsAidlIface = nullptr;
 sp<IGnssXtra> gnssXtraIface = nullptr;
 sp<IAGnssRil_V1_0> agnssRilIface = nullptr;
@@ -709,35 +716,25 @@
     return Void();
 }
 
-/*
- * GnssGeofenceCallback class implements the callback methods for the
- * IGnssGeofence interface.
- */
-struct GnssGeofenceCallback : public IGnssGeofenceCallback {
-    // Methods from ::android::hardware::gps::V1_0::IGnssGeofenceCallback follow.
-    Return<void> gnssGeofenceTransitionCb(
-            int32_t geofenceId,
-            const GnssLocation_V1_0& location,
-            GeofenceTransition transition,
-            hardware::gnss::V1_0::GnssUtcTime timestamp) override;
-    Return<void>
-    gnssGeofenceStatusCb(
-            GeofenceAvailability status,
-            const GnssLocation_V1_0& location) override;
-    Return<void> gnssGeofenceAddCb(int32_t geofenceId,
-                                   GeofenceStatus status) override;
-    Return<void> gnssGeofenceRemoveCb(int32_t geofenceId,
-                                      GeofenceStatus status) override;
-    Return<void> gnssGeofencePauseCb(int32_t geofenceId,
-                                     GeofenceStatus status) override;
-    Return<void> gnssGeofenceResumeCb(int32_t geofenceId,
-                                      GeofenceStatus status) override;
+/** Util class for GnssGeofenceCallback methods. */
+struct GnssGeofenceCallbackUtil {
+    template <class T>
+    static void gnssGeofenceTransitionCb(int geofenceId, const T& location, int transition,
+                                         int64_t timestampMillis);
+    template <class T>
+    static void gnssGeofenceStatusCb(int availability, const T& lastLocation);
+    static void gnssGeofenceAddCb(int geofenceId, int status);
+    static void gnssGeofenceRemoveCb(int geofenceId, int status);
+    static void gnssGeofencePauseCb(int geofenceId, int status);
+    static void gnssGeofenceResumeCb(int geofenceId, int status);
+
+private:
+    GnssGeofenceCallbackUtil() = delete;
 };
 
-Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb(
-        int32_t geofenceId, const GnssLocation_V1_0& location,
-        GeofenceTransition transition,
-        hardware::gnss::V1_0::GnssUtcTime timestamp) {
+template <class T>
+void GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(int geofenceId, const T& location,
+                                                        int transition, int64_t timestamp) {
     JNIEnv* env = getJniEnv();
 
     jobject jLocation = translateGnssLocation(env, location);
@@ -751,27 +748,22 @@
 
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     env->DeleteLocalRef(jLocation);
-    return Void();
 }
 
-Return<void>
-GnssGeofenceCallback::gnssGeofenceStatusCb(GeofenceAvailability status,
-                                           const GnssLocation_V1_0& location) {
+template <class T>
+void GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(int availability, const T& lastLocation) {
     JNIEnv* env = getJniEnv();
 
-    jobject jLocation = translateGnssLocation(env, location);
+    jobject jLocation = translateGnssLocation(env, lastLocation);
 
-    env->CallVoidMethod(mCallbacksObj, method_reportGeofenceStatus, status,
-                        jLocation);
+    env->CallVoidMethod(mCallbacksObj, method_reportGeofenceStatus, availability, jLocation);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     env->DeleteLocalRef(jLocation);
-    return Void();
 }
 
-Return<void> GnssGeofenceCallback::gnssGeofenceAddCb(int32_t geofenceId,
-                                                    GeofenceStatus status) {
+void GnssGeofenceCallbackUtil::gnssGeofenceAddCb(int geofenceId, int status) {
     JNIEnv* env = getJniEnv();
-    if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) {
+    if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
         ALOGE("%s: Error in adding a Geofence: %d\n", __func__, status);
     }
 
@@ -780,13 +772,11 @@
                         geofenceId,
                         status);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
 }
 
-Return<void> GnssGeofenceCallback::gnssGeofenceRemoveCb(int32_t geofenceId,
-                                                       GeofenceStatus status) {
+void GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(int geofenceId, int status) {
     JNIEnv* env = getJniEnv();
-    if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) {
+    if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
         ALOGE("%s: Error in removing a Geofence: %d\n", __func__, status);
     }
 
@@ -794,13 +784,11 @@
                         method_reportGeofenceRemoveStatus,
                         geofenceId, status);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
 }
 
-Return<void> GnssGeofenceCallback::gnssGeofencePauseCb(int32_t geofenceId,
-                                                      GeofenceStatus status) {
+void GnssGeofenceCallbackUtil::gnssGeofencePauseCb(int geofenceId, int status) {
     JNIEnv* env = getJniEnv();
-    if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) {
+    if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
         ALOGE("%s: Error in pausing Geofence: %d\n", __func__, status);
     }
 
@@ -808,13 +796,11 @@
                         method_reportGeofencePauseStatus,
                         geofenceId, status);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
 }
 
-Return<void> GnssGeofenceCallback::gnssGeofenceResumeCb(int32_t geofenceId,
-                                                       GeofenceStatus status) {
+void GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(int geofenceId, int status) {
     JNIEnv* env = getJniEnv();
-    if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) {
+    if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
         ALOGE("%s: Error in resuming Geofence: %d\n", __func__, status);
     }
 
@@ -822,6 +808,104 @@
                         method_reportGeofenceResumeStatus,
                         geofenceId, status);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+/*
+ * GnssGeofenceCallbackAidl class implements the callback methods for the IGnssGeofence AIDL
+ * interface.
+ */
+struct GnssGeofenceCallbackAidl : public android::hardware::gnss::BnGnssGeofenceCallback {
+    Status gnssGeofenceTransitionCb(int geofenceId, const GnssLocationAidl& location,
+                                    int transition, int64_t timestampMillis) override;
+    Status gnssGeofenceStatusCb(int availability, const GnssLocationAidl& lastLocation) override;
+    Status gnssGeofenceAddCb(int geofenceId, int status) override;
+    Status gnssGeofenceRemoveCb(int geofenceId, int status) override;
+    Status gnssGeofencePauseCb(int geofenceId, int status) override;
+    Status gnssGeofenceResumeCb(int geofenceId, int status) override;
+};
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceTransitionCb(int geofenceId,
+                                                          const GnssLocationAidl& location,
+                                                          int transition, int64_t timestampMillis) {
+    GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(geofenceId, location, transition,
+                                                       timestampMillis);
+    return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceStatusCb(int availability,
+                                                      const GnssLocationAidl& lastLocation) {
+    GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(availability, lastLocation);
+    return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceAddCb(int geofenceId, int status) {
+    GnssGeofenceCallbackUtil::gnssGeofenceAddCb(geofenceId, status);
+    return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceRemoveCb(int geofenceId, int status) {
+    GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(geofenceId, status);
+    return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofencePauseCb(int geofenceId, int status) {
+    GnssGeofenceCallbackUtil::gnssGeofencePauseCb(geofenceId, status);
+    return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceResumeCb(int geofenceId, int status) {
+    GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(geofenceId, status);
+    return Status::ok();
+}
+
+/*
+ * GnssGeofenceCallback class implements the callback methods for the
+ * IGnssGeofence HIDL interface.
+ */
+struct GnssGeofenceCallback : public IGnssGeofenceCallback {
+    // Methods from ::android::hardware::gps::V1_0::IGnssGeofenceCallback follow.
+    Return<void> gnssGeofenceTransitionCb(int32_t geofenceId, const GnssLocation_V1_0& location,
+                                          GeofenceTransition transition,
+                                          hardware::gnss::V1_0::GnssUtcTime timestamp) override;
+    Return<void> gnssGeofenceStatusCb(GeofenceAvailability status,
+                                      const GnssLocation_V1_0& location) override;
+    Return<void> gnssGeofenceAddCb(int32_t geofenceId, GeofenceStatus status) override;
+    Return<void> gnssGeofenceRemoveCb(int32_t geofenceId, GeofenceStatus status) override;
+    Return<void> gnssGeofencePauseCb(int32_t geofenceId, GeofenceStatus status) override;
+    Return<void> gnssGeofenceResumeCb(int32_t geofenceId, GeofenceStatus status) override;
+};
+
+Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb(
+        int32_t geofenceId, const GnssLocation_V1_0& location, GeofenceTransition transition,
+        hardware::gnss::V1_0::GnssUtcTime timestamp) {
+    GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(geofenceId, location, (int)transition,
+                                                       (int64_t)timestamp);
+    return Void();
+}
+
+Return<void> GnssGeofenceCallback::gnssGeofenceStatusCb(GeofenceAvailability availability,
+                                                        const GnssLocation_V1_0& location) {
+    GnssGeofenceCallbackUtil::gnssGeofenceStatusCb((int)availability, location);
+    return Void();
+}
+
+Return<void> GnssGeofenceCallback::gnssGeofenceAddCb(int32_t geofenceId, GeofenceStatus status) {
+    GnssGeofenceCallbackUtil::gnssGeofenceAddCb(geofenceId, (int)status);
+    return Void();
+}
+
+Return<void> GnssGeofenceCallback::gnssGeofenceRemoveCb(int32_t geofenceId, GeofenceStatus status) {
+    GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(geofenceId, (int)status);
+    return Void();
+}
+
+Return<void> GnssGeofenceCallback::gnssGeofencePauseCb(int32_t geofenceId, GeofenceStatus status) {
+    GnssGeofenceCallbackUtil::gnssGeofencePauseCb(geofenceId, (int)status);
+    return Void();
+}
+
+Return<void> GnssGeofenceCallback::gnssGeofenceResumeCb(int32_t geofenceId, GeofenceStatus status) {
+    GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(geofenceId, (int)status);
     return Void();
 }
 
@@ -1279,11 +1363,13 @@
         android_location_gnss_hal_GnssNative_set_gps_service_handle();
     }
 
-    if (gnssHal == nullptr) {
+    if (gnssHal == nullptr && gnssHalAidl == nullptr) {
         ALOGE("Unable to get GPS service\n");
         return;
     }
 
+    // TODO: linkToDeath for AIDL HAL
+
     gnssHalDeathRecipient = new GnssDeathRecipient();
     hardware::Return<bool> linked = gnssHal->linkToDeath(gnssHalDeathRecipient, /*cookie*/ 0);
     if (!linked.isOk()) {
@@ -1303,7 +1389,7 @@
         } else {
             ALOGD("Unable to get a handle to PSDS AIDL interface.");
         }
-    } else {
+    } else if (gnssHal != nullptr) {
         auto gnssXtra = gnssHal->getExtensionXtra();
         if (!gnssXtra.isOk()) {
             ALOGD("Unable to get a handle to Xtra");
@@ -1320,7 +1406,7 @@
             agnssRilIface_V2_0 = agnssRil_V2_0;
             agnssRilIface = agnssRilIface_V2_0;
         }
-    } else {
+    } else if (gnssHal != nullptr) {
         auto agnssRil_V1_0 = gnssHal->getExtensionAGnssRil();
         if (!agnssRil_V1_0.isOk()) {
             ALOGD("Unable to get a handle to AGnssRil");
@@ -1336,7 +1422,7 @@
         } else {
             agnssIface_V2_0 = agnss_V2_0;
         }
-    } else {
+    } else if (gnssHal != nullptr) {
         auto agnss_V1_0 = gnssHal->getExtensionAGnss();
         if (!agnss_V1_0.isOk()) {
             ALOGD("Unable to get a handle to AGnss");
@@ -1345,11 +1431,13 @@
         }
     }
 
-    auto gnssNavigationMessage = gnssHal->getExtensionGnssNavigationMessage();
-    if (!gnssNavigationMessage.isOk()) {
-        ALOGD("Unable to get a handle to GnssNavigationMessage");
-    } else {
-        gnssNavigationMessageIface = gnssNavigationMessage;
+    if (gnssHal != nullptr) {
+        auto gnssNavigationMessage = gnssHal->getExtensionGnssNavigationMessage();
+        if (!gnssNavigationMessage.isOk()) {
+            ALOGD("Unable to get a handle to GnssNavigationMessage");
+        } else {
+            gnssNavigationMessageIface = gnssNavigationMessage;
+        }
     }
 
     // Allow all causal combinations between IGnss.hal and IGnssMeasurement.hal. That means,
@@ -1387,12 +1475,12 @@
                     std::make_unique<android::gnss::GnssMeasurement_V1_1>(gnssMeasurement);
         }
     }
-    if (gnssMeasurementIface == nullptr) {
-         auto gnssMeasurement = gnssHal->getExtensionGnssMeasurement();
-         if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V1_0")) {
-             gnssMeasurementIface =
-                     std::make_unique<android::gnss::GnssMeasurement_V1_0>(gnssMeasurement);
-         }
+    if (gnssHal != nullptr && gnssMeasurementIface == nullptr) {
+        auto gnssMeasurement = gnssHal->getExtensionGnssMeasurement();
+        if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V1_0")) {
+            gnssMeasurementIface =
+                    std::make_unique<android::gnss::GnssMeasurement_V1_0>(gnssMeasurement);
+        }
     }
 
     if (gnssHal_V2_1 != nullptr) {
@@ -1434,7 +1522,7 @@
             gnssDebugIface = gnssDebugIface_V2_0;
         }
     }
-    if (gnssDebugIface == nullptr) {
+    if (gnssHal != nullptr && gnssDebugIface == nullptr) {
         auto gnssDebug = gnssHal->getExtensionGnssDebug();
         if (!gnssDebug.isOk()) {
             ALOGD("Unable to get a handle to GnssDebug");
@@ -1443,11 +1531,13 @@
         }
     }
 
-    auto gnssNi = gnssHal->getExtensionGnssNi();
-    if (!gnssNi.isOk()) {
-        ALOGD("Unable to get a handle to GnssNi");
-    } else {
-        gnssNiIface = gnssNi;
+    if (gnssHal != nullptr) {
+        auto gnssNi = gnssHal->getExtensionGnssNi();
+        if (!gnssNi.isOk()) {
+            ALOGD("Unable to get a handle to GnssNi");
+        } else {
+            gnssNiIface = gnssNi;
+        }
     }
 
     if (gnssHalAidl != nullptr) {
@@ -1488,11 +1578,17 @@
         }
     }
 
-    auto gnssGeofencing = gnssHal->getExtensionGnssGeofencing();
-    if (!gnssGeofencing.isOk()) {
-        ALOGD("Unable to get a handle to GnssGeofencing");
-    } else {
-        gnssGeofencingIface = gnssGeofencing;
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        sp<IGnssGeofenceAidl> gnssGeofenceAidl;
+        auto status = gnssHalAidl->getExtensionGnssGeofence(&gnssGeofenceAidl);
+        if (checkAidlStatus(status, "Unable to get a handle to GnssGeofence interface.")) {
+            gnssGeofenceAidlIface = gnssGeofenceAidl;
+        }
+    } else if (gnssHal != nullptr) {
+        auto gnssGeofencing = gnssHal->getExtensionGnssGeofencing();
+        if (checkHidlReturn(gnssGeofencing, "Unable to get a handle to GnssGeofencing")) {
+            gnssGeofencingIface = gnssGeofencing;
+        }
     }
 
     if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
@@ -1507,7 +1603,7 @@
             gnssBatchingIface = std::make_unique<gnss::GnssBatching_V2_0>(gnssBatching_V2_0);
         }
     }
-    if (gnssBatchingIface == nullptr) {
+    if (gnssHal != nullptr && gnssBatchingIface == nullptr) {
         auto gnssBatching_V1_0 = gnssHal->getExtensionGnssBatching();
         if (checkHidlReturn(gnssBatching_V1_0, "Unable to get a handle to GnssBatching")) {
             gnssBatchingIface = std::make_unique<gnss::GnssBatching_V1_0>(gnssBatching_V1_0);
@@ -1568,7 +1664,7 @@
     /*
      * Fail if the main interface fails to initialize
      */
-    if (gnssHal == nullptr) {
+    if (gnssHal == nullptr && gnssHalAidl == nullptr) {
         ALOGE("Unable to initialize GNSS HAL.");
         return JNI_FALSE;
     }
@@ -1583,7 +1679,7 @@
         result = gnssHal_V2_0->setCallback_2_0(gnssCbIface);
     } else if (gnssHal_V1_1 != nullptr) {
         result = gnssHal_V1_1->setCallback_1_1(gnssCbIface);
-    } else {
+    } else if (gnssHal != nullptr) {
         result = gnssHal->setCallback(gnssCbIface);
     }
 
@@ -1630,10 +1726,18 @@
     }
 
     // Set IGnssGeofencing.hal callback.
-    sp<IGnssGeofenceCallback> gnssGeofencingCbIface = new GnssGeofenceCallback();
-    if (gnssGeofencingIface != nullptr) {
+    if (gnssGeofenceAidlIface != nullptr) {
+        sp<IGnssGeofenceCallbackAidl> gnssGeofenceCallbackAidl = new GnssGeofenceCallbackAidl();
+        auto status = gnssGeofenceAidlIface->setCallback(gnssGeofenceCallbackAidl);
+        if (!checkAidlStatus(status, "IGnssGeofenceAidl setCallback() failed.")) {
+            gnssGeofenceAidlIface = nullptr;
+        }
+    } else if (gnssGeofencingIface != nullptr) {
+        sp<IGnssGeofenceCallback> gnssGeofencingCbIface = new GnssGeofenceCallback();
         auto status = gnssGeofencingIface->setCallback(gnssGeofencingCbIface);
-        checkHidlReturn(status, "IGnssGeofencing setCallback() failed.");
+        if (!checkHidlReturn(status, "IGnssGeofencing setCallback() failed.")) {
+            gnssGeofencingIface = nullptr;
+        }
     } else {
         ALOGI("Unable to initialize IGnssGeofencing interface.");
     }
@@ -1693,12 +1797,15 @@
 }
 
 static void android_location_gnss_hal_GnssNative_cleanup(JNIEnv* /* env */, jclass) {
-    if (gnssHal == nullptr) {
-        return;
+    if (gnssHalAidl != nullptr) {
+        auto status = gnssHalAidl->close();
+        checkAidlStatus(status, "IGnssAidl close() failed.");
     }
 
-    auto result = gnssHal->cleanup();
-    checkHidlReturn(result, "IGnss cleanup() failed.");
+    if (gnssHal != nullptr) {
+        auto result = gnssHal->cleanup();
+        checkHidlReturn(result, "IGnss cleanup() failed.");
+    }
 }
 
 static jboolean android_location_gnss_hal_GnssNative_set_position_mode(
@@ -2177,57 +2284,85 @@
 
 static jboolean android_location_gnss_hal_GnssNative_is_geofence_supported(JNIEnv* /* env */,
                                                                            jclass) {
-    return (gnssGeofencingIface != nullptr) ? JNI_TRUE : JNI_FALSE;
+    if (gnssGeofencingIface == nullptr && gnssGeofenceAidlIface == nullptr) {
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
 }
 
 static jboolean android_location_gnss_hal_GnssNative_add_geofence(
         JNIEnv* /* env */, jclass, jint geofenceId, jdouble latitude, jdouble longitude,
         jdouble radius, jint last_transition, jint monitor_transition,
         jint notification_responsiveness, jint unknown_timer) {
-    if (gnssGeofencingIface == nullptr) {
-        ALOGE("%s: IGnssGeofencing interface not available.", __func__);
-        return JNI_FALSE;
+    if (gnssGeofenceAidlIface != nullptr) {
+        auto status =
+                gnssGeofenceAidlIface->addGeofence(geofenceId, latitude, longitude, radius,
+                                                   last_transition, monitor_transition,
+                                                   notification_responsiveness, unknown_timer);
+        return checkAidlStatus(status, "IGnssGeofenceAidl addGeofence() failed.");
     }
 
-    auto result = gnssGeofencingIface->addGeofence(
-            geofenceId, latitude, longitude, radius,
-            static_cast<IGnssGeofenceCallback::GeofenceTransition>(last_transition),
-            monitor_transition, notification_responsiveness, unknown_timer);
-    return checkHidlReturn(result, "IGnssGeofencing addGeofence() failed.");
+    if (gnssGeofencingIface != nullptr) {
+        auto result = gnssGeofencingIface
+                              ->addGeofence(geofenceId, latitude, longitude, radius,
+                                            static_cast<IGnssGeofenceCallback::GeofenceTransition>(
+                                                    last_transition),
+                                            monitor_transition, notification_responsiveness,
+                                            unknown_timer);
+        return checkHidlReturn(result, "IGnssGeofencing addGeofence() failed.");
+    }
+
+    ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+    return JNI_FALSE;
 }
 
 static jboolean android_location_gnss_hal_GnssNative_remove_geofence(JNIEnv* /* env */, jclass,
                                                                      jint geofenceId) {
-    if (gnssGeofencingIface == nullptr) {
-        ALOGE("%s: IGnssGeofencing interface not available.", __func__);
-        return JNI_FALSE;
+    if (gnssGeofenceAidlIface != nullptr) {
+        auto status = gnssGeofenceAidlIface->removeGeofence(geofenceId);
+        return checkAidlStatus(status, "IGnssGeofenceAidl removeGeofence() failed.");
     }
 
-    auto result = gnssGeofencingIface->removeGeofence(geofenceId);
-    return checkHidlReturn(result, "IGnssGeofencing removeGeofence() failed.");
+    if (gnssGeofencingIface != nullptr) {
+        auto result = gnssGeofencingIface->removeGeofence(geofenceId);
+        return checkHidlReturn(result, "IGnssGeofencing removeGeofence() failed.");
+    }
+
+    ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+    return JNI_FALSE;
 }
 
 static jboolean android_location_gnss_hal_GnssNative_pause_geofence(JNIEnv* /* env */, jclass,
                                                                     jint geofenceId) {
-    if (gnssGeofencingIface == nullptr) {
-        ALOGE("%s: IGnssGeofencing interface not available.", __func__);
-        return JNI_FALSE;
+    if (gnssGeofenceAidlIface != nullptr) {
+        auto status = gnssGeofenceAidlIface->pauseGeofence(geofenceId);
+        return checkAidlStatus(status, "IGnssGeofenceAidl pauseGeofence() failed.");
     }
 
-    auto result = gnssGeofencingIface->pauseGeofence(geofenceId);
-    return checkHidlReturn(result, "IGnssGeofencing pauseGeofence() failed.");
+    if (gnssGeofencingIface != nullptr) {
+        auto result = gnssGeofencingIface->pauseGeofence(geofenceId);
+        return checkHidlReturn(result, "IGnssGeofencing pauseGeofence() failed.");
+    }
+
+    ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+    return JNI_FALSE;
 }
 
 static jboolean android_location_gnss_hal_GnssNative_resume_geofence(JNIEnv* /* env */, jclass,
                                                                      jint geofenceId,
                                                                      jint monitor_transition) {
-    if (gnssGeofencingIface == nullptr) {
-        ALOGE("%s: IGnssGeofencing interface not available.", __func__);
-        return JNI_FALSE;
+    if (gnssGeofenceAidlIface != nullptr) {
+        auto status = gnssGeofenceAidlIface->resumeGeofence(geofenceId, monitor_transition);
+        return checkAidlStatus(status, "IGnssGeofenceAidl resumeGeofence() failed.");
     }
 
-    auto result = gnssGeofencingIface->resumeGeofence(geofenceId, monitor_transition);
-    return checkHidlReturn(result, "IGnssGeofencing resumeGeofence() failed.");
+    if (gnssGeofencingIface != nullptr) {
+        auto result = gnssGeofencingIface->resumeGeofence(geofenceId, monitor_transition);
+        return checkHidlReturn(result, "IGnssGeofencing resumeGeofence() failed.");
+    }
+
+    ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+    return JNI_FALSE;
 }
 
 static jboolean android_location_gnss_hal_GnssNative_is_antenna_info_supported(JNIEnv* env,
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 429edf1..2f4dd57 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -26,6 +26,10 @@
     <xs:element name="displayConfiguration">
         <xs:complexType>
             <xs:sequence>
+                <xs:element type="densityMap" name="densityMap" minOccurs="0" maxOccurs="1">
+                    <xs:annotation name="nullable"/>
+                    <xs:annotation name="final"/>
+                </xs:element>
                 <xs:element type="nitsMap" name="screenBrightnessMap">
                     <xs:annotation name="nonnull"/>
                     <xs:annotation name="final"/>
@@ -181,5 +185,27 @@
         </xs:sequence>
     </xs:complexType>
 
+    <xs:complexType name="densityMap">
+        <xs:sequence>
+            <xs:element name="density" type="density" maxOccurs="unbounded" minOccurs="1">
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
 
+    <xs:complexType name="density">
+        <xs:sequence>
+            <xs:element type="xs:nonNegativeInteger" name="width">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+            <xs:element type="xs:nonNegativeInteger" name="height">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+            <xs:element type="xs:nonNegativeInteger" name="density">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
 </xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index ad18602..5b2b87c 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -1,8 +1,24 @@
 // Signature format: 2.0
 package com.android.server.display.config {
 
+  public class Density {
+    ctor public Density();
+    method @NonNull public final java.math.BigInteger getDensity();
+    method @NonNull public final java.math.BigInteger getHeight();
+    method @NonNull public final java.math.BigInteger getWidth();
+    method public final void setDensity(@NonNull java.math.BigInteger);
+    method public final void setHeight(@NonNull java.math.BigInteger);
+    method public final void setWidth(@NonNull java.math.BigInteger);
+  }
+
+  public class DensityMap {
+    ctor public DensityMap();
+    method public java.util.List<com.android.server.display.config.Density> getDensity();
+  }
+
   public class DisplayConfiguration {
     ctor public DisplayConfiguration();
+    method @Nullable public final com.android.server.display.config.DensityMap getDensityMap();
     method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
     method public final com.android.server.display.config.SensorDetails getLightSensor();
     method public final com.android.server.display.config.SensorDetails getProxSensor();
@@ -13,6 +29,7 @@
     method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease();
     method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease();
     method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease();
+    method public final void setDensityMap(@Nullable com.android.server.display.config.DensityMap);
     method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
     method public final void setLightSensor(com.android.server.display.config.SensorDetails);
     method public final void setProxSensor(com.android.server.display.config.SensorDetails);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3839a9f..efac4d5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10587,7 +10587,12 @@
                         return null;
                     }
                 }
-                if (!mUserManager.canAddMoreUsers()) {
+
+                String userType = demo ? UserManager.USER_TYPE_FULL_DEMO
+                        : UserManager.USER_TYPE_FULL_SECONDARY;
+                int userInfoFlags = ephemeral ? UserInfo.FLAG_EPHEMERAL : 0;
+
+                if (!mUserManager.canAddMoreUsers(userType)) {
                     if (targetSdkVersion >= Build.VERSION_CODES.P) {
                         throw new ServiceSpecificException(
                                 UserManager.USER_OPERATION_ERROR_MAX_USERS, "user limit reached");
@@ -10596,9 +10601,6 @@
                     }
                 }
 
-                int userInfoFlags = ephemeral ? UserInfo.FLAG_EPHEMERAL : 0;
-                String userType = demo ? UserManager.USER_TYPE_FULL_DEMO
-                        : UserManager.USER_TYPE_FULL_SECONDARY;
                 String[] disallowedPackages = null;
                 if (!leaveAllSystemAppsEnabled) {
                     disallowedPackages = mOverlayPackagesProvider.getNonRequiredApps(admin,
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2f9e334..3ede408 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -398,6 +398,8 @@
     private static final String UWB_APEX_SERVICE_JAR_PATH =
             "/apex/com.android.uwb/javalib/service-uwb.jar";
     private static final String UWB_SERVICE_CLASS = "com.android.server.uwb.UwbService";
+    private static final String SAFETY_CENTER_SERVICE_CLASS =
+            "com.android.safetycenter.SafetyCenterService";
 
     private static final String SUPPLEMENTALPROCESS_APEX_PATH =
             "/apex/com.android.supplementalprocess/javalib/service-supplementalprocess.jar";
@@ -2716,6 +2718,10 @@
         mSystemServiceManager.startBootPhase(t, SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY);
         t.traceEnd();
 
+        t.traceBegin("StartSafetyCenterService");
+        mSystemServiceManager.startService(SAFETY_CENTER_SERVICE_CLASS);
+        t.traceEnd();
+
         t.traceBegin("AppSearchManagerService");
         mSystemServiceManager.startService(APP_SEARCH_MANAGER_SERVICE_CLASS);
         t.traceEnd();
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java
new file mode 100644
index 0000000..3f69f1b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.display;
+
+import static com.android.server.display.DensityMap.Entry;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DensityMapTest {
+
+    @Test
+    public void testConstructor_withBadConfig_throwsException() {
+        assertThrows(IllegalStateException.class, () ->
+                DensityMap.createByOwning(new Entry[]{
+                        new Entry(1080, 1920, 320),
+                        new Entry(1080, 1920, 320)})
+        );
+
+        assertThrows(IllegalStateException.class, () ->
+                DensityMap.createByOwning(new Entry[]{
+                        new Entry(1080, 1920, 320),
+                        new Entry(1920, 1080, 120)})
+        );
+
+        assertThrows(IllegalStateException.class, () ->
+                DensityMap.createByOwning(new Entry[]{
+                        new Entry(1080, 1920, 320),
+                        new Entry(2160, 3840, 120)})
+        );
+
+        assertThrows(IllegalStateException.class, () ->
+                DensityMap.createByOwning(new Entry[]{
+                        new Entry(1080, 1920, 320),
+                        new Entry(3840, 2160, 120)})
+        );
+
+        // Two entries with the same diagonal
+        assertThrows(IllegalStateException.class, () ->
+                DensityMap.createByOwning(new Entry[]{
+                        new Entry(500, 500, 123),
+                        new Entry(100, 700, 456)})
+        );
+    }
+
+    @Test
+    public void testGetDensityForResolution_withResolutionMatch_returnsDensityFromConfig() {
+        DensityMap densityMap = DensityMap.createByOwning(new Entry[]{
+                new Entry(720, 1280, 213),
+                new Entry(1080, 1920, 320),
+                new Entry(2160, 3840, 640)});
+
+        assertEquals(213, densityMap.getDensityForResolution(720, 1280));
+        assertEquals(213, densityMap.getDensityForResolution(1280, 720));
+
+        assertEquals(320, densityMap.getDensityForResolution(1080, 1920));
+        assertEquals(320, densityMap.getDensityForResolution(1920, 1080));
+
+        assertEquals(640, densityMap.getDensityForResolution(2160, 3840));
+        assertEquals(640, densityMap.getDensityForResolution(3840, 2160));
+    }
+
+    @Test
+    public void testGetDensityForResolution_withDiagonalMatch_returnsDensityFromConfig() {
+        DensityMap densityMap = DensityMap.createByOwning(
+                        new Entry[]{ new Entry(500, 500, 123)});
+
+        // 500x500 has the same diagonal as 100x700
+        assertEquals(123, densityMap.getDensityForResolution(100, 700));
+    }
+
+    @Test
+    public void testGetDensityForResolution_withOneEntry_withNoMatch_returnsExtrapolatedDensity() {
+        DensityMap densityMap = DensityMap.createByOwning(
+                new Entry[]{ new Entry(1080, 1920, 320)});
+
+        assertEquals(320, densityMap.getDensityForResolution(1081, 1920));
+        assertEquals(320, densityMap.getDensityForResolution(1080, 1921));
+
+        assertEquals(640, densityMap.getDensityForResolution(2160, 3840));
+        assertEquals(640, densityMap.getDensityForResolution(3840, 2160));
+
+        assertEquals(213, densityMap.getDensityForResolution(720, 1280));
+        assertEquals(213, densityMap.getDensityForResolution(1280, 720));
+    }
+
+    @Test
+    public void testGetDensityForResolution_withTwoEntries_withNoMatch_returnExtrapolatedDensity() {
+        DensityMap densityMap = DensityMap.createByOwning(new Entry[]{
+                new Entry(1080, 1920, 320),
+                new Entry(2160, 3840, 320)});
+
+        // Resolution is smaller than all entries
+        assertEquals(213, densityMap.getDensityForResolution(720, 1280));
+        assertEquals(213, densityMap.getDensityForResolution(1280, 720));
+
+        // Resolution is bigger than all entries
+        assertEquals(320 * 2, densityMap.getDensityForResolution(2160 * 2, 3840 * 2));
+        assertEquals(320 * 2, densityMap.getDensityForResolution(3840 * 2, 2160 * 2));
+    }
+
+    @Test
+    public void testGetDensityForResolution_withNoMatch_returnsInterpolatedDensity() {
+        {
+            DensityMap densityMap = DensityMap.createByOwning(new Entry[]{
+                    new Entry(1080, 1920, 320),
+                    new Entry(2160, 3840, 320)});
+
+            assertEquals(320, densityMap.getDensityForResolution(2000, 2000));
+        }
+
+        {
+            DensityMap densityMap = DensityMap.createByOwning(new Entry[]{
+                    new Entry(720, 1280, 213),
+                    new Entry(2160, 3840, 640)});
+
+            assertEquals(320, densityMap.getDensityForResolution(1080, 1920));
+            assertEquals(320, densityMap.getDensityForResolution(1920, 1080));
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java b/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java
index 3ace3f4..a1d4c20 100644
--- a/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java
+++ b/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java
@@ -66,7 +66,7 @@
         when(mHelper.isBluetoothOn()).thenReturn(true);
         Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange());
 
-        when(mHelper.isA2dpOrHearingAidConnected()).thenReturn(true);
+        when(mHelper.isMediaProfileConnected()).thenReturn(true);
         Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange());
 
         when(mHelper.isAirplaneModeOn()).thenReturn(true);
@@ -83,7 +83,7 @@
     public void testHandleAirplaneModeChange_NotInvokeAirplaneModeChanged_NotPopToast() {
         mBluetoothAirplaneModeListener.mToastCount = BluetoothAirplaneModeListener.MAX_TOAST_COUNT;
         when(mHelper.isBluetoothOn()).thenReturn(true);
-        when(mHelper.isA2dpOrHearingAidConnected()).thenReturn(true);
+        when(mHelper.isMediaProfileConnected()).thenReturn(true);
         when(mHelper.isAirplaneModeOn()).thenReturn(true);
         mBluetoothAirplaneModeListener.handleAirplaneModeChange();
 
@@ -97,7 +97,7 @@
     public void testHandleAirplaneModeChange_NotInvokeAirplaneModeChanged_PopToast() {
         mBluetoothAirplaneModeListener.mToastCount = 0;
         when(mHelper.isBluetoothOn()).thenReturn(true);
-        when(mHelper.isA2dpOrHearingAidConnected()).thenReturn(true);
+        when(mHelper.isMediaProfileConnected()).thenReturn(true);
         when(mHelper.isAirplaneModeOn()).thenReturn(true);
         mBluetoothAirplaneModeListener.handleAirplaneModeChange();
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 086e3c0..a83d51b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -68,6 +68,7 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
+import android.accessibilityservice.MagnificationConfig;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -650,8 +651,12 @@
         final float scale = 1.8f;
         final float centerX = 50.5f;
         final float centerY = 100.5f;
-        when(mMockMagnificationProcessor.setScaleAndCenter(displayId,
-                scale, centerX, centerY, true, SERVICE_ID)).thenReturn(true);
+        MagnificationConfig config = new MagnificationConfig.Builder()
+                .setScale(scale)
+                .setCenterX(centerX)
+                .setCenterY(centerY).build();
+        when(mMockMagnificationProcessor.setMagnificationConfig(displayId, config, true,
+                SERVICE_ID)).thenReturn(true);
         when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(false);
 
         final boolean result = mServiceConnection.setMagnificationScaleAndCenter(
@@ -665,8 +670,12 @@
         final float scale = 1.8f;
         final float centerX = 50.5f;
         final float centerY = 100.5f;
-        when(mMockMagnificationProcessor.setScaleAndCenter(displayId,
-                scale, centerX, centerY, true, SERVICE_ID)).thenReturn(true);
+        MagnificationConfig config = new MagnificationConfig.Builder()
+                .setScale(scale)
+                .setCenterX(centerX)
+                .setCenterY(centerY).build();
+        when(mMockMagnificationProcessor.setMagnificationConfig(displayId, config, true,
+                SERVICE_ID)).thenReturn(true);
         when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
 
         final boolean result = mServiceConnection.setMagnificationScaleAndCenter(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
index c412b94..2fd2816 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
@@ -16,25 +16,32 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.MagnificationConfig.FULLSCREEN_MODE;
+import static android.accessibilityservice.MagnificationConfig.WINDOW_MODE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.accessibilityservice.MagnificationConfig;
 import android.graphics.Region;
 
 import com.android.server.accessibility.magnification.FullScreenMagnificationController;
 import com.android.server.accessibility.magnification.MagnificationController;
 import com.android.server.accessibility.magnification.MagnificationProcessor;
+import com.android.server.accessibility.magnification.WindowMagnificationManager;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-
+import org.mockito.stubbing.Answer;
 
 /**
  * Tests for the {@link MagnificationProcessor}
@@ -42,57 +49,115 @@
 public class MagnificationProcessorTest {
 
     private static final int TEST_DISPLAY = 0;
-
+    private static final int SERVICE_ID = 42;
+    private static final float TEST_SCALE = 1.8f;
+    private static final float TEST_CENTER_X = 50.5f;
+    private static final float TEST_CENTER_Y = 100.5f;
     private MagnificationProcessor mMagnificationProcessor;
     @Mock
     private MagnificationController mMockMagnificationController;
     @Mock
     private FullScreenMagnificationController mMockFullScreenMagnificationController;
+    @Mock
+    private WindowMagnificationManager mMockWindowMagnificationManager;
+    FullScreenMagnificationControllerStub mFullScreenMagnificationControllerStub;
+    WindowMagnificationManagerStub mWindowMagnificationManagerStub;
 
     @Before
+
     public void setup() {
         MockitoAnnotations.initMocks(this);
-
+        mFullScreenMagnificationControllerStub = new FullScreenMagnificationControllerStub(
+                mMockFullScreenMagnificationController);
+        mWindowMagnificationManagerStub = new WindowMagnificationManagerStub(
+                mMockWindowMagnificationManager);
         when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn(
                 mMockFullScreenMagnificationController);
+        when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn(
+                mMockWindowMagnificationManager);
         mMagnificationProcessor = new MagnificationProcessor(mMockMagnificationController);
     }
 
     @Test
-    public void getScale() {
-        final float result = 2;
-        when(mMockFullScreenMagnificationController.getScale(TEST_DISPLAY)).thenReturn(result);
+    public void getScale_fullscreenMode_expectedValue() {
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(FULLSCREEN_MODE)
+                .setScale(TEST_SCALE).build();
+        setMagnificationActivated(TEST_DISPLAY, config);
 
         float scale = mMagnificationProcessor.getScale(TEST_DISPLAY);
 
-        assertEquals(scale, result, 0);
+        assertEquals(scale, TEST_SCALE, 0);
     }
 
     @Test
-    public void getCenterX_canControlMagnification_returnCenterX() {
-        final float result = 200;
-        when(mMockFullScreenMagnificationController.getCenterX(TEST_DISPLAY)).thenReturn(result);
+    public void getScale_windowMode_expectedValue() {
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(WINDOW_MODE)
+                .setScale(TEST_SCALE).build();
+        setMagnificationActivated(TEST_DISPLAY, config);
+
+        float scale = mMagnificationProcessor.getScale(TEST_DISPLAY);
+
+        assertEquals(scale, TEST_SCALE, 0);
+    }
+
+    @Test
+    public void getCenterX_canControlFullscreenMagnification_returnCenterX() {
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(FULLSCREEN_MODE)
+                .setCenterX(TEST_CENTER_X).build();
+        setMagnificationActivated(TEST_DISPLAY, config);
 
         float centerX = mMagnificationProcessor.getCenterX(
                 TEST_DISPLAY,  /* canControlMagnification= */true);
 
-        assertEquals(centerX, result, 0);
+        assertEquals(centerX, TEST_CENTER_X, 0);
     }
 
     @Test
-    public void getCenterY_canControlMagnification_returnCenterY() {
-        final float result = 300;
-        when(mMockFullScreenMagnificationController.getCenterY(TEST_DISPLAY)).thenReturn(result);
+    public void getCenterX_canControlWindowMagnification_returnCenterX() {
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(WINDOW_MODE)
+                .setCenterX(TEST_CENTER_X).build();
+        setMagnificationActivated(TEST_DISPLAY, config);
+
+        float centerX = mMagnificationProcessor.getCenterX(
+                TEST_DISPLAY,  /* canControlMagnification= */true);
+
+        assertEquals(centerX, TEST_CENTER_X, 0);
+    }
+
+    @Test
+    public void getCenterY_canControlFullscreenMagnification_returnCenterY() {
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(FULLSCREEN_MODE)
+                .setCenterY(TEST_CENTER_Y).build();
+        setMagnificationActivated(TEST_DISPLAY, config);
 
         float centerY = mMagnificationProcessor.getCenterY(
                 TEST_DISPLAY,  /* canControlMagnification= */false);
 
-        assertEquals(centerY, result, 0);
+        assertEquals(centerY, TEST_CENTER_Y, 0);
     }
 
     @Test
-    public void getMagnificationRegion_canControlMagnification_returnRegion() {
+    public void getCenterY_canControlWindowMagnification_returnCenterY() {
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(WINDOW_MODE)
+                .setCenterY(TEST_CENTER_Y).build();
+        setMagnificationActivated(TEST_DISPLAY, config);
+
+        float centerY = mMagnificationProcessor.getCenterY(
+                TEST_DISPLAY,  /* canControlMagnification= */false);
+
+        assertEquals(centerY, TEST_CENTER_Y, 0);
+    }
+
+    @Test
+    public void getMagnificationRegion_canControlFullscreenMagnification_returnRegion() {
         final Region region = new Region(10, 20, 100, 200);
+        setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE);
         mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
                 region,  /* canControlMagnification= */true);
 
@@ -101,14 +166,25 @@
     }
 
     @Test
-    public void getMagnificationRegion_notRegistered_shouldRegisterThenUnregister() {
+    public void getMagnificationRegion_canControlWindowMagnification_returnRegion() {
         final Region region = new Region(10, 20, 100, 200);
+        setMagnificationActivated(TEST_DISPLAY, WINDOW_MODE);
+        mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
+                region,  /* canControlMagnification= */true);
+
+        verify(mMockWindowMagnificationManager).getMagnificationSourceBounds(eq(TEST_DISPLAY),
+                eq(region));
+    }
+
+    @Test
+    public void getMagnificationRegion_fullscreenModeNotRegistered_shouldRegisterThenUnregister() {
+        final Region region = new Region(10, 20, 100, 200);
+        setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE);
         doAnswer((invocation) -> {
             ((Region) invocation.getArguments()[1]).set(region);
             return null;
         }).when(mMockFullScreenMagnificationController).getMagnificationRegion(eq(TEST_DISPLAY),
                 any());
-        when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false);
 
         final Region result = new Region();
         mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
@@ -119,44 +195,242 @@
     }
 
     @Test
-    public void getMagnificationCenterX_notRegistered_shouldRegisterThenUnregister() {
-        final float centerX = 480.0f;
-        when(mMockFullScreenMagnificationController.getCenterX(TEST_DISPLAY)).thenReturn(centerX);
-        when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false);
+    public void getMagnificationCenterX_fullscreenModeNotRegistered_shouldRegisterThenUnregister() {
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(FULLSCREEN_MODE)
+                .setCenterX(TEST_CENTER_X).build();
+        setMagnificationActivated(TEST_DISPLAY, config);
 
         final float result = mMagnificationProcessor.getCenterX(
                 TEST_DISPLAY,  /* canControlMagnification= */ true);
-        assertEquals(centerX, result, 0);
+        assertEquals(TEST_CENTER_X, result, 0);
         verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY);
         verify(mMockFullScreenMagnificationController).unregister(TEST_DISPLAY);
     }
 
     @Test
-    public void getMagnificationCenterY_notRegistered_shouldRegisterThenUnregister() {
-        final float centerY = 640.0f;
-        when(mMockFullScreenMagnificationController.getCenterY(TEST_DISPLAY)).thenReturn(centerY);
-        when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false);
+    public void getMagnificationCenterY_fullscreenModeNotRegistered_shouldRegisterThenUnregister() {
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(FULLSCREEN_MODE)
+                .setCenterY(TEST_CENTER_Y).build();
+        setMagnificationActivated(TEST_DISPLAY, config);
 
         final float result = mMagnificationProcessor.getCenterY(
                 TEST_DISPLAY,  /* canControlMagnification= */ true);
-        assertEquals(centerY, result, 0);
+        assertEquals(TEST_CENTER_Y, result, 0);
         verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY);
         verify(mMockFullScreenMagnificationController).unregister(TEST_DISPLAY);
     }
 
     @Test
-    public void setMagnificationScaleAndCenter_notRegistered_shouldRegister() {
-        final int serviceId = 42;
-        final float scale = 1.8f;
-        final float centerX = 50.5f;
-        final float centerY = 100.5f;
-        when(mMockFullScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
-                scale, centerX, centerY, true, serviceId)).thenReturn(true);
-        when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false);
+    public void getCurrentMode_configDefaultMode_returnActivatedMode() {
+        final int targetMode = WINDOW_MODE;
+        setMagnificationActivated(TEST_DISPLAY, targetMode);
 
-        final boolean result = mMagnificationProcessor.setScaleAndCenter(
-                TEST_DISPLAY, scale, centerX, centerY, true, serviceId);
+        int currentMode = mMagnificationProcessor.getControllingMode(TEST_DISPLAY);
+
+        assertEquals(WINDOW_MODE, currentMode);
+    }
+
+    @Test
+    public void reset_fullscreenMagnificationActivated() {
+        setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE);
+
+        mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false);
+
+        verify(mMockFullScreenMagnificationController).reset(TEST_DISPLAY, false);
+    }
+
+    @Test
+    public void reset_windowMagnificationActivated() {
+        setMagnificationActivated(TEST_DISPLAY, WINDOW_MODE);
+
+        mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false);
+
+        verify(mMockWindowMagnificationManager).reset(TEST_DISPLAY);
+    }
+
+    @Test
+    public void setMagnificationConfig_fullscreenModeNotRegistered_shouldRegister() {
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(FULLSCREEN_MODE)
+                .setScale(TEST_SCALE)
+                .setCenterX(TEST_CENTER_X)
+                .setCenterY(TEST_CENTER_Y).build();
+        setMagnificationActivated(TEST_DISPLAY, config);
+
+        final boolean result = mMagnificationProcessor.setMagnificationConfig(
+                TEST_DISPLAY, config, true, SERVICE_ID);
         assertTrue(result);
         verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY);
     }
+
+    @Test
+    public void setMagnificationConfig_windowMode_enableMagnification() {
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(WINDOW_MODE)
+                .setScale(TEST_SCALE)
+                .setCenterX(TEST_CENTER_X)
+                .setCenterY(TEST_CENTER_Y).build();
+        setMagnificationActivated(TEST_DISPLAY, config);
+
+        final boolean result = mMagnificationProcessor.setMagnificationConfig(
+                TEST_DISPLAY, config, true, SERVICE_ID);
+
+        assertTrue(result);
+    }
+
+    @Test
+    public void setMagnificationConfig_fullscreenEnabled_expectedConfigValues() {
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(FULLSCREEN_MODE)
+                .setScale(TEST_SCALE)
+                .setCenterX(TEST_CENTER_X)
+                .setCenterY(TEST_CENTER_Y).build();
+        setMagnificationActivated(TEST_DISPLAY, config);
+     //   mMockFullScreenMagnificationController.unregister(TEST_DISPLAY);
+        mMagnificationProcessor.setMagnificationConfig(
+                TEST_DISPLAY, config, true, SERVICE_ID);
+
+        final MagnificationConfig result = mMagnificationProcessor.getMagnificationConfig(
+                TEST_DISPLAY);
+
+        assertConfigEquals(config, result);
+    }
+
+    @Test
+    public void setMagnificationConfig_windowEnabled_expectedConfigValues() {
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(WINDOW_MODE)
+                .setScale(TEST_SCALE)
+                .setCenterX(TEST_CENTER_X)
+                .setCenterY(TEST_CENTER_Y).build();
+        setMagnificationActivated(TEST_DISPLAY, config);
+
+        mMagnificationProcessor.setMagnificationConfig(
+                TEST_DISPLAY, config, true, SERVICE_ID);
+
+        final MagnificationConfig result = mMagnificationProcessor.getMagnificationConfig(
+                TEST_DISPLAY);
+
+        assertConfigEquals(config, result);
+    }
+
+    private void setMagnificationActivated(int displayId, int configMode) {
+        setMagnificationActivated(displayId,
+                new MagnificationConfig.Builder().setMode(configMode).build());
+    }
+
+    private void setMagnificationActivated(int displayId, MagnificationConfig config) {
+        when(mMockMagnificationController.isActivated(displayId, config.getMode())).thenReturn(
+                true);
+        mMagnificationProcessor.setMagnificationConfig(displayId, config, false, SERVICE_ID);
+        if (config.getMode() == FULLSCREEN_MODE) {
+            when(mMockMagnificationController.isActivated(displayId, WINDOW_MODE)).thenReturn(
+                    false);
+            mFullScreenMagnificationControllerStub.resetAndStubMethods();
+            mMockFullScreenMagnificationController.setScaleAndCenter(displayId, config.getScale(),
+                    config.getCenterX(), config.getCenterY(), true, SERVICE_ID);
+        } else if (config.getMode() == WINDOW_MODE) {
+            when(mMockMagnificationController.isActivated(displayId, FULLSCREEN_MODE)).thenReturn(
+                    false);
+            mWindowMagnificationManagerStub.resetAndStubMethods();
+            mMockWindowMagnificationManager.enableWindowMagnification(displayId, config.getScale(),
+                    config.getCenterX(), config.getCenterY());
+        }
+    }
+
+    private void assertConfigEquals(MagnificationConfig expected, MagnificationConfig actual) {
+        assertEquals(expected.getMode(), actual.getMode());
+        assertEquals(expected.getScale(), actual.getScale(), 0);
+        assertEquals(expected.getCenterX(), actual.getCenterX(), 0);
+        assertEquals(expected.getCenterY(), actual.getCenterY(), 0);
+    }
+
+    private static class FullScreenMagnificationControllerStub {
+        private final FullScreenMagnificationController mScreenMagnificationController;
+        private float mScale = 1.0f;
+        private float mCenterX = 0;
+        private float mCenterY = 0;
+        private boolean mIsRegistered = false;
+
+        FullScreenMagnificationControllerStub(
+                FullScreenMagnificationController screenMagnificationController) {
+            mScreenMagnificationController = screenMagnificationController;
+        }
+
+        private void stubMethods() {
+            doAnswer(invocation -> mScale).when(mScreenMagnificationController).getScale(
+                    TEST_DISPLAY);
+            doAnswer(invocation -> mCenterX).when(mScreenMagnificationController).getCenterX(
+                    TEST_DISPLAY);
+            doAnswer(invocation -> mCenterY).when(mScreenMagnificationController).getCenterY(
+                    TEST_DISPLAY);
+            doAnswer(invocation -> mIsRegistered).when(mScreenMagnificationController).isRegistered(
+                    TEST_DISPLAY);
+            Answer enableMagnificationStubAnswer = invocation -> {
+                mScale = invocation.getArgument(1);
+                mCenterX = invocation.getArgument(2);
+                mCenterY = invocation.getArgument(3);
+                return true;
+            };
+            doAnswer(enableMagnificationStubAnswer).when(
+                    mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), anyFloat(),
+                    anyFloat(), anyFloat(), eq(true), eq(SERVICE_ID));
+
+            Answer registerStubAnswer = invocation -> {
+                mIsRegistered = true;
+                return true;
+            };
+            doAnswer(registerStubAnswer).when(
+                    mScreenMagnificationController).register(eq(TEST_DISPLAY));
+
+            Answer unregisterStubAnswer = invocation -> {
+                mIsRegistered = false;
+                return true;
+            };
+            doAnswer(unregisterStubAnswer).when(
+                    mScreenMagnificationController).unregister(eq(TEST_DISPLAY));
+        }
+
+        public void resetAndStubMethods() {
+            Mockito.reset(mScreenMagnificationController);
+            stubMethods();
+        }
+    }
+
+    private static class WindowMagnificationManagerStub {
+        private final WindowMagnificationManager mWindowMagnificationManager;
+        private float mScale = 1.0f;
+        private float mCenterX = 0;
+        private float mCenterY = 0;
+
+        WindowMagnificationManagerStub(
+                WindowMagnificationManager windowMagnificationManager) {
+            mWindowMagnificationManager = windowMagnificationManager;
+        }
+
+        private void stubMethods() {
+            doAnswer(invocation -> mScale).when(mWindowMagnificationManager).getScale(
+                    TEST_DISPLAY);
+            doAnswer(invocation -> mCenterX).when(mWindowMagnificationManager).getCenterX(
+                    TEST_DISPLAY);
+            doAnswer(invocation -> mCenterY).when(mWindowMagnificationManager).getCenterY(
+                    TEST_DISPLAY);
+            Answer enableWindowMagnificationStubAnswer = invocation -> {
+                mScale = invocation.getArgument(1);
+                mCenterX = invocation.getArgument(2);
+                mCenterY = invocation.getArgument(3);
+                return true;
+            };
+            doAnswer(enableWindowMagnificationStubAnswer).when(
+                    mWindowMagnificationManager).enableWindowMagnification(eq(TEST_DISPLAY),
+                    anyFloat(), anyFloat(), anyFloat());
+        }
+
+        public void resetAndStubMethods() {
+            Mockito.reset(mWindowMagnificationManager);
+            stubMethods();
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index 15ba358..02c0aca 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -377,6 +377,17 @@
     }
 
     @Test
+    public void resetMagnification_enabled_windowMagnifierDisabled() {
+        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
+        assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+
+        mWindowMagnificationManager.reset(TEST_DISPLAY);
+
+        assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+    }
+
+    @Test
     public void onScreenOff_windowMagnifierIsEnabled_removeButtonAndDisableWindowMagnification()
             throws RemoteException {
         mWindowMagnificationManager.requestConnection(true);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerInternalWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerInternalWrapper.java
new file mode 100644
index 0000000..968a75c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerInternalWrapper.java
@@ -0,0 +1,37 @@
+/*
+ * 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.hdmi;
+
+/**
+ * Fake class which stubs PowerManagerInternalWrapper (useful for testing).
+ */
+public class FakePowerManagerInternalWrapper extends PowerManagerInternalWrapper {
+
+    private long mIdleDurationMs = -1;
+
+    /**
+     * Sets the duration (in milliseconds) that this device has been idle for.
+     */
+    public void setIdleDuration(long idleDurationMs) {
+        mIdleDurationMs = idleDurationMs;
+    }
+
+    @Override
+    public boolean wasDeviceIdleFor(long ms) {
+        return ms <= mIdleDurationMs;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 055459c..2d13e69 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -76,6 +76,8 @@
     private int mPlaybackLogicalAddress;
     private boolean mWokenUp;
     private boolean mActiveMediaSessionsPaused;
+    private FakePowerManagerInternalWrapper mPowerManagerInternal =
+            new FakePowerManagerInternalWrapper();
 
     @Before
     public void setUp() {
@@ -146,6 +148,7 @@
         mHdmiControlService.initService();
         mPowerManager = new FakePowerManagerWrapper(context);
         mHdmiControlService.setPowerManager(mPowerManager);
+        mHdmiControlService.setPowerManagerInternal(mPowerManagerInternal);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mPlaybackPhysicalAddress = 0x2000;
         mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress);
@@ -1969,4 +1972,41 @@
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortPressed);
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortReleased);
     }
+
+    @Test
+    public void onHotplugInAfterHotplugOut_noStandbyAfterDelay() {
+        mPowerManager.setInteractive(true);
+        mNativeWrapper.onHotplugEvent(1, false);
+        mTestLooper.dispatchAll();
+
+        mTestLooper.moveTimeForward(
+                HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS / 2);
+        mNativeWrapper.onHotplugEvent(1, true);
+        mTestLooper.dispatchAll();
+
+        mPowerManagerInternal.setIdleDuration(
+                HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
+        mTestLooper.moveTimeForward(HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mPowerManager.isInteractive()).isTrue();
+    }
+
+    @Test
+    public void onHotplugOut_standbyAfterDelay_onlyAfterDeviceIdle() {
+        mPowerManager.setInteractive(true);
+        mNativeWrapper.onHotplugEvent(1, false);
+        mTestLooper.dispatchAll();
+
+        mPowerManagerInternal.setIdleDuration(0);
+        mTestLooper.moveTimeForward(HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mPowerManager.isInteractive()).isTrue();
+
+        mPowerManagerInternal.setIdleDuration(
+                HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
+        mTestLooper.moveTimeForward(HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mPowerManager.isInteractive()).isFalse();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java
index bc2d256..bcd216d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java
@@ -49,8 +49,11 @@
 
     @Override
     protected void tearDown() throws Exception {
-        setCaller(CALLING_PACKAGE_1, USER_0);
-        mService.getPackageShortcutForTest(CALLING_PACKAGE_1, USER_0).removeAllShortcutsAsync();
+        if (mService.isAppSearchEnabled()) {
+            setCaller(CALLING_PACKAGE_1, USER_0);
+            mService.getPackageShortcutForTest(CALLING_PACKAGE_1, USER_0)
+                    .removeAllShortcutsAsync();
+        }
         super.tearDown();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index b76c279..ea0c073 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -207,6 +207,65 @@
         assertThat(hasUser(user2.id)).isTrue();
     }
 
+    /**
+     * Tests that UserManager knows how many users can be created.
+     *
+     * We can only test this with regular secondary users, since some other user types have weird
+     * rules about when or if they count towards the max.
+     */
+    @MediumTest
+    @Test
+    public void testAddTooManyUsers() throws Exception {
+        final String userType = UserManager.USER_TYPE_FULL_SECONDARY;
+        final UserTypeDetails userTypeDetails = UserTypeFactory.getUserTypes().get(userType);
+
+        final int maxUsersForType = userTypeDetails.getMaxAllowed();
+        final int maxUsersOverall = UserManager.getMaxSupportedUsers();
+
+        int currentUsersOfType = 0;
+        int currentUsersOverall = 0;
+        final List<UserInfo> userList = mUserManager.getAliveUsers();
+        for (UserInfo user : userList) {
+            currentUsersOverall++;
+            if (userType.equals(user.userType)) {
+                currentUsersOfType++;
+            }
+        }
+
+        final int remainingUserType = maxUsersForType == UserTypeDetails.UNLIMITED_NUMBER_OF_USERS ?
+                Integer.MAX_VALUE : maxUsersForType - currentUsersOfType;
+        final int remainingOverall = maxUsersOverall - currentUsersOverall;
+        final int remaining = Math.min(remainingUserType, remainingOverall);
+
+        Slog.v(TAG, "maxUsersForType=" + maxUsersForType
+                + ", maxUsersOverall=" + maxUsersOverall
+                + ", currentUsersOfType=" + currentUsersOfType
+                + ", currentUsersOverall=" + currentUsersOverall
+                + ", remaining=" + remaining);
+
+        assumeTrue("Device supports too many users for this test to be practical", remaining < 20);
+
+        int usersAdded;
+        for (usersAdded = 0; usersAdded < remaining; usersAdded++) {
+            Slog.v(TAG, "Adding user " + usersAdded);
+            assertThat(mUserManager.canAddMoreUsers()).isTrue();
+            assertThat(mUserManager.canAddMoreUsers(userType)).isTrue();
+
+            final UserInfo user = createUser("User " + usersAdded, userType, 0);
+            assertThat(user).isNotNull();
+            assertThat(hasUser(user.id)).isTrue();
+        }
+        Slog.v(TAG, "Added " + usersAdded + " users.");
+
+        assertWithMessage("Still thinks more users of that type can be added")
+                .that(mUserManager.canAddMoreUsers(userType)).isFalse();
+        if (currentUsersOverall + usersAdded >= maxUsersOverall) {
+            assertThat(mUserManager.canAddMoreUsers()).isFalse();
+        }
+
+        assertThat(createUser("User beyond", userType, 0)).isNull();
+    }
+
     @MediumTest
     @Test
     public void testRemoveUser() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 117680b..809b2f9 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -48,13 +48,13 @@
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
-                .setAutoDetectionEnabled(true)
-                .setLocationEnabled(true)
-                .setGeoDetectionEnabled(true)
+                .setAutoDetectionEnabledSetting(true)
+                .setLocationEnabledSetting(true)
+                .setGeoDetectionEnabledSetting(true)
                 .build();
         {
             ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
-                    .setAutoDetectionEnabled(true)
+                    .setAutoDetectionEnabledSetting(true)
                     .build();
             assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
@@ -79,7 +79,7 @@
 
         {
             ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
-                    .setAutoDetectionEnabled(false)
+                    .setAutoDetectionEnabledSetting(false)
                     .build();
             assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
@@ -110,13 +110,13 @@
                 .setUserConfigAllowed(false)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
-                .setAutoDetectionEnabled(true)
-                .setLocationEnabled(true)
-                .setGeoDetectionEnabled(true)
+                .setAutoDetectionEnabledSetting(true)
+                .setLocationEnabledSetting(true)
+                .setGeoDetectionEnabledSetting(true)
                 .build();
         {
             ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
-                    .setAutoDetectionEnabled(true)
+                    .setAutoDetectionEnabledSetting(true)
                     .build();
             assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
@@ -142,7 +142,7 @@
 
         {
             ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
-                    .setAutoDetectionEnabled(false)
+                    .setAutoDetectionEnabledSetting(false)
                     .build();
             assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
@@ -174,13 +174,13 @@
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(false)
                 .setGeoDetectionFeatureSupported(false)
-                .setAutoDetectionEnabled(true)
-                .setLocationEnabled(true)
-                .setGeoDetectionEnabled(true)
+                .setAutoDetectionEnabledSetting(true)
+                .setLocationEnabledSetting(true)
+                .setGeoDetectionEnabledSetting(true)
                 .build();
         {
             ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
-                    .setAutoDetectionEnabled(true)
+                    .setAutoDetectionEnabledSetting(true)
                     .build();
             assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
@@ -203,7 +203,7 @@
         }
         {
             ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
-                    .setAutoDetectionEnabled(false)
+                    .setAutoDetectionEnabledSetting(false)
                     .build();
             assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
@@ -236,13 +236,13 @@
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(false)
-                .setAutoDetectionEnabled(true)
-                .setLocationEnabled(true)
-                .setGeoDetectionEnabled(true)
+                .setAutoDetectionEnabledSetting(true)
+                .setLocationEnabledSetting(true)
+                .setGeoDetectionEnabledSetting(true)
                 .build();
         {
             ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
-                    .setAutoDetectionEnabled(true)
+                    .setAutoDetectionEnabledSetting(true)
                     .build();
             assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
@@ -266,7 +266,7 @@
         }
         {
             ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
-                    .setAutoDetectionEnabled(false)
+                    .setAutoDetectionEnabledSetting(false)
                     .build();
             assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index ac2b27f..19269e0 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -190,6 +190,9 @@
                     createTimeZoneConfiguration(true /* autoDetectionEnabled */);
             mTimeZoneDetectorService.updateConfiguration(autoDetectEnabledConfiguration);
 
+            // The configuration update notification is asynchronous.
+            mTestHandler.waitForMessagesToBeProcessed();
+
             verify(mMockContext).enforceCallingPermission(
                     eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
                     anyString());
@@ -369,16 +372,16 @@
     }
 
     private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
-        // Default geo detection settings from auto detection settings - they are not important to
-        // the tests.
+        // Default geo detection settings from the auto detection setting - they are not important
+        // to the tests.
         final boolean geoDetectionEnabled = autoDetectionEnabled;
         return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setUserConfigAllowed(true)
-                .setAutoDetectionEnabled(autoDetectionEnabled)
-                .setLocationEnabled(geoDetectionEnabled)
-                .setGeoDetectionEnabled(geoDetectionEnabled)
+                .setAutoDetectionEnabledSetting(autoDetectionEnabled)
+                .setLocationEnabledSetting(geoDetectionEnabled)
+                .setGeoDetectionEnabledSetting(geoDetectionEnabled)
                 .build();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index e6036c4..91e8d16 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -89,9 +89,9 @@
                     .setUserConfigAllowed(false)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
-                    .setAutoDetectionEnabled(false)
-                    .setLocationEnabled(true)
-                    .setGeoDetectionEnabled(false)
+                    .setAutoDetectionEnabledSetting(false)
+                    .setLocationEnabledSetting(true)
+                    .setGeoDetectionEnabledSetting(false)
                     .build();
 
     private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
@@ -99,9 +99,9 @@
                     .setUserConfigAllowed(false)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
-                    .setAutoDetectionEnabled(true)
-                    .setLocationEnabled(true)
-                    .setGeoDetectionEnabled(true)
+                    .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(true)
+                    .setGeoDetectionEnabledSetting(true)
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
@@ -109,9 +109,9 @@
                     .setUserConfigAllowed(true)
                     .setTelephonyDetectionFeatureSupported(false)
                     .setGeoDetectionFeatureSupported(false)
-                    .setAutoDetectionEnabled(false)
-                    .setLocationEnabled(true)
-                    .setGeoDetectionEnabled(false)
+                    .setAutoDetectionEnabledSetting(false)
+                    .setLocationEnabledSetting(true)
+                    .setGeoDetectionEnabledSetting(false)
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
@@ -119,9 +119,9 @@
                     .setUserConfigAllowed(true)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
-                    .setAutoDetectionEnabled(false)
-                    .setLocationEnabled(true)
-                    .setGeoDetectionEnabled(false)
+                    .setAutoDetectionEnabledSetting(false)
+                    .setLocationEnabledSetting(true)
+                    .setGeoDetectionEnabledSetting(false)
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_DISABLED =
@@ -129,9 +129,9 @@
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setUserConfigAllowed(true)
-                    .setAutoDetectionEnabled(true)
-                    .setLocationEnabled(true)
-                    .setGeoDetectionEnabled(false)
+                    .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(true)
+                    .setGeoDetectionEnabledSetting(false)
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_ENABLED =
@@ -139,9 +139,9 @@
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setUserConfigAllowed(true)
-                    .setAutoDetectionEnabled(true)
-                    .setLocationEnabled(true)
-                    .setGeoDetectionEnabled(true)
+                    .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(true)
+                    .setGeoDetectionEnabledSetting(true)
                     .build();
 
     private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
@@ -531,7 +531,7 @@
             boolean geoDetectionEnabled) {
         ConfigurationInternal geoTzEnabledConfig =
                 new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED_GEO_DISABLED)
-                        .setGeoDetectionEnabled(geoDetectionEnabled)
+                        .setGeoDetectionEnabledSetting(geoDetectionEnabled)
                         .build();
         Script script = new Script()
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
@@ -792,8 +792,8 @@
 
         // Update the config and confirm that the config metrics state updates also.
         expectedInternalConfig = new ConfigurationInternal.Builder(expectedInternalConfig)
-                .setAutoDetectionEnabled(true)
-                .setGeoDetectionEnabled(true)
+                .setAutoDetectionEnabledSetting(true)
+                .setGeoDetectionEnabledSetting(true)
                 .build();
 
         expectedDeviceTimeZoneId = geolocationTimeZoneSuggestion.getZoneIds().get(0);
@@ -963,7 +963,7 @@
         Script simulateSetAutoMode(boolean autoDetectionEnabled) {
             ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
                     mFakeEnvironment.getCurrentUserConfigurationInternal())
-                    .setAutoDetectionEnabled(autoDetectionEnabled)
+                    .setAutoDetectionEnabledSetting(autoDetectionEnabled)
                     .build();
             simulateConfigurationInternalChange(newConfig);
             return this;
@@ -975,7 +975,7 @@
         Script simulateSetGeoDetectionEnabled(boolean geoDetectionEnabled) {
             ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
                     mFakeEnvironment.getCurrentUserConfigurationInternal())
-                    .setGeoDetectionEnabled(geoDetectionEnabled)
+                    .setGeoDetectionEnabledSetting(geoDetectionEnabled)
                     .build();
             simulateConfigurationInternalChange(newConfig);
             return this;
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
index 16ac1d6..e3da90e 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
@@ -41,14 +41,14 @@
     }
 
     private static ConfigurationInternal createUserConfig(
-            @UserIdInt int userId, boolean geoDetectionEnabled) {
+            @UserIdInt int userId, boolean geoDetectionEnabledSetting) {
         return new ConfigurationInternal.Builder(userId)
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
-                .setAutoDetectionEnabled(true)
-                .setLocationEnabled(true)
-                .setGeoDetectionEnabled(geoDetectionEnabled)
+                .setAutoDetectionEnabledSetting(true)
+                .setLocationEnabledSetting(true)
+                .setGeoDetectionEnabledSetting(geoDetectionEnabledSetting)
                 .build();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
index 7d24a2f..a9cbad2 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -16,6 +16,19 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationAttributes.USAGE_ALARM;
+import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+import static android.os.VibrationAttributes.USAGE_UNKNOWN;
+import static android.os.Vibrator.VIBRATION_INTENSITY_HIGH;
+import static android.os.Vibrator.VIBRATION_INTENSITY_LOW;
+import static android.os.Vibrator.VIBRATION_INTENSITY_MEDIUM;
+import static android.os.Vibrator.VIBRATION_INTENSITY_OFF;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -39,9 +52,7 @@
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.UserHandle;
-import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
@@ -70,16 +81,19 @@
 public class VibrationSettingsTest {
 
     private static final int UID = 1;
-    private static final int USER_OPERATION_TIMEOUT_MILLIS = 60_000; // 1 min
     private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
     private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
             .setBatterySaverEnabled(true).build();
 
-    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
-    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
-    @Mock private VibrationSettings.OnVibratorSettingsChanged mListenerMock;
-    @Mock private PowerManagerInternal mPowerManagerInternalMock;
+    @Mock
+    private VibrationSettings.OnVibratorSettingsChanged mListenerMock;
+    @Mock
+    private PowerManagerInternal mPowerManagerInternalMock;
 
     private TestLooper mTestLooper;
     private ContextWrapper mContextSpy;
@@ -129,14 +143,12 @@
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
         setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
         setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_ALARMS);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
 
-        verify(mListenerMock, times(7)).onChange();
+        verify(mListenerMock, times(8)).onChange();
     }
 
     @Test
@@ -171,89 +183,83 @@
         VibrationSettings vibrationSettings = new VibrationSettings(mContextSpy,
                 new Handler(mTestLooper.getLooper()));
 
-        assertFalse(vibrationSettings.shouldVibrateForRingerMode(
-                VibrationAttributes.USAGE_RINGTONE));
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_ALARM));
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_TOUCH));
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(
-                VibrationAttributes.USAGE_NOTIFICATION));
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(
-                VibrationAttributes.USAGE_COMMUNICATION_REQUEST));
+        assertFalse(vibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_ALARM));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_TOUCH));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_NOTIFICATION));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_COMMUNICATION_REQUEST));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_HARDWARE_FEEDBACK));
     }
 
     @Test
     public void shouldVibrateForRingerMode_withoutRingtoneUsage_returnsTrue() {
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_ALARM));
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_TOUCH));
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(
-                VibrationAttributes.USAGE_NOTIFICATION));
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(
-                VibrationAttributes.USAGE_COMMUNICATION_REQUEST));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_ALARM));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_TOUCH));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_NOTIFICATION));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_COMMUNICATION_REQUEST));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_HARDWARE_FEEDBACK));
     }
 
     @Test
     public void shouldVibrateForRingerMode_withVibrateWhenRinging_ignoreSettingsForSilentMode() {
-        int usageRingtone = VibrationAttributes.USAGE_RINGTONE;
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
 
         setRingerMode(AudioManager.RINGER_MODE_SILENT);
-        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_MAX);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
     }
 
     @Test
     public void shouldVibrateForRingerMode_withApplyRampingRinger_ignoreSettingsForSilentMode() {
-        int usageRingtone = VibrationAttributes.USAGE_RINGTONE;
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
         setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
 
         setRingerMode(AudioManager.RINGER_MODE_SILENT);
-        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_MAX);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
     }
 
     @Test
     public void shouldVibrateForRingerMode_withAllSettingsOff_onlyVibratesForVibrateMode() {
-        int usageRingtone = VibrationAttributes.USAGE_RINGTONE;
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
         setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
 
         setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_SILENT);
-        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_MAX);
-        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
     }
 
     @Test
     public void shouldVibrateForUid_withForegroundOnlyUsage_returnsTrueWhInForeground() {
-        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_TOUCH));
+        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_TOUCH));
 
         mVibrationSettings.mUidObserver.onUidStateChanged(
                 UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
-        assertFalse(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_TOUCH));
+        assertFalse(mVibrationSettings.shouldVibrateForUid(UID, USAGE_TOUCH));
     }
 
     @Test
@@ -261,38 +267,32 @@
         mVibrationSettings.mUidObserver.onUidStateChanged(
                 UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
 
-        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_ALARM));
-        assertTrue(mVibrationSettings.shouldVibrateForUid(UID,
-                VibrationAttributes.USAGE_COMMUNICATION_REQUEST));
-        assertTrue(mVibrationSettings.shouldVibrateForUid(UID,
-                VibrationAttributes.USAGE_NOTIFICATION));
-        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_RINGTONE));
+        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_ALARM));
+        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_COMMUNICATION_REQUEST));
+        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_NOTIFICATION));
+        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_RINGTONE));
     }
 
     @Test
     public void shouldVibrateForPowerMode_withLowPowerAndAllowedUsage_returnTrue() {
         mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
 
-        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(VibrationAttributes.USAGE_ALARM));
-        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(
-                VibrationAttributes.USAGE_RINGTONE));
-        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(
-                VibrationAttributes.USAGE_COMMUNICATION_REQUEST));
+        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_ALARM));
+        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_RINGTONE));
+        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_COMMUNICATION_REQUEST));
     }
 
     @Test
     public void shouldVibrateForPowerMode_withRestrictedUsage_returnsFalseWhileInLowPowerMode() {
         mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
 
-        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(VibrationAttributes.USAGE_TOUCH));
-        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(
-                VibrationAttributes.USAGE_NOTIFICATION));
+        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_TOUCH));
+        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_NOTIFICATION));
 
         mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
 
-        assertFalse(mVibrationSettings.shouldVibrateForPowerMode(VibrationAttributes.USAGE_TOUCH));
-        assertFalse(mVibrationSettings.shouldVibrateForPowerMode(
-                VibrationAttributes.USAGE_NOTIFICATION));
+        assertFalse(mVibrationSettings.shouldVibrateForPowerMode(USAGE_TOUCH));
+        assertFalse(mVibrationSettings.shouldVibrateForPowerMode(USAGE_NOTIFICATION));
     }
 
     @Test
@@ -324,108 +324,128 @@
 
     @Test
     public void getDefaultIntensity_beforeSystemReady_returnsMediumToAllExceptAlarm() {
-        mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_HIGH);
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_HIGH);
-        mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_HIGH);
+        mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_HIGH);
+        mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_HIGH);
+        mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_HIGH);
 
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
 
         VibrationSettings vibrationSettings = new VibrationSettings(mContextSpy,
                 new Handler(mTestLooper.getLooper()));
 
-        assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
-                vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_ALARM));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_UNKNOWN));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(
-                        VibrationAttributes.USAGE_PHYSICAL_EMULATION));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                vibrationSettings.getDefaultIntensity(USAGE_ALARM));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                vibrationSettings.getDefaultIntensity(USAGE_TOUCH));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                vibrationSettings.getDefaultIntensity(USAGE_HARDWARE_FEEDBACK));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                vibrationSettings.getDefaultIntensity(USAGE_PHYSICAL_EMULATION));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                vibrationSettings.getDefaultIntensity(USAGE_NOTIFICATION));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                vibrationSettings.getDefaultIntensity(USAGE_UNKNOWN));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                vibrationSettings.getDefaultIntensity(USAGE_RINGTONE));
     }
 
     @Test
     public void getDefaultIntensity_returnsIntensityFromVibratorService() {
-        mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_HIGH);
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
+        mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_HIGH);
+        mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_MEDIUM);
+        mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_LOW);
 
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
 
-        assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_ALARM));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_UNKNOWN));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getDefaultIntensity(
-                        VibrationAttributes.USAGE_PHYSICAL_EMULATION));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_LOW,
-                mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getDefaultIntensity(USAGE_ALARM));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getDefaultIntensity(USAGE_TOUCH));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getDefaultIntensity(USAGE_HARDWARE_FEEDBACK));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getDefaultIntensity(USAGE_PHYSICAL_EMULATION));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                mVibrationSettings.getDefaultIntensity(USAGE_NOTIFICATION));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                mVibrationSettings.getDefaultIntensity(USAGE_UNKNOWN));
+        assertEquals(VIBRATION_INTENSITY_LOW,
+                mVibrationSettings.getDefaultIntensity(USAGE_RINGTONE));
     }
 
     @Test
     public void getCurrentIntensity_returnsIntensityFromSettings() {
-        mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_OFF);
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_OFF);
-        mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_OFF);
+        mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_OFF);
+        mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_OFF);
+        mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_OFF);
 
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
+                VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
 
-        assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_ALARM));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_TOUCH));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_UNKNOWN));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getCurrentIntensity(
-                        VibrationAttributes.USAGE_PHYSICAL_EMULATION));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_LOW,
-                mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE));
+        assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getCurrentIntensity(USAGE_ALARM));
+        assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
+        assertEquals(VIBRATION_INTENSITY_LOW,
+                mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
+        assertEquals(VIBRATION_INTENSITY_LOW,
+                mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                mVibrationSettings.getCurrentIntensity(USAGE_NOTIFICATION));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                mVibrationSettings.getCurrentIntensity(USAGE_UNKNOWN));
+        assertEquals(VIBRATION_INTENSITY_LOW,
+                mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
     }
 
     @Test
     public void getCurrentIntensity_updateTriggeredAfterUserSwitched() {
-        mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
-        assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE));
+        mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
 
         // Switching user is not working with FakeSettingsProvider.
         // Testing the broadcast flow manually.
         Settings.System.putIntForUser(mContextSpy.getContentResolver(),
-                Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW,
+                Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW,
                 UserHandle.USER_CURRENT);
         mVibrationSettings.mUserReceiver.onReceive(mContextSpy,
                 new Intent(Intent.ACTION_USER_SWITCHED));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_LOW,
-                mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE));
+        assertEquals(VIBRATION_INTENSITY_LOW,
+                mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
+    }
+
+    @Test
+    public void getCurrentIntensity_noHardwareFeedbackValueUsesHapticFeedbackValue() {
+        mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        assertEquals(VIBRATION_INTENSITY_OFF, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
+        // If haptic feedback is off, fallback to default value.
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
+
+        // Switching user is not working with FakeSettingsProvider.
+        // Testing the broadcast flow manually.
+        Settings.System.putIntForUser(mContextSpy.getContentResolver(),
+                Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH,
+                UserHandle.USER_CURRENT);
+        mVibrationSettings.mUserReceiver.onReceive(mContextSpy,
+                new Intent(Intent.ACTION_USER_SWITCHED));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 1266b2e..afc2b87 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -854,12 +854,19 @@
         ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
                 mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME,
                         DEFAULT_USER_ID);
+
+        // committing empty locales, when no config is set should return false.
+        assertFalse(packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()).commit());
+
         // committing new configuration returns true;
         assertTrue(packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
                 .commit());
         // applying the same configuration returns false.
         assertFalse(packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
                 .commit());
+
+        // committing empty locales and undefined nightMode should return true (deletes the
+        // pre-existing record) if some config was previously set.
         assertTrue(packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList())
                 .setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 6fa306b..5062706 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -334,6 +334,18 @@
     }
 
     @Test
+    public void testExitAnimationDone_beforeAppTransition() {
+        final Task task = createTask(mDisplayContent);
+        final WindowState win = createAppWindow(task, ACTIVITY_TYPE_STANDARD, "Win");
+        spyOn(win);
+        win.mAnimatingExit = true;
+        mDisplayContent.mAppTransition.setTimeout();
+        mDisplayContent.mAppTransitionController.handleAppTransitionReady();
+
+        verify(win).onExitAnimationDone();
+    }
+
+    @Test
     public void testGetAnimationTargets_windowsAreBeingReplaced() {
         // [DisplayContent] -+- [Task1] - [ActivityRecord1] (opening, visible)
         //                                       +- [AppWindow1] (being-replaced)
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 08be15e..d7a0ab3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -681,7 +681,7 @@
 
         final int maxWidth = 300;
         final int resultingHeight = (maxWidth * baseHeight) / baseWidth;
-        final int resultingDensity = baseDensity;
+        final int resultingDensity = (baseDensity * maxWidth) / baseWidth;
 
         displayContent.setMaxUiWidth(maxWidth);
         verifySizes(displayContent, maxWidth, resultingHeight, resultingDensity);
@@ -756,6 +756,33 @@
     }
 
     @Test
+    public void testSetForcedDensity() {
+        final DisplayContent displayContent = createDisplayNoUpdateDisplayInfo();
+        final int baseWidth = 1280;
+        final int baseHeight = 720;
+        final int baseDensity = 320;
+
+        displayContent.mInitialDisplayWidth = baseWidth;
+        displayContent.mInitialDisplayHeight = baseHeight;
+        displayContent.mInitialDisplayDensity = baseDensity;
+        displayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity);
+
+        final int forcedDensity = 600;
+
+        // Verify that forcing the density is honored and the size doesn't change.
+        displayContent.setForcedDensity(forcedDensity, 0 /* userId */);
+        verifySizes(displayContent, baseWidth, baseHeight, forcedDensity);
+
+        // Verify that forcing the density is idempotent.
+        displayContent.setForcedDensity(forcedDensity, 0 /* userId */);
+        verifySizes(displayContent, baseWidth, baseHeight, forcedDensity);
+
+        // Verify that forcing resolution won't affect the already forced density.
+        displayContent.setForcedSize(1800, 1200);
+        verifySizes(displayContent, 1800, 1200, forcedDensity);
+    }
+
+    @Test
     public void testDisplayCutout_rot0() {
         final DisplayContent dc = createNewDisplay();
         dc.mInitialDisplayWidth = 200;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index f366f57..caaf4e4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -54,7 +54,6 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
-import android.window.ITransitionPlayer;
 
 import androidx.test.filters.SmallTest;
 
@@ -313,11 +312,7 @@
         wallpaperWindow.setHasSurface(true);
 
         // Set-up mock shell transitions
-        final IBinder mockBinder = mock(IBinder.class);
-        final ITransitionPlayer mockPlayer = mock(ITransitionPlayer.class);
-        doReturn(mockBinder).when(mockPlayer).asBinder();
-        mWm.mAtmService.getTransitionController().registerTransitionPlayer(mockPlayer,
-                null /* appThread */);
+        registerTestTransitionPlayer();
 
         Transition transit =
                 mWm.mAtmService.getTransitionController().createTransition(TRANSIT_OPEN);
@@ -338,10 +333,21 @@
         assertFalse(token.isVisibleRequested());
         assertTrue(token.isVisible());
 
-        transit.onTransactionReady(transit.getSyncId(), mock(SurfaceControl.Transaction.class));
-        transit.finishTransition();
+        final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        token.finishSync(t, false /* cancel */);
+        transit.onTransactionReady(transit.getSyncId(), t);
+        dc.mTransitionController.finishTransition(transit);
         assertFalse(wallpaperWindow.isVisible());
         assertFalse(token.isVisible());
+
+        // Assume wallpaper was visible. When transaction is ready without wallpaper target,
+        // wallpaper should be requested to be invisible.
+        token.setVisibility(true);
+        transit = dc.mTransitionController.createTransition(TRANSIT_CLOSE);
+        dc.mTransitionController.collect(token);
+        transit.onTransactionReady(transit.getSyncId(), t);
+        assertFalse(token.isVisibleRequested());
+        assertTrue(token.isVisible());
     }
 
     private WindowState createWallpaperTargetWindow(DisplayContent dc) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 722aee7..e5dc557 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -500,8 +500,6 @@
         mDisplayContent.assignChildLayers(mTransaction);
 
         assertWindowHigher(splitWindow1, belowTaskWindow);
-        assertWindowHigher(splitWindow1, belowTaskWindow);
-        assertWindowHigher(splitWindow2, belowTaskWindow);
         assertWindowHigher(splitWindow2, belowTaskWindow);
         assertWindowHigher(mDockedDividerWindow, splitWindow1);
         assertWindowHigher(mDockedDividerWindow, splitWindow2);
@@ -509,6 +507,39 @@
         assertWindowHigher(pinnedWindow, aboveTaskWindow);
     }
 
+
+    @Test
+    public void testDockedDividerPosition_noAboveTask() {
+        final Task pinnedTask =
+                createTask(mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        final WindowState pinnedWindow =
+                createAppWindow(pinnedTask, ACTIVITY_TYPE_STANDARD, "pinnedWindow");
+
+        final Task belowTask =
+                createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState belowTaskWindow =
+                createAppWindow(belowTask, ACTIVITY_TYPE_STANDARD, "belowTaskWindow");
+
+        final Task splitScreenTask1 =
+                createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+        final WindowState splitWindow1 =
+                createAppWindow(splitScreenTask1, ACTIVITY_TYPE_STANDARD, "splitWindow1");
+        final Task splitScreenTask2 =
+                createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+        final WindowState splitWindow2 =
+                createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2");
+        splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2);
+        splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1);
+
+        mDisplayContent.assignChildLayers(mTransaction);
+
+        assertWindowHigher(splitWindow1, belowTaskWindow);
+        assertWindowHigher(splitWindow2, belowTaskWindow);
+        assertWindowHigher(mDockedDividerWindow, splitWindow1);
+        assertWindowHigher(mDockedDividerWindow, splitWindow2);
+        assertWindowHigher(pinnedWindow, mDockedDividerWindow);
+    }
+
     @Test
     public void testAttachNavBarWhenEnteringRecents_expectNavBarHigherThanIme() {
         // create RecentsAnimationController
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index f4f06fd..96c78bc 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -191,7 +191,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         mContext.registerReceiver(mBroadcastReceiver, filter, null, handler,
-                Context.RECEIVER_NOT_EXPORTED);
+                Context.RECEIVER_EXPORTED);
     }
 
     public boolean showSessionLocked(Bundle args, int flags,
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index 7d857a2..fabe612 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -856,9 +856,10 @@
             mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
+                    // TODO(b/207392528: use portIndex API once implemented)
                     int result =
-                            EuiccService.this.onSwitchToSubscriptionWithPort(
-                                    slotId, portIndex, iccid, forceDeactivateSim);
+                            EuiccService.this.onSwitchToSubscription(
+                                    slotId, iccid, forceDeactivateSim);
                     try {
                         callback.onComplete(result);
                     } catch (RemoteException e) {
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 23cf511..e88106c 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -1,6 +1,8 @@
 package android.telephony;
 
 import android.annotation.IntDef;
+import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
 import android.telecom.Connection;
 import android.telephony.data.ApnSetting;
 
@@ -664,4 +666,59 @@
         TelephonyManager.THERMAL_MITIGATION_RESULT_INVALID_STATE,
         TelephonyManager.THERMAL_MITIGATION_RESULT_UNKNOWN_ERROR})
     public @interface ThermalMitigationResult {}
+
+    /**
+     * Per Android API guideline 8.15, annotation can't be public APIs. So duplicate
+     * android.net.NetworkCapabilities.NetCapability here. Must update here when new capabilities
+     * are added in {@link NetworkCapabilities}.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "NET_CAPABILITY_" }, value = {
+            NetworkCapabilities.NET_CAPABILITY_MMS,
+            NetworkCapabilities.NET_CAPABILITY_SUPL,
+            NetworkCapabilities.NET_CAPABILITY_DUN,
+            NetworkCapabilities.NET_CAPABILITY_FOTA,
+            NetworkCapabilities.NET_CAPABILITY_IMS,
+            NetworkCapabilities.NET_CAPABILITY_CBS,
+            NetworkCapabilities.NET_CAPABILITY_WIFI_P2P,
+            NetworkCapabilities.NET_CAPABILITY_IA,
+            NetworkCapabilities.NET_CAPABILITY_RCS,
+            NetworkCapabilities.NET_CAPABILITY_XCAP,
+            NetworkCapabilities.NET_CAPABILITY_EIMS,
+            NetworkCapabilities.NET_CAPABILITY_NOT_METERED,
+            NetworkCapabilities.NET_CAPABILITY_INTERNET,
+            NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED,
+            NetworkCapabilities.NET_CAPABILITY_TRUSTED,
+            NetworkCapabilities.NET_CAPABILITY_NOT_VPN,
+            NetworkCapabilities.NET_CAPABILITY_VALIDATED,
+            NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL,
+            NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING,
+            NetworkCapabilities.NET_CAPABILITY_FOREGROUND,
+            NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED,
+            NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED,
+            NetworkCapabilities.NET_CAPABILITY_OEM_PAID,
+            NetworkCapabilities.NET_CAPABILITY_MCX,
+            NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+            NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED,
+            NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE,
+            NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL,
+            NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED,
+            NetworkCapabilities.NET_CAPABILITY_ENTERPRISE,
+            NetworkCapabilities.NET_CAPABILITY_VSIM,
+            NetworkCapabilities.NET_CAPABILITY_BIP,
+            NetworkCapabilities.NET_CAPABILITY_HEAD_UNIT,
+    })
+    public @interface NetCapability { }
+
+    /**
+     * Per Android API guideline 8.15, annotation can't be public APIs. So duplicate
+     * android.net.NetworkAgent.ValidationStatus here. Must update here when new validation status
+     * are added in {@link NetworkAgent}.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "VALIDATION_STATUS_" }, value = {
+            NetworkAgent.VALIDATION_STATUS_VALID,
+            NetworkAgent.VALIDATION_STATUS_NOT_VALID
+    })
+    public @interface ValidationStatus {}
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 3f0f50c..6f92c31 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5116,13 +5116,11 @@
     public static final String KEY_APN_PRIORITY_STRING_ARRAY = "apn_priority_string_array";
 
     /**
-     * Network capability priority for determine the satisfy order in telephony. This is used when
-     * the network only allows single PDN. The priority is from the lowest 0 to the highest 100.
-     * The long-lived network request usually has the lowest priority. This allows other short-lived
-     * requests like MMS requests to be established. Emergency request always has the highest
-     * priority.
+     * Network capability priority for determine the satisfy order in telephony. The priority is
+     * from the lowest 0 to the highest 100. The long-lived network shall have the lowest priority.
+     * This allows other short-lived requests like MMS requests to be established. Emergency request
+     * always has the highest priority.
      *
-     * // TODO: Remove KEY_APN_PRIORITY_STRING_ARRAY
      * @hide
      */
     public static final String KEY_TELEPHONY_NETWORK_CAPABILITY_PRIORITIES_STRING_ARRAY =
@@ -5132,17 +5130,17 @@
      * Defines the rules for data retry.
      *
      * The syntax of the retry rule:
-     * 1. Retry based on {@link NetworkCapabilities}
-     * "capabilities=[netCaps1|netCaps2|...], [retry_interval=x], [backoff=[true|false]],
-     *     [maximum_retries=y]"
+     * 1. Retry based on {@link NetworkCapabilities}. Note that only APN-type network capabilities
+     *    are supported.
+     * "capabilities=[netCaps1|netCaps2|...], [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]"
      *
      * 2. Retry based on {@link DataFailCause}
-     * "fail_causes=[cause1|cause2|cause3|...], [retry_interval=x], [backoff=[true|false]],
-     *     [maximum_retries=y]"
+     * "fail_causes=[cause1|cause2|cause3|..], [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]"
      *
-     * 3. Retry based on {@link NetworkCapabilities} and {@link DataFailCause}
+     * 3. Retry based on {@link NetworkCapabilities} and {@link DataFailCause}. Note that only
+     *    APN-type network capabilities are supported.
      * "capabilities=[netCaps1|netCaps2|...], fail_causes=[cause1|cause2|cause3|...],
-     *     [retry_interval=x], [backoff=[true|false]], [maximum_retries=y]"
+     *     [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]"
      *
      * For example,
      * "capabilities=eims, retry_interval=1000, maximum_retries=20" means if the attached
@@ -5151,7 +5149,12 @@
      *
      * "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|2254
      * , maximum_retries=0" means for those fail causes, never retry with timers. Note that
-     * when environment changes, retry can still happens.
+     * when environment changes, retry can still happen.
+     *
+     * "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
+     * "5000|10000|15000|20000|40000|60000|120000|240000|600000|1200000|1800000"
+     * "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s,
+     * 10s, 15s, 20s, 40s, 1m, 2m, 4m, 10m, 20m, 30m, 30m, 30m, until reaching 20 retries.
      *
      * // TODO: remove KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS
      * @hide
@@ -5941,6 +5944,9 @@
                 "enterprise:0", "default:1", "mms:2", "supl:2", "dun:2", "hipri:3", "fota:2",
                 "ims:2", "cbs:2", "ia:2", "emergency:2", "mcx:3", "xcap:3"
         });
+
+        // Do not modify the priority unless you know what you are doing. This will have significant
+        // impacts on the order of data network setup.
         sDefaults.putStringArray(
                 KEY_TELEPHONY_NETWORK_CAPABILITY_PRIORITIES_STRING_ARRAY, new String[] {
                         "eims:90", "supl:80", "mms:70", "xcap:70", "cbs:50", "mcx:50", "fota:50",
@@ -5951,9 +5957,10 @@
                         "capabilities=eims, retry_interval=1000, maximum_retries=20",
                         "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|"
                                 + "2254, maximum_retries=0", // No retry for those causes
-                        "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2000, "
-                                + "backoff=true, maximum_retries=13",
-                        "capabilities=mms|supl|cbs, retry_interval=2000"
+                        "capabilities=mms|supl|cbs, retry_interval=2000",
+                        "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
+                                + "5000|10000|15000|20000|40000|60000|120000|240000|"
+                                + "600000|1200000|1800000, maximum_retries=20"
                 });
         sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]);
         sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index 88efe1f..56bf303 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1076,6 +1076,13 @@
      */
     public static final int SERVICE_TEMPORARILY_UNAVAILABLE = 0x10009;
 
+    /**
+     * The request is not supported by the vendor.
+     *
+     * @hide
+     */
+    public static final int REQUEST_NOT_SUPPORTED = 0x1000A;
+
     private static final Map<Integer, String> sFailCauseMap;
     static {
         sFailCauseMap = new HashMap<>();
diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java
index 7dfe450..0dfab18 100644
--- a/telephony/java/android/telephony/UiccCardInfo.java
+++ b/telephony/java/android/telephony/UiccCardInfo.java
@@ -156,9 +156,11 @@
     @Nullable
     @Deprecated
     public String getIccId() {
-        if (mIccIdAccessRestricted) {
-            throw new UnsupportedOperationException("getIccId from UiccPortInfo");
-        }
+        // Temporarily bypassing exception
+        // TODO: add exception once refactoring completed.
+        //if (mIccIdAccessRestricted) {
+        //    throw new UnsupportedOperationException("getIccId from UiccPortInfo");
+        //}
         //always return ICCID from first port.
         return getPorts().stream().findFirst().get().getIccId();
     }
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 2b1c8c8..a8668e7 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -159,9 +159,11 @@
      */
     @Deprecated
     public boolean getIsActive() {
-        if (mLogicalSlotAccessRestricted) {
-            throw new UnsupportedOperationException("get port status from UiccPortInfo");
-        }
+        // Temporarily bypassing exception
+        // TODO: add exception once refactoring completed.
+        //if (mLogicalSlotAccessRestricted) {
+        //    throw new UnsupportedOperationException("get port status from UiccPortInfo");
+        //}
         //always return status from first port.
         return getPorts().stream().findFirst().get().isActive();
     }
@@ -196,9 +198,11 @@
      */
     @Deprecated
     public int getLogicalSlotIdx() {
-        if (mLogicalSlotAccessRestricted) {
-            throw new UnsupportedOperationException("get logical slot index from UiccPortInfo");
-        }
+        // Temporarily bypassing exception
+        // TODO: add exception once refactoring completed.
+        //if (mLogicalSlotAccessRestricted) {
+        //    throw new UnsupportedOperationException("get logical slot index from UiccPortInfo");
+        //}
         //always return logical slot index from first port.
         //portList always have at least one element.
         return getPorts().stream().findFirst().get().getLogicalSlotIndex();
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index 93903d2..c1d16a9 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -18,13 +18,16 @@
 
 import static android.telephony.data.ApnSetting.ProtocolType;
 
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.net.NetworkCapabilities;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.Annotation.ApnType;
+import android.telephony.Annotation.NetCapability;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyManager.NetworkTypeBitMask;
 import android.telephony.data.ApnSetting.AuthType;
@@ -66,7 +69,13 @@
 
     private final @Nullable TrafficDescriptor mTrafficDescriptor;
 
-    private final boolean mPreferred;
+    private boolean mPreferred;
+
+    /**
+     * The last timestamp of this data profile being used for data network setup. Never add this
+     * to {@link #equals(Object)} and {@link #hashCode()}.
+     */
+    private @ElapsedRealtimeLong long mSetupTimestamp;
 
     private DataProfile(@NonNull Builder builder) {
         mApnSetting = builder.mApnSetting;
@@ -99,6 +108,7 @@
         mApnSetting = source.readParcelable(ApnSetting.class.getClassLoader());
         mTrafficDescriptor = source.readParcelable(TrafficDescriptor.class.getClassLoader());
         mPreferred = source.readBoolean();
+        mSetupTimestamp = source.readLong();
     }
 
     /**
@@ -291,6 +301,16 @@
     }
 
     /**
+     * Set the preferred flag for the data profile.
+     *
+     * @param preferred {@code true} if this data profile is preferred for internet.
+     * @hide
+     */
+    public void setPreferred(boolean preferred) {
+        mPreferred = preferred;
+    }
+
+    /**
      * @return {@code true} if this data profile was used to bring up the last default
      * (i.e internet) data connection successfully, or the one chosen by the user in Settings'
      * APN editor. For one carrier there can be only one profiled preferred.
@@ -315,6 +335,94 @@
         return mTrafficDescriptor;
     }
 
+    /**
+     * Check if this data profile can satisfy certain network capabilities
+     *
+     * @param networkCapabilities The network capabilities. Note that the non-APN-type capabilities
+     * will be ignored.
+     *
+     * @return {@code true} if this data profile can satisfy the given network capabilities.
+     * @hide
+     */
+    public boolean canSatisfy(@NonNull @NetCapability int[] networkCapabilities) {
+        if (mApnSetting != null) {
+            for (int netCap : networkCapabilities) {
+                if (!canSatisfy(netCap)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Check if this data profile can satisfy a certain network capability.
+     *
+     * @param networkCapability The network capability. Note that the non-APN-type capability
+     * will always be satisfied.
+     * @return {@code true} if this data profile can satisfy the given network capability.
+     * @hide
+     */
+    public boolean canSatisfy(@NetCapability int networkCapability) {
+        return mApnSetting != null && mApnSetting.canHandleType(
+                networkCapabilityToApnType(networkCapability));
+    }
+
+    /**
+     * Convert network capability into APN type.
+     *
+     * @param networkCapability Network capability.
+     * @return APN type.
+     * @hide
+     */
+    private static @ApnType int networkCapabilityToApnType(@NetCapability int networkCapability) {
+        switch (networkCapability) {
+            case NetworkCapabilities.NET_CAPABILITY_MMS:
+                return ApnSetting.TYPE_MMS;
+            case NetworkCapabilities.NET_CAPABILITY_SUPL:
+                return ApnSetting.TYPE_SUPL;
+            case NetworkCapabilities.NET_CAPABILITY_DUN:
+                return ApnSetting.TYPE_DUN;
+            case NetworkCapabilities.NET_CAPABILITY_FOTA:
+                return ApnSetting.TYPE_FOTA;
+            case NetworkCapabilities.NET_CAPABILITY_IMS:
+                return ApnSetting.TYPE_IMS;
+            case NetworkCapabilities.NET_CAPABILITY_CBS:
+                return ApnSetting.TYPE_CBS;
+            case NetworkCapabilities.NET_CAPABILITY_XCAP:
+                return ApnSetting.TYPE_XCAP;
+            case NetworkCapabilities.NET_CAPABILITY_EIMS:
+                return ApnSetting.TYPE_EMERGENCY;
+            case NetworkCapabilities.NET_CAPABILITY_INTERNET:
+                return ApnSetting.TYPE_DEFAULT;
+            case NetworkCapabilities.NET_CAPABILITY_MCX:
+                return ApnSetting.TYPE_MCX;
+            case NetworkCapabilities.NET_CAPABILITY_IA:
+                return ApnSetting.TYPE_IA;
+            default:
+                return ApnSetting.TYPE_NONE;
+        }
+    }
+
+    /**
+     * Set the timestamp of this data profile being used for data network setup.
+     *
+     * @hide
+     */
+    public void setLastSetupTimestamp(@ElapsedRealtimeLong long timestamp) {
+        mSetupTimestamp = timestamp;
+    }
+
+    /**
+     * @return the timestamp of this data profile being used for data network setup.
+     *
+     * @hide
+     */
+    public @ElapsedRealtimeLong long getLastSetupTimestamp() {
+        return mSetupTimestamp;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -323,8 +431,8 @@
     @NonNull
     @Override
     public String toString() {
-        return "DataProfile=" + mApnSetting + ", " + mTrafficDescriptor + ", preferred="
-                + mPreferred;
+        return "[DataProfile=" + mApnSetting + ", " + mTrafficDescriptor + ", preferred="
+                + mPreferred + "]";
     }
 
     @Override
@@ -333,6 +441,7 @@
         dest.writeParcelable(mApnSetting, flags);
         dest.writeParcelable(mTrafficDescriptor, flags);
         dest.writeBoolean(mPreferred);
+        dest.writeLong(mSetupTimestamp);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<DataProfile> CREATOR =
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 937f9dc..15de226 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -119,7 +119,7 @@
     @Test
     public void testNullNetworkDoesNotTriggerDisconnect() throws Exception {
         mGatewayConnection
-                .getUnderlyingNetworkTrackerCallback()
+                .getUnderlyingNetworkControllerCallback()
                 .onSelectedUnderlyingNetworkChanged(null);
         mTestLooper.dispatchAll();
 
@@ -131,7 +131,7 @@
     @Test
     public void testNewNetworkTriggersMigration() throws Exception {
         mGatewayConnection
-                .getUnderlyingNetworkTrackerCallback()
+                .getUnderlyingNetworkControllerCallback()
                 .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2);
         mTestLooper.dispatchAll();
 
@@ -143,7 +143,7 @@
     @Test
     public void testSameNetworkDoesNotTriggerMigration() throws Exception {
         mGatewayConnection
-                .getUnderlyingNetworkTrackerCallback()
+                .getUnderlyingNetworkControllerCallback()
                 .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
         mTestLooper.dispatchAll();
 
@@ -203,7 +203,7 @@
         triggerChildOpened();
 
         mGatewayConnection
-                .getUnderlyingNetworkTrackerCallback()
+                .getUnderlyingNetworkControllerCallback()
                 .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2);
         getChildSessionCallback()
                 .onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform());
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
index d1f3a21..3c70759 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
@@ -64,7 +64,7 @@
     @Test
     public void testNullNetworkTriggersDisconnect() throws Exception {
         mGatewayConnection
-                .getUnderlyingNetworkTrackerCallback()
+                .getUnderlyingNetworkControllerCallback()
                 .onSelectedUnderlyingNetworkChanged(null);
         mTestLooper.dispatchAll();
 
@@ -76,7 +76,7 @@
     @Test
     public void testNewNetworkTriggersReconnect() throws Exception {
         mGatewayConnection
-                .getUnderlyingNetworkTrackerCallback()
+                .getUnderlyingNetworkControllerCallback()
                 .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2);
         mTestLooper.dispatchAll();
 
@@ -89,7 +89,7 @@
     @Test
     public void testSameNetworkDoesNotTriggerReconnect() throws Exception {
         mGatewayConnection
-                .getUnderlyingNetworkTrackerCallback()
+                .getUnderlyingNetworkControllerCallback()
                 .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
         mTestLooper.dispatchAll();
 
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
index 2056eea..f3eb82f 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
@@ -78,7 +78,7 @@
     @Test
     public void testNetworkChangesTriggerStateTransitions() throws Exception {
         mGatewayConnection
-                .getUnderlyingNetworkTrackerCallback()
+                .getUnderlyingNetworkControllerCallback()
                 .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
         mTestLooper.dispatchAll();
 
@@ -89,7 +89,7 @@
     @Test
     public void testNullNetworkDoesNotTriggerStateTransition() throws Exception {
         mGatewayConnection
-                .getUnderlyingNetworkTrackerCallback()
+                .getUnderlyingNetworkControllerCallback()
                 .onSelectedUnderlyingNetworkChanged(null);
         mTestLooper.dispatchAll();
 
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
index 1c85979..6568cdd 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
@@ -58,7 +58,7 @@
     @Test
     public void testNewNetworkTriggerRetry() throws Exception {
         mGatewayConnection
-                .getUnderlyingNetworkTrackerCallback()
+                .getUnderlyingNetworkControllerCallback()
                 .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2);
         mTestLooper.dispatchAll();
 
@@ -72,7 +72,7 @@
     @Test
     public void testSameNetworkDoesNotTriggerRetry() throws Exception {
         mGatewayConnection
-                .getUnderlyingNetworkTrackerCallback()
+                .getUnderlyingNetworkControllerCallback()
                 .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
         mTestLooper.dispatchAll();
 
@@ -86,7 +86,7 @@
     @Test
     public void testNullNetworkTriggersDisconnect() throws Exception {
         mGatewayConnection
-                .getUnderlyingNetworkTrackerCallback()
+                .getUnderlyingNetworkControllerCallback()
                 .onSelectedUnderlyingNetworkChanged(null);
         mTestLooper.dispatchAll();
 
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index 2b0037e..b9dfda3 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -59,7 +59,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
+import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -238,14 +238,14 @@
     }
 
     @Test
-    public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkTracker() {
+    public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkController() {
         verifyWakeLockSetUp();
 
         final TelephonySubscriptionSnapshot updatedSnapshot =
                 mock(TelephonySubscriptionSnapshot.class);
         mGatewayConnection.updateSubscriptionSnapshot(updatedSnapshot);
 
-        verify(mUnderlyingNetworkTracker).updateSubscriptionSnapshot(eq(updatedSnapshot));
+        verify(mUnderlyingNetworkController).updateSubscriptionSnapshot(eq(updatedSnapshot));
         verifyWakeLockAcquired();
 
         mTestLooper.dispatchAll();
@@ -256,13 +256,13 @@
     @Test
     public void testNonNullUnderlyingNetworkRecordUpdateCancelsAlarm() {
         mGatewayConnection
-                .getUnderlyingNetworkTrackerCallback()
+                .getUnderlyingNetworkControllerCallback()
                 .onSelectedUnderlyingNetworkChanged(null);
 
         verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
 
         mGatewayConnection
-                .getUnderlyingNetworkTrackerCallback()
+                .getUnderlyingNetworkControllerCallback()
                 .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
 
         verify(mDisconnectRequestAlarm).cancel();
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 64d0bca..8a0af2d 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -16,7 +16,6 @@
 
 package com.android.server.vcn;
 
-import static com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
 import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
 import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent;
 import static com.android.server.vcn.VcnTestUtils.setupIpSecManager;
@@ -62,6 +61,8 @@
 import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
 import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback;
 import com.android.server.vcn.VcnGatewayConnection.VcnWakeLock;
+import com.android.server.vcn.routeselection.UnderlyingNetworkController;
+import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
 
 import org.junit.Before;
 import org.mockito.ArgumentCaptor;
@@ -137,7 +138,7 @@
     @NonNull protected final VcnGatewayConnectionConfig mConfig;
     @NonNull protected final VcnGatewayStatusCallback mGatewayStatusCallback;
     @NonNull protected final VcnGatewayConnection.Dependencies mDeps;
-    @NonNull protected final UnderlyingNetworkTracker mUnderlyingNetworkTracker;
+    @NonNull protected final UnderlyingNetworkController mUnderlyingNetworkController;
     @NonNull protected final VcnWakeLock mWakeLock;
     @NonNull protected final WakeupMessage mTeardownTimeoutAlarm;
     @NonNull protected final WakeupMessage mDisconnectRequestAlarm;
@@ -158,7 +159,7 @@
         mConfig = VcnGatewayConnectionConfigTest.buildTestConfig();
         mGatewayStatusCallback = mock(VcnGatewayStatusCallback.class);
         mDeps = mock(VcnGatewayConnection.Dependencies.class);
-        mUnderlyingNetworkTracker = mock(UnderlyingNetworkTracker.class);
+        mUnderlyingNetworkController = mock(UnderlyingNetworkController.class);
         mWakeLock = mock(VcnWakeLock.class);
         mTeardownTimeoutAlarm = mock(WakeupMessage.class);
         mDisconnectRequestAlarm = mock(WakeupMessage.class);
@@ -176,9 +177,9 @@
         doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper();
         doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
 
-        doReturn(mUnderlyingNetworkTracker)
+        doReturn(mUnderlyingNetworkController)
                 .when(mDeps)
-                .newUnderlyingNetworkTracker(any(), any(), any(), any());
+                .newUnderlyingNetworkController(any(), any(), any(), any());
         doReturn(mWakeLock)
                 .when(mDeps)
                 .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any());
diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
similarity index 86%
rename from tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
rename to tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
index 5af69b5..c954cb8 100644
--- a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.server.vcn;
+package com.android.server.vcn.routeselection;
 
 import static com.android.server.vcn.VcnTestUtils.setupSystemService;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -48,10 +50,11 @@
 import android.util.ArraySet;
 
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import com.android.server.vcn.UnderlyingNetworkTracker.NetworkBringupCallback;
-import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkListener;
-import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
-import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
+import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.VcnNetworkProvider;
+import com.android.server.vcn.routeselection.UnderlyingNetworkController.NetworkBringupCallback;
+import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback;
+import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkListener;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -64,7 +67,7 @@
 import java.util.Set;
 import java.util.UUID;
 
-public class UnderlyingNetworkTrackerTest {
+public class UnderlyingNetworkControllerTest {
     private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
     private static final int INITIAL_SUB_ID_1 = 1;
     private static final int INITIAL_SUB_ID_2 = 2;
@@ -102,14 +105,14 @@
     @Mock private TelephonyManager mTelephonyManager;
     @Mock private CarrierConfigManager mCarrierConfigManager;
     @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
-    @Mock private UnderlyingNetworkTrackerCallback mNetworkTrackerCb;
+    @Mock private UnderlyingNetworkControllerCallback mNetworkControllerCb;
     @Mock private Network mNetwork;
 
     @Captor private ArgumentCaptor<UnderlyingNetworkListener> mUnderlyingNetworkListenerCaptor;
 
     private TestLooper mTestLooper;
     private VcnContext mVcnContext;
-    private UnderlyingNetworkTracker mUnderlyingNetworkTracker;
+    private UnderlyingNetworkController mUnderlyingNetworkController;
 
     @Before
     public void setUp() {
@@ -140,12 +143,9 @@
 
         when(mSubscriptionSnapshot.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(INITIAL_SUB_IDS);
 
-        mUnderlyingNetworkTracker =
-                new UnderlyingNetworkTracker(
-                        mVcnContext,
-                        SUB_GROUP,
-                        mSubscriptionSnapshot,
-                        mNetworkTrackerCb);
+        mUnderlyingNetworkController =
+                new UnderlyingNetworkController(
+                        mVcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb);
     }
 
     private void resetVcnContext() {
@@ -181,11 +181,8 @@
                         mVcnNetworkProvider,
                         true /* isInTestMode */);
 
-        new UnderlyingNetworkTracker(
-                vcnContext,
-                SUB_GROUP,
-                mSubscriptionSnapshot,
-                mNetworkTrackerCb);
+        new UnderlyingNetworkController(
+                vcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb);
 
         verify(cm)
                 .registerNetworkCallback(
@@ -233,7 +230,7 @@
                 mock(TelephonySubscriptionSnapshot.class);
         when(subscriptionUpdate.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(UPDATED_SUB_IDS);
 
-        mUnderlyingNetworkTracker.updateSubscriptionSnapshot(subscriptionUpdate);
+        mUnderlyingNetworkController.updateSubscriptionSnapshot(subscriptionUpdate);
 
         // verify that initially-filed bringup requests are unregistered (cell + wifi)
         verify(mConnectivityManager, times(INITIAL_SUB_IDS.size() + 3))
@@ -255,7 +252,7 @@
         return getExpectedRequestBase()
                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                 .setSubscriptionIds(netCapsSubIds)
-                .setSignalStrength(UnderlyingNetworkTracker.WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT)
+                .setSignalStrength(WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT)
                 .build();
     }
 
@@ -264,7 +261,7 @@
         return getExpectedRequestBase()
                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                 .setSubscriptionIds(netCapsSubIds)
-                .setSignalStrength(UnderlyingNetworkTracker.WIFI_EXIT_RSSI_THRESHOLD_DEFAULT)
+                .setSignalStrength(WIFI_EXIT_RSSI_THRESHOLD_DEFAULT)
                 .build();
     }
 
@@ -304,7 +301,7 @@
 
     @Test
     public void testTeardown() {
-        mUnderlyingNetworkTracker.teardown();
+        mUnderlyingNetworkController.teardown();
 
         // Expect 5 NetworkBringupCallbacks to be unregistered: 1 for WiFi, 2 for Cellular (1x for
         // each subId), and 1 for each of the Wifi signal strength thresholds
@@ -368,7 +365,7 @@
                         networkCapabilities,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
-        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
         return cb;
     }
 
@@ -384,7 +381,7 @@
                         UPDATED_NETWORK_CAPABILITIES,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
-        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
     }
 
     @Test
@@ -399,7 +396,7 @@
                         INITIAL_NETWORK_CAPABILITIES,
                         UPDATED_LINK_PROPERTIES,
                         false /* isBlocked */);
-        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
     }
 
     @Test
@@ -414,11 +411,13 @@
                         SUSPENDED_NETWORK_CAPABILITIES,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
-        verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verify(mNetworkControllerCb, times(1))
+                .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
         // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't
         // change.
         cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES);
-        verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verify(mNetworkControllerCb, times(1))
+                .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
     }
 
     @Test
@@ -434,11 +433,13 @@
                         INITIAL_NETWORK_CAPABILITIES,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
-        verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verify(mNetworkControllerCb, times(1))
+                .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
         // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't
         // change.
         cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
-        verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verify(mNetworkControllerCb, times(1))
+                .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
     }
 
     @Test
@@ -453,7 +454,7 @@
                         INITIAL_NETWORK_CAPABILITIES,
                         INITIAL_LINK_PROPERTIES,
                         true /* isBlocked */);
-        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
     }
 
     @Test
@@ -462,7 +463,7 @@
 
         cb.onLost(mNetwork);
 
-        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(null);
+        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null);
     }
 
     @Test
@@ -471,20 +472,20 @@
 
         cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
 
-        // Verify no more calls to the UnderlyingNetworkTrackerCallback when the
+        // Verify no more calls to the UnderlyingNetworkControllerCallback when the
         // UnderlyingNetworkRecord does not actually change
-        verifyNoMoreInteractions(mNetworkTrackerCb);
+        verifyNoMoreInteractions(mNetworkControllerCb);
     }
 
     @Test
     public void testRecordTrackerCallbackNotifiedAfterTeardown() {
         UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
-        mUnderlyingNetworkTracker.teardown();
+        mUnderlyingNetworkController.teardown();
 
         cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES);
 
         // Verify that the only call was during onAvailable()
-        verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(any());
+        verify(mNetworkControllerCb, times(1)).onSelectedUnderlyingNetworkChanged(any());
     }
 
     // TODO (b/187991063): Add tests for network prioritization