Merge "Add owners to wear specific resources."
diff --git a/Android.bp b/Android.bp
index 778aa55..95cdea0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -69,6 +69,7 @@
         // Java/AIDL sources under frameworks/base
         ":framework-annotations",
         ":framework-blobstore-sources",
+        ":framework-connectivity-nsd-sources",
         ":framework-core-sources",
         ":framework-drm-sources",
         ":framework-graphics-nonupdatable-sources",
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index d729019..e1e6e47 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -357,7 +357,7 @@
         pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println();
         pw.decreaseIndent();
         pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println();
-        pw.print("Min satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
+        pw.print("Max satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
 
         pw.println();
         pw.println("Actions:");
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
index 7bf0e6d..a4e7b80 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
@@ -33,8 +33,8 @@
     /** Lazily populated set of rewards covered by this policy. */
     private final SparseArray<Reward> mRewards = new SparseArray<>();
     private final int[] mCostModifiers;
-    private final long mMaxSatiatedBalance;
-    private final long mMaxSatiatedCirculation;
+    private long mMaxSatiatedBalance;
+    private long mMaxSatiatedCirculation;
 
     CompleteEconomicPolicy(@NonNull InternalResourceService irs) {
         super(irs);
@@ -53,6 +53,19 @@
             mCostModifiers[i] = costModifiers.valueAt(i);
         }
 
+        updateMaxBalances();
+    }
+
+    @Override
+    void setup() {
+        super.setup();
+        for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
+            mEnabledEconomicPolicies.valueAt(i).setup();
+        }
+        updateMaxBalances();
+    }
+
+    private void updateMaxBalances() {
         long max = 0;
         for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
             max += mEnabledEconomicPolicies.valueAt(i).getMaxSatiatedBalance();
@@ -67,14 +80,6 @@
     }
 
     @Override
-    void setup() {
-        super.setup();
-        for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
-            mEnabledEconomicPolicies.valueAt(i).setup();
-        }
-    }
-
-    @Override
     long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
         long min = 0;
         for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 437a101..20a300a 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -847,15 +847,16 @@
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
 
+            boolean dumpAll = true;
             if (!ArrayUtils.isEmpty(args)) {
                 String arg = args[0];
                 if ("-h".equals(arg) || "--help".equals(arg)) {
                     dumpHelp(pw);
                     return;
                 } else if ("-a".equals(arg)) {
-                    // -a is passed when dumping a bug report so we have to acknowledge the
-                    // argument. However, we currently don't do anything differently for bug
-                    // reports.
+                    // -a is passed when dumping a bug report. Bug reports have a time limit for
+                    // each service dump, so we can't dump everything.
+                    dumpAll = false;
                 } else if (arg.length() > 0 && arg.charAt(0) == '-') {
                     pw.println("Unknown option: " + arg);
                     return;
@@ -864,7 +865,7 @@
 
             final long identityToken = Binder.clearCallingIdentity();
             try {
-                dumpInternal(new IndentingPrintWriter(pw, "  "));
+                dumpInternal(new IndentingPrintWriter(pw, "  "), dumpAll);
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
             }
@@ -1098,7 +1099,7 @@
         pw.println("  [package] is an optional package name to limit the output to.");
     }
 
-    private void dumpInternal(final IndentingPrintWriter pw) {
+    private void dumpInternal(final IndentingPrintWriter pw, final boolean dumpAll) {
         synchronized (mLock) {
             pw.print("Is enabled: ");
             pw.println(mIsEnabled);
@@ -1127,7 +1128,7 @@
             mCompleteEconomicPolicy.dump(pw);
 
             pw.println();
-            mScribe.dumpLocked(pw);
+            mScribe.dumpLocked(pw, dumpAll);
 
             pw.println();
             mAgent.dumpLocked(pw);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index 2318026..1f8ce26 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -332,7 +332,7 @@
         pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println();
         pw.decreaseIndent();
         pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println();
-        pw.print("Min satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
+        pw.print("Max satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
 
         pw.println();
         pw.println("Actions:");
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index 42f3d9a..86968ef 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -509,7 +509,7 @@
     }
 
     @GuardedBy("mIrs.getLock()")
-    void dumpLocked(IndentingPrintWriter pw) {
+    void dumpLocked(IndentingPrintWriter pw, boolean dumpAll) {
         pw.println("Ledgers:");
         pw.increaseIndent();
         mLedgers.forEach((userId, pkgName, ledger) -> {
@@ -519,7 +519,7 @@
             }
             pw.println();
             pw.increaseIndent();
-            ledger.dump(pw, MAX_NUM_TRANSACTION_DUMP);
+            ledger.dump(pw, dumpAll ? Integer.MAX_VALUE : MAX_NUM_TRANSACTION_DUMP);
             pw.decreaseIndent();
         });
         pw.decreaseIndent();
diff --git a/core/api/current.txt b/core/api/current.txt
index 2501c02..fd429f0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -3264,9 +3264,9 @@
     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
+    field public static final int MAGNIFICATION_MODE_DEFAULT = 0; // 0x0
+    field public static final int MAGNIFICATION_MODE_FULLSCREEN = 1; // 0x1
+    field public static final int MAGNIFICATION_MODE_WINDOW = 2; // 0x2
   }
 
   public static final class MagnificationConfig.Builder {
@@ -3275,7 +3275,7 @@
     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);
+    method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setScale(@FloatRange(from=1.0f, to=8.0f) float);
   }
 
   public final class TouchInteractionController {
@@ -9561,6 +9561,20 @@
     field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED";
   }
 
+  public final class BluetoothLeAudioCodecConfig {
+    method @NonNull public String getCodecName();
+    method public int getCodecType();
+    method public static int getMaxCodecType();
+    field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240
+    field public static final int SOURCE_CODEC_TYPE_LC3 = 0; // 0x0
+  }
+
+  public static final class BluetoothLeAudioCodecConfig.Builder {
+    ctor public BluetoothLeAudioCodecConfig.Builder();
+    method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig build();
+    method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setCodecType(int);
+  }
+
   public final class BluetoothManager {
     method public android.bluetooth.BluetoothAdapter getAdapter();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(int);
@@ -9967,6 +9981,9 @@
 
   public final class AssociationRequest implements android.os.Parcelable {
     method public int describeContents();
+    method @Nullable public String getDeviceProfile();
+    method @Nullable public CharSequence getDisplayName();
+    method public boolean isSingleDevice();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationRequest> CREATOR;
     field public static final String DEVICE_PROFILE_WATCH = "android.app.role.COMPANION_DEVICE_WATCH";
@@ -16443,6 +16460,7 @@
     ctor public SurfaceTexture(boolean);
     method public void attachToGLContext(int);
     method public void detachFromGLContext();
+    method public long getDataSpace();
     method public long getTimestamp();
     method public void getTransformMatrix(float[]);
     method public boolean isReleased();
@@ -17660,6 +17678,52 @@
     method public int getMinFrequency();
   }
 
+  public final class DataSpace {
+    method public static long getRange(long);
+    method public static long getStandard(long);
+    method public static long getTransfer(long);
+    method public static long pack(long, long, long);
+    field public static final long DATASPACE_ADOBE_RGB = 151715840L; // 0x90b0000L
+    field public static final long DATASPACE_BT2020 = 147193856L; // 0x8c60000L
+    field public static final long DATASPACE_BT2020_PQ = 163971072L; // 0x9c60000L
+    field public static final long DATASPACE_BT601_525 = 281280512L; // 0x10c40000L
+    field public static final long DATASPACE_BT601_625 = 281149440L; // 0x10c20000L
+    field public static final long DATASPACE_BT709 = 281083904L; // 0x10c10000L
+    field public static final long DATASPACE_DCI_P3 = 155844608L; // 0x94a0000L
+    field public static final long DATASPACE_DISPLAY_P3 = 143261696L; // 0x88a0000L
+    field public static final long DATASPACE_JFIF = 146931712L; // 0x8c20000L
+    field public static final long DATASPACE_SCRGB = 411107328L; // 0x18810000L
+    field public static final long DATASPACE_SCRGB_LINEAR = 406913024L; // 0x18410000L
+    field public static final long DATASPACE_SRGB = 142671872L; // 0x8810000L
+    field public static final long DATASPACE_SRGB_LINEAR = 138477568L; // 0x8410000L
+    field public static final long DATASPACE_UNKNOWN = 0L; // 0x0L
+    field public static final long RANGE_EXTENDED = 402653184L; // 0x18000000L
+    field public static final long RANGE_FULL = 134217728L; // 0x8000000L
+    field public static final long RANGE_LIMITED = 268435456L; // 0x10000000L
+    field public static final long RANGE_UNSPECIFIED = 0L; // 0x0L
+    field public static final long STANDARD_ADOBE_RGB = 720896L; // 0xb0000L
+    field public static final long STANDARD_BT2020 = 393216L; // 0x60000L
+    field public static final long STANDARD_BT2020_CONSTANT_LUMINANCE = 458752L; // 0x70000L
+    field public static final long STANDARD_BT470M = 524288L; // 0x80000L
+    field public static final long STANDARD_BT601_525 = 262144L; // 0x40000L
+    field public static final long STANDARD_BT601_525_UNADJUSTED = 327680L; // 0x50000L
+    field public static final long STANDARD_BT601_625 = 131072L; // 0x20000L
+    field public static final long STANDARD_BT601_625_UNADJUSTED = 196608L; // 0x30000L
+    field public static final long STANDARD_BT709 = 65536L; // 0x10000L
+    field public static final long STANDARD_DCI_P3 = 655360L; // 0xa0000L
+    field public static final long STANDARD_FILM = 589824L; // 0x90000L
+    field public static final long STANDARD_UNSPECIFIED = 0L; // 0x0L
+    field public static final long TRANSFER_GAMMA2_2 = 16777216L; // 0x1000000L
+    field public static final long TRANSFER_GAMMA2_6 = 20971520L; // 0x1400000L
+    field public static final long TRANSFER_GAMMA2_8 = 25165824L; // 0x1800000L
+    field public static final long TRANSFER_HLG = 33554432L; // 0x2000000L
+    field public static final long TRANSFER_LINEAR = 4194304L; // 0x400000L
+    field public static final long TRANSFER_SMPTE_170M = 12582912L; // 0xc00000L
+    field public static final long TRANSFER_SRGB = 8388608L; // 0x800000L
+    field public static final long TRANSFER_ST2084 = 29360128L; // 0x1c00000L
+    field public static final long TRANSFER_UNSPECIFIED = 0L; // 0x0L
+  }
+
   public class GeomagneticField {
     ctor public GeomagneticField(float, float, float, long);
     method public float getDeclination();
@@ -21598,6 +21662,7 @@
   public abstract class Image implements java.lang.AutoCloseable {
     method public abstract void close();
     method public android.graphics.Rect getCropRect();
+    method public long getDataSpace();
     method public abstract int getFormat();
     method @Nullable public android.hardware.HardwareBuffer getHardwareBuffer();
     method public abstract int getHeight();
@@ -21605,6 +21670,7 @@
     method public abstract long getTimestamp();
     method public abstract int getWidth();
     method public void setCropRect(android.graphics.Rect);
+    method public void setDataSpace(long);
     method public void setTimestamp(long);
   }
 
@@ -31790,7 +31856,7 @@
     method @Nullable public <T> T[] readParcelableArray(@Nullable ClassLoader, @NonNull Class<T>);
     method @Deprecated @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
     method @Nullable public <T> android.os.Parcelable.Creator<T> readParcelableCreator(@Nullable ClassLoader, @NonNull Class<T>);
-    method @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
+    method @Deprecated @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
     method @NonNull public <T> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader, @NonNull Class<T>);
     method @Nullable public android.os.PersistableBundle readPersistableBundle();
     method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
@@ -43120,7 +43186,7 @@
     method public boolean isDataCapable();
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isDataConnectionAllowed();
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabled();
-    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabledForReason(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean isDataEnabledForReason(int);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled();
     method public boolean isEmergencyNumber(@NonNull String);
     method public boolean isHearingAidCompatibilitySupported();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fb8e0c7..ddaa96d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -83,6 +83,7 @@
     field public static final String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT";
     field public static final String CAPTURE_VOICE_COMMUNICATION_OUTPUT = "android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT";
     field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
+    field public static final String CHANGE_APP_LAUNCH_TIME_ESTIMATE = "android.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE";
     field public static final String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST";
     field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
     field public static final String COMPANION_APPROVE_WIFI_CONNECTIONS = "android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS";
@@ -996,8 +997,12 @@
     field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
     field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
     field @RequiresPermission(android.Manifest.permission.MANAGE_FACTORY_RESET_PROTECTION) public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
+    field public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
+    field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+    field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
     field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
     field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
+    field public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
     field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
     field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
     field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
@@ -1020,6 +1025,10 @@
     field public static final String REQUIRED_APP_MANAGED_DEVICE = "android.app.REQUIRED_APP_MANAGED_DEVICE";
     field public static final String REQUIRED_APP_MANAGED_PROFILE = "android.app.REQUIRED_APP_MANAGED_PROFILE";
     field public static final String REQUIRED_APP_MANAGED_USER = "android.app.REQUIRED_APP_MANAGED_USER";
+    field public static final int RESULT_DEVICE_OWNER_SET = 123; // 0x7b
+    field public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR = 1; // 0x1
+    field public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR = 2; // 0x2
+    field public static final int RESULT_WORK_PROFILE_CREATED = 122; // 0x7a
     field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4
     field public static final int STATE_USER_PROFILE_FINALIZED = 5; // 0x5
     field public static final int STATE_USER_SETUP_COMPLETE = 2; // 0x2
@@ -1923,6 +1932,8 @@
     method public void reportUsageStop(@NonNull android.app.Activity, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBucket(String, int);
     method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBuckets(java.util.Map<java.lang.String,java.lang.Integer>);
+    method @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE) public void setEstimatedLaunchTime(@NonNull String, long);
+    method @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE) public void setEstimatedLaunchTimes(@NonNull java.util.Map<java.lang.String,java.lang.Long>);
     method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void unregisterAppUsageLimitObserver(int);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterAppUsageObserver(int);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterUsageSessionObserver(int);
@@ -9199,6 +9210,7 @@
 
   public final class StorageVolume implements android.os.Parcelable {
     method @NonNull public String getId();
+    method public boolean isStub();
   }
 
 }
@@ -9762,7 +9774,9 @@
     field public static final int MATCH_ALL_APN_SET_ID = -1; // 0xffffffff
     field public static final String MAX_CONNECTIONS = "max_conns";
     field public static final String MODEM_PERSIST = "modem_cognitive";
-    field public static final String MTU = "mtu";
+    field @Deprecated public static final String MTU = "mtu";
+    field public static final String MTU_V4 = "mtu_v4";
+    field public static final String MTU_V6 = "mtu_v6";
     field public static final int NO_APN_SET_ID = 0; // 0x0
     field public static final String TIME_LIMIT_FOR_MAX_CONNECTIONS = "max_conns_time";
     field public static final int UNEDITED = 0; // 0x0
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c1ab070..6b4d773 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3298,7 +3298,7 @@
     method @NonNull public android.window.WindowContainerTransaction reparentTasks(@Nullable android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, @Nullable int[], @Nullable int[], boolean);
     method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
     method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int);
-    method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken);
+    method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken, boolean);
     method @NonNull public android.window.WindowContainerTransaction setAdjacentTaskFragments(@NonNull android.os.IBinder, @Nullable android.os.IBinder, @Nullable android.window.WindowContainerTransaction.TaskFragmentAdjacentParams);
     method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
     method @NonNull public android.window.WindowContainerTransaction setBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
diff --git a/core/java/android/accessibilityservice/MagnificationConfig.java b/core/java/android/accessibilityservice/MagnificationConfig.java
index 8884508..74c91d6 100644
--- a/core/java/android/accessibilityservice/MagnificationConfig.java
+++ b/core/java/android/accessibilityservice/MagnificationConfig.java
@@ -16,6 +16,7 @@
 
 package android.accessibilityservice;
 
+import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.os.Parcel;
@@ -29,20 +30,21 @@
  * magnification.
  *
  * <p>
- * When the magnification config uses {@link #DEFAULT_MODE},
+ * When the magnification config uses {@link #MAGNIFICATION_MODE_DEFAULT},
  * {@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.
+ * When the magnification config uses {@link #MAGNIFICATION_MODE_FULLSCREEN}.
+ * {@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.
+ * When the magnification config uses {@link #MAGNIFICATION_MODE_WINDOW}.
+ * {@link AccessibilityService} will be able to control the activated window magnifier
+ * on the display.
  * </p>
  *
  * <p>
@@ -54,22 +56,23 @@
 public final class MagnificationConfig implements Parcelable {
 
     /** The controlling magnification mode. It controls the activated magnifier. */
-    public static final int DEFAULT_MODE = 0;
+    public static final int MAGNIFICATION_MODE_DEFAULT = 0;
     /** The controlling magnification mode. It controls fullscreen magnifier. */
-    public static final int FULLSCREEN_MODE = 1;
+    public static final int MAGNIFICATION_MODE_FULLSCREEN = 1;
     /** The controlling magnification mode. It controls window magnifier. */
-    public static final int WINDOW_MODE = 2;
+    public static final int MAGNIFICATION_MODE_WINDOW = 2;
 
+    /** @hide */
     @IntDef(prefix = {"MAGNIFICATION_MODE"}, value = {
-            DEFAULT_MODE,
-            FULLSCREEN_MODE,
-            WINDOW_MODE,
+            MAGNIFICATION_MODE_DEFAULT,
+            MAGNIFICATION_MODE_FULLSCREEN,
+            MAGNIFICATION_MODE_WINDOW,
     })
     @Retention(RetentionPolicy.SOURCE)
-    @interface MAGNIFICATION_MODE {
+    @interface MagnificationMode {
     }
 
-    private int mMode = DEFAULT_MODE;
+    private int mMode = MAGNIFICATION_MODE_DEFAULT;
     private float mScale = Float.NaN;
     private float mCenterX = Float.NaN;
     private float mCenterY = Float.NaN;
@@ -107,9 +110,9 @@
     /**
      * 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
+     * @return the X coordinate. If the controlling magnifier is {@link #MAGNIFICATION_MODE_WINDOW}
+     * but not enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link
+     * #MAGNIFICATION_MODE_FULLSCREEN} but not enabled, it returns 0
      */
     public float getCenterX() {
         return mCenterX;
@@ -118,9 +121,9 @@
     /**
      * 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
+     * @return the Y coordinate If the controlling magnifier is {@link #MAGNIFICATION_MODE_WINDOW}
+     * but not enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link
+     * #MAGNIFICATION_MODE_FULLSCREEN} but not enabled, it returns 0
      */
     public float getCenterY() {
         return mCenterY;
@@ -159,7 +162,7 @@
      */
     public static final class Builder {
 
-        private int mMode = DEFAULT_MODE;
+        private int mMode = MAGNIFICATION_MODE_DEFAULT;
         private float mScale = Float.NaN;
         private float mCenterX = Float.NaN;
         private float mCenterY = Float.NaN;
@@ -177,7 +180,7 @@
          * @return This builder
          */
         @NonNull
-        public MagnificationConfig.Builder setMode(@MAGNIFICATION_MODE int mode) {
+        public MagnificationConfig.Builder setMode(@MagnificationMode int mode) {
             mMode = mode;
             return this;
         }
@@ -185,20 +188,22 @@
         /**
          * Sets the magnification scale.
          *
-         * @param scale The magnification scale
+         * @param scale The magnification scale, in the range [1, 8]
          * @return This builder
          */
         @NonNull
-        public MagnificationConfig.Builder setScale(float scale) {
+        public MagnificationConfig.Builder setScale(@FloatRange(from = 1f, to = 8f) float scale) {
             mScale = scale;
             return this;
         }
 
         /**
          * Sets the X coordinate of the center of the magnification viewport.
+         * The controlling magnifier will apply the given position.
          *
          * @param centerX the screen-relative X coordinate around which to
-         *                center and scale, or {@link Float#NaN} to leave unchanged
+         *                center and scale that is in the range [0, screenWidth],
+         *                or {@link Float#NaN} to leave unchanged
          * @return This builder
          */
         @NonNull
@@ -209,9 +214,11 @@
 
         /**
          * Sets the Y coordinate of the center of the magnification viewport.
+         * The controlling magnifier will apply the given position.
          *
          * @param centerY the screen-relative Y coordinate around which to
-         *                center and scale, or {@link Float#NaN} to leave unchanged
+         *                center and scale that is in the range [0, screenHeight],
+         *                or {@link Float#NaN} to leave unchanged
          * @return This builder
          */
         @NonNull
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index cf95ffe..ee74bb8 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -470,6 +470,105 @@
             = "android.app.action.PROVISION_FINALIZATION";
 
     /**
+     * Activity action: starts the managed profile provisioning flow inside the device management
+     * role holder.
+     *
+     * <p>During the managed profile provisioning flow, the platform-provided provisioning handler
+     * will delegate provisioning to the device management role holder, by firing this intent.
+     * Third-party mobile device management applications attempting to fire this intent will
+     * receive a {@link SecurityException}.
+     *
+     * <p>Device management role holders are required to have a handler for this intent action.
+     *
+     * <p>A result code of {@link Activity#RESULT_OK} implies that managed profile provisioning
+     * finished successfully. If it did not, a result code of {@link Activity#RESULT_CANCELED}
+     * is used instead.
+     *
+     * @see #ACTION_PROVISION_MANAGED_PROFILE
+     *
+     * @hide
+     */
+    // TODO(b/208628038): Uncomment when the permission is in place
+    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE =
+            "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
+
+    /**
+     * Result code that can be returned by the {@link
+     * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} or {@link
+     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent handlers if a work
+     * profile has been created.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RESULT_WORK_PROFILE_CREATED = 122;
+
+    /**
+     * Result code that can be returned by the {@link
+     * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} or {@link
+     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent handlers if the
+     * device owner was set.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RESULT_DEVICE_OWNER_SET = 123;
+
+    /**
+     * Activity action: starts the trusted source provisioning flow inside the device management
+     * role holder.
+     *
+     * <p>During the trusted source provisioning flow, the platform-provided provisioning handler
+     * will delegate provisioning to the device management role holder, by firing this intent.
+     * Third-party mobile device management applications attempting to fire this intent will
+     * receive a {@link SecurityException}.
+     *
+     * <p>Device management role holders are required to have a handler for this intent action.
+     *
+     * <p>The result codes can be either {@link #RESULT_WORK_PROFILE_CREATED}, {@link
+     * #RESULT_DEVICE_OWNER_SET} or {@link Activity#RESULT_CANCELED} if provisioning failed.
+     *
+     * @see #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE
+     *
+     * @hide
+     */
+    // TODO(b/208628038): Uncomment when the permission is in place
+    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE =
+            "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+
+    /**
+     * Activity action: starts the provisioning finalization flow inside the device management
+     * role holder.
+     *
+     * <p>During the provisioning finalization flow, the platform-provided provisioning handler
+     * will delegate provisioning to the device management role holder, by firing this intent.
+     * Third-party mobile device management applications attempting to fire this intent will
+     * receive a {@link SecurityException}.
+     *
+     * <p>Device management role holders are required to have a handler for this intent action.
+     *
+     * <p>This handler forwards the result from the admin app's {@link
+     * #ACTION_ADMIN_POLICY_COMPLIANCE} handler. Result code {@link Activity#RESULT_CANCELED}
+     * implies the provisioning finalization flow has failed.
+     *
+     * @see #ACTION_PROVISION_FINALIZATION
+     *
+     * @hide
+     */
+    // TODO(b/208628038): Uncomment when the permission is in place
+    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION =
+            "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
+
+    /**
      * Action: Bugreport sharing with device owner has been accepted by the user.
      *
      * @hide
@@ -2821,6 +2920,43 @@
             "android.app.action.ADMIN_POLICY_COMPLIANCE";
 
     /**
+     * Activity action: Starts the device management role holder updater.
+     *
+     * <p>The activity must handle the device management role holder update and set the intent
+     * result to either {@link Activity#RESULT_OK} if the update was successful, {@link
+     * #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR} if it encounters a problem
+     * that may be solved by relaunching it again, or {@link
+     * #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR} if it encounters a problem
+     * that will not be solved by relaunching it again.
+     *
+     * @hide
+     */
+    // TODO(b/208628038): Uncomment when the permission is in place
+    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER =
+            "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
+
+    /**
+     * Result code that can be returned by the {@link #ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER}
+     * handler if it encounters a problem that may be solved by relaunching it again.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR = 1;
+
+    /**
+     * Result code that can be returned by the {@link #ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER}
+     * handler if it encounters a problem that will not be solved by relaunching it again.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR = 2;
+
+    /**
      * Maximum supported password length. Kind-of arbitrary.
      * @hide
      */
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index ceab02f..aac23d8 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -103,6 +103,14 @@
     String SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK = "enable_telephony_fallback";
 
     /**
+     * A shell command that dumps a {@link
+     * com.android.server.timezonedetector.MetricsTimeZoneDetectorState} object to stdout for
+     * debugging.
+     * @hide
+     */
+    String SHELL_COMMAND_DUMP_METRICS = "dump_metrics";
+
+    /**
      * A shared utility method to create a {@link ManualTimeZoneSuggestion}.
      *
      * @hide
diff --git a/core/java/android/app/usage/AppLaunchEstimateInfo.java b/core/java/android/app/usage/AppLaunchEstimateInfo.java
new file mode 100644
index 0000000..2085d00
--- /dev/null
+++ b/core/java/android/app/usage/AppLaunchEstimateInfo.java
@@ -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 android.app.usage;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A pair of {package, estimated launch time} to denote the estimated launch time for a given
+ * package.
+ * Used as a vehicle of data across the binder IPC.
+ *
+ * @hide
+ */
+public final class AppLaunchEstimateInfo implements Parcelable {
+
+    public final String packageName;
+    @CurrentTimeMillisLong
+    public final long estimatedLaunchTime;
+
+    private AppLaunchEstimateInfo(Parcel in) {
+        packageName = in.readString();
+        estimatedLaunchTime = in.readLong();
+    }
+
+    public AppLaunchEstimateInfo(String packageName,
+            @CurrentTimeMillisLong long estimatedLaunchTime) {
+        this.packageName = packageName;
+        this.estimatedLaunchTime = estimatedLaunchTime;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(packageName);
+        dest.writeLong(estimatedLaunchTime);
+    }
+
+    @NonNull
+    public static final Creator<AppLaunchEstimateInfo> CREATOR =
+            new Creator<AppLaunchEstimateInfo>() {
+                @Override
+                public AppLaunchEstimateInfo createFromParcel(Parcel source) {
+                    return new AppLaunchEstimateInfo(source);
+                }
+
+                @Override
+                public AppLaunchEstimateInfo[] newArray(int size) {
+                    return new AppLaunchEstimateInfo[size];
+                }
+            };
+}
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 585eb61..170d766 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -51,6 +51,8 @@
     void setAppStandbyBucket(String packageName, int bucket, int userId);
     ParceledListSlice getAppStandbyBuckets(String callingPackage, int userId);
     void setAppStandbyBuckets(in ParceledListSlice appBuckets, int userId);
+    void setEstimatedLaunchTime(String packageName, long estimatedLaunchTime, int userId);
+    void setEstimatedLaunchTimes(in ParceledListSlice appLaunchTimes, int userId);
     void registerAppUsageObserver(int observerId, in String[] packages, long timeLimitMs,
             in PendingIntent callback, String callingPackage);
     void unregisterAppUsageObserver(int observerId, String callingPackage);
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index ac7a318..3cbb24b 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -16,6 +16,7 @@
 
 package android.app.usage;
 
+import android.annotation.CurrentTimeMillisLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -772,6 +773,72 @@
     }
 
     /**
+     * Changes an app's estimated launch time. An app is considered "launched" when a user opens
+     * one of its {@link android.app.Activity Activities}. The provided time is persisted across
+     * reboots and is used unless 1) the time is more than a week in the future and the platform
+     * thinks the app will be launched sooner, 2) the estimated time has passed. Passing in
+     * {@link Long#MAX_VALUE} effectively clears the previously set launch time for the app.
+     *
+     * @param packageName         The package name of the app to set the bucket for.
+     * @param estimatedLaunchTime The next time the app is expected to be launched. Units are in
+     *                            milliseconds since epoch (the same as
+     *                            {@link System#currentTimeMillis()}).
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE)
+    public void setEstimatedLaunchTime(@NonNull String packageName,
+            @CurrentTimeMillisLong long estimatedLaunchTime) {
+        if (packageName == null) {
+            throw new NullPointerException("package name cannot be null");
+        }
+        if (estimatedLaunchTime <= 0) {
+            throw new IllegalArgumentException("estimated launch time must be positive");
+        }
+        try {
+            mService.setEstimatedLaunchTime(packageName, estimatedLaunchTime, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Changes the estimated launch times for multiple apps at once. The map is keyed by the
+     * package name and the value is the estimated launch time.
+     *
+     * @param estimatedLaunchTimes A map of package name to estimated launch time.
+     * @see #setEstimatedLaunchTime(String, long)
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE)
+    public void setEstimatedLaunchTimes(@NonNull Map<String, Long> estimatedLaunchTimes) {
+        if (estimatedLaunchTimes == null) {
+            throw new NullPointerException("estimatedLaunchTimes cannot be null");
+        }
+        final List<AppLaunchEstimateInfo> estimateList =
+                new ArrayList<>(estimatedLaunchTimes.size());
+        for (Map.Entry<String, Long> estimateEntry : estimatedLaunchTimes.entrySet()) {
+            final String pkgName = estimateEntry.getKey();
+            if (pkgName == null) {
+                throw new NullPointerException("package name cannot be null");
+            }
+            final Long estimatedLaunchTime = estimateEntry.getValue();
+            if (estimatedLaunchTime == null || estimatedLaunchTime <= 0) {
+                throw new IllegalArgumentException("estimated launch time must be positive");
+            }
+            estimateList.add(new AppLaunchEstimateInfo(pkgName, estimatedLaunchTime));
+        }
+        final ParceledListSlice<AppLaunchEstimateInfo> slice =
+                new ParceledListSlice<>(estimateList);
+        try {
+            mService.setEstimatedLaunchTimes(slice, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * @hide
      * Register an app usage limit observer that receives a callback on the provided intent when
      * the sum of usages of apps and tokens in the {@code observed} array exceeds the
diff --git a/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java b/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java
new file mode 100644
index 0000000..dcaf4b6
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents the codec configuration for a Bluetooth LE Audio source device.
+ * <p>Contains the source codec type.
+ * <p>The source codec type values are the same as those supported by the
+ * device hardware.
+ *
+ * {@see BluetoothLeAudioCodecConfig}
+ */
+public final class BluetoothLeAudioCodecConfig {
+    // Add an entry for each source codec here.
+
+    /** @hide */
+    @IntDef(prefix = "SOURCE_CODEC_TYPE_", value = {
+            SOURCE_CODEC_TYPE_LC3,
+            SOURCE_CODEC_TYPE_INVALID
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SourceCodecType {};
+
+    public static final int SOURCE_CODEC_TYPE_LC3 = 0;
+    public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000;
+
+    /**
+     * Represents the count of valid source codec types. Can be accessed via
+     * {@link #getMaxCodecType}.
+     */
+    private static final int SOURCE_CODEC_TYPE_MAX = 1;
+
+    private final @SourceCodecType int mCodecType;
+
+    /**
+     * Creates a new BluetoothLeAudioCodecConfig.
+     *
+     * @param codecType the source codec type
+     */
+    private BluetoothLeAudioCodecConfig(@SourceCodecType int codecType) {
+        mCodecType = codecType;
+    }
+
+    @Override
+    public String toString() {
+        return "{codecName:" + getCodecName() + "}";
+    }
+
+    /**
+     * Gets the codec type.
+     *
+     * @return the codec type
+     */
+    public @SourceCodecType int getCodecType() {
+        return mCodecType;
+    }
+
+    /**
+     * Returns the valid codec types count.
+     */
+    public static int getMaxCodecType() {
+        return SOURCE_CODEC_TYPE_MAX;
+    }
+
+    /**
+     * Gets the codec name.
+     *
+     * @return the codec name
+     */
+    public @NonNull String getCodecName() {
+        switch (mCodecType) {
+            case SOURCE_CODEC_TYPE_LC3:
+                return "LC3";
+            case SOURCE_CODEC_TYPE_INVALID:
+                return "INVALID CODEC";
+            default:
+                break;
+        }
+        return "UNKNOWN CODEC(" + mCodecType + ")";
+    }
+
+    /**
+     * Builder for {@link BluetoothLeAudioCodecConfig}.
+     * <p> By default, the codec type will be set to
+     * {@link BluetoothLeAudioCodecConfig#SOURCE_CODEC_TYPE_INVALID}
+     */
+    public static final class Builder {
+        private int mCodecType = BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID;
+
+        /**
+         * Set codec type for Bluetooth codec config.
+         *
+         * @param codecType of this codec
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setCodecType(@SourceCodecType int codecType) {
+            mCodecType = codecType;
+            return this;
+        }
+
+        /**
+         * Build {@link BluetoothLeAudioCodecConfig}.
+         * @return new BluetoothLeAudioCodecConfig built
+         */
+        public @NonNull BluetoothLeAudioCodecConfig build() {
+            return new BluetoothLeAudioCodecConfig(mCodecType);
+        }
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 0cf9f9f..4f2dba75 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -436,6 +436,8 @@
                 return "OPP";
             case HEARING_AID:
                 return "HEARING_AID";
+            case LE_AUDIO:
+                return "LE_AUDIO";
             default:
                 return "UNKNOWN_PROFILE";
         }
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 1dc161c..6e1f8b5 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -119,18 +119,18 @@
      * address, bonded devices are also searched among. This allows to obtain the necessary app
      * privileges even if the device is already paired.
      */
-    private boolean mSingleDevice = false;
+    private final boolean mSingleDevice;
 
     /**
      * If set, only devices matching either of the given filters will be shown to the user
      */
     @DataClass.PluralOf("deviceFilter")
-    private @NonNull List<DeviceFilter<?>> mDeviceFilters = new ArrayList<>();
+    private final @NonNull List<DeviceFilter<?>> mDeviceFilters;
 
     /**
-     * If set, association will be requested as a corresponding kind of device
+     * Profile of the device.
      */
-    private @Nullable @DeviceProfile String mDeviceProfile = null;
+    private final @Nullable @DeviceProfile String mDeviceProfile;
 
     /**
      * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
@@ -157,7 +157,7 @@
      *
      * @hide
      */
-    private @Nullable String mCallingPackage = null;
+    private @Nullable String mCallingPackage;
 
     /**
      * The user-readable description of the device profile's privileges.
@@ -166,7 +166,7 @@
      *
      * @hide
      */
-    private @Nullable String mDeviceProfilePrivilegesDescription = null;
+    private @Nullable String mDeviceProfilePrivilegesDescription;
 
     /**
      * The time at which his request was created
@@ -182,7 +182,22 @@
      *
      * @hide
      */
-    private boolean mSkipPrompt = false;
+    private boolean mSkipPrompt;
+
+    /**
+     * @return profile of the companion device.
+     */
+    public @Nullable @DeviceProfile String getDeviceProfile() {
+        return mDeviceProfile;
+    }
+
+    /**
+     * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
+     * "self-managed" association.
+     */
+    public @Nullable CharSequence getDisplayName() {
+        return mDisplayName;
+    }
 
     /**
      * Whether the association is to be managed by the companion application.
@@ -210,6 +225,17 @@
         return mForceConfirmation;
     }
 
+    /**
+     * Whether only a single device should match the provided filter.
+     *
+     * When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac
+     * address, bonded devices are also searched among. This allows to obtain the necessary app
+     * privileges even if the device is already paired.
+     */
+    public boolean isSingleDevice() {
+        return mSingleDevice;
+    }
+
     /** @hide */
     public void setCallingPackage(@NonNull String pkg) {
         mCallingPackage = pkg;
@@ -226,12 +252,6 @@
     }
 
     /** @hide */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public boolean isSingleDevice() {
-        return mSingleDevice;
-    }
-
-    /** @hide */
     @NonNull
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public List<DeviceFilter<?>> getDeviceFilters() {
@@ -386,7 +406,7 @@
      * @param deviceFilters
      *   If set, only devices matching either of the given filters will be shown to the user
      * @param deviceProfile
-     *   If set, association will be requested as a corresponding kind of device
+     *   Profile of the device.
      * @param displayName
      *   The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
      *   "self-managed" association.
@@ -443,27 +463,6 @@
     }
 
     /**
-     * If set, association will be requested as a corresponding kind of device
-     *
-     * @hide
-     */
-    @DataClass.Generated.Member
-    public @Nullable @DeviceProfile String getDeviceProfile() {
-        return mDeviceProfile;
-    }
-
-    /**
-     * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
-     * "self-managed" association.
-     *
-     * @hide
-     */
-    @DataClass.Generated.Member
-    public @Nullable CharSequence getDisplayName() {
-        return mDisplayName;
-    }
-
-    /**
      * The app package making the request.
      *
      * Populated by the system.
@@ -655,10 +654,10 @@
     };
 
     @DataClass.Generated(
-            time = 1637228802427L,
+            time = 1638368698639L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java",
-            inputSignatures = "public static final  java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\nprivate  boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate final @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate final  boolean mSelfManaged\nprivate final  boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate  long mCreationTime\nprivate  boolean mSkipPrompt\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isSelfManaged()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isForceConfirmation()\npublic  void setCallingPackage(java.lang.String)\npublic  void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic  void setSkipPrompt(boolean)\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nprivate  void onConstructed()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate  boolean mSelfManaged\nprivate  boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false, genConstDefs=false)")
+            inputSignatures = "public static final  java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\nprivate final  boolean mSingleDevice\nprivate final @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate final @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate final @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate final  boolean mSelfManaged\nprivate final  boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate  long mCreationTime\nprivate  boolean mSkipPrompt\npublic @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String getDeviceProfile()\npublic @android.annotation.Nullable java.lang.CharSequence getDisplayName()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isSelfManaged()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isForceConfirmation()\npublic  boolean isSingleDevice()\npublic  void setCallingPackage(java.lang.String)\npublic  void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic  void setSkipPrompt(boolean)\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nprivate  void onConstructed()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate  boolean mSelfManaged\nprivate  boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false, genConstDefs=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
new file mode 100644
index 0000000..65383c5
--- /dev/null
+++ b/core/java/android/hardware/DataSpace.java
@@ -0,0 +1,649 @@
+/*
+ * 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 android.hardware;
+
+import android.annotation.LongDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * DataSpace identifies three components of colors - standard (primaries), transfer and range.
+ *
+ * <p>A DataSpace describes how buffer data, such as from an {@link android.media.Image Image}
+ * or a {@link android.hardware.HardwareBuffer HardwareBuffer}
+ * should be interpreted by both applications and typical hardware.</p>
+ *
+ * <p>As buffer information is not guaranteed to be representative of color information,
+ * while DataSpace is typically used to describe three aspects of interpreting colors,
+ * some DataSpaces may describe other typical interpretations of buffer data
+ * such as depth information.</p>
+ *
+ * <p>Note that while {@link android.graphics.ColorSpace ColorSpace} and {@code DataSpace}
+ * are similar concepts, they are not equivalent. Not all ColorSpaces,
+ * such as {@link android.graphics.ColorSpace.Named#ACES ColorSpace.Named.ACES},
+ * are able to be understood by typical hardware blocks so they cannot be DataSpaces.</p>
+ *
+ * <h3>Standard aspect</h3>
+ *
+ * <p>Defines the chromaticity coordinates of the source primaries in terms of
+ * the CIE 1931 definition of x and y specified in ISO 11664-1.</p>
+ *
+ * <h3>Transfer aspect</h3>
+ *
+ * <p>Transfer characteristics are the opto-electronic transfer characteristic
+ * at the source as a function of linear optical intensity (luminance).</p>
+ *
+ * <p>For digital signals, E corresponds to the recorded value. Normally, the
+ * transfer function is applied in RGB space to each of the R, G and B
+ * components independently. This may result in color shift that can be
+ * minized by applying the transfer function in Lab space only for the L
+ * component. Implementation may apply the transfer function in RGB space
+ * for all pixel formats if desired.</p>
+ *
+ * <h3>Range aspect</h3>
+ *
+ * <p>Defines the range of values corresponding to the unit range of {@code 0-1}.</p>
+ */
+
+public final class DataSpace {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @LongDef(flag = true, value = {
+        STANDARD_UNSPECIFIED,
+        STANDARD_BT709,
+        STANDARD_BT601_625,
+        STANDARD_BT601_625_UNADJUSTED,
+        STANDARD_BT601_525,
+        STANDARD_BT601_525_UNADJUSTED,
+        STANDARD_BT2020,
+        STANDARD_BT2020_CONSTANT_LUMINANCE,
+        STANDARD_BT470M,
+        STANDARD_FILM,
+        STANDARD_DCI_P3,
+        STANDARD_ADOBE_RGB
+    })
+    public @interface DataSpaceStandard {};
+
+    private static final long STANDARD_MASK = 63 << 16;
+
+    /**
+     * Chromacity coordinates are unknown or are determined by the application.
+     */
+    public static final long STANDARD_UNSPECIFIED  = 0 << 16;
+    /**
+     * Use the unadjusted {@code KR = 0.2126}, {@code KB = 0.0722} luminance interpretation
+     * for RGB conversion.
+     *
+     * <pre>
+     * Primaries:       x       y
+     *  green           0.300   0.600
+     *  blue            0.150   0.060
+     *  red             0.640   0.330
+     *  white (D65)     0.3127  0.3290 </pre>
+     */
+    public static final long STANDARD_BT709 = 1 << 16;
+    /**
+     * Use the adjusted {@code KR = 0.299}, {@code KB = 0.114} luminance interpretation
+     * for RGB conversion from the one purely determined by the primaries
+     * to minimize the color shift into RGB space that uses BT.709
+     * primaries.
+     *
+     * <pre>
+     * Primaries:       x       y
+     *  green           0.290   0.600
+     *  blue            0.150   0.060
+     *  red             0.640   0.330
+     *  white (D65)     0.3127  0.3290 </pre>
+     */
+    public static final long STANDARD_BT601_625 = 2 << 16;
+    /**
+     * Use the unadjusted {@code KR = 0.222}, {@code KB = 0.071} luminance interpretation
+     * for RGB conversion.
+     *
+     * <pre>
+     * Primaries:       x       y
+     *  green           0.290   0.600
+     *  blue            0.150   0.060
+     *  red             0.640   0.330
+     *  white (D65)     0.3127  0.3290 </pre>
+     */
+    public static final long STANDARD_BT601_625_UNADJUSTED = 3 << 16;
+    /**
+     * Use the adjusted {@code KR = 0.299}, {@code KB = 0.114} luminance interpretation
+     * for RGB conversion from the one purely determined by the primaries
+     * to minimize the color shift into RGB space that uses BT.709
+     * primaries.
+     *
+     * <pre>
+     * Primaries:       x       y
+     *  green           0.310   0.595
+     *  blue            0.155   0.070
+     *  red             0.630   0.340
+     *  white (D65)     0.3127  0.3290 </pre>
+     */
+    public static final long STANDARD_BT601_525 = 4 << 16;
+    /**
+     * Use the unadjusted {@code KR = 0.212}, {@code KB = 0.087} luminance interpretation
+     * for RGB conversion (as in SMPTE 240M).
+     *
+     * <pre>
+     * Primaries:       x       y
+     *  green           0.310   0.595
+     *  blue            0.155   0.070
+     *  red             0.630   0.340
+     *  white (D65)     0.3127  0.3290 </pre>
+     */
+    public static final long STANDARD_BT601_525_UNADJUSTED = 5 << 16;
+    /**
+     * Use the unadjusted {@code KR = 0.2627}, {@code KB = 0.0593} luminance interpretation
+     * for RGB conversion.
+     *
+     * <pre>
+     * Primaries:       x       y
+     *  green           0.170   0.797
+     *  blue            0.131   0.046
+     *  red             0.708   0.292
+     *  white (D65)     0.3127  0.3290 </pre>
+     */
+    public static final long STANDARD_BT2020 = 6 << 16;
+    /**
+     * Use the unadjusted {@code KR = 0.2627}, {@code KB = 0.0593} luminance interpretation
+     * for RGB conversion using the linear domain.
+     *
+     * <pre>
+     * Primaries:       x       y
+     *  green           0.170   0.797
+     *  blue            0.131   0.046
+     *  red             0.708   0.292
+     *  white (D65)     0.3127  0.3290 </pre>
+     */
+    public static final long STANDARD_BT2020_CONSTANT_LUMINANCE = 7 << 16;
+    /**
+     * Use the unadjusted {@code KR = 0.30}, {@code KB = 0.11} luminance interpretation
+     * for RGB conversion.
+     *
+     * <pre>
+     * Primaries:       x      y
+     *  green           0.21   0.71
+     *  blue            0.14   0.08
+     *  red             0.67   0.33
+     *  white (C)       0.310  0.316 </pre>
+     */
+    public static final long STANDARD_BT470M = 8 << 16;
+    /**
+     * Use the unadjusted {@code KR = 0.254}, {@code KB = 0.068} luminance interpretation
+     * for RGB conversion.
+     *
+     * <pre>
+     * Primaries:       x       y
+     *  green           0.243   0.692
+     *  blue            0.145   0.049
+     *  red             0.681   0.319
+     *  white (C)       0.310   0.316 </pre>
+     */
+    public static final long STANDARD_FILM = 9 << 16;
+    /**
+     * SMPTE EG 432-1 and SMPTE RP 431-2.
+     *
+     * <pre>
+     * Primaries:       x       y
+     *  green           0.265   0.690
+     *  blue            0.150   0.060
+     *  red             0.680   0.320
+     *  white (D65)     0.3127  0.3290 </pre>
+     */
+    public static final long STANDARD_DCI_P3 = 10 << 16;
+    /**
+     * Adobe RGB primaries.
+     *
+     * <pre>
+     * Primaries:       x       y
+     *  green           0.210   0.710
+     *  blue            0.150   0.060
+     *  red             0.640   0.330
+     *  white (D65)     0.3127  0.3290 </pre>
+     */
+    public static final long STANDARD_ADOBE_RGB = 11 << 16;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @LongDef(flag = true, value = {
+        TRANSFER_UNSPECIFIED,
+        TRANSFER_LINEAR,
+        TRANSFER_SRGB,
+        TRANSFER_SMPTE_170M,
+        TRANSFER_GAMMA2_2,
+        TRANSFER_GAMMA2_6,
+        TRANSFER_GAMMA2_8,
+        TRANSFER_ST2084,
+        TRANSFER_HLG
+    })
+    public @interface DataSpaceTransfer {};
+
+    private static final long TRANSFER_MASK = 31 << 22;
+
+    /**
+     * Transfer characteristics are unknown or are determined by the
+     * application.
+     */
+    public static final long TRANSFER_UNSPECIFIED = 0 << 22;
+    /**
+     * Linear transfer.
+     *
+     * <pre>{@code
+     * Transfer characteristic curve:
+     *  E = L
+     *      L - luminance of image 0 <= L <= 1 for conventional colorimetry
+     *      E - corresponding electrical signal}</pre>
+     */
+    public static final long TRANSFER_LINEAR = 1 << 22;
+    /**
+     * sRGB transfer.
+     *
+     * <pre>{@code
+     * Transfer characteristic curve:
+     * E = 1.055 * L^(1/2.4) - 0.055  for 0.0031308 <= L <= 1
+     *   = 12.92 * L                  for 0 <= L < 0.0031308
+     *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
+     *     E - corresponding electrical signal}</pre>
+     *
+     * Use for RGB formats.
+     */
+    public static final long TRANSFER_SRGB = 2 << 22;
+    /**
+     * SMPTE 170M transfer.
+     *
+     * <pre>{@code
+     * Transfer characteristic curve:
+     * E = 1.099 * L ^ 0.45 - 0.099  for 0.018 <= L <= 1
+     *   = 4.500 * L                 for 0 <= L < 0.018
+     *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
+     *     E - corresponding electrical signal}</pre>
+     *
+     * Use for YCbCr formats.
+     */
+    public static final long TRANSFER_SMPTE_170M = 3 << 22;
+    /**
+     * Display gamma 2.2.
+     *
+     * <pre>{@code
+     * Transfer characteristic curve:
+     * E = L ^ (1/2.2)
+     *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
+     *     E - corresponding electrical signal}</pre>
+     */
+    public static final long TRANSFER_GAMMA2_2 = 4 << 22;
+    /**
+     *  Display gamma 2.6.
+     *
+     * <pre>{@code
+     * Transfer characteristic curve:
+     * E = L ^ (1/2.6)
+     *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
+     *     E - corresponding electrical signal}</pre>
+     */
+    public static final long TRANSFER_GAMMA2_6 = 5 << 22;
+    /**
+     *  Display gamma 2.8.
+     *
+     * <pre>{@code
+     * Transfer characteristic curve:
+     * E = L ^ (1/2.8)
+     *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
+     *     E - corresponding electrical signal}</pre>
+     */
+    public static final long TRANSFER_GAMMA2_8 = 6 << 22;
+    /**
+     * SMPTE ST 2084 (Dolby Perceptual Quantizer).
+     *
+     * <pre>{@code
+     * Transfer characteristic curve:
+     * E = ((c1 + c2 * L^n) / (1 + c3 * L^n)) ^ m
+     * c1 = c3 - c2 + 1 = 3424 / 4096 = 0.8359375
+     * c2 = 32 * 2413 / 4096 = 18.8515625
+     * c3 = 32 * 2392 / 4096 = 18.6875
+     * m = 128 * 2523 / 4096 = 78.84375
+     * n = 0.25 * 2610 / 4096 = 0.1593017578125
+     *     L - luminance of image 0 <= L <= 1 for HDR colorimetry.
+     *         L = 1 corresponds to 10000 cd/m2
+     *     E - corresponding electrical signal}</pre>
+     */
+    public static final long TRANSFER_ST2084 = 7 << 22;
+    /**
+     * ARIB STD-B67 Hybrid Log Gamma.
+     *
+     * <pre>{@code
+     * Transfer characteristic curve:
+     * E = r * L^0.5                 for 0 <= L <= 1
+     *   = a * ln(L - b) + c         for 1 < L
+     * a = 0.17883277
+     * b = 0.28466892
+     * c = 0.55991073
+     * r = 0.5
+     *     L - luminance of image 0 <= L for HDR colorimetry. L = 1 corresponds
+     *         to reference white level of 100 cd/m2
+     *     E - corresponding electrical signal}</pre>
+     */
+    public static final long TRANSFER_HLG = 8 << 22;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @LongDef(flag = true, value = {
+        RANGE_UNSPECIFIED,
+        RANGE_FULL,
+        RANGE_LIMITED,
+        RANGE_EXTENDED
+    })
+    public @interface DataSpaceRange {};
+
+    private static final long RANGE_MASK = 7 << 27;
+
+    /**
+     * Range characteristics are unknown or are determined by the application.
+     */
+    public static final long RANGE_UNSPECIFIED = 0 << 27;
+    /**
+     * Full range uses all values for Y, Cb and Cr from
+     * {@code 0} to {@code 2^b-1}, where b is the bit depth of the color format.
+     */
+    public static final long RANGE_FULL = 1 << 27;
+    /**
+     * Limited range uses values {@code 16/256*2^b} to {@code 235/256*2^b} for Y, and
+     * {@code 1/16*2^b} to {@code 15/16*2^b} for Cb, Cr, R, G and B, where b is the bit depth of
+     * the color format.
+     *
+     * <p>E.g. For 8-bit-depth formats:
+     * Luma (Y) samples should range from 16 to 235, inclusive
+     * Chroma (Cb, Cr) samples should range from 16 to 240, inclusive
+     *
+     * For 10-bit-depth formats:
+     * Luma (Y) samples should range from 64 to 940, inclusive
+     * Chroma (Cb, Cr) samples should range from 64 to 960, inclusive. </p>
+     */
+    public static final long RANGE_LIMITED = 2 << 27;
+    /**
+     * Extended range is used for scRGB only.
+     *
+     * <p>Intended for use with floating point pixel formats. [0.0 - 1.0] is the standard
+     * sRGB space. Values outside the range [0.0 - 1.0] can encode
+     * color outside the sRGB gamut. [-0.5, 7.5] is the scRGB range.
+     * Used to blend/merge multiple dataspaces on a single display.</p>
+     */
+    public static final long RANGE_EXTENDED = 3 << 27;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @LongDef(flag = true, value = {
+        DATASPACE_UNKNOWN,
+        DATASPACE_SCRGB_LINEAR,
+        DATASPACE_SRGB,
+        DATASPACE_SCRGB,
+        DATASPACE_DISPLAY_P3,
+        DATASPACE_BT2020_PQ,
+        DATASPACE_ADOBE_RGB,
+        DATASPACE_JFIF,
+        DATASPACE_BT601_625,
+        DATASPACE_BT601_525,
+        DATASPACE_BT2020,
+        DATASPACE_BT709,
+        DATASPACE_DCI_P3,
+        DATASPACE_SRGB_LINEAR
+    })
+    public @interface NamedDataSpace {};
+
+    /**
+     * Default-assumption data space, when not explicitly specified.
+     *
+     * <p>It is safest to assume a buffer is an image with sRGB primaries and
+     * encoding ranges, but the consumer and/or the producer of the data may
+     * simply be using defaults. No automatic gamma transform should be
+     * expected, except for a possible display gamma transform when drawn to a
+     * screen.</p>
+     */
+    public static final long DATASPACE_UNKNOWN = 0;
+    /**
+     * scRGB linear encoding.
+     *
+     * <p>Composed of the following -</p>
+     * <pre>
+     *   Primaries: STANDARD_BT709
+     *   Transfer: TRANSFER_LINEAR
+     *   Range: RANGE_EXTENDED</pre>
+     *
+     * The values are floating point.
+     * A pixel value of 1.0, 1.0, 1.0 corresponds to sRGB white (D65) at 80 nits.
+     * Values beyond the range [0.0 - 1.0] would correspond to other colors
+     * spaces and/or HDR content.
+     */
+    public static final long DATASPACE_SCRGB_LINEAR = 406913024;
+    /**
+     * sRGB gamma encoding.
+     *
+     * <p>Composed of the following -</p>
+     * <pre>
+     *   Primaries: STANDARD_BT709
+     *   Transfer: TRANSFER_SRGB
+     *   Range: RANGE_FULL</pre>
+     *
+     * When written, the inverse transformation is performed.
+     *
+     * The alpha component, if present, is always stored in linear space and
+     * is left unmodified when read or written.
+     */
+    public static final long DATASPACE_SRGB = 142671872;
+    /**
+     * scRGB gamma encoding.
+     *
+     * <p>Composed of the following -</p>
+     * <pre>
+     *   Primaries: STANDARD_BT709
+     *   Transfer: TRANSFER_SRGB
+     *   Range: RANGE_EXTENDED</pre>
+     *
+     * The values are floating point.
+     *
+     * A pixel value of 1.0, 1.0, 1.0 corresponds to sRGB white (D65) at 80 nits.
+     * Values beyond the range [0.0 - 1.0] would correspond to other colors
+     * spaces and/or HDR content.
+     */
+    public static final long DATASPACE_SCRGB = 411107328;
+    /**
+     * Display P3 encoding.
+     *
+     * <p>Composed of the following -</p>
+     * <pre>
+     *   Primaries: STANDARD_DCI_P3
+     *   Transfer: TRANSFER_SRGB
+     *   Range: RANGE_FULL</pre>
+     */
+    public static final long DATASPACE_DISPLAY_P3 = 143261696;
+    /**
+     * ITU-R Recommendation 2020 (BT.2020)
+     *
+     * Ultra High-definition television.
+     *
+     * <p>Composed of the following -</p>
+     * <pre>
+     *   Primaries: STANDARD_BT2020
+     *   Transfer: TRANSFER_ST2084
+     *   Range: RANGE_FULL</pre>
+     */
+    public static final long DATASPACE_BT2020_PQ = 163971072;
+    /**
+     * Adobe RGB encoding.
+     *
+     * <p>Composed of the following -</p>
+     * <pre>
+     *   Primaries: STANDARD_ADOBE_RGB
+     *   Transfer: TRANSFER_GAMMA2_2
+     *   Range: RANGE_FULL</pre>
+     *
+     * Note: Application is responsible for gamma encoding the data.
+     */
+    public static final long DATASPACE_ADOBE_RGB = 151715840;
+    /**
+     * JPEG File Interchange Format (JFIF).
+     *
+     * <p>Composed of the following -</p>
+     * <pre>
+     *   Primaries: STANDARD_BT601_625
+     *   Transfer: TRANSFER_SMPTE_170M
+     *   Range: RANGE_FULL</pre>
+     *
+     * Same model as BT.601-625, but all values (Y, Cb, Cr) range from {@code 0} to {@code 255}
+     */
+    public static final long DATASPACE_JFIF = 146931712;
+    /**
+     * ITU-R Recommendation 601 (BT.601) - 525-line
+     *
+     * Standard-definition television, 525 Lines (NTSC).
+     *
+     * <p>Composed of the following -</p>
+     * <pre>
+     *   Primaries: STANDARD_BT601_625
+     *   Transfer: TRANSFER_SMPTE_170M
+     *   Range: RANGE_LIMITED</pre>
+     */
+    public static final long DATASPACE_BT601_625 = 281149440;
+    /**
+     * ITU-R Recommendation 709 (BT.709)
+     *
+     * High-definition television.
+     *
+     * <p>Composed of the following -</p>
+     * <pre>
+     *   Primaries: STANDARD_BT601_525
+     *   Transfer: TRANSFER_SMPTE_170M
+     *   Range: RANGE_LIMITED</pre>
+     */
+    public static final long DATASPACE_BT601_525 = 281280512;
+    /**
+     * ITU-R Recommendation 2020 (BT.2020)
+     *
+     * Ultra High-definition television.
+     *
+     * <p>Composed of the following -</p>
+     * <pre>
+     *   Primaries: STANDARD_BT2020
+     *   Transfer: TRANSFER_SMPTE_170M
+     *   Range: RANGE_FULL</pre>
+     */
+    public static final long DATASPACE_BT2020 = 147193856;
+    /**
+     * ITU-R Recommendation 709 (BT.709)
+     *
+     * High-definition television.
+     *
+     * <p>Composed of the following -</p>
+     * <pre>
+     *   Primaries: STANDARD_BT709
+     *   Transfer: TRANSFER_SMPTE_170M
+     *   Range: RANGE_LIMITED</pre>
+     */
+    public static final long DATASPACE_BT709 = 281083904;
+    /**
+     * SMPTE EG 432-1 and SMPTE RP 431-2
+     *
+     * Digital Cinema DCI-P3.
+     *
+     * <p>Composed of the following -</p>
+     * <pre>
+     *   Primaries: STANDARD_DCI_P3
+     *   Transfer: TRANSFER_GAMMA2_6
+     *   Range: RANGE_FULL</pre>
+     *
+     * Note: Application is responsible for gamma encoding the data as
+     * a 2.6 gamma encoding is not supported in HW.
+     */
+    public static final long DATASPACE_DCI_P3 = 155844608;
+    /**
+     * sRGB linear encoding.
+     *
+     * <p>Composed of the following -</p>
+     * <pre>
+     *   Primaries: STANDARD_BT709
+     *   Transfer: TRANSFER_LINEAR
+     *   Range: RANGE_FULL</pre>
+     *
+     * The values are encoded using the full range ([0,255] for 8-bit) for all
+     * components.
+     */
+    public static final long DATASPACE_SRGB_LINEAR = 138477568;
+
+    private DataSpace() {}
+
+    /**
+     * Pack the dataSpace value using standard, transfer and range field value.
+     * Field values should be in the correct bits place.
+     *
+     * @param standard Chromaticity coordinates of source primaries
+     * @param transfer Opto-electronic transfer characteristic at the source
+     * @param range The range of values
+     *
+     * @return The long dataspace packed by standard, transfer and range value
+     */
+    public static @NamedDataSpace long pack(@DataSpaceStandard long standard,
+                                        @DataSpaceTransfer long transfer,
+                                        @DataSpaceRange long range) {
+        if ((standard & STANDARD_MASK) != standard) {
+            throw new IllegalArgumentException("Invalid standard " + standard);
+        }
+        if ((transfer & TRANSFER_MASK) != transfer) {
+            throw new IllegalArgumentException("Invalid transfer " + transfer);
+        }
+        if ((range & RANGE_MASK) != range) {
+            throw new IllegalArgumentException("Invalid range " + range);
+        }
+        return standard | transfer | range;
+    }
+
+    /**
+     * Unpack the standard field value from the packed dataSpace value.
+     *
+     * @param dataSpace The packed dataspace value
+     *
+     * @return The standard aspect
+     */
+    public static @DataSpaceStandard long getStandard(@NamedDataSpace long dataSpace) {
+        @DataSpaceStandard long standard = dataSpace & STANDARD_MASK;
+        return standard;
+    }
+
+    /**
+     * Unpack the transfer field value from the packed dataSpace value
+     *
+     * @param dataSpace The packed dataspace value
+     *
+     * @return The transfer aspect
+     */
+    public static @DataSpaceTransfer long getTransfer(@NamedDataSpace long dataSpace) {
+        @DataSpaceTransfer long transfer = dataSpace & TRANSFER_MASK;
+        return transfer;
+    }
+
+    /**
+     * Unpack the range field value from the packed dataSpace value
+     *
+     * @param dataSpace The packed dataspace value
+     *
+     * @return The range aspect
+     */
+    public static @DataSpaceRange long getRange(@NamedDataSpace long dataSpace) {
+        @DataSpaceRange long range = dataSpace & RANGE_MASK;
+        return range;
+    }
+}
diff --git a/core/java/android/hardware/fingerprint/FingerprintStateListener.java b/core/java/android/hardware/fingerprint/FingerprintStateListener.java
index cf914c5..551f512 100644
--- a/core/java/android/hardware/fingerprint/FingerprintStateListener.java
+++ b/core/java/android/hardware/fingerprint/FingerprintStateListener.java
@@ -49,10 +49,10 @@
      * Defines behavior in response to state update
      * @param newState new state of fingerprint sensor
      */
-    public void onStateChanged(@FingerprintStateListener.State int newState) {};
+    public void onStateChanged(@FingerprintStateListener.State int newState) {}
 
     /**
      * Invoked when enrollment state changes for the specified user
      */
-    public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {};
+    public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {}
 }
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
new file mode 100644
index 0000000..6b33e4f
--- /dev/null
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
@@ -0,0 +1,291 @@
+/*
+ * 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.net.vcn;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
+import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
+import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
+import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.PersistableBundle;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.vcn.util.PersistableBundleUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+// TODO: Add documents
+/** @hide */
+public final class VcnCellUnderlyingNetworkPriority extends VcnUnderlyingNetworkPriority {
+    private static final String ALLOWED_NETWORK_PLMN_IDS_KEY = "mAllowedNetworkPlmnIds";
+    @NonNull private final Set<String> mAllowedNetworkPlmnIds;
+    private static final String ALLOWED_SPECIFIC_CARRIER_IDS_KEY = "mAllowedSpecificCarrierIds";
+    @NonNull private final Set<Integer> mAllowedSpecificCarrierIds;
+
+    private static final String ALLOW_ROAMING_KEY = "mAllowRoaming";
+    private final boolean mAllowRoaming;
+
+    private static final String REQUIRE_OPPORTUNISTIC_KEY = "mRequireOpportunistic";
+    private final boolean mRequireOpportunistic;
+
+    private VcnCellUnderlyingNetworkPriority(
+            int networkQuality,
+            boolean allowMetered,
+            Set<String> allowedNetworkPlmnIds,
+            Set<Integer> allowedSpecificCarrierIds,
+            boolean allowRoaming,
+            boolean requireOpportunistic) {
+        super(NETWORK_PRIORITY_TYPE_CELL, networkQuality, allowMetered);
+        mAllowedNetworkPlmnIds = new ArraySet<>(allowedNetworkPlmnIds);
+        mAllowedSpecificCarrierIds = new ArraySet<>(allowedSpecificCarrierIds);
+        mAllowRoaming = allowRoaming;
+        mRequireOpportunistic = requireOpportunistic;
+
+        validate();
+    }
+
+    /** @hide */
+    @Override
+    protected void validate() {
+        super.validate();
+        validatePlmnIds(mAllowedNetworkPlmnIds);
+        Objects.requireNonNull(mAllowedSpecificCarrierIds, "allowedCarrierIds is null");
+    }
+
+    private static void validatePlmnIds(Set<String> allowedNetworkPlmnIds) {
+        Objects.requireNonNull(allowedNetworkPlmnIds, "allowedNetworkPlmnIds is null");
+
+        // A valid PLMN is a concatenation of MNC and MCC, and thus consists of 5 or 6 decimal
+        // digits.
+        for (String id : allowedNetworkPlmnIds) {
+            if ((id.length() == 5 || id.length() == 6) && id.matches("[0-9]+")) {
+                continue;
+            } else {
+                throw new IllegalArgumentException("Found invalid PLMN ID: " + id);
+            }
+        }
+    }
+
+    /** @hide */
+    @NonNull
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public static VcnCellUnderlyingNetworkPriority fromPersistableBundle(
+            @NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle is null");
+
+        final int networkQuality = in.getInt(NETWORK_QUALITY_KEY);
+        final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY);
+
+        final PersistableBundle plmnIdsBundle =
+                in.getPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY);
+        Objects.requireNonNull(plmnIdsBundle, "plmnIdsBundle is null");
+        final Set<String> allowedNetworkPlmnIds =
+                new ArraySet<String>(
+                        PersistableBundleUtils.toList(plmnIdsBundle, STRING_DESERIALIZER));
+
+        final PersistableBundle specificCarrierIdsBundle =
+                in.getPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY);
+        Objects.requireNonNull(specificCarrierIdsBundle, "specificCarrierIdsBundle is null");
+        final Set<Integer> allowedSpecificCarrierIds =
+                new ArraySet<Integer>(
+                        PersistableBundleUtils.toList(
+                                specificCarrierIdsBundle, INTEGER_DESERIALIZER));
+
+        final boolean allowRoaming = in.getBoolean(ALLOW_ROAMING_KEY);
+        final boolean requireOpportunistic = in.getBoolean(REQUIRE_OPPORTUNISTIC_KEY);
+
+        return new VcnCellUnderlyingNetworkPriority(
+                networkQuality,
+                allowMetered,
+                allowedNetworkPlmnIds,
+                allowedSpecificCarrierIds,
+                allowRoaming,
+                requireOpportunistic);
+    }
+
+    /** @hide */
+    @Override
+    @NonNull
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public PersistableBundle toPersistableBundle() {
+        final PersistableBundle result = super.toPersistableBundle();
+
+        final PersistableBundle plmnIdsBundle =
+                PersistableBundleUtils.fromList(
+                        new ArrayList<>(mAllowedNetworkPlmnIds), STRING_SERIALIZER);
+        result.putPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY, plmnIdsBundle);
+
+        final PersistableBundle specificCarrierIdsBundle =
+                PersistableBundleUtils.fromList(
+                        new ArrayList<>(mAllowedSpecificCarrierIds), INTEGER_SERIALIZER);
+        result.putPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY, specificCarrierIdsBundle);
+
+        result.putBoolean(ALLOW_ROAMING_KEY, mAllowRoaming);
+        result.putBoolean(REQUIRE_OPPORTUNISTIC_KEY, mRequireOpportunistic);
+
+        return result;
+    }
+
+    /** Retrieve the allowed PLMN IDs, or an empty set if any PLMN ID is acceptable. */
+    @NonNull
+    public Set<String> getAllowedPlmnIds() {
+        return Collections.unmodifiableSet(mAllowedNetworkPlmnIds);
+    }
+
+    /**
+     * Retrieve the allowed specific carrier IDs, or an empty set if any specific carrier ID is
+     * acceptable.
+     */
+    @NonNull
+    public Set<Integer> getAllowedSpecificCarrierIds() {
+        return Collections.unmodifiableSet(mAllowedSpecificCarrierIds);
+    }
+
+    /** Return if roaming is allowed. */
+    public boolean allowRoaming() {
+        return mAllowRoaming;
+    }
+
+    /** Return if requiring an opportunistic network. */
+    public boolean requireOpportunistic() {
+        return mRequireOpportunistic;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                super.hashCode(),
+                mAllowedNetworkPlmnIds,
+                mAllowedSpecificCarrierIds,
+                mAllowRoaming,
+                mRequireOpportunistic);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (!super.equals(other)) {
+            return false;
+        }
+
+        if (!(other instanceof VcnCellUnderlyingNetworkPriority)) {
+            return false;
+        }
+
+        final VcnCellUnderlyingNetworkPriority rhs = (VcnCellUnderlyingNetworkPriority) other;
+        return Objects.equals(mAllowedNetworkPlmnIds, rhs.mAllowedNetworkPlmnIds)
+                && Objects.equals(mAllowedSpecificCarrierIds, rhs.mAllowedSpecificCarrierIds)
+                && mAllowRoaming == rhs.mAllowRoaming
+                && mRequireOpportunistic == rhs.mRequireOpportunistic;
+    }
+
+    /** This class is used to incrementally build WifiNetworkPriority objects. */
+    public static class Builder extends VcnUnderlyingNetworkPriority.Builder<Builder> {
+        @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();
+        @NonNull private final Set<Integer> mAllowedSpecificCarrierIds = new ArraySet<>();
+
+        private boolean mAllowRoaming = false;
+        private boolean mRequireOpportunistic = false;
+
+        /** Construct a Builder object. */
+        public Builder() {}
+
+        /**
+         * Set allowed operator PLMN IDs.
+         *
+         * <p>This is used to distinguish cases where roaming agreements may dictate a different
+         * priority from a partner's networks.
+         *
+         * @param allowedNetworkPlmnIds the allowed operator PLMN IDs in String. Defaults to an
+         *     empty set, allowing ANY PLMN ID. A valid PLMN is a concatenation of MNC and MCC, and
+         *     thus consists of 5 or 6 decimal digits. See {@link SubscriptionInfo#getMccString()}
+         *     and {@link SubscriptionInfo#getMncString()}.
+         */
+        @NonNull
+        public Builder setAllowedPlmnIds(@NonNull Set<String> allowedNetworkPlmnIds) {
+            validatePlmnIds(allowedNetworkPlmnIds);
+
+            mAllowedNetworkPlmnIds.clear();
+            mAllowedNetworkPlmnIds.addAll(allowedNetworkPlmnIds);
+            return this;
+        }
+
+        /**
+         * Set allowed specific carrier IDs.
+         *
+         * @param allowedSpecificCarrierIds the allowed specific carrier IDs. Defaults to an empty
+         *     set, allowing ANY carrier ID. See {@link TelephonyManager#getSimSpecificCarrierId()}.
+         */
+        @NonNull
+        public Builder setAllowedSpecificCarrierIds(
+                @NonNull Set<Integer> allowedSpecificCarrierIds) {
+            Objects.requireNonNull(allowedSpecificCarrierIds, "allowedCarrierIds is null");
+            mAllowedSpecificCarrierIds.clear();
+            mAllowedSpecificCarrierIds.addAll(allowedSpecificCarrierIds);
+            return this;
+        }
+
+        /**
+         * Set if roaming is allowed.
+         *
+         * @param allowRoaming the flag to indicate if roaming is allowed. Defaults to {@code
+         *     false}.
+         */
+        @NonNull
+        public Builder setAllowRoaming(boolean allowRoaming) {
+            mAllowRoaming = allowRoaming;
+            return this;
+        }
+
+        /**
+         * Set if requiring an opportunistic network.
+         *
+         * @param requireOpportunistic the flag to indicate if caller requires an opportunistic
+         *     network. Defaults to {@code false}.
+         */
+        @NonNull
+        public Builder setRequireOpportunistic(boolean requireOpportunistic) {
+            mRequireOpportunistic = requireOpportunistic;
+            return this;
+        }
+
+        /** Build the VcnCellUnderlyingNetworkPriority. */
+        @NonNull
+        public VcnCellUnderlyingNetworkPriority build() {
+            return new VcnCellUnderlyingNetworkPriority(
+                    mNetworkQuality,
+                    mAllowMetered,
+                    mAllowedNetworkPlmnIds,
+                    mAllowedSpecificCarrierIds,
+                    mAllowRoaming,
+                    mRequireOpportunistic);
+        }
+
+        /** @hide */
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
+}
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index 752ef3e..de4ada2 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -16,6 +16,7 @@
 package android.net.vcn;
 
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 
@@ -41,6 +42,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.Objects;
 import java.util.Set;
 import java.util.SortedSet;
@@ -157,6 +159,34 @@
                 TimeUnit.MINUTES.toMillis(5),
                 TimeUnit.MINUTES.toMillis(15)
             };
+
+    private static final LinkedHashSet<VcnUnderlyingNetworkPriority>
+            DEFAULT_UNDERLYING_NETWORK_PRIORITIES = new LinkedHashSet<>();
+
+    static {
+        DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add(
+                new VcnCellUnderlyingNetworkPriority.Builder()
+                        .setNetworkQuality(NETWORK_QUALITY_OK)
+                        .setAllowMetered(true /* allowMetered */)
+                        .setAllowRoaming(true /* allowRoaming */)
+                        .setRequireOpportunistic(true /* requireOpportunistic */)
+                        .build());
+
+        DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add(
+                new VcnWifiUnderlyingNetworkPriority.Builder()
+                        .setNetworkQuality(NETWORK_QUALITY_OK)
+                        .setAllowMetered(true /* allowMetered */)
+                        .build());
+
+        DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add(
+                new VcnCellUnderlyingNetworkPriority.Builder()
+                        .setNetworkQuality(NETWORK_QUALITY_OK)
+                        .setAllowMetered(true /* allowMetered */)
+                        .setAllowRoaming(true /* allowRoaming */)
+                        .setRequireOpportunistic(false /* requireOpportunistic */)
+                        .build());
+    }
+
     private static final String GATEWAY_CONNECTION_NAME_KEY = "mGatewayConnectionName";
     @NonNull private final String mGatewayConnectionName;
 
@@ -166,6 +196,9 @@
     private static final String EXPOSED_CAPABILITIES_KEY = "mExposedCapabilities";
     @NonNull private final SortedSet<Integer> mExposedCapabilities;
 
+    private static final String UNDERLYING_NETWORK_PRIORITIES_KEY = "mUnderlyingNetworkPriorities";
+    @NonNull private final LinkedHashSet<VcnUnderlyingNetworkPriority> mUnderlyingNetworkPriorities;
+
     private static final String MAX_MTU_KEY = "mMaxMtu";
     private final int mMaxMtu;
 
@@ -177,6 +210,7 @@
             @NonNull String gatewayConnectionName,
             @NonNull IkeTunnelConnectionParams tunnelConnectionParams,
             @NonNull Set<Integer> exposedCapabilities,
+            @NonNull LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities,
             @NonNull long[] retryIntervalsMs,
             @IntRange(from = MIN_MTU_V6) int maxMtu) {
         mGatewayConnectionName = gatewayConnectionName;
@@ -185,6 +219,11 @@
         mRetryIntervalsMs = retryIntervalsMs;
         mMaxMtu = maxMtu;
 
+        mUnderlyingNetworkPriorities = new LinkedHashSet<>(underlyingNetworkPriorities);
+        if (mUnderlyingNetworkPriorities.isEmpty()) {
+            mUnderlyingNetworkPriorities.addAll(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+        }
+
         validate();
     }
 
@@ -198,12 +237,19 @@
 
         final PersistableBundle exposedCapsBundle =
                 in.getPersistableBundle(EXPOSED_CAPABILITIES_KEY);
+        final PersistableBundle networkPrioritiesBundle =
+                in.getPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY);
 
         mGatewayConnectionName = in.getString(GATEWAY_CONNECTION_NAME_KEY);
         mTunnelConnectionParams =
                 TunnelConnectionParamsUtils.fromPersistableBundle(tunnelConnectionParamsBundle);
         mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
                 exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER));
+        mUnderlyingNetworkPriorities =
+                new LinkedHashSet<>(
+                        PersistableBundleUtils.toList(
+                                networkPrioritiesBundle,
+                                VcnUnderlyingNetworkPriority::fromPersistableBundle));
         mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY);
         mMaxMtu = in.getInt(MAX_MTU_KEY);
 
@@ -221,6 +267,7 @@
             checkValidCapability(cap);
         }
 
+        Objects.requireNonNull(mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null");
         Objects.requireNonNull(mRetryIntervalsMs, "retryIntervalsMs was null");
         validateRetryInterval(mRetryIntervalsMs);
 
@@ -303,6 +350,18 @@
     }
 
     /**
+     * Retrieve the configured VcnUnderlyingNetworkPriority list, or a default list if it is not
+     * configured.
+     *
+     * @see Builder#setVcnUnderlyingNetworkPriorities(LinkedHashSet<VcnUnderlyingNetworkPriority>)
+     * @hide
+     */
+    @NonNull
+    public LinkedHashSet<VcnUnderlyingNetworkPriority> getVcnUnderlyingNetworkPriorities() {
+        return new LinkedHashSet<>(mUnderlyingNetworkPriorities);
+    }
+
+    /**
      * Retrieves the configured retry intervals.
      *
      * @see Builder#setRetryIntervalsMillis(long[])
@@ -338,10 +397,15 @@
                 PersistableBundleUtils.fromList(
                         new ArrayList<>(mExposedCapabilities),
                         PersistableBundleUtils.INTEGER_SERIALIZER);
+        final PersistableBundle networkPrioritiesBundle =
+                PersistableBundleUtils.fromList(
+                        new ArrayList<>(mUnderlyingNetworkPriorities),
+                        VcnUnderlyingNetworkPriority::toPersistableBundle);
 
         result.putString(GATEWAY_CONNECTION_NAME_KEY, mGatewayConnectionName);
         result.putPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY, tunnelConnectionParamsBundle);
         result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle);
+        result.putPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY, networkPrioritiesBundle);
         result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs);
         result.putInt(MAX_MTU_KEY, mMaxMtu);
 
@@ -379,6 +443,11 @@
         @NonNull private final String mGatewayConnectionName;
         @NonNull private final IkeTunnelConnectionParams mTunnelConnectionParams;
         @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet();
+
+        @NonNull
+        private final LinkedHashSet<VcnUnderlyingNetworkPriority> mUnderlyingNetworkPriorities =
+                new LinkedHashSet<>(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+
         @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
         private int mMaxMtu = DEFAULT_MAX_MTU;
 
@@ -450,6 +519,33 @@
         }
 
         /**
+         * Set the VcnUnderlyingNetworkPriority list.
+         *
+         * @param underlyingNetworkPriorities a list of unique VcnUnderlyingNetworkPriorities that
+         *     are ordered from most to least preferred, or an empty list to use the default
+         *     prioritization. The default network prioritization is Opportunistic cellular, Carrier
+         *     WiFi and Macro cellular
+         * @return
+         */
+        /** @hide */
+        @NonNull
+        public Builder setVcnUnderlyingNetworkPriorities(
+                @NonNull LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities) {
+            Objects.requireNonNull(
+                    mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null");
+
+            mUnderlyingNetworkPriorities.clear();
+
+            if (underlyingNetworkPriorities.isEmpty()) {
+                mUnderlyingNetworkPriorities.addAll(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+            } else {
+                mUnderlyingNetworkPriorities.addAll(underlyingNetworkPriorities);
+            }
+
+            return this;
+        }
+
+        /**
          * Set the retry interval between VCN establishment attempts upon successive failures.
          *
          * <p>The last retry interval will be repeated until safe mode is entered, or a connection
@@ -513,6 +609,7 @@
                     mGatewayConnectionName,
                     mTunnelConnectionParams,
                     mExposedCapabilities,
+                    mUnderlyingNetworkPriorities,
                     mRetryIntervalsMs,
                     mMaxMtu);
         }
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java
new file mode 100644
index 0000000..82f6ae7
--- /dev/null
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java
@@ -0,0 +1,181 @@
+/*
+ * 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.net.vcn;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+// TODO: Add documents
+/** @hide */
+public abstract class VcnUnderlyingNetworkPriority {
+    /** @hide */
+    protected static final int NETWORK_PRIORITY_TYPE_WIFI = 1;
+    /** @hide */
+    protected static final int NETWORK_PRIORITY_TYPE_CELL = 2;
+
+    /** Denotes that network quality needs to be OK */
+    public static final int NETWORK_QUALITY_OK = 10000;
+    /** Denotes that any network quality is acceptable */
+    public static final int NETWORK_QUALITY_ANY = Integer.MAX_VALUE;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NETWORK_QUALITY_OK, NETWORK_QUALITY_ANY})
+    public @interface NetworkQuality {}
+
+    private static final String NETWORK_PRIORITY_TYPE_KEY = "mNetworkPriorityType";
+    private final int mNetworkPriorityType;
+
+    /** @hide */
+    protected static final String NETWORK_QUALITY_KEY = "mNetworkQuality";
+    private final int mNetworkQuality;
+
+    /** @hide */
+    protected static final String ALLOW_METERED_KEY = "mAllowMetered";
+    private final boolean mAllowMetered;
+
+    /** @hide */
+    protected VcnUnderlyingNetworkPriority(
+            int networkPriorityType, int networkQuality, boolean allowMetered) {
+        mNetworkPriorityType = networkPriorityType;
+        mNetworkQuality = networkQuality;
+        mAllowMetered = allowMetered;
+    }
+
+    private static void validateNetworkQuality(int networkQuality) {
+        Preconditions.checkArgument(
+                networkQuality == NETWORK_QUALITY_ANY || networkQuality == NETWORK_QUALITY_OK,
+                "Invalid networkQuality:" + networkQuality);
+    }
+
+    /** @hide */
+    protected void validate() {
+        validateNetworkQuality(mNetworkQuality);
+    }
+
+    /** @hide */
+    @NonNull
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public static VcnUnderlyingNetworkPriority fromPersistableBundle(
+            @NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle is null");
+
+        final int networkPriorityType = in.getInt(NETWORK_PRIORITY_TYPE_KEY);
+        switch (networkPriorityType) {
+            case NETWORK_PRIORITY_TYPE_WIFI:
+                return VcnWifiUnderlyingNetworkPriority.fromPersistableBundle(in);
+            case NETWORK_PRIORITY_TYPE_CELL:
+                return VcnCellUnderlyingNetworkPriority.fromPersistableBundle(in);
+            default:
+                throw new IllegalArgumentException(
+                        "Invalid networkPriorityType:" + networkPriorityType);
+        }
+    }
+
+    /** @hide */
+    @NonNull
+    PersistableBundle toPersistableBundle() {
+        final PersistableBundle result = new PersistableBundle();
+
+        result.putInt(NETWORK_PRIORITY_TYPE_KEY, mNetworkPriorityType);
+        result.putInt(NETWORK_QUALITY_KEY, mNetworkQuality);
+        result.putBoolean(ALLOW_METERED_KEY, mAllowMetered);
+
+        return result;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNetworkPriorityType, mNetworkQuality, mAllowMetered);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (!(other instanceof VcnUnderlyingNetworkPriority)) {
+            return false;
+        }
+
+        final VcnUnderlyingNetworkPriority rhs = (VcnUnderlyingNetworkPriority) other;
+        return mNetworkPriorityType == rhs.mNetworkPriorityType
+                && mNetworkQuality == rhs.mNetworkQuality
+                && mAllowMetered == rhs.mAllowMetered;
+    }
+
+    /** Retrieve the required network quality. */
+    @NetworkQuality
+    public int getNetworkQuality() {
+        return mNetworkQuality;
+    }
+
+    /** Return if a metered network is allowed. */
+    public boolean allowMetered() {
+        return mAllowMetered;
+    }
+
+    /**
+     * This class is used to incrementally build VcnUnderlyingNetworkPriority objects.
+     *
+     * @param <T> The subclass to be built.
+     */
+    public abstract static class Builder<T extends Builder<T>> {
+        /** @hide */
+        protected int mNetworkQuality = NETWORK_QUALITY_ANY;
+        /** @hide */
+        protected boolean mAllowMetered = false;
+
+        /** @hide */
+        protected Builder() {}
+
+        /**
+         * Set the required network quality.
+         *
+         * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY
+         */
+        @NonNull
+        public T setNetworkQuality(@NetworkQuality int networkQuality) {
+            validateNetworkQuality(networkQuality);
+
+            mNetworkQuality = networkQuality;
+            return self();
+        }
+
+        /**
+         * Set if a metered network is allowed.
+         *
+         * @param allowMetered the flag to indicate if a metered network is allowed, defaults to
+         *     {@code false}
+         */
+        @NonNull
+        public T setAllowMetered(boolean allowMetered) {
+            mAllowMetered = allowMetered;
+            return self();
+        }
+
+        /** @hide */
+        abstract T self();
+    }
+}
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
new file mode 100644
index 0000000..fc7e7e2
--- /dev/null
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
@@ -0,0 +1,120 @@
+/*
+ * 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.net.vcn;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+// TODO: Add documents
+/** @hide */
+public final class VcnWifiUnderlyingNetworkPriority extends VcnUnderlyingNetworkPriority {
+    private static final String SSID_KEY = "mSsid";
+    @Nullable private final String mSsid;
+
+    private VcnWifiUnderlyingNetworkPriority(
+            int networkQuality, boolean allowMetered, String ssid) {
+        super(NETWORK_PRIORITY_TYPE_WIFI, networkQuality, allowMetered);
+        mSsid = ssid;
+
+        validate();
+    }
+
+    /** @hide */
+    @NonNull
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public static VcnWifiUnderlyingNetworkPriority fromPersistableBundle(
+            @NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle is null");
+
+        final int networkQuality = in.getInt(NETWORK_QUALITY_KEY);
+        final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY);
+        final String ssid = in.getString(SSID_KEY);
+        return new VcnWifiUnderlyingNetworkPriority(networkQuality, allowMetered, ssid);
+    }
+
+    /** @hide */
+    @Override
+    @NonNull
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public PersistableBundle toPersistableBundle() {
+        final PersistableBundle result = super.toPersistableBundle();
+        result.putString(SSID_KEY, mSsid);
+        return result;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), mSsid);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (!super.equals(other)) {
+            return false;
+        }
+
+        if (!(other instanceof VcnWifiUnderlyingNetworkPriority)) {
+            return false;
+        }
+
+        final VcnWifiUnderlyingNetworkPriority rhs = (VcnWifiUnderlyingNetworkPriority) other;
+        return mSsid == rhs.mSsid;
+    }
+
+    /** Retrieve the required SSID, or {@code null} if there is no requirement on SSID. */
+    @Nullable
+    public String getSsid() {
+        return mSsid;
+    }
+
+    /** This class is used to incrementally build VcnWifiUnderlyingNetworkPriority objects. */
+    public static class Builder extends VcnUnderlyingNetworkPriority.Builder<Builder> {
+        @Nullable private String mSsid;
+
+        /** Construct a Builder object. */
+        public Builder() {}
+
+        /**
+         * Set the required SSID.
+         *
+         * @param ssid the required SSID, or {@code null} if any SSID is acceptable.
+         */
+        @NonNull
+        public Builder setSsid(@Nullable String ssid) {
+            mSsid = ssid;
+            return this;
+        }
+
+        /** Build the VcnWifiUnderlyingNetworkPriority. */
+        @NonNull
+        public VcnWifiUnderlyingNetworkPriority build() {
+            return new VcnWifiUnderlyingNetworkPriority(mNetworkQuality, mAllowMetered, mSsid);
+        }
+
+        /** @hide */
+        @Override
+        Builder self() {
+            return this;
+        }
+    }
+}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index afd0ff7..3664515 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -3646,7 +3646,14 @@
      * list was {@code null}, {@code list} is cleared.
      *
      * @see #writeParcelableList(List, int)
+     *
+     * @deprecated Use the type-safer version {@link #readParcelableList(List, ClassLoader, Class)}
+     *      starting from Android {@link Build.VERSION_CODES#TIRAMISU}. Also consider changing the
+     *      format to use {@link #readTypedList(List, Parcelable.Creator)} if possible (eg. if the
+     *      items' class is final) since this is also more performant. Note that changing to the
+     *      latter also requires changing the writes.
      */
+    @Deprecated
     @NonNull
     public final <T extends Parcelable> List<T> readParcelableList(@NonNull List<T> list,
             @Nullable ClassLoader cl) {
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index 3e01c53..b7e3068 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -238,7 +238,7 @@
         public static final int DISABLED = 9;
     }
 
-    private IUpdateEngine mUpdateEngine;
+    private final IUpdateEngine mUpdateEngine;
     private IUpdateEngineCallback mUpdateEngineCallback = null;
     private final Object mUpdateEngineCallbackLock = new Object();
 
@@ -248,6 +248,9 @@
     public UpdateEngine() {
         mUpdateEngine = IUpdateEngine.Stub.asInterface(
                 ServiceManager.getService(UPDATE_ENGINE_SERVICE));
+        if (mUpdateEngine == null) {
+            throw new IllegalStateException("Failed to find update_engine");
+        }
     }
 
     /**
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 9612ca6..5831573 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -136,7 +136,7 @@
     /**
      * @hide
      */
-    @IntDef(prefix = { "FLAG_" }, value = {
+    @IntDef(prefix = { "FLAG_" }, flag = true, value = {
             FLAG_BYPASS_INTERRUPTION_POLICY,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -162,7 +162,8 @@
     private final int mFlags;
     private final int mOriginalAudioUsage;
 
-    private VibrationAttributes(int usage, int audioUsage, int flags) {
+    private VibrationAttributes(@Usage int usage, @AudioAttributes.AttributeUsage int audioUsage,
+            @Flag int flags) {
         mUsage = usage;
         mOriginalAudioUsage = audioUsage;
         mFlags = flags & FLAG_ALL_SUPPORTED;
@@ -172,6 +173,7 @@
      * Return the vibration usage class.
      * @return USAGE_CLASS_ALARM, USAGE_CLASS_FEEDBACK or USAGE_CLASS_UNKNOWN
      */
+    @UsageClass
     public int getUsageClass() {
         return mUsage & USAGE_CLASS_MASK;
     }
@@ -180,6 +182,7 @@
      * Return the vibration usage.
      * @return one of the values that can be set in {@link Builder#setUsage(int)}
      */
+    @Usage
     public int getUsage() {
         return mUsage;
     }
@@ -188,6 +191,7 @@
      * Return the flags.
      * @return a combined mask of all flags
      */
+    @Flag
     public int getFlags() {
         return mFlags;
     }
@@ -196,7 +200,7 @@
      * Check whether a flag is set
      * @return true if a flag is set and false otherwise
      */
-    public boolean isFlagSet(int flag) {
+    public boolean isFlagSet(@Flag int flag) {
         return (mFlags & flag) > 0;
     }
 
@@ -206,6 +210,7 @@
      * @hide
      */
     @TestApi
+    @AudioAttributes.AttributeUsage
     public int getAudioUsage() {
         if (mOriginalAudioUsage != AudioAttributes.USAGE_UNKNOWN) {
             // Return same audio usage set in the Builder.
@@ -292,7 +297,7 @@
     }
 
     /** @hide */
-    public static String usageToString(int usage) {
+    public static String usageToString(@Usage int usage) {
         switch (usage) {
             case USAGE_UNKNOWN:
                 return "UNKNOWN";
@@ -419,7 +424,7 @@
          * {@link VibrationAttributes#USAGE_MEDIA}.
          * @return the same Builder instance.
          */
-        public @NonNull Builder setUsage(int usage) {
+        public @NonNull Builder setUsage(@Usage int usage) {
             mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN;
             mUsage = usage;
             return this;
@@ -431,7 +436,7 @@
          * @param mask Bit range that should be changed.
          * @return the same Builder instance.
          */
-        public @NonNull Builder setFlags(int flags, int mask) {
+        public @NonNull Builder setFlags(@Flag int flags, int mask) {
             mask &= FLAG_ALL_SUPPORTED;
             mFlags = (mFlags & ~mask) | (flags & mask);
             return this;
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 2adcbc3..0c2f8b6 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -99,6 +99,7 @@
     @UnsupportedAppUsage
     private final boolean mRemovable;
     private final boolean mEmulated;
+    private final boolean mStub;
     private final boolean mAllowMassStorage;
     private final long mMaxFileSize;
     private final UserHandle mOwner;
@@ -137,8 +138,9 @@
 
     /** {@hide} */
     public StorageVolume(String id, File path, File internalPath, String description,
-            boolean primary, boolean removable, boolean emulated, boolean allowMassStorage,
-            long maxFileSize, UserHandle owner, UUID uuid, String fsUuid, String state) {
+            boolean primary, boolean removable, boolean emulated, boolean stub,
+            boolean allowMassStorage, long maxFileSize, UserHandle owner, UUID uuid, String fsUuid,
+            String state) {
         mId = Preconditions.checkNotNull(id);
         mPath = Preconditions.checkNotNull(path);
         mInternalPath = Preconditions.checkNotNull(internalPath);
@@ -146,6 +148,7 @@
         mPrimary = primary;
         mRemovable = removable;
         mEmulated = emulated;
+        mStub = stub;
         mAllowMassStorage = allowMassStorage;
         mMaxFileSize = maxFileSize;
         mOwner = Preconditions.checkNotNull(owner);
@@ -162,6 +165,7 @@
         mPrimary = in.readInt() != 0;
         mRemovable = in.readInt() != 0;
         mEmulated = in.readInt() != 0;
+        mStub = in.readInt() != 0;
         mAllowMassStorage = in.readInt() != 0;
         mMaxFileSize = in.readLong();
         mOwner = in.readParcelable(null);
@@ -264,13 +268,23 @@
     /**
      * Returns true if the volume is emulated.
      *
-     * @return is removable
+     * @return is emulated
      */
     public boolean isEmulated() {
         return mEmulated;
     }
 
     /**
+     * Returns true if the volume is a stub volume (a volume managed from outside Android).
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isStub() {
+        return mStub;
+    }
+
+    /**
      * Returns true if this volume can be shared via USB mass storage.
      *
      * @return whether mass storage is allowed
@@ -506,6 +520,7 @@
         pw.printPair("mPrimary", mPrimary);
         pw.printPair("mRemovable", mRemovable);
         pw.printPair("mEmulated", mEmulated);
+        pw.printPair("mStub", mStub);
         pw.printPair("mAllowMassStorage", mAllowMassStorage);
         pw.printPair("mMaxFileSize", mMaxFileSize);
         pw.printPair("mOwner", mOwner);
@@ -540,6 +555,7 @@
         parcel.writeInt(mPrimary ? 1 : 0);
         parcel.writeInt(mRemovable ? 1 : 0);
         parcel.writeInt(mEmulated ? 1 : 0);
+        parcel.writeInt(mStub ? 1 : 0);
         parcel.writeInt(mAllowMassStorage ? 1 : 0);
         parcel.writeLong(mMaxFileSize);
         parcel.writeParcelable(mOwner, flags);
@@ -621,6 +637,7 @@
                     mPrimary,
                     mRemovable,
                     mEmulated,
+                    /* stub= */ false,
                     /* allowMassStorage= */ false,
                     /* maxFileSize= */ 0,
                     mOwner,
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 39a2e13..ebd143c 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -404,6 +404,7 @@
 
         final boolean removable;
         final boolean emulated;
+        final boolean stub = type == TYPE_STUB;
         final boolean allowMassStorage = false;
         final String envState = reportUnmounted
                 ? Environment.MEDIA_UNMOUNTED : getEnvironmentForState(state);
@@ -459,8 +460,8 @@
         }
 
         return new StorageVolume(id, userPath, internalPath, description, isPrimary(), removable,
-                emulated, allowMassStorage, maxFileSize, new UserHandle(userId),
-                uuid, derivedFsUuid, envState);
+                emulated, stub, allowMassStorage, maxFileSize, new UserHandle(userId), uuid,
+                derivedFsUuid, envState);
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/os/storage/VolumeRecord.java b/core/java/android/os/storage/VolumeRecord.java
index 1cc982e7..eac09aa 100644
--- a/core/java/android/os/storage/VolumeRecord.java
+++ b/core/java/android/os/storage/VolumeRecord.java
@@ -105,6 +105,7 @@
         final boolean primary = false;
         final boolean removable = true;
         final boolean emulated = false;
+        final boolean stub = false;
         final boolean allowMassStorage = false;
         final long maxFileSize = 0;
         final UserHandle user = new UserHandle(UserHandle.USER_NULL);
@@ -116,7 +117,8 @@
         }
 
         return new StorageVolume(id, userPath, internalPath, description, primary, removable,
-                emulated, allowMassStorage, maxFileSize, user, null /* uuid */, fsUuid, envState);
+                emulated, stub, allowMassStorage, maxFileSize, user, null /* uuid */, fsUuid,
+                envState);
     }
 
     public void dump(IndentingPrintWriter pw) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e267db4..7bb290d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11427,22 +11427,38 @@
                 "night_display_forced_auto_mode_available";
 
         /**
-        * If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment
-        * to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been
-        * exceeded.
-        * @hide
-        */
+         * If UTC time between two NITZ signals is greater than this value then the second signal
+         * cannot be ignored.
+         *
+         * <p>This value is in milliseconds. It is used for telephony-based time and time zone
+         * detection.
+         * @hide
+         */
         @Readable
         public static final String NITZ_UPDATE_DIFF = "nitz_update_diff";
 
         /**
-        * The length of time in milli-seconds that automatic small adjustments to
-        * SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded.
-        * @hide
-        */
+         * If the elapsed realtime between two NITZ signals is greater than this value then the
+         * second signal cannot be ignored.
+         *
+         * <p>This value is in milliseconds. It is used for telephony-based time and time zone
+         * detection.
+         * @hide
+         */
         @Readable
         public static final String NITZ_UPDATE_SPACING = "nitz_update_spacing";
 
+        /**
+         * If the device connects to a telephony network and was disconnected from a telephony
+         * network for less than this time, a previously received NITZ signal can be restored.
+         *
+         * <p>This value is in milliseconds. It is used for telephony-based time and time zone
+         * detection.
+         * @hide
+         */
+        public static final String NITZ_NETWORK_DISCONNECT_RETENTION =
+                "nitz_network_disconnect_retention";
+
         /** Preferred NTP server. {@hide} */
         @Readable
         public static final String NTP_SERVER = "ntp_server";
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 5d84af0..50a44a1 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3775,6 +3775,25 @@
         public static final String NETWORK_TYPE_BITMASK = "network_type_bitmask";
 
         /**
+         * Lingering radio technology (network type) bitmask.
+         * To check what values can be contained, refer to the NETWORK_TYPE_ constants in
+         * {@link android.telephony.TelephonyManager}.
+         * Bitmask for a radio tech R is (1 << (R - 1))
+         * <P>Type: INTEGER (long)</P>
+         * @hide
+         */
+        public static final String LINGERING_NETWORK_TYPE_BITMASK =
+                "lingering_network_type_bitmask";
+
+        /**
+         * Sets whether the PDU session brought up by this APN should always be on.
+         * See 3GPP TS 23.501 section 5.6.13
+         * <P>Type: INTEGER</P>
+         * @hide
+         */
+        public static final String ALWAYS_ON = "always_on";
+
+        /**
          * MVNO type:
          * {@code SPN (Service Provider Name), IMSI, GID (Group Identifier Level 1)}.
          * <P>Type: TEXT</P>
@@ -3852,11 +3871,31 @@
          * connected, in bytes.
          * <p>Type: INTEGER </p>
          * @hide
+         * @deprecated use {@link #MTU_V4} or {@link #MTU_V6} instead
          */
         @SystemApi
+        @Deprecated
         public static final String MTU = "mtu";
 
         /**
+         * The MTU (maximum transmit unit) size of the mobile interface for IPv4 to which the APN is
+         * connected, in bytes.
+         * <p>Type: INTEGER </p>
+         * @hide
+         */
+        @SystemApi
+        public static final String MTU_V4 = "mtu_v4";
+
+        /**
+         * The MTU (maximum transmit unit) size of the mobile interface for IPv6 to which the APN is
+         * connected, in bytes.
+         * <p>Type: INTEGER </p>
+         * @hide
+         */
+        @SystemApi
+        public static final String MTU_V6 = "mtu_v6";
+
+        /**
          * APN edit status. APN could be added/edited/deleted by a user or carrier.
          * see all possible returned APN edit status.
          * <ul>
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index a45a91e..414a7f1 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3681,14 +3681,14 @@
                                     if (!mEdgeGlowBottom.isFinished()) {
                                         mEdgeGlowBottom.onRelease();
                                     }
-                                    invalidateTopGlow();
+                                    invalidateEdgeEffects();
                                 } else if (incrementalDeltaY < 0) {
                                     mEdgeGlowBottom.onPullDistance((float) overscroll / getHeight(),
                                             1.f - (float) x / getWidth());
                                     if (!mEdgeGlowTop.isFinished()) {
                                         mEdgeGlowTop.onRelease();
                                     }
-                                    invalidateBottomGlow();
+                                    invalidateEdgeEffects();
                                 }
                             }
                         }
@@ -3728,7 +3728,7 @@
                             if (!mEdgeGlowBottom.isFinished()) {
                                 mEdgeGlowBottom.onRelease();
                             }
-                            invalidateTopGlow();
+                            invalidateEdgeEffects();
                         } else if (rawDeltaY < 0) {
                             mEdgeGlowBottom.onPullDistance(
                                     (float) -overScrollDistance / getHeight(),
@@ -3736,7 +3736,7 @@
                             if (!mEdgeGlowTop.isFinished()) {
                                 mEdgeGlowTop.onRelease();
                             }
-                            invalidateBottomGlow();
+                            invalidateEdgeEffects();
                         }
                     }
                 }
@@ -3782,17 +3782,21 @@
         // First allow releasing existing overscroll effect:
         float consumed = 0;
         if (mEdgeGlowTop.getDistance() != 0) {
-            consumed = mEdgeGlowTop.onPullDistance((float) deltaY / getHeight(),
-                    (float) x / getWidth());
-            if (consumed != 0f) {
-                invalidateTopGlow();
+            if (canScrollUp()) {
+                mEdgeGlowTop.onRelease();
+            } else {
+                consumed = mEdgeGlowTop.onPullDistance((float) deltaY / getHeight(),
+                        (float) x / getWidth());
             }
+            invalidateEdgeEffects();
         } else if (mEdgeGlowBottom.getDistance() != 0) {
-            consumed = -mEdgeGlowBottom.onPullDistance((float) -deltaY / getHeight(),
-                    1f - (float) x / getWidth());
-            if (consumed != 0f) {
-                invalidateBottomGlow();
+            if (canScrollDown()) {
+                mEdgeGlowBottom.onRelease();
+            } else {
+                consumed = -mEdgeGlowBottom.onPullDistance((float) -deltaY / getHeight(),
+                        1f - (float) x / getWidth());
             }
+            invalidateEdgeEffects();
         }
         int pixelsConsumed = Math.round(consumed * getHeight());
         return deltaY - pixelsConsumed;
@@ -3802,30 +3806,16 @@
      * @return <code>true</code> if either the top or bottom edge glow is currently active or
      * <code>false</code> if it has no value to release.
      */
-    private boolean isGlowActive() {
-        return mEdgeGlowBottom.getDistance() != 0 || mEdgeGlowTop.getDistance() != 0;
+    private boolean doesTouchStopStretch() {
+        return (mEdgeGlowBottom.getDistance() != 0 && !canScrollDown())
+                || (mEdgeGlowTop.getDistance() != 0 && !canScrollUp());
     }
 
-    private void invalidateTopGlow() {
+    private void invalidateEdgeEffects() {
         if (!shouldDisplayEdgeEffects()) {
             return;
         }
-        final boolean clipToPadding = getClipToPadding();
-        final int top = clipToPadding ? mPaddingTop : 0;
-        final int left = clipToPadding ? mPaddingLeft : 0;
-        final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
-        invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
-    }
-
-    private void invalidateBottomGlow() {
-        if (!shouldDisplayEdgeEffects()) {
-            return;
-        }
-        final boolean clipToPadding = getClipToPadding();
-        final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
-        final int left = clipToPadding ? mPaddingLeft : 0;
-        final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
-        invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
+        invalidate();
     }
 
     @Override
@@ -4468,7 +4458,7 @@
                 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
                 canvas.translate(translateX, edgeY);
                 if (mEdgeGlowTop.draw(canvas)) {
-                    invalidateTopGlow();
+                    invalidateEdgeEffects();
                 }
                 canvas.restoreToCount(restoreCount);
             }
@@ -4482,7 +4472,7 @@
                 canvas.translate(edgeX, edgeY);
                 canvas.rotate(180, width, 0);
                 if (mEdgeGlowBottom.draw(canvas)) {
-                    invalidateBottomGlow();
+                    invalidateEdgeEffects();
                 }
                 canvas.restoreToCount(restoreCount);
             }
@@ -4572,7 +4562,7 @@
                 mActivePointerId = ev.getPointerId(0);
 
                 int motionPosition = findMotionRow(y);
-                if (isGlowActive()) {
+                if (doesTouchStopStretch()) {
                     // Pressed during edge effect, so this is considered the same as a fling catch.
                     touchMode = mTouchMode = TOUCH_MODE_FLING;
                 } else if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
@@ -6579,7 +6569,7 @@
      */
     public void setBottomEdgeEffectColor(@ColorInt int color) {
         mEdgeGlowBottom.setColor(color);
-        invalidateBottomGlow();
+        invalidateEdgeEffects();
     }
 
     /**
@@ -6593,7 +6583,7 @@
      */
     public void setTopEdgeEffectColor(@ColorInt int color) {
         mEdgeGlowTop.setColor(color);
-        invalidateTopGlow();
+        invalidateEdgeEffects();
     }
 
     /**
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 7a960c6..5e75797 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -368,10 +368,12 @@
      */
     @NonNull
     public WindowContainerTransaction setAdjacentRoots(
-            @NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2) {
+            @NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2,
+            boolean moveTogether) {
         mHierarchyOps.add(HierarchyOp.createForAdjacentRoots(
                 root1.asBinder(),
-                root2.asBinder()));
+                root2.asBinder(),
+                moveTogether));
         return this;
     }
 
@@ -975,6 +977,9 @@
 
         private boolean mReparentTopOnly;
 
+        // TODO(b/207185041): Remove this once having a single-top root for split screen.
+        private boolean mMoveAdjacentTogether;
+
         @Nullable
         private int[]  mWindowingModes;
 
@@ -1033,10 +1038,13 @@
                     .build();
         }
 
-        public static HierarchyOp createForAdjacentRoots(IBinder root1, IBinder root2) {
+        /** Create a hierarchy op for setting adjacent root tasks. */
+        public static HierarchyOp createForAdjacentRoots(IBinder root1, IBinder root2,
+                boolean moveTogether) {
             return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS)
                     .setContainer(root1)
                     .setReparentContainer(root2)
+                    .setMoveAdjacentTogether(moveTogether)
                     .build();
         }
 
@@ -1070,6 +1078,7 @@
             mReparent = copy.mReparent;
             mToTop = copy.mToTop;
             mReparentTopOnly = copy.mReparentTopOnly;
+            mMoveAdjacentTogether = copy.mMoveAdjacentTogether;
             mWindowingModes = copy.mWindowingModes;
             mActivityTypes = copy.mActivityTypes;
             mLaunchOptions = copy.mLaunchOptions;
@@ -1084,6 +1093,7 @@
             mReparent = in.readStrongBinder();
             mToTop = in.readBoolean();
             mReparentTopOnly = in.readBoolean();
+            mMoveAdjacentTogether = in.readBoolean();
             mWindowingModes = in.createIntArray();
             mActivityTypes = in.createIntArray();
             mLaunchOptions = in.readBundle();
@@ -1128,6 +1138,10 @@
             return mReparentTopOnly;
         }
 
+        public boolean getMoveAdjacentTogether() {
+            return mMoveAdjacentTogether;
+        }
+
         public int[] getWindowingModes() {
             return mWindowingModes;
         }
@@ -1175,7 +1189,8 @@
                     return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}";
                 case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
                     return "{SetAdjacentRoot: container=" + mContainer
-                            + " adjacentRoot=" + mReparent + "}";
+                            + " adjacentRoot=" + mReparent + " mMoveAdjacentTogether="
+                            + mMoveAdjacentTogether + "}";
                 case HIERARCHY_OP_TYPE_LAUNCH_TASK:
                     return "{LaunchTask: " + mLaunchOptions + "}";
                 case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT:
@@ -1212,6 +1227,7 @@
             dest.writeStrongBinder(mReparent);
             dest.writeBoolean(mToTop);
             dest.writeBoolean(mReparentTopOnly);
+            dest.writeBoolean(mMoveAdjacentTogether);
             dest.writeIntArray(mWindowingModes);
             dest.writeIntArray(mActivityTypes);
             dest.writeBundle(mLaunchOptions);
@@ -1251,6 +1267,8 @@
 
             private boolean mReparentTopOnly;
 
+            private boolean mMoveAdjacentTogether;
+
             @Nullable
             private int[]  mWindowingModes;
 
@@ -1293,6 +1311,11 @@
                 return this;
             }
 
+            Builder setMoveAdjacentTogether(boolean moveAdjacentTogether) {
+                mMoveAdjacentTogether = moveAdjacentTogether;
+                return this;
+            }
+
             Builder setWindowingModes(@Nullable int[] windowingModes) {
                 mWindowingModes = windowingModes;
                 return this;
@@ -1336,6 +1359,7 @@
                         : null;
                 hierarchyOp.mToTop = mToTop;
                 hierarchyOp.mReparentTopOnly = mReparentTopOnly;
+                hierarchyOp.mMoveAdjacentTogether = mMoveAdjacentTogether;
                 hierarchyOp.mLaunchOptions = mLaunchOptions;
                 hierarchyOp.mActivityIntent = mActivityIntent;
                 hierarchyOp.mPendingIntent = mPendingIntent;
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index 0723766..4dc9aa5 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -328,7 +328,7 @@
                 if (multiStateCounter != null) {
                     if (mAccumulatedMultiStateChargeMicroCoulomb == null) {
                         mAccumulatedMultiStateChargeMicroCoulomb =
-                                new LongMultiStateCounter[numWrittenEntries];
+                                new LongMultiStateCounter[mAccumulatedChargeMicroCoulomb.length];
                     }
                     mAccumulatedMultiStateChargeMicroCoulomb[index] = multiStateCounter;
                 }
diff --git a/core/jni/android_graphics_SurfaceTexture.cpp b/core/jni/android_graphics_SurfaceTexture.cpp
index 2f12289..0f647ea 100644
--- a/core/jni/android_graphics_SurfaceTexture.cpp
+++ b/core/jni/android_graphics_SurfaceTexture.cpp
@@ -346,6 +346,11 @@
     return surfaceTexture->getTimestamp();
 }
 
+static jlong SurfaceTexture_getDataSpace(JNIEnv* env, jobject thiz) {
+    sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));
+    return surfaceTexture->getCurrentDataSpace();
+}
+
 static void SurfaceTexture_release(JNIEnv* env, jobject thiz)
 {
     sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));
@@ -361,17 +366,18 @@
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gSurfaceTextureMethods[] = {
-    {"nativeInit",                 "(ZIZLjava/lang/ref/WeakReference;)V", (void*)SurfaceTexture_init },
-    {"nativeFinalize",             "()V",   (void*)SurfaceTexture_finalize },
-    {"nativeSetDefaultBufferSize", "(II)V", (void*)SurfaceTexture_setDefaultBufferSize },
-    {"nativeUpdateTexImage",       "()V",   (void*)SurfaceTexture_updateTexImage },
-    {"nativeReleaseTexImage",      "()V",   (void*)SurfaceTexture_releaseTexImage },
-    {"nativeDetachFromGLContext",  "()I",   (void*)SurfaceTexture_detachFromGLContext },
-    {"nativeAttachToGLContext",    "(I)I",   (void*)SurfaceTexture_attachToGLContext },
-    {"nativeGetTransformMatrix",   "([F)V", (void*)SurfaceTexture_getTransformMatrix },
-    {"nativeGetTimestamp",         "()J",   (void*)SurfaceTexture_getTimestamp },
-    {"nativeRelease",              "()V",   (void*)SurfaceTexture_release },
-    {"nativeIsReleased",           "()Z",   (void*)SurfaceTexture_isReleased },
+        {"nativeInit", "(ZIZLjava/lang/ref/WeakReference;)V", (void*)SurfaceTexture_init},
+        {"nativeFinalize", "()V", (void*)SurfaceTexture_finalize},
+        {"nativeSetDefaultBufferSize", "(II)V", (void*)SurfaceTexture_setDefaultBufferSize},
+        {"nativeUpdateTexImage", "()V", (void*)SurfaceTexture_updateTexImage},
+        {"nativeReleaseTexImage", "()V", (void*)SurfaceTexture_releaseTexImage},
+        {"nativeDetachFromGLContext", "()I", (void*)SurfaceTexture_detachFromGLContext},
+        {"nativeAttachToGLContext", "(I)I", (void*)SurfaceTexture_attachToGLContext},
+        {"nativeGetTransformMatrix", "([F)V", (void*)SurfaceTexture_getTransformMatrix},
+        {"nativeGetTimestamp", "()J", (void*)SurfaceTexture_getTimestamp},
+        {"nativeGetDataSpace", "()J", (void*)SurfaceTexture_getDataSpace},
+        {"nativeRelease", "()V", (void*)SurfaceTexture_release},
+        {"nativeIsReleased", "()Z", (void*)SurfaceTexture_isReleased},
 };
 
 int register_android_graphics_SurfaceTexture(JNIEnv* env)
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 268871b..8b45907 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2284,10 +2284,8 @@
     return jStatus;
 }
 
-static jint
-android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP(
-                        JNIEnv *env, jobject thiz, jobject jEncodingFormatList)
-{
+static jint android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia(
+        JNIEnv *env, jobject thiz, jint deviceType, jobject jEncodingFormatList) {
     ALOGV("%s", __FUNCTION__);
     jint jStatus = AUDIO_JAVA_SUCCESS;
     if (!env->IsInstanceOf(jEncodingFormatList, gArrayListClass)) {
@@ -2295,8 +2293,10 @@
         return (jint)AUDIO_JAVA_BAD_VALUE;
     }
     std::vector<audio_format_t> encodingFormats;
-    status_t status = AudioSystem::getHwOffloadEncodingFormatsSupportedForA2DP(
-                          &encodingFormats);
+    status_t status =
+            AudioSystem::getHwOffloadFormatsSupportedForBluetoothMedia(static_cast<audio_devices_t>(
+                                                                               deviceType),
+                                                                       &encodingFormats);
     if (status != NO_ERROR) {
         ALOGE("%s: error %d", __FUNCTION__, status);
         jStatus = nativeToJavaStatus(status);
@@ -2875,8 +2875,8 @@
          {"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids},
          {"isHapticPlaybackSupported", "()Z",
           (void *)android_media_AudioSystem_isHapticPlaybackSupported},
-         {"getHwOffloadEncodingFormatsSupportedForA2DP", "(Ljava/util/ArrayList;)I",
-          (void *)android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP},
+         {"getHwOffloadFormatsSupportedForBluetoothMedia", "(ILjava/util/ArrayList;)I",
+          (void *)android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia},
          {"setSupportedSystemUsages", "([I)I",
           (void *)android_media_AudioSystem_setSupportedSystemUsages},
          {"setAllowedCapturePolicy", "(II)I",
@@ -2919,7 +2919,6 @@
           "[Landroid/media/AudioDeviceAttributes;)Z",
           (void *)android_media_AudioSystem_canBeSpatialized}};
 
-
 static const JNINativeMethod gEventHandlerMethods[] = {
     {"native_setup",
         "(Ljava/lang/Object;)V",
diff --git a/core/proto/android/app/time_zone_detector.proto b/core/proto/android/app/time_zone_detector.proto
index b33ca1d..b52aa82 100644
--- a/core/proto/android/app/time_zone_detector.proto
+++ b/core/proto/android/app/time_zone_detector.proto
@@ -32,16 +32,8 @@
 }
 
 /*
- * An obfuscated and simplified time zone suggestion for metrics use.
- *
- * The suggestion's time zone IDs (which relate to location) are obfuscated by
- * mapping them to an ordinal. When the ordinal is assigned consistently across
- * several objects (i.e. so the same time zone ID is always mapped to the same
- * ordinal), this allows comparisons between those objects. For example, we can
- * answer "did these two suggestions agree?", "does the suggestion match the
- * device's current time zone?", without leaking knowledge of location. Ordinals
- * are also significantly more compact than full IANA TZDB IDs, albeit highly
- * unstable and of limited use.
+ * A generic-form time zone suggestion for metrics use. Required to be a superset of the
+ * MetricsTimeZoneSuggestion proto defined in atoms.proto to ensure binary compatibility.
  */
 message MetricsTimeZoneSuggestion {
   option (android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -55,5 +47,24 @@
   // The ordinals for time zone(s) in the suggestion. Always empty for
   // UNCERTAIN, and can be empty for CERTAIN, for example when the device is in
   // a disputed area / on an ocean.
-  repeated uint32 time_zone_ordinals = 2;
+  //
+  // The suggestion's time zone IDs (which relate to location) are obfuscated by
+  // mapping them to an ordinal. When the ordinal is assigned consistently across
+  // several objects (i.e. so the same time zone ID is always mapped to the same
+  // ordinal), this allows comparisons between those objects. For example, we can
+  // answer "did these two suggestions agree?", "does the suggestion match the
+  // device's current time zone?", without leaking knowledge of location. Ordinals
+  // are also significantly more compact than full IANA TZDB IDs, albeit unstable
+  // and of limited use.
+  repeated int32 time_zone_ordinals = 2;
+
+  // The actual time zone ID(s) in the suggestion. Similar to time_zone_ordinals
+  // but contains the actual string IDs.
+  //
+  // This information is only captured / reported for some devices based on the
+  // value of a server side flag, i.e. it could be enabled for internal testers.
+  // Therefore the list can be empty even when time_zone_ordinals is populated.
+  //
+  // When enabled, see time_zone_ordinals for the expected number of values.
+  repeated string time_zone_ids = 3;
 }
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index db5d49d..b406578 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -672,18 +672,31 @@
     optional SettingProto new_contact_aggregator = 79 [ (android.privacy).dest = DEST_AUTOMATIC ];
     optional SettingProto night_display_forced_auto_mode_available = 80 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
-    message NitzUpdate {
+    message Nitz {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
 
-        // If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment to
-        // SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been
-        // exceeded.
-        optional SettingProto diff = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
-        // The length of time in milli-seconds that automatic small adjustments to
-        // SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded.
-        optional SettingProto spacing = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        // If UTC time between two NITZ signals is greater than this value then the second signal
+        // cannot be ignored.
+        //
+        // This value is in milliseconds. It is used for telephony-based time and time zone
+        // detection.
+        optional SettingProto update_diff = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+        // If the elapsed realtime between two NITZ signals is greater than this value then the
+        // second signal cannot be ignored.
+        //
+        // This value is in milliseconds. It is used for telephony-based time and time zone
+        // detection.
+        optional SettingProto update_spacing = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+        // If the device connects to a telephony network and was disconnected from a telephony
+        // network for less than this time, a previously received NITZ signal can be restored.
+        //
+        // This value is in milliseconds. It is used for telephony-based time and time zone
+        // detection.
+        optional SettingProto network_disconnect_retention = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
-    optional NitzUpdate nitz_update = 81;
+    optional Nitz nitz = 81;
 
     message Notification {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index aada7eb..316ea34 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4880,6 +4880,11 @@
     <permission android:name="android.permission.CHANGE_APP_IDLE_STATE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @hide @SystemApi Allows an application to change the estimated launch time of an app.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @hide @SystemApi Allows an application to temporarily allowlist an inactive app to
          access the network and acquire wakelocks.
          <p>Not for use by third-party applications. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f6a0e61..688bced 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3505,20 +3505,35 @@
 
     <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
     is pressed during fingerprint enrollment. -->
-    <string name="fp_enrollment_powerbutton_intent_title">Turn off screen?</string>
+    <string name="fp_power_button_enrollment_title">Continue setup?</string>
 
     <!-- [CHAR LIMIT=NONE] Message of dialog shown to confirm device going to sleep if the power
     button is pressed during fingerprint enrollment. -->
-    <string name="fp_enrollment_powerbutton_intent_message">While setting up your fingerprint, you
-        pressed the Power button.\n\nThis usually turns off your screen.</string>
+    <string name="fp_power_button_enrollment_message">You pressed the power button — this usually turns off the screen.\n\nTry tapping lightly while setting up your fingerprint.</string>
 
     <!-- [CHAR LIMIT=20] Positive button of dialog shown to confirm device going to sleep if the
     power button is pressed during fingerprint enrollment. -->
-    <string name="fp_enrollment_powerbutton_intent_positive_button">Turn off</string>
+    <string name="fp_power_button_enrollment_positive_button">Turn off screen</string>
 
     <!-- [CHAR LIMIT=20] Negative button of dialog shown to confirm device going to sleep if the
     power button is pressed during fingerprint enrollment. -->
-    <string name="fp_enrollment_powerbutton_intent_negative_button">Cancel</string>
+    <string name="fp_power_button_enrollment_negative_button">Continue setup</string>
+
+    <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
+    is pressed during biometric prompt when a side fingerprint sensor is present. -->
+    <string name="fp_power_button_bp_title">Continue verifying your fingerprint?</string>
+
+    <!-- [CHAR LIMIT=NONE] Message of dialog shown to confirm device going to sleep if the power
+    button is pressed during biometric prompt when a side fingerprint sensor is present. -->
+    <string name="fp_power_button_bp_message">You pressed the power button — this usually turns off the screen.\n\nTry tapping lightly to verify your fingerprint.</string>
+
+    <!-- [CHAR LIMIT=20] Positive button of dialog shown to confirm device going to sleep if the
+    power button is pressed during biometric prompt when a side fingerprint sensor is present. -->
+    <string name="fp_power_button_bp_positive_button">Turn off screen</string>
+
+    <!-- [CHAR LIMIT=20] Negative button of dialog shown to confirm device going to sleep if the
+    power button is pressed during biometric prompt when a side fingerprint sensor is present. -->
+    <string name="fp_power_button_bp_negative_button">Continue</string>
 
     <!-- Notification text to tell the user that a heavy-weight application is running. -->
     <string name="heavy_weight_notification"><xliff:g id="app">%1$s</xliff:g> running</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0495122..76e9774 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1840,10 +1840,14 @@
   <java-symbol type="string" name="bugreport_status" />
   <java-symbol type="string" name="bugreport_title" />
   <java-symbol type="string" name="faceunlock_multiple_failures" />
-  <java-symbol type="string" name="fp_enrollment_powerbutton_intent_title" />
-  <java-symbol type="string" name="fp_enrollment_powerbutton_intent_message" />
-  <java-symbol type="string" name="fp_enrollment_powerbutton_intent_positive_button" />
-  <java-symbol type="string" name="fp_enrollment_powerbutton_intent_negative_button" />
+  <java-symbol type="string" name="fp_power_button_bp_title" />
+  <java-symbol type="string" name="fp_power_button_bp_message" />
+  <java-symbol type="string" name="fp_power_button_bp_positive_button" />
+  <java-symbol type="string" name="fp_power_button_bp_negative_button" />
+  <java-symbol type="string" name="fp_power_button_enrollment_title" />
+  <java-symbol type="string" name="fp_power_button_enrollment_message" />
+  <java-symbol type="string" name="fp_power_button_enrollment_positive_button" />
+  <java-symbol type="string" name="fp_power_button_enrollment_negative_button" />
   <java-symbol type="string" name="global_actions" />
   <java-symbol type="string" name="global_action_power_off" />
   <java-symbol type="string" name="global_action_power_options" />
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java
new file mode 100644
index 0000000..6471492
--- /dev/null
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.bluetooth;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit test cases for {@link BluetoothLeAudioCodecConfig}.
+ */
+public class BluetoothLeAudioCodecConfigTest extends TestCase {
+    private int[] mCodecTypeArray = new int[] {
+        BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3,
+        BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID,
+    };
+
+    @SmallTest
+    public void testBluetoothLeAudioCodecConfig_valid_get_methods() {
+
+        for (int codecIdx = 0; codecIdx < mCodecTypeArray.length; codecIdx++) {
+            int codecType = mCodecTypeArray[codecIdx];
+
+            BluetoothLeAudioCodecConfig leAudioCodecConfig =
+                    buildBluetoothLeAudioCodecConfig(codecType);
+
+            if (codecType == BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) {
+                assertEquals("LC3", leAudioCodecConfig.getCodecName());
+            }
+            if (codecType == BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
+                assertEquals("INVALID CODEC", leAudioCodecConfig.getCodecName());
+            }
+
+            assertEquals(1, leAudioCodecConfig.getMaxCodecType());
+            assertEquals(codecType, leAudioCodecConfig.getCodecType());
+        }
+    }
+
+    private BluetoothLeAudioCodecConfig buildBluetoothLeAudioCodecConfig(int sourceCodecType) {
+        return new BluetoothLeAudioCodecConfig.Builder()
+                    .setCodecType(sourceCodecType)
+                    .build();
+
+    }
+}
diff --git a/core/tests/coretests/res/drawable/custom_drawable.xml b/core/tests/coretests/res/drawable/custom_drawable.xml
new file mode 100644
index 0000000..ebb821f
--- /dev/null
+++ b/core/tests/coretests/res/drawable/custom_drawable.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+
+<drawable xmlns:android="http://schemas.android.com/apk/res/android"
+          class="android.window.CustomDrawable"
+          android:drawable="@drawable/bitmap_drawable"
+          android:inset="10dp" />
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/window/CustomDrawable.java b/core/tests/coretests/src/android/window/CustomDrawable.java
new file mode 100644
index 0000000..c25f877
--- /dev/null
+++ b/core/tests/coretests/src/android/window/CustomDrawable.java
@@ -0,0 +1,25 @@
+/*
+ * 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.graphics.drawable.InsetDrawable;
+
+public class CustomDrawable extends InsetDrawable {
+    public CustomDrawable() {
+        super(null, 0);
+    }
+}
diff --git a/core/tests/coretests/src/android/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java
index 83280f1..656e756 100644
--- a/core/tests/coretests/src/android/window/WindowContextTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextTest.java
@@ -23,6 +23,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
@@ -47,6 +48,8 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.frameworks.coretests.R;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -242,6 +245,12 @@
         mInstrumentation.runOnMainSync(() -> wm.addView(subWindow, subWindowAttrs));
     }
 
+    @Test
+    public void testGetCustomDrawable() {
+        assertNotNull(mWindowContext.getResources().getDrawable(R.drawable.custom_drawable,
+                null /* theme */));
+    }
+
     private WindowContext createWindowContext() {
         return createWindowContext(TYPE_APPLICATION_OVERLAY);
     }
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index cfcbc7d..f8db0606 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -269,7 +269,7 @@
         onView(withId(R.id.content_preview_thumbnail)).check(matches(isDisplayed()));
     }
 
-    @Test @Ignore
+    @Test
     public void twoOptionsAndUserSelectsOne() throws InterruptedException {
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -298,7 +298,7 @@
         assertThat(chosen[0], is(toChoose));
     }
 
-    @Test @Ignore
+    @Test
     public void fourOptionsStackedIntoOneTarget() throws InterruptedException {
         Intent sendIntent = createSendTextIntent();
 
@@ -351,7 +351,7 @@
         }
     }
 
-    @Test @Ignore
+    @Test
     public void updateChooserCountsAndModelAfterUserSelection() throws InterruptedException {
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -461,7 +461,7 @@
         assertThat(chosen[0], is(toChoose));
     }
 
-    @Test @Ignore
+    @Test
     public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
@@ -500,7 +500,7 @@
         assertThat(chosen[0], is(toChoose));
     }
 
-    @Test @Ignore
+    @Test
     public void hasLastChosenActivityAndOtherProfile() throws Exception {
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
@@ -1549,7 +1549,7 @@
         assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets));
     }
 
-    @Test @Ignore
+    @Test
     public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException {
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
@@ -1947,7 +1947,7 @@
                         .SharesheetTargetSelectedEvent.SHARESHEET_COPY_TARGET_SELECTED.getId()));
     }
 
-    @Test @Ignore
+    @Test
     public void testSwitchProfileLogging() throws InterruptedException {
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index f83f401..0a2670a 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -512,6 +512,8 @@
         <permission name="android.permission.NOTIFY_PENDING_SYSTEM_UPDATE" />
         <!-- Permission required for GTS test - GtsAssistIntentTestCases -->
         <permission name="android.permission.MANAGE_VOICE_KEYPHRASES" />
+        <!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
+        <permission name="android.permission.LOCK_DEVICE" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 9b2effc..d84a24d 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -17,7 +17,9 @@
 package android.graphics;
 
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.hardware.DataSpace.NamedDataSpace;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
@@ -348,6 +350,14 @@
     }
 
     /**
+     * Retrieve the dataspace associated with the texture image.
+     */
+    @SuppressLint("MethodNameUnits")
+    public @NamedDataSpace long getDataSpace() {
+        return nativeGetDataSpace();
+    }
+
+    /**
      * {@code release()} frees all the buffers and puts the SurfaceTexture into the
      * 'abandoned' state. Once put in this state the SurfaceTexture can never
      * leave it. When in the 'abandoned' state, all methods of the
@@ -416,6 +426,7 @@
     private native void nativeFinalize();
     private native void nativeGetTransformMatrix(float[] mtx);
     private native long nativeGetTimestamp();
+    private native long nativeGetDataSpace();
     private native void nativeSetDefaultBufferSize(int width, int height);
     private native void nativeUpdateTexImage();
     private native void nativeReleaseTexImage();
diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java
index 850c551..6fa1a69 100644
--- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java
+++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java
@@ -89,7 +89,7 @@
         // specific sensor (the one that hasn't changed), and 2) currently the only
         // signal to developers is the UserNotAuthenticatedException, which doesn't
         // indicate a specific sensor.
-        boolean canUnlockViaBiometrics = true;
+        boolean canUnlockViaBiometrics = biometricSids.length > 0;
         for (long sid : biometricSids) {
             if (!keySids.contains(sid)) {
                 canUnlockViaBiometrics = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 0c3a6b2..5161092 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -95,9 +95,8 @@
 
         // Update bitmap
         val fg = InsetDrawable(overflowBtn?.drawable, overflowIconInset)
-        bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(
-                ColorDrawable(colorAccent), fg),
-            null /* user */, true /* shrinkNonAdaptiveIcons */).icon
+        bitmap = iconFactory.createBadgedIconBitmap(
+                AdaptiveIconDrawable(ColorDrawable(colorAccent), fg)).icon
 
         // Update dot path
         dotPath = PathParser.createPathFromPathData(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 932f879..91aff3e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -189,9 +189,7 @@
             BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
                     b.isImportantConversation());
             info.badgeBitmap = badgeBitmapInfo.icon;
-            info.bubbleBitmap = iconFactory.createBadgedIconBitmap(bubbleDrawable,
-                    null /* user */,
-                    true /* shrinkNonAdaptiveIcons */).icon;
+            info.bubbleBitmap = iconFactory.createBadgedIconBitmap(bubbleDrawable).icon;
 
             // Dot color & placement
             Path iconPath = PathParser.createPathFromPathData(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 0b3b25a..e30e6c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -794,7 +794,8 @@
         if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             // Make the stages adjacent to each other so they occlude what's behind them.
-            wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+            wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token,
+                    true /* moveTogether */);
             wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
             mTaskOrganizer.applyTransaction(wct);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
index 8d7fbce..a17942f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
@@ -634,7 +634,8 @@
             mUseLegacySplit = mContext.getResources().getBoolean(R.bool.config_useLegacySplit);
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             // Make the stages adjacent to each other so they occlude what's behind them.
-            wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+            wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token,
+                    true /* moveTogether */);
 
             // Only sets side stage as launch-adjacent-flag-root when the device is not using legacy
             // split to prevent new split behavior confusing users.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index a163f37..413627d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -390,8 +390,7 @@
                     final ShapeIconFactory factory = new ShapeIconFactory(
                             SplashscreenContentDrawer.this.mContext,
                             scaledIconDpi, mFinalIconSize);
-                    final Bitmap bitmap = factory.createScaledBitmapWithoutShadow(
-                            iconDrawable, true /* shrinkNonAdaptiveIcons */);
+                    final Bitmap bitmap = factory.createScaledBitmapWithoutShadow(iconDrawable);
                     Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                     createIconDrawable(new BitmapDrawable(bitmap), true);
                 }
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index bbd4c81..35b6170 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -384,7 +384,16 @@
         return base::unexpected(IOError::PAGES_MISSING);
       }
 
-      auto offset = dtohl(entry_offset_ptr.value());
+      uint32_t offset;
+      uint16_t res_idx;
+      if (type->flags & ResTable_type::FLAG_SPARSE) {
+        auto sparse_entry = entry_offset_ptr.convert<ResTable_sparseTypeEntry>();
+        offset = dtohs(sparse_entry->offset) * 4u;
+        res_idx  = dtohs(sparse_entry->idx);
+      } else {
+        offset = dtohl(entry_offset_ptr.value());
+        res_idx = entry_idx;
+      }
       if (offset != ResTable_type::NO_ENTRY) {
         auto entry = type.offset(dtohl(type->entriesStart) + offset).convert<ResTable_entry>();
         if (!entry) {
@@ -394,7 +403,7 @@
         if (dtohl(entry->key.index) == static_cast<uint32_t>(*key_idx)) {
           // The package ID will be overridden by the caller (due to runtime assignment of package
           // IDs for shared libraries).
-          return make_resid(0x00, *type_idx + type_id_offset_ + 1, entry_idx);
+          return make_resid(0x00, *type_idx + type_id_offset_ + 1, res_idx);
         }
       }
     }
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index f356c8130..d214e2d 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -95,6 +95,38 @@
   ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
 }
 
+TEST(LoadedArscTest, FindSparseEntryApp) {
+  std::string contents;
+  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
+                                      &contents));
+
+  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
+                                                                   contents.length());
+  ASSERT_THAT(loaded_arsc, NotNull());
+
+  const LoadedPackage* package =
+      loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_v26));
+  ASSERT_THAT(package, NotNull());
+
+  const uint8_t type_index = get_type_id(sparse::R::string::only_v26) - 1;
+  const uint16_t entry_index = get_entry_id(sparse::R::string::only_v26);
+
+  const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+  ASSERT_THAT(type_spec, NotNull());
+  ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
+
+  // Ensure that AAPT2 sparsely encoded the v26 config as expected.
+  auto type_entry = std::find_if(
+    type_spec->type_entries.begin(), type_spec->type_entries.end(),
+    [](const TypeSpec::TypeEntry& x) { return x.config.sdkVersion == 26; });
+  ASSERT_NE(type_entry, type_spec->type_entries.end());
+  ASSERT_NE(type_entry->type->flags & ResTable_type::FLAG_SPARSE, 0);
+
+  // Test fetching a resource with only sparsely encoded configs by name.
+  auto id = package->FindEntryByName(u"string", u"only_v26");
+  ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_v26, 0));
+}
+
 TEST(LoadedArscTest, LoadSharedLibrary) {
   std::string contents;
   ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc",
diff --git a/libs/androidfw/tests/data/sparse/R.h b/libs/androidfw/tests/data/sparse/R.h
index 243e74f..2492dbf 100644
--- a/libs/androidfw/tests/data/sparse/R.h
+++ b/libs/androidfw/tests/data/sparse/R.h
@@ -27,21 +27,22 @@
   struct integer {
     enum : uint32_t {
       foo_0 = 0x7f010000,
-      foo_1 = 0x7f010000,
-      foo_2 = 0x7f010000,
-      foo_3 = 0x7f010000,
-      foo_4 = 0x7f010000,
-      foo_5 = 0x7f010000,
-      foo_6 = 0x7f010000,
-      foo_7 = 0x7f010000,
-      foo_8 = 0x7f010000,
-      foo_9 = 0x7f010000,
+      foo_1 = 0x7f010001,
+      foo_2 = 0x7f010002,
+      foo_3 = 0x7f010003,
+      foo_4 = 0x7f010004,
+      foo_5 = 0x7f010005,
+      foo_6 = 0x7f010006,
+      foo_7 = 0x7f010007,
+      foo_8 = 0x7f010008,
+      foo_9 = 0x7f010009,
     };
   };
 
   struct string {
     enum : uint32_t {
       foo_999 = 0x7f0203e7,
+      only_v26 = 0x7f0203e8
     };
   };
 };
diff --git a/libs/androidfw/tests/data/sparse/gen_strings.sh b/libs/androidfw/tests/data/sparse/gen_strings.sh
index e7e1d60..4ea5468 100755
--- a/libs/androidfw/tests/data/sparse/gen_strings.sh
+++ b/libs/androidfw/tests/data/sparse/gen_strings.sh
@@ -14,5 +14,7 @@
     fi
 done
 echo "</resources>" >> $OUTPUT_default
+
+echo "  <string name=\"only_v26\">only v26</string>" >> $OUTPUT_v26
 echo "</resources>" >> $OUTPUT_v26
 
diff --git a/libs/androidfw/tests/data/sparse/not_sparse.apk b/libs/androidfw/tests/data/sparse/not_sparse.apk
index 599a370..b08a621 100644
--- a/libs/androidfw/tests/data/sparse/not_sparse.apk
+++ b/libs/androidfw/tests/data/sparse/not_sparse.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml b/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
index b6f8299..d116087e 100644
--- a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
+++ b/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
@@ -333,4 +333,5 @@
   <string name="foo_993">9930</string>
   <string name="foo_996">9960</string>
   <string name="foo_999">9990</string>
+  <string name="only_v26">only v26</string>
 </resources>
diff --git a/libs/androidfw/tests/data/sparse/sparse.apk b/libs/androidfw/tests/data/sparse/sparse.apk
index 1f9bba3..9fd01fb 100644
--- a/libs/androidfw/tests/data/sparse/sparse.apk
+++ b/libs/androidfw/tests/data/sparse/sparse.apk
Binary files differ
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 337b45c..faae9a5 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -33,6 +33,7 @@
 import android.app.compat.CompatChanges;
 import android.bluetooth.BluetoothCodecConfig;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudioCodecConfig;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -6768,30 +6769,56 @@
 
     /**
      * Returns a list of audio formats that corresponds to encoding formats
-     * supported on offload path for A2DP playback.
+     * supported on offload path for A2DP and LE audio playback.
      *
+     * @param deviceType Indicates the target device type {@link AudioSystem.DeviceType}
      * @return a list of {@link BluetoothCodecConfig} objects containing encoding formats
-     * supported for offload A2DP playback
+     * supported for offload A2DP playback or a list of {@link BluetoothLeAudioCodecConfig}
+     * objects containing encoding formats supported for offload LE Audio playback
      * @hide
      */
-    public List<BluetoothCodecConfig> getHwOffloadEncodingFormatsSupportedForA2DP() {
+    public List<?> getHwOffloadFormatsSupportedForBluetoothMedia(
+            @AudioSystem.DeviceType int deviceType) {
         ArrayList<Integer> formatsList = new ArrayList<Integer>();
-        ArrayList<BluetoothCodecConfig> codecConfigList = new ArrayList<BluetoothCodecConfig>();
+        ArrayList<BluetoothCodecConfig> a2dpCodecConfigList = new ArrayList<BluetoothCodecConfig>();
+        ArrayList<BluetoothLeAudioCodecConfig> leAudioCodecConfigList =
+                new ArrayList<BluetoothLeAudioCodecConfig>();
 
-        int status = AudioSystem.getHwOffloadEncodingFormatsSupportedForA2DP(formatsList);
+        if (deviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP
+                && deviceType != AudioSystem.DEVICE_OUT_BLE_HEADSET) {
+            throw new IllegalArgumentException(
+                    "Illegal devicetype for the getHwOffloadFormatsSupportedForBluetoothMedia");
+        }
+
+        int status = AudioSystem.getHwOffloadFormatsSupportedForBluetoothMedia(deviceType,
+                                                                                formatsList);
         if (status != AudioManager.SUCCESS) {
-            Log.e(TAG, "getHwOffloadEncodingFormatsSupportedForA2DP failed:" + status);
-            return codecConfigList;
+            Log.e(TAG, "getHwOffloadFormatsSupportedForBluetoothMedia for deviceType "
+                    + deviceType + " failed:" + status);
+            return a2dpCodecConfigList;
         }
 
-        for (Integer format : formatsList) {
-            int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format);
-            if (btSourceCodec
-                    != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
-                codecConfigList.add(new BluetoothCodecConfig(btSourceCodec));
+        if (deviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+            for (Integer format : formatsList) {
+                int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format);
+                if (btSourceCodec != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
+                    a2dpCodecConfigList.add(new BluetoothCodecConfig(btSourceCodec));
+                }
             }
+            return a2dpCodecConfigList;
+        } else if (deviceType == AudioSystem.DEVICE_OUT_BLE_HEADSET) {
+            for (Integer format : formatsList) {
+                int btLeAudioCodec = AudioSystem.audioFormatToBluetoothLeAudioSourceCodec(format);
+                if (btLeAudioCodec != BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
+                    leAudioCodecConfigList.add(new BluetoothLeAudioCodecConfig.Builder()
+                                                .setCodecType(btLeAudioCodec)
+                                                .build());
+                }
+            }
+            return leAudioCodecConfigList;
         }
-        return codecConfigList;
+        Log.e(TAG, "Input deviceType " + deviceType + " doesn't support.");
+        return a2dpCodecConfigList;
     }
 
     // Since we need to calculate the changes since THE LAST NOTIFICATION, and not since the
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 16cb5f4..cc37c38 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -22,6 +22,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
 import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothLeAudioCodecConfig;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -236,6 +237,9 @@
     public static final int AUDIO_FORMAT_APTX_HD        = 0x21000000;
     /** @hide */
     public static final int AUDIO_FORMAT_LDAC           = 0x23000000;
+    /** @hide */
+    public static final int AUDIO_FORMAT_LC3            = 0x2B000000;
+
 
     /** @hide */
     @IntDef(flag = false, prefix = "AUDIO_FORMAT_", value = {
@@ -245,11 +249,26 @@
             AUDIO_FORMAT_SBC,
             AUDIO_FORMAT_APTX,
             AUDIO_FORMAT_APTX_HD,
-            AUDIO_FORMAT_LDAC }
+            AUDIO_FORMAT_LDAC}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioFormatNativeEnumForBtCodec {}
 
+    /** @hide */
+    @IntDef(flag = false, prefix = "AUDIO_FORMAT_", value = {
+        AUDIO_FORMAT_LC3}
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AudioFormatNativeEnumForBtLeAudioCodec {}
+
+    /** @hide */
+    @IntDef(flag = false, prefix = "DEVICE_", value = {
+            DEVICE_OUT_BLUETOOTH_A2DP,
+            DEVICE_OUT_BLE_HEADSET}
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DeviceType {}
+
     /**
      * @hide
      * Convert audio format enum values to Bluetooth codec values
@@ -271,6 +290,21 @@
 
     /**
      * @hide
+     * Convert audio format enum values to Bluetooth LE audio codec values
+     */
+    public static int audioFormatToBluetoothLeAudioSourceCodec(
+            @AudioFormatNativeEnumForBtLeAudioCodec int audioFormat) {
+        switch (audioFormat) {
+            case AUDIO_FORMAT_LC3: return BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3;
+            default:
+                Log.e(TAG, "Unknown audio format 0x" + Integer.toHexString(audioFormat)
+                        + " for conversion to BT LE audio codec");
+                return BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID;
+        }
+    }
+
+    /**
+     * @hide
      * Convert a Bluetooth codec to an audio format enum
      * @param btCodec the codec to convert.
      * @return the audio format, or {@link #AUDIO_FORMAT_DEFAULT} if unknown
@@ -1761,10 +1795,10 @@
 
     /**
      * @hide
-     * Returns a list of audio formats (codec) supported on the A2DP offload path.
+     * Returns a list of audio formats (codec) supported on the A2DP and LE audio offload path.
      */
-    public static native int getHwOffloadEncodingFormatsSupportedForA2DP(
-            ArrayList<Integer> formatList);
+    public static native int getHwOffloadFormatsSupportedForBluetoothMedia(
+            @DeviceType int deviceType, ArrayList<Integer> formatList);
 
     /** @hide */
     public static native int setSurroundFormatEnabled(int audioFormat, boolean enabled);
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index e979a1b5..5261555 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -17,9 +17,12 @@
 package android.media;
 
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Rect;
+import android.hardware.DataSpace;
+import android.hardware.DataSpace.NamedDataSpace;
 import android.hardware.HardwareBuffer;
 
 import java.nio.ByteBuffer;
@@ -280,6 +283,31 @@
         return;
     }
 
+    private @NamedDataSpace long mDataSpace = DataSpace.DATASPACE_UNKNOWN;
+
+    /**
+     * Get the dataspace associated with this frame.
+     */
+    @SuppressLint("MethodNameUnits")
+    public @NamedDataSpace long getDataSpace() {
+        throwISEIfImageIsInvalid();
+        return mDataSpace;
+    }
+
+    /**
+     * Set the dataspace associated with this frame.
+     * <p>
+     * If dataspace for an image is not set, dataspace value depends on {@link android.view.Surface}
+     * that is provided in the {@link ImageWriter} constructor.
+     * </p>
+     *
+     * @param dataSpace The Dataspace to be set for this image
+     */
+    public void setDataSpace(@NamedDataSpace long dataSpace) {
+        throwISEIfImageIsInvalid();
+        mDataSpace = dataSpace;
+    }
+
     private Rect mCropRect;
 
     /**
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 5656dff..bd0f32e 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -16,7 +16,6 @@
 
 package android.media;
 
-import android.annotation.CallbackExecutor;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.graphics.GraphicBuffer;
@@ -28,7 +27,6 @@
 import android.hardware.camera2.MultiResolutionImageReader;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.Message;
 import android.view.Surface;
 
 import dalvik.system.VMRuntime;
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 1b74367..1fc2cf9 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -23,9 +23,9 @@
 import android.graphics.ImageFormat.Format;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.utils.SurfaceUtils;
-import android.hardware.HardwareBuffer;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -454,8 +454,9 @@
         }
 
         Rect crop = image.getCropRect();
-        nativeQueueInputImage(mNativeContext, image, image.getTimestamp(), crop.left, crop.top,
-                crop.right, crop.bottom, image.getTransform(), image.getScalingMode());
+        nativeQueueInputImage(mNativeContext, image, image.getTimestamp(), image.getDataSpace(),
+                crop.left, crop.top, crop.right, crop.bottom, image.getTransform(),
+                image.getScalingMode());
 
         /**
          * Only remove and cleanup the Images that are owned by this
@@ -642,13 +643,13 @@
         Rect crop = image.getCropRect();
         if (image.getNativeContext() != 0) {
             nativeAttachAndQueueImage(mNativeContext, image.getNativeContext(), image.getFormat(),
-                    image.getTimestamp(), crop.left, crop.top, crop.right, crop.bottom,
-                    image.getTransform(), image.getScalingMode());
+                    image.getTimestamp(), image.getDataSpace(), crop.left, crop.top, crop.right,
+                    crop.bottom, image.getTransform(), image.getScalingMode());
         } else {
             GraphicBuffer gb = GraphicBuffer.createFromHardwareBuffer(image.getHardwareBuffer());
             nativeAttachAndQueueGraphicBuffer(mNativeContext, gb, image.getFormat(),
-                    image.getTimestamp(), crop.left, crop.top, crop.right, crop.bottom,
-                    image.getTransform(), image.getScalingMode());
+                    image.getTimestamp(), image.getDataSpace(), crop.left, crop.top, crop.right,
+                    crop.bottom, image.getTransform(), image.getScalingMode());
             gb.destroy();
             image.close();
         }
@@ -976,15 +977,15 @@
     private synchronized native void nativeDequeueInputImage(long nativeCtx, Image wi);
 
     private synchronized native void nativeQueueInputImage(long nativeCtx, Image image,
-            long timestampNs, int left, int top, int right, int bottom, int transform,
-            int scalingMode);
+            long timestampNs, long dataSpace, int left, int top, int right, int bottom,
+            int transform, int scalingMode);
 
     private synchronized native int nativeAttachAndQueueImage(long nativeCtx,
-            long imageNativeBuffer, int imageFormat, long timestampNs, int left,
-            int top, int right, int bottom, int transform, int scalingMode);
+            long imageNativeBuffer, int imageFormat, long timestampNs, long dataSpace,
+            int left, int top, int right, int bottom, int transform, int scalingMode);
     private synchronized native int nativeAttachAndQueueGraphicBuffer(long nativeCtx,
-            GraphicBuffer graphicBuffer, int imageFormat, long timestampNs, int left,
-            int top, int right, int bottom, int transform, int scalingMode);
+            GraphicBuffer graphicBuffer, int imageFormat, long timestampNs, long dataSpace,
+            int left, int top, int right, int bottom, int transform, int scalingMode);
 
     private synchronized native void cancelImage(long nativeCtx, Image image);
 
diff --git a/media/java/android/media/tv/AitInfo.aidl b/media/java/android/media/tv/AitInfo.aidl
new file mode 100644
index 0000000..b7d9fe8
--- /dev/null
+++ b/media/java/android/media/tv/AitInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+parcelable AitInfo;
diff --git a/media/java/android/media/tv/AitInfo.java b/media/java/android/media/tv/AitInfo.java
new file mode 100644
index 0000000..5f8487d
--- /dev/null
+++ b/media/java/android/media/tv/AitInfo.java
@@ -0,0 +1,87 @@
+/*
+ * 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;
+
+/**
+ * AIT info.
+ * @hide
+ */
+public final class AitInfo implements Parcelable {
+    static final String TAG = "AitInfo";
+
+    public static final Creator<AitInfo> CREATOR = new Creator<AitInfo>() {
+        @Override
+        public AitInfo createFromParcel(Parcel in) {
+            return new AitInfo(in);
+        }
+
+        @Override
+        public AitInfo[] newArray(int size) {
+            return new AitInfo[size];
+        }
+    };
+
+    private final int mType;
+    private final int mVersion;
+
+    private AitInfo(Parcel in) {
+        mType = in.readInt();
+        mVersion = in.readInt();
+    }
+
+    /**
+     * Constructs AIT info.
+     */
+    public AitInfo(int type, int version) {
+        mType = type;
+        mVersion = version;
+    }
+
+    /**
+     * Gets type.
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Gets version.
+     */
+    public int getVersion() {
+        return mVersion;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mType);
+        dest.writeInt(mVersion);
+    }
+
+    @Override
+    public String toString() {
+        return "type=" + mType + ";version=" + mVersion;
+    }
+}
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index 5dad633..6f7db4a 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -17,12 +17,13 @@
 package android.media.tv;
 
 import android.content.ComponentName;
+import android.media.tv.AitInfo;
+import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.ITvInputSession;
 import android.net.Uri;
 import android.media.tv.TvTrackInfo;
 import android.os.Bundle;
 import android.view.InputChannel;
-import android.media.tv.BroadcastInfoResponse;
 
 /**
  * Interface a client of the ITvInputManager implements, to identify itself and receive information
@@ -44,9 +45,10 @@
     void onTimeShiftStatusChanged(int status, int seq);
     void onTimeShiftStartPositionChanged(long timeMs, int seq);
     void onTimeShiftCurrentPositionChanged(long timeMs, int seq);
+    void onAitInfoUpdated(in AitInfo aitInfo, int seq);
 
+    void onTuned(in Uri channelUri, int seq);
     // For the recording session
-    void onTuned(int seq, in Uri channelUri);
     void onRecordingStopped(in Uri recordedProgramUri, int seq);
     void onError(int error, int seq);
 
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index eaf89ba..0070898 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.graphics.Rect;
 import android.media.PlaybackParams;
+import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.DvbDeviceInfo;
 import android.media.tv.ITvInputClient;
 import android.media.tv.ITvInputHardware;
@@ -35,7 +36,6 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.view.Surface;
-import android.media.tv.BroadcastInfoRequest;
 
 /**
  * Interface to the TV input manager service.
@@ -73,6 +73,8 @@
     void setCaptionEnabled(in IBinder sessionToken, boolean enabled, int userId);
     void selectTrack(in IBinder sessionToken, int type, in String trackId, int userId);
 
+    void setIAppNotificationEnabled(in IBinder sessionToken, boolean enabled, int userId);
+
     void sendAppPrivateCommand(in IBinder sessionToken, in String action, in Bundle data,
             int userId);
 
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 1eab650..984a551 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -18,11 +18,11 @@
 
 import android.graphics.Rect;
 import android.media.PlaybackParams;
+import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.TvTrackInfo;
 import android.net.Uri;
 import android.os.Bundle;
 import android.view.Surface;
-import android.media.tv.BroadcastInfoRequest;
 
 /**
  * Sub-interface of ITvInputService which is created per session and has its own context.
@@ -41,6 +41,8 @@
     void setCaptionEnabled(boolean enabled);
     void selectTrack(int type, in String trackId);
 
+    void setIAppNotificationEnabled(boolean enable);
+
     void appPrivateCommand(in String action, in Bundle data);
 
     void createOverlayView(in IBinder windowToken, in Rect frame);
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index d857c00..9a0aaa3 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -16,11 +16,12 @@
 
 package android.media.tv;
 
+import android.media.tv.AitInfo;
+import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.ITvInputSession;
 import android.net.Uri;
 import android.media.tv.TvTrackInfo;
 import android.os.Bundle;
-import android.media.tv.BroadcastInfoResponse;
 
 /**
  * Helper interface for ITvInputSession to allow the TV input to notify the system service when a
@@ -41,6 +42,7 @@
     void onTimeShiftStatusChanged(int status);
     void onTimeShiftStartPositionChanged(long timeMs);
     void onTimeShiftCurrentPositionChanged(long timeMs);
+    void onAitInfoUpdated(in AitInfo aitInfo);
 
     // For the recording session
     void onTuned(in Uri channelUri);
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 4257145..6539472 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -71,6 +71,7 @@
     private static final int DO_PAUSE_RECORDING = 22;
     private static final int DO_RESUME_RECORDING = 23;
     private static final int DO_REQUEST_BROADCAST_INFO = 24;
+    private static final int DO_SET_IAPP_NOTIFICATION_ENABLED = 25;
 
     private final boolean mIsRecordingSession;
     private final HandlerCaller mCaller;
@@ -239,6 +240,10 @@
                 mTvInputSessionImpl.requestBroadcastInfo((BroadcastInfoRequest) msg.obj);
                 break;
             }
+            case DO_SET_IAPP_NOTIFICATION_ENABLED: {
+                mTvInputSessionImpl.setIAppNotificationEnabled((Boolean) msg.obj);
+                break;
+            }
             default: {
                 Log.w(TAG, "Unhandled message code: " + msg.what);
                 break;
@@ -307,6 +312,12 @@
     }
 
     @Override
+    public void setIAppNotificationEnabled(boolean enabled) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageO(DO_SET_IAPP_NOTIFICATION_ENABLED, enabled));
+    }
+
+    @Override
     public void appPrivateCommand(String action, Bundle data) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action,
                 data));
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index bafb03b..b655a61 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -627,14 +627,20 @@
         public void onTimeShiftCurrentPositionChanged(Session session, long timeMs) {
         }
 
-        // For the recording session only
         /**
-         * This is called when the recording session has been tuned to the given channel and is
-         * ready to start recording.
+         * This is called when AIT info is updated.
+         * @param session A {@link TvInputManager.Session} associated with this callback.
+         * @param aitInfo The current AIT info.
+         */
+        public void onAitInfoUpdated(Session session, AitInfo aitInfo) {
+        }
+
+        /**
+         * This is called when the session has been tuned to the given channel.
          *
          * @param channelUri The URI of a channel.
          */
-        void onTuned(Session session, Uri channelUri) {
+        public void onTuned(Session session, Uri channelUri) {
         }
 
         // For the recording session only
@@ -807,12 +813,23 @@
             });
         }
 
-        // For the recording session only
+        void postAitInfoUpdated(final AitInfo aitInfo) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onAitInfoUpdated(mSession, aitInfo);
+                }
+            });
+        }
+
         void postTuned(final Uri channelUri) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
                     mSessionCallback.onTuned(mSession, channelUri);
+                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
+                        mSession.getIAppSession().notifyTuned(channelUri);
+                    }
                 }
             });
         }
@@ -838,12 +855,16 @@
         }
 
         void postBroadcastInfoResponse(final BroadcastInfoResponse response) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mSession.getIAppSession().notifyBroadcastInfoResponse(response);
-                }
-            });
+            if (mSession.mIAppNotificationEnabled) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mSession.getIAppSession() != null) {
+                            mSession.getIAppSession().notifyBroadcastInfoResponse(response);
+                        }
+                    }
+                });
+            }
         }
     }
 
@@ -1209,7 +1230,19 @@
             }
 
             @Override
-            public void onTuned(int seq, Uri channelUri) {
+            public void onAitInfoUpdated(AitInfo aitInfo, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postAitInfoUpdated(aitInfo);
+                }
+            }
+
+            @Override
+            public void onTuned(Uri channelUri, int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
                     if (record == null) {
@@ -1217,6 +1250,13 @@
                         return;
                     }
                     record.postTuned(channelUri);
+                    // TODO: synchronized and wrap the channelUri
+                    if (record.mSession.mIAppNotificationEnabled) {
+                        TvIAppManager.Session iappSession = record.mSession.mIAppSession;
+                        if (iappSession != null) {
+                            iappSession.notifyTuned(channelUri);
+                        }
+                    }
                 }
             }
 
@@ -2068,6 +2108,7 @@
         private int mVideoHeight;
 
         private TvIAppManager.Session mIAppSession;
+        private boolean mIAppNotificationEnabled = false;
 
         private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
                 int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
@@ -2340,6 +2381,25 @@
         }
 
         /**
+         * Enables interactive app notification.
+         * @param enabled {@code true} if you want to enable interactive app notifications.
+         *                {@code false} otherwise.
+         * @hide
+         */
+        public void setIAppNotificationEnabled(boolean enabled) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.setIAppNotificationEnabled(mToken, enabled, mUserId);
+                mIAppNotificationEnabled = enabled;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Responds to onTracksChanged() and updates the internal track information. Returns true if
          * there is an update.
          */
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index d285b13..6743dd6 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -828,6 +828,27 @@
         }
 
         /**
+         * Notifies AIT info updated.
+         * @hide
+         */
+        public void notifyAitInfoUpdated(@NonNull final AitInfo aitInfo) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) Log.d(TAG, "notifyAitInfoUpdated");
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onAitInfoUpdated(aitInfo);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in notifyAitInfoUpdated", e);
+                    }
+                }
+            });
+        }
+
+        /**
          * 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.
          *
@@ -1021,6 +1042,14 @@
         }
 
         /**
+         * Enables or disables interactive app notification.
+         * @param enabled {@code true} to enable, {@code false} to disable.
+         * @hide
+         */
+        public void onSetIAppNotificationEnabled(boolean enabled) {
+        }
+
+        /**
          * Processes a private command sent from the application to the TV input. This can be used
          * to provide domain-specific features that are only known between certain TV inputs and
          * their clients.
@@ -1369,6 +1398,13 @@
         }
 
         /**
+         * Calls {@link #onSetIAppNotificationEnabled}.
+         */
+        void setIAppNotificationEnabled(boolean enabled) {
+            onSetIAppNotificationEnabled(enabled);
+        }
+
+        /**
          * Calls {@link #onAppPrivateCommand}.
          */
         void appPrivateCommand(String action, Bundle data) {
diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java
index 180e2bd..87d7a0b 100644
--- a/media/java/android/media/tv/TvRecordingClient.java
+++ b/media/java/android/media/tv/TvRecordingClient.java
@@ -472,7 +472,7 @@
         }
 
         @Override
-        void onTuned(TvInputManager.Session session, Uri channelUri) {
+        public void onTuned(TvInputManager.Session session, Uri channelUri) {
             if (DEBUG) {
                 Log.d(TAG, "onTuned()");
             }
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 6994d28..4a12cd7 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -34,8 +34,6 @@
 import android.media.tv.TvInputManager.Session;
 import android.media.tv.TvInputManager.Session.FinishedInputEventCallback;
 import android.media.tv.TvInputManager.SessionCallback;
-import android.media.tv.interactive.TvIAppManager;
-import android.media.tv.interactive.TvIAppView;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -483,6 +481,18 @@
     }
 
     /**
+     * Enables interactive app notification.
+     * @param enabled {@code true} if you want to enable interactive app notifications.
+     *                {@code false} otherwise.
+     * @hide
+     */
+    public void setIAppNotificationEnabled(boolean enabled) {
+        if (mSession != null) {
+            mSession.setIAppNotificationEnabled(enabled);
+        }
+    }
+
+    /**
      * Plays a given recorded TV program.
      *
      * @param inputId The ID of the TV input that created the given recorded program.
@@ -1050,6 +1060,24 @@
         public void onTimeShiftStatusChanged(
                 String inputId, @TvInputManager.TimeShiftStatus int status) {
         }
+
+        /**
+         * This is called when the AIT info has been updated.
+         *
+         * @param aitInfo The current AIT info.
+         * @hide
+         */
+        public void onAitInfoUpdated(String inputId, AitInfo aitInfo) {
+        }
+
+        /**
+         * This is called when the session has been tuned to the given channel.
+         *
+         * @param channelUri The URI of a channel.
+         * @hide
+         */
+        public void onTuned(String inputId, Uri channelUri) {
+        }
     }
 
     /**
@@ -1346,5 +1374,33 @@
                 mTimeShiftPositionCallback.onTimeShiftCurrentPositionChanged(mInputId, timeMs);
             }
         }
+
+        @Override
+        public void onAitInfoUpdated(Session session, AitInfo aitInfo) {
+            if (DEBUG) {
+                Log.d(TAG, "onAitInfoUpdated(aitInfo=" + aitInfo + ")");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onAitInfoUpdated - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onAitInfoUpdated(mInputId, aitInfo);
+            }
+        }
+
+        @Override
+        public void onTuned(Session session, Uri channelUri) {
+            if (DEBUG) {
+                Log.d(TAG, "onTuned(channelUri=" + channelUri + ")");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onTuned - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onTuned(mInputId, channelUri);
+            }
+        }
     }
 }
diff --git a/media/java/android/media/tv/interactive/ITvIAppClient.aidl b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
index 39c438a..9fc1fe7 100644
--- a/media/java/android/media/tv/interactive/ITvIAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
@@ -17,6 +17,7 @@
 package android.media.tv.interactive;
 import android.media.tv.BroadcastInfoRequest;
 
+import android.media.tv.BroadcastInfoRequest;
 import android.view.InputChannel;
 
 /**
diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
index 25e1ace..cd87a09 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
@@ -16,10 +16,12 @@
 
 package android.media.tv.interactive;
 
+import android.graphics.Rect;
+import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.interactive.ITvIAppClient;
 import android.media.tv.interactive.ITvIAppManagerCallback;
 import android.media.tv.interactive.TvIAppInfo;
-import android.media.tv.BroadcastInfoResponse;
+import android.net.Uri;
 import android.view.Surface;
 
 /**
@@ -28,16 +30,23 @@
  */
 interface ITvIAppManager {
     List<TvIAppInfo> getTvIAppServiceList(int userId);
+    void prepare(String tiasId, int type, int userId);
     void startIApp(in IBinder sessionToken, int userId);
     void createSession(
             in ITvIAppClient client, in String iAppServiceId, int type, int seq, int userId);
     void releaseSession(in IBinder sessionToken, int userId);
+    void notifyTuned(in IBinder sessionToken, in Uri channelUri, int userId);
     void setSurface(in IBinder sessionToken, in Surface surface, int userId);
     void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
             int userId);
     void notifyBroadcastInfoResponse(in IBinder sessionToken, in BroadcastInfoResponse response,
             int UserId);
 
+    void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
+            int userId);
+    void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId);
+    void removeMediaView(in IBinder sessionToken, int userId);
+
     void registerCallback(in ITvIAppManagerCallback callback, int userId);
     void unregisterCallback(in ITvIAppManagerCallback callback, int userId);
 }
\ 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 1dee9cc..af15dd8 100644
--- a/media/java/android/media/tv/interactive/ITvIAppService.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppService.aidl
@@ -30,4 +30,5 @@
     void unregisterCallback(in ITvIAppServiceCallback callback);
     void createSession(in InputChannel channel, in ITvIAppSessionCallback callback,
             in String iAppServiceId, int type);
+    void prepare(int type);
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppSession.aidl b/media/java/android/media/tv/interactive/ITvIAppSession.aidl
index 440b3d30..0d37a2b 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppSession.aidl
@@ -16,6 +16,9 @@
 
 package android.media.tv.interactive;
 
+import android.graphics.Rect;
+import android.net.Uri;
+import android.media.tv.BroadcastInfoResponse;
 import android.view.Surface;
 import android.media.tv.BroadcastInfoResponse;
 
@@ -26,7 +29,12 @@
 oneway interface ITvIAppSession {
     void startIApp();
     void release();
+    void notifyTuned(in Uri channelUri);
     void setSurface(in Surface surface);
     void dispatchSurfaceChanged(int format, int width, int height);
     void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
+
+    void createMediaView(in IBinder windowToken, in Rect frame);
+    void relayoutMediaView(in Rect frame);
+    void removeMediaView();
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
index d308463..d2b966e 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.media.tv.interactive;
 
+import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.interactive.ITvIAppSession;
 import android.media.tv.BroadcastInfoRequest;
 
diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java
index ae35edc..fed9769 100644
--- a/media/java/android/media/tv/interactive/TvIAppManager.java
+++ b/media/java/android/media/tv/interactive/TvIAppManager.java
@@ -20,9 +20,11 @@
 import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.graphics.Rect;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvInputManager;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -35,6 +37,7 @@
 import android.view.InputEvent;
 import android.view.InputEventSender;
 import android.view.Surface;
+import android.view.View;
 
 import com.android.internal.util.Preconditions;
 
@@ -332,6 +335,18 @@
     }
 
     /**
+     * Prepares TV IApp service for the given type.
+     * @hide
+     */
+    public void prepare(String tvIAppServiceId, int type) {
+        try {
+            mService.prepare(tvIAppServiceId, type, mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Registers a {@link TvIAppManager.TvIAppCallback}.
      *
      * @param callback A callback used to monitor status of the TV IApp services.
@@ -443,6 +458,67 @@
         }
 
         /**
+         * Creates a media view. Once the media view is created, {@link #relayoutMediaView}
+         * should be called whenever the layout of its containing view is changed.
+         * {@link #removeMediaView()} should be called to remove the media view.
+         * Since a session can have only one media view, this method should be called only once
+         * or it can be called again after calling {@link #removeMediaView()}.
+         *
+         * @param view A view for interactive app.
+         * @param frame A position of the media view.
+         * @throws IllegalStateException if {@code view} is not attached to a window.
+         */
+        void createMediaView(@NonNull View view, @NonNull Rect frame) {
+            Preconditions.checkNotNull(view);
+            Preconditions.checkNotNull(frame);
+            if (view.getWindowToken() == null) {
+                throw new IllegalStateException("view must be attached to a window");
+            }
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.createMediaView(mToken, view.getWindowToken(), frame, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Relayouts the current media view.
+         *
+         * @param frame A new position of the media view.
+         */
+        void relayoutMediaView(@NonNull Rect frame) {
+            Preconditions.checkNotNull(frame);
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.relayoutMediaView(mToken, frame, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Removes the current media view.
+         */
+        void removeMediaView() {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.removeMediaView(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Notifies of any structural changes (format or size) of the surface passed in
          * {@link #setSurface}.
          *
@@ -533,6 +609,21 @@
             releaseInternal();
         }
 
+        /**
+         * Notifies IAPP session when a channels is tuned.
+         */
+        public void notifyTuned(Uri channelUri) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyTuned(mToken, channelUri, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         private void flushPendingEventsLocked() {
             mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
 
diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java
index fe087ca..027b890 100644
--- a/media/java/android/media/tv/interactive/TvIAppService.java
+++ b/media/java/android/media/tv/interactive/TvIAppService.java
@@ -20,18 +20,25 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
+import android.app.ActivityManager;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
+import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.InputEvent;
@@ -39,6 +46,9 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
 
 import com.android.internal.os.SomeArgs;
 
@@ -52,6 +62,8 @@
     private static final boolean DEBUG = false;
     private static final String TAG = "TvIAppService";
 
+    private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000;
+
     // TODO: cleanup and unhide APIs.
 
     /**
@@ -106,10 +118,23 @@
                 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args)
                         .sendToTarget();
             }
+
+            @Override
+            public void prepare(int type) {
+                onPrepare(type);
+            }
         };
         return tvIAppServiceBinder;
     }
 
+    /**
+     * Prepares TV IApp service for the given type.
+     * @hide
+     */
+    public void onPrepare(int type) {
+        // TODO: make it abstract when unhide
+    }
+
 
     /**
      * Returns a concrete implementation of {@link Session}.
@@ -141,8 +166,16 @@
         private final List<Runnable> mPendingActions = new ArrayList<>();
 
         private final Context mContext;
-        private final Handler mHandler;
+        final Handler mHandler;
+        private final WindowManager mWindowManager;
+        private WindowManager.LayoutParams mWindowParams;
         private Surface mSurface;
+        private FrameLayout mMediaViewContainer;
+        private View mMediaView;
+        private MediaViewCleanUpTask mMediaViewCleanUpTask;
+        private boolean mMediaViewEnabled;
+        private IBinder mWindowToken;
+        private Rect mMediaFrame;
 
         /**
          * Creates a new Session.
@@ -151,10 +184,41 @@
          */
         public Session(Context context) {
             mContext = context;
+            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
             mHandler = new Handler(context.getMainLooper());
         }
 
         /**
+         * Enables or disables the media view.
+         *
+         * <p>By default, the media view is disabled. Must be called explicitly after the
+         * session is created to enable the media view.
+         *
+         * <p>The TV IApp service can disable its media view when needed.
+         *
+         * @param enable {@code true} if you want to enable the media view. {@code false}
+         *            otherwise.
+         */
+        public void setMediaViewEnabled(final boolean enable) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (enable == mMediaViewEnabled) {
+                        return;
+                    }
+                    mMediaViewEnabled = enable;
+                    if (enable) {
+                        if (mWindowToken != null) {
+                            createMediaView(mWindowToken, mMediaFrame);
+                        }
+                    } else {
+                        removeMediaView(false);
+                    }
+                }
+            });
+        }
+
+        /**
          * Starts TvIAppService session.
          * @hide
          */
@@ -187,11 +251,27 @@
         }
 
         /**
-         * Called when a broadcast info response is received from TIS.
+         * Called when the size of the media view is changed by the application.
          *
-         * @param response response received from TIS.
+         * <p>This is always called at least once when the session is created regardless of whether
+         * the media view is enabled or not. The media view container size is the same as the
+         * containing {@link TvIAppView}. Note that the size of the underlying surface can be
+         * different if the surface was changed by calling {@link #layoutSurface}.
+         *
+         * @param width The width of the media view.
+         * @param height The height of the media view.
          */
-        public void onNotifyBroadcastInfoResponse(BroadcastInfoResponse response) {
+        public void onMediaViewSizeChanged(int width, int height) {
+        }
+
+        /**
+         * Called when the application requests to create an media view. Each session
+         * implementation can override this method and return its own view.
+         *
+         * @return a view attached to the media window
+         */
+        public View onCreateMediaView() {
+            return null;
         }
 
         /**
@@ -202,6 +282,20 @@
         }
 
         /**
+         * Called when the corresponding TV input tuned to a channel.
+         * @hide
+         */
+        public void onTuned(Uri channelUri) {
+        }
+
+        /**
+         * Called when a broadcast info response is received.
+         * @hide
+         */
+        public void onBroadcastInfoResponse(BroadcastInfoResponse response) {
+        }
+
+        /**
          * TODO: JavaDoc of APIs related to input events.
          * @hide
          */
@@ -288,6 +382,10 @@
             });
         }
 
+        /**
+         * Requests broadcast related information from the related TV input.
+         * @param request
+         */
         public void requestBroadcastInfo(@NonNull final BroadcastInfoRequest request) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
@@ -318,6 +416,32 @@
                 mSurface.release();
                 mSurface = null;
             }
+            synchronized (mLock) {
+                mSessionCallback = null;
+                mPendingActions.clear();
+            }
+            // Removes the media view lastly so that any hanging on the main thread can be handled
+            // in {@link #scheduleMediaViewCleanup}.
+            removeMediaView(true);
+        }
+
+        void notifyTuned(Uri channelUri) {
+            if (DEBUG) {
+                Log.d(TAG, "notifyTuned (channelUri=" + channelUri + ")");
+            }
+            onTuned(channelUri);
+        }
+
+
+        /**
+         * Calls {@link #onBroadcastInfoResponse}.
+         */
+        void notifyBroadcastInfoResponse(BroadcastInfoResponse response) {
+            if (DEBUG) {
+                Log.d(TAG, "notifyBroadcastInfoResponse (requestId="
+                        + response.getRequestId() + ")");
+            }
+            onBroadcastInfoResponse(response);
         }
 
         /**
@@ -386,18 +510,6 @@
             onSurfaceChanged(format, width, height);
         }
 
-        /**
-         *
-         * Calls {@link #notifyBroadcastInfoResponse}.
-         */
-        void notifyBroadcastInfoResponse(BroadcastInfoResponse response) {
-            if (DEBUG) {
-                Log.d(TAG, "notifyBroadcastInfoResponse (requestId="
-                        + response.getRequestId() + ")");
-            }
-            onNotifyBroadcastInfoResponse(response);
-        }
-
         private void executeOrPostRunnableOnMainThread(Runnable action) {
             synchronized (mLock) {
                 if (mSessionCallback == null) {
@@ -413,6 +525,137 @@
                 }
             }
         }
+
+        /**
+         * Creates an media view. This calls {@link #onCreateMediaView} to get a view to attach
+         * to the media window.
+         *
+         * @param windowToken A window token of the application.
+         * @param frame A position of the media view.
+         */
+        void createMediaView(IBinder windowToken, Rect frame) {
+            if (mMediaViewContainer != null) {
+                removeMediaView(false);
+            }
+            if (DEBUG) Log.d(TAG, "create media view(" + frame + ")");
+            mWindowToken = windowToken;
+            mMediaFrame = frame;
+            onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+            if (!mMediaViewEnabled) {
+                return;
+            }
+            mMediaView = onCreateMediaView();
+            if (mMediaView == null) {
+                return;
+            }
+            if (mMediaViewCleanUpTask != null) {
+                mMediaViewCleanUpTask.cancel(true);
+                mMediaViewCleanUpTask = null;
+            }
+            // Creates a container view to check hanging on the media view detaching.
+            // Adding/removing the media view to/from the container make the view attach/detach
+            // logic run on the main thread.
+            mMediaViewContainer = new FrameLayout(mContext.getApplicationContext());
+            mMediaViewContainer.addView(mMediaView);
+
+            int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+            // We make the overlay view non-focusable and non-touchable so that
+            // the application that owns the window token can decide whether to consume or
+            // dispatch the input events.
+            int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+            if (ActivityManager.isHighEndGfx()) {
+                flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+            }
+            mWindowParams = new WindowManager.LayoutParams(
+                    frame.right - frame.left, frame.bottom - frame.top,
+                    frame.left, frame.top, type, flags, PixelFormat.TRANSPARENT);
+            mWindowParams.privateFlags |=
+                    WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+            mWindowParams.gravity = Gravity.START | Gravity.TOP;
+            mWindowParams.token = windowToken;
+            mWindowManager.addView(mMediaViewContainer, mWindowParams);
+        }
+
+        /**
+         * Relayouts the current media view.
+         *
+         * @param frame A new position of the media view.
+         */
+        void relayoutMediaView(Rect frame) {
+            if (DEBUG) Log.d(TAG, "relayoutMediaView(" + frame + ")");
+            if (mMediaFrame == null || mMediaFrame.width() != frame.width()
+                    || mMediaFrame.height() != frame.height()) {
+                // Note: relayoutMediaView is called whenever TvIAppView's layout is changed
+                // regardless of setMediaViewEnabled.
+                onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+            }
+            mMediaFrame = frame;
+            if (!mMediaViewEnabled || mMediaViewContainer == null) {
+                return;
+            }
+            mWindowParams.x = frame.left;
+            mWindowParams.y = frame.top;
+            mWindowParams.width = frame.right - frame.left;
+            mWindowParams.height = frame.bottom - frame.top;
+            mWindowManager.updateViewLayout(mMediaViewContainer, mWindowParams);
+        }
+
+        /**
+         * Removes the current media view.
+         */
+        void removeMediaView(boolean clearWindowToken) {
+            if (DEBUG) Log.d(TAG, "removeMediaView(" + mMediaViewContainer + ")");
+            if (clearWindowToken) {
+                mWindowToken = null;
+                mMediaFrame = null;
+            }
+            if (mMediaViewContainer != null) {
+                // Removes the media view from the view hierarchy in advance so that it can be
+                // cleaned up in the {@link MediaViewCleanUpTask} if the remove process is
+                // hanging.
+                mMediaViewContainer.removeView(mMediaView);
+                mMediaView = null;
+                mWindowManager.removeView(mMediaViewContainer);
+                mMediaViewContainer = null;
+                mWindowParams = null;
+            }
+        }
+
+        /**
+         * Schedules a task which checks whether the media view is detached and kills the process
+         * if it is not. Note that this method is expected to be called in a non-main thread.
+         */
+        void scheduleMediaViewCleanup() {
+            View mediaViewParent = mMediaViewContainer;
+            if (mediaViewParent != null) {
+                mMediaViewCleanUpTask = new MediaViewCleanUpTask();
+                mMediaViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
+                        mediaViewParent);
+            }
+        }
+    }
+
+    private static final class MediaViewCleanUpTask extends AsyncTask<View, Void, Void> {
+        @Override
+        protected Void doInBackground(View... views) {
+            View mediaViewParent = views[0];
+            try {
+                Thread.sleep(DETACH_MEDIA_VIEW_TIMEOUT_MS);
+            } catch (InterruptedException e) {
+                return null;
+            }
+            if (isCancelled()) {
+                return null;
+            }
+            if (mediaViewParent.isAttachedToWindow()) {
+                Log.e(TAG, "Time out on releasing media view. Killing "
+                        + mediaViewParent.getContext().getPackageName());
+                android.os.Process.killProcess(Process.myPid());
+            }
+            return null;
+        }
     }
 
     /**
@@ -440,10 +683,16 @@
 
         @Override
         public void release() {
+            mSessionImpl.scheduleMediaViewCleanup();
             mSessionImpl.release();
         }
 
         @Override
+        public void notifyTuned(Uri channelUri) {
+            mSessionImpl.notifyTuned(channelUri);
+        }
+
+        @Override
         public void setSurface(Surface surface) {
             mSessionImpl.setSurface(surface);
         }
@@ -458,6 +707,21 @@
             mSessionImpl.notifyBroadcastInfoResponse(response);
         }
 
+        @Override
+        public void createMediaView(IBinder windowToken, Rect frame) {
+            mSessionImpl.createMediaView(windowToken, frame);
+        }
+
+        @Override
+        public void relayoutMediaView(Rect frame) {
+            mSessionImpl.relayoutMediaView(frame);
+        }
+
+        @Override
+        public void removeMediaView() {
+            mSessionImpl.removeMediaView(true);
+        }
+
         private final class TvIAppEventReceiver extends InputEventReceiver {
             TvIAppEventReceiver(InputChannel inputChannel, Looper looper) {
                 super(inputChannel, looper);
diff --git a/media/java/android/media/tv/interactive/TvIAppView.java b/media/java/android/media/tv/interactive/TvIAppView.java
index 1b25c23..8031981 100644
--- a/media/java/android/media/tv/interactive/TvIAppView.java
+++ b/media/java/android/media/tv/interactive/TvIAppView.java
@@ -20,6 +20,8 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.media.tv.TvInputManager;
 import android.media.tv.TvView;
 import android.media.tv.interactive.TvIAppManager.Session;
@@ -65,6 +67,9 @@
     private int mSurfaceViewTop;
     private int mSurfaceViewBottom;
 
+    private boolean mMediaViewCreated;
+    private Rect mMediaViewFrame;
+
     private final AttributeSet mAttrs;
     private final int mDefStyleAttr;
     private final XmlResourceParser mParser;
@@ -119,6 +124,18 @@
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        createSessionMediaView();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        removeSessionMediaView();
+        super.onDetachedFromWindow();
+    }
+
+    @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         if (DEBUG) {
             Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right
@@ -147,6 +164,11 @@
     protected void onVisibilityChanged(View changedView, int visibility) {
         super.onVisibilityChanged(changedView, visibility);
         mSurfaceView.setVisibility(visibility);
+        if (visibility == View.VISIBLE) {
+            createSessionMediaView();
+        } else {
+            removeSessionMediaView();
+        }
     }
 
     private void resetSurfaceView() {
@@ -155,7 +177,12 @@
             removeView(mSurfaceView);
         }
         mSurface = null;
-        mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr);
+        mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) {
+            @Override
+            protected void updateSurface() {
+                super.updateSurface();
+                relayoutSessionMediaView();
+            }};
         // The surface view's content should be treated as secure all the time.
         mSurfaceView.setSecure(true);
         mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
@@ -170,6 +197,46 @@
         resetInternal();
     }
 
+    private void createSessionMediaView() {
+        // TODO: handle z-order
+        if (mSession == null || !isAttachedToWindow() || mMediaViewCreated) {
+            return;
+        }
+        mMediaViewFrame = getViewFrameOnScreen();
+        mSession.createMediaView(this, mMediaViewFrame);
+        mMediaViewCreated = true;
+    }
+
+    private void removeSessionMediaView() {
+        if (mSession == null || !mMediaViewCreated) {
+            return;
+        }
+        mSession.removeMediaView();
+        mMediaViewCreated = false;
+        mMediaViewFrame = null;
+    }
+
+    private void relayoutSessionMediaView() {
+        if (mSession == null || !isAttachedToWindow() || !mMediaViewCreated) {
+            return;
+        }
+        Rect viewFrame = getViewFrameOnScreen();
+        if (viewFrame.equals(mMediaViewFrame)) {
+            return;
+        }
+        mSession.relayoutMediaView(viewFrame);
+        mMediaViewFrame = viewFrame;
+    }
+
+    private Rect getViewFrameOnScreen() {
+        Rect frame = new Rect();
+        getGlobalVisibleRect(frame);
+        RectF frameF = new RectF(frame);
+        getMatrix().mapRect(frameF);
+        frameF.round(frame);
+        return frame;
+    }
+
     private void setSessionSurface(Surface surface) {
         if (mSession == null) {
             return;
@@ -214,6 +281,7 @@
         mSessionCallback = null;
         if (mSession != null) {
             setSessionSurface(null);
+            removeSessionMediaView();
             mUseRequestedSurfaceLayout = false;
             mSession.release();
             mSession = null;
@@ -287,6 +355,7 @@
                         dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
                     }
                 }
+                createSessionMediaView();
             } else {
                 // Failed to create
                 // Todo: forward error to Tv App
@@ -303,6 +372,8 @@
                 Log.w(TAG, "onSessionReleased - session not created");
                 return;
             }
+            mMediaViewCreated = false;
+            mMediaViewFrame = null;
             mSessionCallback = null;
             mSession = null;
         }
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 5174c0c..021507c 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -48,6 +48,7 @@
 #define ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID       "mNativeContext"
 #define ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID   "mNativeBuffer"
 #define ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID       "mTimestamp"
+#define ANDROID_MEDIA_SURFACEIMAGE_DS_JNI_ID       "mDataSpace"
 #define ANDROID_MEDIA_SURFACEIMAGE_TF_JNI_ID       "mTransform"
 #define ANDROID_MEDIA_SURFACEIMAGE_SM_JNI_ID       "mScalingMode"
 
@@ -71,6 +72,7 @@
 static struct {
     jfieldID mNativeBuffer;
     jfieldID mTimestamp;
+    jfieldID mDataSpace;
     jfieldID mTransform;
     jfieldID mScalingMode;
     jfieldID mPlanes;
@@ -319,6 +321,12 @@
                         "can't find android/graphics/ImageReader.%s",
                         ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID);
 
+    gSurfaceImageClassInfo.mDataSpace = env->GetFieldID(
+            imageClazz, ANDROID_MEDIA_SURFACEIMAGE_DS_JNI_ID, "J");
+    LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mDataSpace == NULL,
+                        "can't find android/graphics/ImageReader.%s",
+                        ANDROID_MEDIA_SURFACEIMAGE_DS_JNI_ID);
+
     gSurfaceImageClassInfo.mTransform = env->GetFieldID(
             imageClazz, ANDROID_MEDIA_SURFACEIMAGE_TF_JNI_ID, "I");
     LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mTransform == NULL,
@@ -619,6 +627,8 @@
     Image_setBufferItem(env, image, buffer);
     env->SetLongField(image, gSurfaceImageClassInfo.mTimestamp,
             static_cast<jlong>(buffer->mTimestamp));
+    env->SetLongField(image, gSurfaceImageClassInfo.mDataSpace,
+            static_cast<jlong>(buffer->mDataSpace));
     auto transform = buffer->mTransform;
     if (buffer->mTransformToDisplayInverse) {
         transform |= NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index b291ac95b..0a5490d 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -53,6 +53,7 @@
 } gImageWriterClassInfo;
 
 static struct {
+    jfieldID mDataSpace;
     jfieldID mNativeBuffer;
     jfieldID mNativeFenceFd;
     jfieldID mPlanes;
@@ -87,6 +88,9 @@
     void setBufferHeight(int height) { mHeight = height; }
     int getBufferHeight() { return mHeight; }
 
+    void setBufferDataSpace(android_dataspace dataSpace) { mDataSpace = dataSpace; }
+    android_dataspace getBufferDataSpace() { return mDataSpace; }
+
     void queueAttachedFlag(bool isAttached) {
         Mutex::Autolock l(mAttachedFlagQueueLock);
         mAttachedFlagQueue.push_back(isAttached);
@@ -105,6 +109,7 @@
     int mFormat;
     int mWidth;
     int mHeight;
+    android_dataspace mDataSpace;
 
     // Class for a shared thread used to detach buffers from buffer queues
     // to discard buffers after consumers are done using them.
@@ -316,7 +321,7 @@
 // -------------------------------Private method declarations--------------
 
 static void Image_setNativeContext(JNIEnv* env, jobject thiz,
-        sp<GraphicBuffer> buffer, int fenceFd);
+        sp<GraphicBuffer> buffer, int fenceFd, long dataSpace);
 static void Image_getNativeContext(JNIEnv* env, jobject thiz,
         GraphicBuffer** buffer, int* fenceFd);
 static void Image_unlockIfLocked(JNIEnv* env, jobject thiz);
@@ -328,6 +333,12 @@
     jclass imageClazz = env->FindClass("android/media/ImageWriter$WriterSurfaceImage");
     LOG_ALWAYS_FATAL_IF(imageClazz == NULL,
             "can't find android/media/ImageWriter$WriterSurfaceImage");
+
+    gSurfaceImageClassInfo.mDataSpace = env->GetFieldID(
+            imageClazz, "mDataSpace", "J");
+    LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mDataSpace == NULL,
+            "can't find android/media/ImageWriter$WriterSurfaceImage.mDataSpace");
+
     gSurfaceImageClassInfo.mNativeBuffer = env->GetFieldID(
             imageClazz, IMAGE_BUFFER_JNI_ID, "J");
     LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mNativeBuffer == NULL,
@@ -465,6 +476,7 @@
             jniThrowRuntimeException(env, "Failed to set Surface dataspace");
             return 0;
         }
+        ctx->setBufferDataSpace(nativeDataspace);
         surfaceFormat = userFormat;
     }
 
@@ -544,7 +556,7 @@
     // 3. need use lockAsync here, as it will handle the dequeued fence for us automatically.
 
     // Finally, set the native info into image object.
-    Image_setNativeContext(env, image, buffer, fenceFd);
+    Image_setNativeContext(env, image, buffer, fenceFd, ctx->getBufferDataSpace());
 }
 
 static void ImageWriter_close(JNIEnv* env, jobject thiz, jlong nativeCtx) {
@@ -605,12 +617,12 @@
 
     anw->cancelBuffer(anw.get(), buffer, fenceFd);
 
-    Image_setNativeContext(env, image, NULL, -1);
+    Image_setNativeContext(env, image, NULL, -1, HAL_DATASPACE_UNKNOWN);
 }
 
 static void ImageWriter_queueImage(JNIEnv* env, jobject thiz, jlong nativeCtx, jobject image,
-        jlong timestampNs, jint left, jint top, jint right, jint bottom, jint transform,
-        jint scalingMode) {
+        jlong timestampNs, jlong dataSpace, jint left, jint top, jint right,
+        jint bottom, jint transform, jint scalingMode) {
     ALOGV("%s", __FUNCTION__);
     JNIImageWriterContext* const ctx = reinterpret_cast<JNIImageWriterContext *>(nativeCtx);
     if (ctx == NULL || thiz == NULL) {
@@ -642,6 +654,15 @@
         return;
     }
 
+    // Set dataSpace
+    ALOGV("dataSpace to be queued: %" PRId64, dataSpace);
+    res = native_window_set_buffers_data_space(
+        anw.get(), static_cast<android_dataspace>(dataSpace));
+    if (res != OK) {
+        jniThrowRuntimeException(env, "Set dataspace failed");
+        return;
+    }
+
     // Set crop
     android_native_rect_t cropRect;
     cropRect.left = left;
@@ -689,12 +710,12 @@
     }
 
     // Clear the image native context: end of this image's lifecycle in public API.
-    Image_setNativeContext(env, image, NULL, -1);
+    Image_setNativeContext(env, image, NULL, -1, HAL_DATASPACE_UNKNOWN);
 }
 
 static status_t attachAndQeueuGraphicBuffer(JNIEnv* env, JNIImageWriterContext *ctx,
-        sp<Surface> surface, sp<GraphicBuffer> gb, jlong timestampNs, jint left, jint top,
-        jint right, jint bottom, jint transform, jint scalingMode) {
+        sp<Surface> surface, sp<GraphicBuffer> gb, jlong timestampNs, jlong dataSpace,
+        jint left, jint top, jint right, jint bottom, jint transform, jint scalingMode) {
     status_t res = OK;
     // Step 1. Attach Image
     res = surface->attachBuffer(gb.get());
@@ -713,8 +734,8 @@
     }
     sp < ANativeWindow > anw = surface;
 
-    // Step 2. Set timestamp, crop, transform and scaling mode. Note that we do not need unlock the
-    // image because it was not locked.
+    // Step 2. Set timestamp, dataspace, crop, transform and scaling mode.
+    // Note that we do not need unlock the image because it was not locked.
     ALOGV("timestamp to be queued: %" PRId64, timestampNs);
     res = native_window_set_buffers_timestamp(anw.get(), timestampNs);
     if (res != OK) {
@@ -722,6 +743,14 @@
         return res;
     }
 
+    ALOGV("dataSpace to be queued: %" PRId64, dataSpace);
+    res = native_window_set_buffers_data_space(
+        anw.get(), static_cast<android_dataspace>(dataSpace));
+    if (res != OK) {
+        jniThrowRuntimeException(env, "Set dataSpace failed");
+        return res;
+    }
+
     android_native_rect_t cropRect;
     cropRect.left = left;
     cropRect.top = top;
@@ -775,8 +804,8 @@
 }
 
 static jint ImageWriter_attachAndQueueImage(JNIEnv* env, jobject thiz, jlong nativeCtx,
-        jlong nativeBuffer, jint imageFormat, jlong timestampNs, jint left, jint top,
-        jint right, jint bottom, jint transform, jint scalingMode) {
+        jlong nativeBuffer, jint imageFormat, jlong timestampNs, jlong dataSpace,
+        jint left, jint top, jint right, jint bottom, jint transform, jint scalingMode) {
     ALOGV("%s", __FUNCTION__);
     JNIImageWriterContext* const ctx = reinterpret_cast<JNIImageWriterContext *>(nativeCtx);
     if (ctx == NULL || thiz == NULL) {
@@ -801,12 +830,12 @@
         return -1;
     }
 
-    return attachAndQeueuGraphicBuffer(env, ctx, surface, buffer->mGraphicBuffer, timestampNs, left,
-            top, right, bottom, transform, scalingMode);
+    return attachAndQeueuGraphicBuffer(env, ctx, surface, buffer->mGraphicBuffer, timestampNs,
+            dataSpace, left, top, right, bottom, transform, scalingMode);
 }
 
 static jint ImageWriter_attachAndQueueGraphicBuffer(JNIEnv* env, jobject thiz, jlong nativeCtx,
-        jobject buffer, jint format, jlong timestampNs, jint left, jint top,
+        jobject buffer, jint format, jlong timestampNs, jlong dataSpace, jint left, jint top,
         jint right, jint bottom, jint transform, jint scalingMode) {
     ALOGV("%s", __FUNCTION__);
     JNIImageWriterContext* const ctx = reinterpret_cast<JNIImageWriterContext *>(nativeCtx);
@@ -830,9 +859,8 @@
                 "Trying to attach an invalid graphic buffer");
         return -1;
     }
-
-    return attachAndQeueuGraphicBuffer(env, ctx, surface, graphicBuffer, timestampNs, left,
-            top, right, bottom, transform, scalingMode);
+    return attachAndQeueuGraphicBuffer(env, ctx, surface, graphicBuffer, timestampNs,
+            dataSpace, left, top, right, bottom, transform, scalingMode);
 }
 
 // --------------------------Image methods---------------------------------------
@@ -853,7 +881,7 @@
 }
 
 static void Image_setNativeContext(JNIEnv* env, jobject thiz,
-        sp<GraphicBuffer> buffer, int fenceFd) {
+        sp<GraphicBuffer> buffer, int fenceFd, long dataSpace) {
     ALOGV("%s:", __FUNCTION__);
     GraphicBuffer* p = NULL;
     Image_getNativeContext(env, thiz, &p, /*fenceFd*/NULL);
@@ -867,6 +895,8 @@
             reinterpret_cast<jlong>(buffer.get()));
 
     env->SetIntField(thiz, gSurfaceImageClassInfo.mNativeFenceFd, reinterpret_cast<jint>(fenceFd));
+
+    env->SetLongField(thiz, gSurfaceImageClassInfo.mDataSpace, dataSpace);
 }
 
 static void Image_unlockIfLocked(JNIEnv* env, jobject thiz) {
@@ -1066,12 +1096,15 @@
     {"nativeInit",              "(Ljava/lang/Object;Landroid/view/Surface;IIII)J",
                                                               (void*)ImageWriter_init },
     {"nativeClose",              "(J)V",                      (void*)ImageWriter_close },
-    {"nativeAttachAndQueueImage", "(JJIJIIIIII)I",          (void*)ImageWriter_attachAndQueueImage },
+    {"nativeAttachAndQueueImage",
+        "(JJIJJIIIIII)I",
+        (void*)ImageWriter_attachAndQueueImage },
     {"nativeAttachAndQueueGraphicBuffer",
-        "(JLandroid/graphics/GraphicBuffer;IJIIIIII)I",
+        "(JLandroid/graphics/GraphicBuffer;IJJIIIIII)I",
         (void*)ImageWriter_attachAndQueueGraphicBuffer },
     {"nativeDequeueInputImage", "(JLandroid/media/Image;)V",  (void*)ImageWriter_dequeueImage },
-    {"nativeQueueInputImage",   "(JLandroid/media/Image;JIIIIII)V",  (void*)ImageWriter_queueImage },
+    {"nativeQueueInputImage",   "(JLandroid/media/Image;JJIIIIII)V",
+                                                               (void*)ImageWriter_queueImage },
     {"cancelImage",             "(JLandroid/media/Image;)V",   (void*)ImageWriter_cancelImage },
 };
 
diff --git a/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java b/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java
index 48be6fe..669ad62 100644
--- a/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java
+++ b/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java
@@ -132,7 +132,7 @@
 
         StorageVolume mainStorage = new StorageVolume(MAIN_STORAGE_ID_STR,
                 mMainStorageDir, mMainStorageDir, "Primary Storage",
-                true, false, true, false, -1, UserHandle.CURRENT, null /* uuid */, "", "");
+                true, false, true, false, false, -1, UserHandle.CURRENT, null /* uuid */, "", "");
 
         final StorageVolume primary = mainStorage;
 
diff --git a/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
index fdf6582..abdc7e5 100644
--- a/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
+++ b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
@@ -132,9 +132,10 @@
         secondaryStorageDir = createNewDir(TEMP_DIR_FILE);
 
         StorageVolume mainStorage = new StorageVolume("1", mainStorageDir, mainStorageDir,
-                "", true, false, true, false, -1, UserHandle.CURRENT, null /* uuid */, "", "");
+                "", true, false, true, false, false, -1, UserHandle.CURRENT, null /* uuid */, "",
+                "");
         StorageVolume secondaryStorage = new StorageVolume("2", secondaryStorageDir,
-                secondaryStorageDir, "", false, false, true, false, -1, UserHandle.CURRENT,
+                secondaryStorageDir, "", false, false, true, false, false, -1, UserHandle.CURRENT,
                 null /* uuid */, "", "");
 
         objectsAdded = new ArrayList<>();
diff --git a/packages/Nsd/OWNERS b/packages/Nsd/OWNERS
new file mode 100644
index 0000000..4862377
--- /dev/null
+++ b/packages/Nsd/OWNERS
@@ -0,0 +1 @@
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
\ No newline at end of file
diff --git a/packages/Nsd/framework/Android.bp b/packages/Nsd/framework/Android.bp
new file mode 100644
index 0000000..2363a9f
--- /dev/null
+++ b/packages/Nsd/framework/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 {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+    name: "framework-connectivity-nsd-internal-sources",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.aidl",
+    ],
+    path: "src",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+filegroup {
+    name: "framework-connectivity-nsd-aidl-export-sources",
+    srcs: [
+        "aidl-export/**/*.aidl",
+    ],
+    path: "aidl-export",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+filegroup {
+    name: "framework-connectivity-nsd-sources",
+    srcs: [
+        ":framework-connectivity-nsd-internal-sources",
+        ":framework-connectivity-nsd-aidl-export-sources",
+    ],
+    visibility: [
+        "//frameworks/base",
+    ],
+}
diff --git a/core/java/android/net/nsd/NsdServiceInfo.aidl b/packages/Nsd/framework/aidl-export/android/net/nsd/NsdServiceInfo.aidl
similarity index 100%
rename from core/java/android/net/nsd/NsdServiceInfo.aidl
rename to packages/Nsd/framework/aidl-export/android/net/nsd/NsdServiceInfo.aidl
diff --git a/core/java/android/net/nsd/INsdManager.aidl b/packages/Nsd/framework/src/android/net/nsd/INsdManager.aidl
similarity index 100%
rename from core/java/android/net/nsd/INsdManager.aidl
rename to packages/Nsd/framework/src/android/net/nsd/INsdManager.aidl
diff --git a/core/java/android/net/nsd/INsdManagerCallback.aidl b/packages/Nsd/framework/src/android/net/nsd/INsdManagerCallback.aidl
similarity index 100%
rename from core/java/android/net/nsd/INsdManagerCallback.aidl
rename to packages/Nsd/framework/src/android/net/nsd/INsdManagerCallback.aidl
diff --git a/core/java/android/net/nsd/INsdServiceConnector.aidl b/packages/Nsd/framework/src/android/net/nsd/INsdServiceConnector.aidl
similarity index 100%
rename from core/java/android/net/nsd/INsdServiceConnector.aidl
rename to packages/Nsd/framework/src/android/net/nsd/INsdServiceConnector.aidl
diff --git a/core/java/android/net/nsd/NsdManager.java b/packages/Nsd/framework/src/android/net/nsd/NsdManager.java
similarity index 100%
rename from core/java/android/net/nsd/NsdManager.java
rename to packages/Nsd/framework/src/android/net/nsd/NsdManager.java
diff --git a/core/java/android/net/nsd/NsdServiceInfo.java b/packages/Nsd/framework/src/android/net/nsd/NsdServiceInfo.java
similarity index 100%
rename from core/java/android/net/nsd/NsdServiceInfo.java
rename to packages/Nsd/framework/src/android/net/nsd/NsdServiceInfo.java
diff --git a/packages/Nsd/service/Android.bp b/packages/Nsd/service/Android.bp
new file mode 100644
index 0000000..529f58d
--- /dev/null
+++ b/packages/Nsd/service/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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 {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+    name: "services.connectivity-nsd-sources",
+    srcs: [
+        "src/**/*.java",
+    ],
+    path: "src",
+    visibility: [
+        "//frameworks/base/services/core",
+    ],
+}
diff --git a/services/core/java/com/android/server/INativeDaemonConnectorCallbacks.java b/packages/Nsd/service/src/com/android/server/INativeDaemonConnectorCallbacks.java
similarity index 100%
rename from services/core/java/com/android/server/INativeDaemonConnectorCallbacks.java
rename to packages/Nsd/service/src/com/android/server/INativeDaemonConnectorCallbacks.java
diff --git a/services/core/java/com/android/server/NativeDaemonConnector.java b/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
similarity index 95%
rename from services/core/java/com/android/server/NativeDaemonConnector.java
rename to packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
index eac767f..02475dd 100644
--- a/services/core/java/com/android/server/NativeDaemonConnector.java
+++ b/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
@@ -26,12 +26,10 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.LocalLog;
-import android.util.Slog;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
 import com.android.server.power.ShutdownThread;
-import com.google.android.collect.Lists;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -40,19 +38,19 @@
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.LinkedList;
+import java.util.Objects;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import java.util.LinkedList;
-import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Generic connector class for interfacing with a native daemon which uses the
  * {@code libsysutils} FrameworkListener protocol.
  */
-final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
+final class NativeDaemonConnector implements Runnable, Handler.Callback {
     private final static boolean VDBG = false;
 
     private final String TAG;
@@ -107,7 +105,7 @@
 
     /**
      * Enable Set debugging mode, which causes messages to also be written to both
-     * {@link Slog} in addition to internal log.
+     * {@link Log} in addition to internal log.
      */
     public void setDebug(boolean debug) {
         mDebug = debug;
@@ -126,7 +124,9 @@
      * calls while holding a lock on the given object.
      */
     public void setWarnIfHeld(Object warnIfHeld) {
-        Preconditions.checkState(mWarnIfHeld == null);
+        if (mWarnIfHeld != null) {
+            throw new IllegalStateException("warnIfHeld is already set.");
+        }
         mWarnIfHeld = Objects.requireNonNull(warnIfHeld);
     }
 
@@ -183,7 +183,7 @@
         // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
         // production devices, even if said native daemons ill-advisedly pick a socket name that
         // starts with __test__, only allow this on debug builds.
-        if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
+        if (mSocket.startsWith("__test__") && Build.isDebuggable()) {
             return new LocalSocketAddress(mSocket);
         } else {
             return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
@@ -375,7 +375,7 @@
         try {
             latch.await();
         } catch (InterruptedException e) {
-            Slog.wtf(TAG, "Interrupted while waiting for unsolicited response handling", e);
+            Log.wtf(TAG, "Interrupted while waiting for unsolicited response handling", e);
         }
     }
 
@@ -462,13 +462,13 @@
     public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
             throws NativeDaemonConnectorException {
         if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
-            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
+            Log.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
                     + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
         }
 
         final long startTime = SystemClock.elapsedRealtime();
 
-        final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
+        final ArrayList<NativeDaemonEvent> events = new ArrayList<>();
 
         final StringBuilder rawBuilder = new StringBuilder();
         final StringBuilder logBuilder = new StringBuilder();
@@ -571,7 +571,7 @@
      */
     public static class Command {
         private String mCmd;
-        private ArrayList<Object> mArguments = Lists.newArrayList();
+        private ArrayList<Object> mArguments = new ArrayList<>();
 
         public Command(String cmd, Object... args) {
             mCmd = cmd;
@@ -586,11 +586,6 @@
         }
     }
 
-    /** {@inheritDoc} */
-    public void monitor() {
-        synchronized (mDaemonLock) { }
-    }
-
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mLocalLog.dump(fd, pw, args);
         pw.println();
@@ -598,12 +593,12 @@
     }
 
     private void log(String logstring) {
-        if (mDebug) Slog.d(TAG, logstring);
+        if (mDebug) Log.d(TAG, logstring);
         mLocalLog.log(logstring);
     }
 
     private void loge(String logstring) {
-        Slog.e(TAG, logstring);
+        Log.e(TAG, logstring);
         mLocalLog.log(logstring);
     }
 
@@ -659,12 +654,12 @@
                 if (found == null) {
                     // didn't find it - make sure our queue isn't too big before adding
                     while (mPendingCmds.size() >= mMaxCount) {
-                        Slog.e("NativeDaemonConnector.ResponseQueue",
+                        Log.e("NativeDaemonConnector.ResponseQueue",
                                 "more buffered than allowed: " + mPendingCmds.size() +
                                 " >= " + mMaxCount);
                         // let any waiter timeout waiting for this
                         PendingCmd pendingCmd = mPendingCmds.remove();
-                        Slog.e("NativeDaemonConnector.ResponseQueue",
+                        Log.e("NativeDaemonConnector.ResponseQueue",
                                 "Removing request: " + pendingCmd.logCmd + " (" +
                                 pendingCmd.cmdNum + ")");
                     }
@@ -706,7 +701,7 @@
                 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
             } catch (InterruptedException e) {}
             if (result == null) {
-                Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
+                Log.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
             }
             return result;
         }
diff --git a/services/core/java/com/android/server/NativeDaemonConnectorException.java b/packages/Nsd/service/src/com/android/server/NativeDaemonConnectorException.java
similarity index 100%
rename from services/core/java/com/android/server/NativeDaemonConnectorException.java
rename to packages/Nsd/service/src/com/android/server/NativeDaemonConnectorException.java
diff --git a/services/core/java/com/android/server/NativeDaemonEvent.java b/packages/Nsd/service/src/com/android/server/NativeDaemonEvent.java
similarity index 93%
rename from services/core/java/com/android/server/NativeDaemonEvent.java
rename to packages/Nsd/service/src/com/android/server/NativeDaemonEvent.java
index e6feda3..5683694 100644
--- a/services/core/java/com/android/server/NativeDaemonEvent.java
+++ b/packages/Nsd/service/src/com/android/server/NativeDaemonEvent.java
@@ -16,8 +16,7 @@
 
 package com.android.server;
 
-import android.util.Slog;
-import com.google.android.collect.Lists;
+import android.util.Log;
 
 import java.io.FileDescriptor;
 import java.util.ArrayList;
@@ -179,7 +178,7 @@
      * {@link #getMessage()} for any events matching the requested code.
      */
     public static String[] filterMessageList(NativeDaemonEvent[] events, int matchCode) {
-        final ArrayList<String> result = Lists.newArrayList();
+        final ArrayList<String> result = new ArrayList<>();
         for (NativeDaemonEvent event : events) {
             if (event.getCode() == matchCode) {
                 result.add(event.getMessage());
@@ -212,7 +211,7 @@
         int wordEnd = -1;
         boolean quoted = false;
 
-        if (DEBUG_ROUTINE) Slog.e(LOGTAG, "parsing '" + rawEvent + "'");
+        if (DEBUG_ROUTINE) Log.e(LOGTAG, "parsing '" + rawEvent + "'");
         if (rawEvent.charAt(current) == '\"') {
             quoted = true;
             current++;
@@ -240,14 +239,14 @@
             word = word.replace("\\\\", "\\");
             word = word.replace("\\\"", "\"");
 
-            if (DEBUG_ROUTINE) Slog.e(LOGTAG, "found '" + word + "'");
+            if (DEBUG_ROUTINE) Log.e(LOGTAG, "found '" + word + "'");
             parsed.add(word);
 
             // find the beginning of the next word - either of these options
             int nextSpace = rawEvent.indexOf(' ', current);
             int nextQuote = rawEvent.indexOf(" \"", current);
             if (DEBUG_ROUTINE) {
-                Slog.e(LOGTAG, "nextSpace=" + nextSpace + ", nextQuote=" + nextQuote);
+                Log.e(LOGTAG, "nextSpace=" + nextSpace + ", nextQuote=" + nextQuote);
             }
             if (nextQuote > -1 && nextQuote <= nextSpace) {
                 quoted = true;
@@ -259,8 +258,8 @@
                 }
             } // else we just start the next word after the current and read til the end
             if (DEBUG_ROUTINE) {
-                Slog.e(LOGTAG, "next loop - current=" + current +
-                        ", length=" + length + ", quoted=" + quoted);
+                Log.e(LOGTAG, "next loop - current=" + current
+                        + ", length=" + length + ", quoted=" + quoted);
             }
         }
         return parsed.toArray(new String[parsed.size()]);
diff --git a/services/core/java/com/android/server/NativeDaemonTimeoutException.java b/packages/Nsd/service/src/com/android/server/NativeDaemonTimeoutException.java
similarity index 100%
rename from services/core/java/com/android/server/NativeDaemonTimeoutException.java
rename to packages/Nsd/service/src/com/android/server/NativeDaemonTimeoutException.java
diff --git a/services/core/java/com/android/server/NsdService.java b/packages/Nsd/service/src/com/android/server/NsdService.java
similarity index 94%
rename from services/core/java/com/android/server/NsdService.java
rename to packages/Nsd/service/src/com/android/server/NsdService.java
index 3e02084..76ecea7 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/packages/Nsd/service/src/com/android/server/NsdService.java
@@ -19,6 +19,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.net.nsd.INsdManager;
@@ -36,12 +37,10 @@
 import android.util.Base64;
 import android.util.Log;
 import android.util.Pair;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.DumpUtils;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.net.module.util.DnsSdTxtRecord;
@@ -228,7 +227,7 @@
                         break;
                     case NsdManager.NATIVE_DAEMON_EVENT:
                     default:
-                        Slog.e(TAG, "Unhandled " + msg);
+                        Log.e(TAG, "Unhandled " + msg);
                         return NOT_HANDLED;
                 }
                 return HANDLED;
@@ -274,7 +273,7 @@
 
             private boolean requestLimitReached(ClientInfo clientInfo) {
                 if (clientInfo.mClientIds.size() >= ClientInfo.MAX_LIMIT) {
-                    if (DBG) Slog.d(TAG, "Exceeded max outstanding requests " + clientInfo);
+                    if (DBG) Log.d(TAG, "Exceeded max outstanding requests " + clientInfo);
                     return true;
                 }
                 return false;
@@ -307,7 +306,7 @@
                         transitionTo(mDisabledState);
                         break;
                     case NsdManager.DISCOVER_SERVICES:
-                        if (DBG) Slog.d(TAG, "Discover services");
+                        if (DBG) Log.d(TAG, "Discover services");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
 
@@ -321,8 +320,8 @@
                         id = getUniqueId();
                         if (discoverServices(id, args.serviceInfo.getServiceType())) {
                             if (DBG) {
-                                Slog.d(TAG, "Discover " + msg.arg2 + " " + id +
-                                        args.serviceInfo.getServiceType());
+                                Log.d(TAG, "Discover " + msg.arg2 + " " + id
+                                        + args.serviceInfo.getServiceType());
                             }
                             storeRequestMap(clientId, id, clientInfo, msg.what);
                             clientInfo.onDiscoverServicesStarted(clientId, args.serviceInfo);
@@ -333,7 +332,7 @@
                         }
                         break;
                     case NsdManager.STOP_DISCOVERY:
-                        if (DBG) Slog.d(TAG, "Stop service discovery");
+                        if (DBG) Log.d(TAG, "Stop service discovery");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
 
@@ -353,7 +352,7 @@
                         }
                         break;
                     case NsdManager.REGISTER_SERVICE:
-                        if (DBG) Slog.d(TAG, "Register service");
+                        if (DBG) Log.d(TAG, "Register service");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
                         if (requestLimitReached(clientInfo)) {
@@ -365,7 +364,7 @@
                         maybeStartDaemon();
                         id = getUniqueId();
                         if (registerService(id, args.serviceInfo)) {
-                            if (DBG) Slog.d(TAG, "Register " + clientId + " " + id);
+                            if (DBG) Log.d(TAG, "Register " + clientId + " " + id);
                             storeRequestMap(clientId, id, clientInfo, msg.what);
                             // Return success after mDns reports success
                         } else {
@@ -375,11 +374,11 @@
                         }
                         break;
                     case NsdManager.UNREGISTER_SERVICE:
-                        if (DBG) Slog.d(TAG, "unregister service");
+                        if (DBG) Log.d(TAG, "unregister service");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
                         if (clientInfo == null) {
-                            Slog.e(TAG, "Unknown connector in unregistration");
+                            Log.e(TAG, "Unknown connector in unregistration");
                             break;
                         }
                         id = clientInfo.mClientIds.get(clientId);
@@ -392,7 +391,7 @@
                         }
                         break;
                     case NsdManager.RESOLVE_SERVICE:
-                        if (DBG) Slog.d(TAG, "Resolve service");
+                        if (DBG) Log.d(TAG, "Resolve service");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
 
@@ -430,7 +429,7 @@
                 ClientInfo clientInfo = mIdToClientInfoMap.get(id);
                 if (clientInfo == null) {
                     String name = NativeResponseCode.nameOf(code);
-                    Slog.e(TAG, String.format("id %d for %s has no client mapping", id, name));
+                    Log.e(TAG, String.format("id %d for %s has no client mapping", id, name));
                     return false;
                 }
 
@@ -441,14 +440,14 @@
                     // SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY,
                     // and we may get in this situation.
                     String name = NativeResponseCode.nameOf(code);
-                    Slog.d(TAG, String.format(
+                    Log.d(TAG, String.format(
                             "Notification %s for listener id %d that is no longer active",
                             name, id));
                     return false;
                 }
                 if (DBG) {
                     String name = NativeResponseCode.nameOf(code);
-                    Slog.d(TAG, String.format("Native daemon message %s: %s", name, raw));
+                    Log.d(TAG, String.format("Native daemon message %s: %s", name, raw));
                 }
                 switch (code) {
                     case NativeResponseCode.SERVICE_FOUND:
@@ -492,7 +491,7 @@
                             ++index;
                         }
                         if (index >= cooked[2].length()) {
-                            Slog.e(TAG, "Invalid service found " + raw);
+                            Log.e(TAG, "Invalid service found " + raw);
                             break;
                         }
                         String name = cooked[2].substring(0, index);
@@ -562,13 +561,13 @@
             char c = s.charAt(i);
             if (c == '\\') {
                 if (++i >= s.length()) {
-                    Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
+                    Log.e(TAG, "Unexpected end of escape sequence in: " + s);
                     break;
                 }
                 c = s.charAt(i);
                 if (c != '.' && c != '\\') {
                     if (i + 2 >= s.length()) {
-                        Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
+                        Log.e(TAG, "Unexpected end of escape sequence in: " + s);
                         break;
                     }
                     c = (char) ((c-'0') * 100 + (s.charAt(i+1)-'0') * 10 + (s.charAt(i+2)-'0'));
@@ -685,7 +684,7 @@
     private boolean isNsdEnabled() {
         boolean ret = mNsdSettings.isEnabled();
         if (DBG) {
-            Slog.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled"));
+            Log.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled"));
         }
         return ret;
     }
@@ -795,12 +794,12 @@
          */
         public boolean execute(Object... args) {
             if (DBG) {
-                Slog.d(TAG, "mdnssd " + Arrays.toString(args));
+                Log.d(TAG, "mdnssd " + Arrays.toString(args));
             }
             try {
                 mNativeConnector.execute("mdnssd", args);
             } catch (NativeDaemonConnectorException e) {
-                Slog.e(TAG, "Failed to execute mdnssd " + Arrays.toString(args), e);
+                Log.e(TAG, "Failed to execute mdnssd " + Arrays.toString(args), e);
                 return false;
             }
             return true;
@@ -831,7 +830,7 @@
 
     private boolean registerService(int regId, NsdServiceInfo service) {
         if (DBG) {
-            Slog.d(TAG, "registerService: " + regId + " " + service);
+            Log.d(TAG, "registerService: " + regId + " " + service);
         }
         String name = service.getServiceName();
         String type = service.getServiceType();
@@ -880,7 +879,12 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump " + TAG
+                    + " due to missing android.permission.DUMP permission");
+            return;
+        }
 
         for (ClientInfo client : mClients.values()) {
             pw.println("Client Info");
@@ -909,7 +913,7 @@
 
         private ClientInfo(INsdManagerCallback cb) {
             mCb = cb;
-            if (DBG) Slog.d(TAG, "New client");
+            if (DBG) Log.d(TAG, "New client");
         }
 
         @Override
@@ -943,8 +947,10 @@
                 clientId = mClientIds.keyAt(i);
                 globalId = mClientIds.valueAt(i);
                 mIdToClientInfoMap.remove(globalId);
-                if (DBG) Slog.d(TAG, "Terminating client-ID " + clientId +
-                        " global-ID " + globalId + " type " + mClientRequests.get(clientId));
+                if (DBG) {
+                    Log.d(TAG, "Terminating client-ID " + clientId
+                            + " global-ID " + globalId + " type " + mClientRequests.get(clientId));
+                }
                 switch (mClientRequests.get(clientId)) {
                     case NsdManager.DISCOVER_SERVICES:
                         stopServiceDiscovery(globalId);
diff --git a/services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java b/packages/Nsd/tests/unit/java/com/android/server/NativeDaemonConnectorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java
rename to packages/Nsd/tests/unit/java/com/android/server/NativeDaemonConnectorTest.java
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index f637ff8..d73e45e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -19,7 +19,6 @@
 import android.graphics.ColorFilter;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.location.LocationManager;
 import android.media.AudioManager;
@@ -44,6 +43,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.UserIcons;
+import com.android.launcher3.icons.BaseIconFactory.IconOptions;
 import com.android.launcher3.icons.IconFactory;
 import com.android.settingslib.drawable.UserIconDrawable;
 import com.android.settingslib.fuelgauge.BatteryStatus;
@@ -525,9 +525,9 @@
     /** Get the corresponding adaptive icon drawable. */
     public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) {
         try (IconFactory iconFactory = IconFactory.obtain(context)) {
-            final Bitmap iconBmp = iconFactory.createBadgedIconBitmap(icon, user,
-                    true /* shrinkNonAdaptiveIcons */).icon;
-            return new BitmapDrawable(context.getResources(), iconBmp);
+            return iconFactory
+                    .createBadgedIconBitmap(icon, new IconOptions().setUser(user))
+                    .newIcon(context);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
index 1d8f71e..78ec58b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
@@ -18,11 +18,13 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.icu.text.ListFormatter;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.text.TextUtils;
@@ -155,9 +157,41 @@
         return set;
     }
 
-    public static void saveInputMethodSubtypeList(PreferenceFragmentCompat context,
+    /**
+     * Save the enabled/disabled input methods and selected subtype states into system settings.
+     *
+     * @param fragment The preference fragment user interact with.
+     * @param resolver The {@link ContentResolver} used to access the database.
+     * @param inputMethodInfos The list of {@link InputMethodInfo} to be checked.
+     * @param hasHardKeyboard {@code true} if the device has the hardware keyboard.
+     */
+    public static void saveInputMethodSubtypeList(PreferenceFragmentCompat fragment,
             ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
             boolean hasHardKeyboard) {
+        saveInputMethodSubtypeListForUserInternal(
+                fragment, resolver, inputMethodInfos, hasHardKeyboard, UserHandle.myUserId());
+    }
+
+    /**
+     * Save the enabled/disabled input methods and selected subtype states into system settings as
+     * given userId.
+     *
+     * @param fragment The preference fragment user interact with.
+     * @param resolver The {@link ContentResolver} used to access the database.
+     * @param inputMethodInfos The list of {@link InputMethodInfo} to be checked.
+     * @param hasHardKeyboard {@code true} if the device has the hardware keyboard.
+     * @param userId The given userId
+     */
+    public static void saveInputMethodSubtypeListForUser(PreferenceFragmentCompat fragment,
+            ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
+            boolean hasHardKeyboard, @UserIdInt int userId) {
+        saveInputMethodSubtypeListForUserInternal(
+                fragment, resolver, inputMethodInfos, hasHardKeyboard, userId);
+    }
+
+    private static void saveInputMethodSubtypeListForUserInternal(PreferenceFragmentCompat fragment,
+            ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
+            boolean hasHardKeyboard, @UserIdInt int userId) {
         String currentInputMethodId = Settings.Secure.getString(resolver,
                 Settings.Secure.DEFAULT_INPUT_METHOD);
         final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
@@ -168,7 +202,7 @@
         boolean needsToResetSelectedSubtype = false;
         for (final InputMethodInfo imi : inputMethodInfos) {
             final String imiId = imi.getId();
-            final Preference pref = context.findPreference(imiId);
+            final Preference pref = fragment.findPreference(imiId);
             if (pref == null) {
                 continue;
             }
@@ -184,8 +218,11 @@
             }
             final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
             final boolean systemIme = imi.isSystem();
+            // Create context as given userId
+            final Context wrapperContext = userId == UserHandle.myUserId() ? fragment.getActivity()
+                    : fragment.getActivity().createContextAsUser(UserHandle.of(userId), 0);
             if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
-                    context.getActivity()).isAlwaysCheckedIme(imi))
+                    wrapperContext).isAlwaysCheckedIme(imi))
                     || isImeChecked) {
                 if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
                     // imiId has just been enabled
@@ -198,7 +235,7 @@
                 for (int i = 0; i < subtypeCount; ++i) {
                     final InputMethodSubtype subtype = imi.getSubtypeAt(i);
                     final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
-                    final TwoStatePreference subtypePref = (TwoStatePreference) context
+                    final TwoStatePreference subtypePref = (TwoStatePreference) fragment
                             .findPreference(imiId + subtypeHashCodeStr);
                     // In the Configure input method screen which does not have subtype preferences.
                     if (subtypePref == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
index 94a0c00..c1ab706 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -18,6 +18,7 @@
 
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
+import android.annotation.UserIdInt;
 import android.app.AlertDialog;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
@@ -75,30 +76,34 @@
     private final OnSavePreferenceListener mOnSaveListener;
     private final InputMethodSettingValuesWrapper mInputMethodSettingValues;
     private final boolean mIsAllowedByOrganization;
+    @UserIdInt
+    private final int mUserId;
 
     private AlertDialog mDialog = null;
 
     /**
      * A preference entry of an input method.
      *
-     * @param context The Context this is associated with.
+     * @param prefContext The Context this preference is associated with.
      * @param imi The {@link InputMethodInfo} of this preference.
      * @param isAllowedByOrganization false if the IME has been disabled by a device or profile
      *     owner.
      * @param onSaveListener The listener called when this preference has been changed and needs
      *     to save the state to shared preference.
+     * @param userId The userId to specify the corresponding user for this preference.
      */
-    public InputMethodPreference(final Context context, final InputMethodInfo imi,
-            final boolean isAllowedByOrganization, final OnSavePreferenceListener onSaveListener) {
-        this(context, imi, imi.loadLabel(context.getPackageManager()), isAllowedByOrganization,
-                onSaveListener);
+    public InputMethodPreference(final Context prefContext, final InputMethodInfo imi,
+            final boolean isAllowedByOrganization, final OnSavePreferenceListener onSaveListener,
+            final @UserIdInt int userId) {
+        this(prefContext, imi, imi.loadLabel(prefContext.getPackageManager()),
+                isAllowedByOrganization, onSaveListener, userId);
     }
 
     @VisibleForTesting
-    InputMethodPreference(final Context context, final InputMethodInfo imi,
+    InputMethodPreference(final Context prefContext, final InputMethodInfo imi,
             final CharSequence title, final boolean isAllowedByOrganization,
-            final OnSavePreferenceListener onSaveListener) {
-        super(context);
+            final OnSavePreferenceListener onSaveListener, final @UserIdInt int userId) {
+        super(prefContext);
         setPersistent(false);
         mImi = imi;
         mIsAllowedByOrganization = isAllowedByOrganization;
@@ -114,7 +119,12 @@
             intent.setClassName(imi.getPackageName(), settingsActivity);
             setIntent(intent);
         }
-        mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(context);
+        // Handle the context by given userId because {@link InputMethodSettingValuesWrapper} is
+        // per-user instance.
+        final Context userAwareContext = userId == UserHandle.myUserId() ? prefContext :
+                getContext().createContextAsUser(UserHandle.of(userId), 0);
+        mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(userAwareContext);
+        mUserId = userId;
         mHasPriorityInSorting = imi.isSystem()
                 && InputMethodAndSubtypeUtil.isValidNonAuxAsciiCapableIme(imi);
         setOnPreferenceClickListener(this);
@@ -130,17 +140,15 @@
         super.onBindViewHolder(holder);
         final Switch switchWidget = getSwitch();
         if (switchWidget != null) {
+            // Avoid default behavior in {@link PrimarySwitchPreference#onBindViewHolder}.
             switchWidget.setOnClickListener(v -> {
-                // no-op, avoid default behavior in {@link PrimarySwitchPreference#onBindViewHolder}
-            });
-            switchWidget.setOnCheckedChangeListener((buttonView, isChecked) -> {
-                // Avoid the invocation after we call {@link PrimarySwitchPreference#setChecked()}
-                // in {@link setCheckedInternal}
-                if (isChecked != isChecked()) {
-                    // Keep switch to previous state because we have to show the dialog first
-                    buttonView.setChecked(!isChecked);
-                    callChangeListener(isChecked());
+                if (!switchWidget.isEnabled()) {
+                    return;
                 }
+                final boolean newValue = !isChecked();
+                // Keep switch to previous state because we have to show the dialog first.
+                switchWidget.setChecked(isChecked());
+                callChangeListener(newValue);
             });
         }
         final ImageView icon = holder.itemView.findViewById(android.R.id.icon);
@@ -187,7 +195,7 @@
             final Intent intent = getIntent();
             if (intent != null) {
                 // Invoke a settings activity of an input method.
-                context.startActivity(intent);
+                context.startActivityAsUser(intent, UserHandle.of(mUserId));
             }
         } catch (final ActivityNotFoundException e) {
             Log.d(TAG, "IME's Settings Activity Not Found", e);
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
index 13c1b82..39e6dce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
@@ -16,13 +16,18 @@
 
 package com.android.settingslib.inputmethod;
 
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
 import android.annotation.UiThread;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -39,20 +44,39 @@
 public class InputMethodSettingValuesWrapper {
     private static final String TAG = InputMethodSettingValuesWrapper.class.getSimpleName();
 
-    private static volatile InputMethodSettingValuesWrapper sInstance;
+    private static final Object sInstanceMapLock = new Object();
+    /**
+     * Manages mapping between user ID and corresponding singleton
+     * {@link InputMethodSettingValuesWrapper} object.
+     */
+    @GuardedBy("sInstanceMapLock")
+    private static SparseArray<InputMethodSettingValuesWrapper> sInstanceMap = new SparseArray<>();
     private final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
     private final ContentResolver mContentResolver;
     private final InputMethodManager mImm;
 
-    public static InputMethodSettingValuesWrapper getInstance(Context context) {
-        if (sInstance == null) {
-            synchronized (TAG) {
-                if (sInstance == null) {
-                    sInstance = new InputMethodSettingValuesWrapper(context);
-                }
+    @AnyThread
+    @NonNull
+    public static InputMethodSettingValuesWrapper getInstance(@NonNull Context context) {
+        final int requestUserId = context.getUserId();
+        InputMethodSettingValuesWrapper valuesWrapper;
+        // First time to create the wrapper.
+        synchronized (sInstanceMapLock) {
+            if (sInstanceMap.size() == 0) {
+                valuesWrapper = new InputMethodSettingValuesWrapper(context);
+                sInstanceMap.put(requestUserId, valuesWrapper);
+                return valuesWrapper;
             }
+            // We have same user context as request.
+            if (sInstanceMap.indexOfKey(requestUserId) >= 0) {
+                return sInstanceMap.get(requestUserId);
+            }
+            // Request by a new user context.
+            valuesWrapper = new InputMethodSettingValuesWrapper(context);
+            sInstanceMap.put(context.getUserId(), valuesWrapper);
         }
-        return sInstance;
+
+        return valuesWrapper;
     }
 
     // Ensure singleton
@@ -64,7 +88,7 @@
 
     public void refreshAllInputMethodAndSubtypes() {
         mMethodList.clear();
-        mMethodList.addAll(mImm.getInputMethodList());
+        mMethodList.addAll(mImm.getInputMethodListAsUser(mContentResolver.getUserId()));
     }
 
     public List<InputMethodInfo> getInputMethodList() {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
index 9962e1c..1e75014 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
@@ -20,6 +20,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.os.UserHandle;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
@@ -112,7 +113,8 @@
                 createInputMethodInfo(systemIme, name),
                 title,
                 true /* isAllowedByOrganization */,
-                p -> {} /* onSavePreferenceListener */);
+                p -> {} /* onSavePreferenceListener */,
+                UserHandle.myUserId());
     }
 
     private static InputMethodInfo createInputMethodInfo(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index a67b565..6072f68 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1067,14 +1067,17 @@
                 Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE,
                 GlobalSettingsProto.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE);
 
-        final long nitzUpdateToken = p.start(GlobalSettingsProto.NITZ_UPDATE);
+        final long nitzToken = p.start(GlobalSettingsProto.NITZ);
         dumpSetting(s, p,
                 Settings.Global.NITZ_UPDATE_DIFF,
-                GlobalSettingsProto.NitzUpdate.DIFF);
+                GlobalSettingsProto.Nitz.UPDATE_DIFF);
         dumpSetting(s, p,
                 Settings.Global.NITZ_UPDATE_SPACING,
-                GlobalSettingsProto.NitzUpdate.SPACING);
-        p.end(nitzUpdateToken);
+                GlobalSettingsProto.Nitz.UPDATE_SPACING);
+        dumpSetting(s, p,
+                Settings.Global.NITZ_NETWORK_DISCONNECT_RETENTION,
+                GlobalSettingsProto.Nitz.NETWORK_DISCONNECT_RETENTION);
+        p.end(nitzToken);
 
         final long notificationToken = p.start(GlobalSettingsProto.NOTIFICATION);
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index c9c93c4..19ed021 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -384,6 +384,7 @@
                     Settings.Global.NETWORK_WATCHLIST_ENABLED,
                     Settings.Global.NEW_CONTACT_AGGREGATOR,
                     Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE,
+                    Settings.Global.NITZ_NETWORK_DISCONNECT_RETENTION,
                     Settings.Global.NITZ_UPDATE_DIFF,
                     Settings.Global.NITZ_UPDATE_SPACING,
                     Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 2b311ee..867ab3c 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -596,6 +596,9 @@
     <!-- Permission required for CTS test - SettingsMultiPaneDeepLinkTest -->
     <uses-permission android:name="android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK" />
 
+    <!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
+    <uses-permission android:name="android.permission.LOCK_DEVICE" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/res/drawable/media_ttt_chip_background.xml b/packages/SystemUI/res/drawable/media_ttt_chip_background.xml
new file mode 100644
index 0000000..3abf4d7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_ttt_chip_background.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ 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.
+  -->
+
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <solid android:color="?androidprv:attr/colorSurface" />
+    <corners android:radius="32dp" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/media_ttt_undo_background.xml b/packages/SystemUI/res/drawable/media_ttt_undo_background.xml
new file mode 100644
index 0000000..ec74ee1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_ttt_undo_background.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <solid android:color="?androidprv:attr/colorAccentPrimary" />
+    <corners android:radius="24dp" />
+</shape>
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 4a5b637..e90a644 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -34,6 +34,7 @@
 
         <TextView
             android:id="@+id/internet_dialog_title"
+            android:ellipsize="end"
             android:gravity="center_vertical|center_horizontal"
             android:layout_width="wrap_content"
             android:layout_height="32dp"
@@ -154,12 +155,18 @@
                         <TextView
                             android:id="@+id/mobile_summary"
                             style="@style/InternetDialog.NetworkSummary"/>
+                        <TextView
+                            android:id="@+id/airplane_mode_summary"
+                            android:text="@string/airplane_mode"
+                            android:visibility="gone"
+                            style="@style/InternetDialog.NetworkSummary"/>
                     </LinearLayout>
 
                     <View
                         android:id="@+id/mobile_toggle_divider"
                         android:layout_width="1dp"
                         android:layout_height="28dp"
+                        android:layout_marginStart="7dp"
                         android:layout_marginEnd="16dp"
                         android:layout_gravity="center_vertical"
                         android:background="?android:attr/textColorSecondary"/>
@@ -370,27 +377,60 @@
                         android:clickable="true"/>
                 </LinearLayout>
             </LinearLayout>
-
             <FrameLayout
-                android:id="@+id/done_layout"
-                android:layout_width="67dp"
+                android:id="@+id/button_layout"
+                android:orientation="horizontal"
+                android:layout_width="match_parent"
                 android:layout_height="48dp"
-                android:layout_marginTop="8dp"
+                android:layout_marginStart="24dp"
                 android:layout_marginEnd="24dp"
+                android:layout_marginTop="8dp"
                 android:layout_marginBottom="34dp"
-                android:layout_gravity="end|center_vertical"
-                android:clickable="true"
-                android:focusable="true">
-                <Button
-                    android:text="@string/inline_done_button"
-                    style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-                    android:layout_width="match_parent"
-                    android:layout_height="36dp"
-                    android:layout_gravity="center"
-                    android:textAppearance="@style/TextAppearance.InternetDialog"
-                    android:textSize="14sp"
-                    android:background="@drawable/internet_dialog_footer_background"
-                    android:clickable="false"/>
+                android:clickable="false"
+                android:focusable="false">
+
+                <FrameLayout
+                    android:id="@+id/apm_layout"
+                    android:layout_width="wrap_content"
+                    android:layout_height="48dp"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:layout_gravity="start|center_vertical"
+                    android:orientation="vertical">
+                    <Button
+                        android:text="@string/turn_off_airplane_mode"
+                        android:ellipsize="end"
+                        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+                        android:layout_width="wrap_content"
+                        android:layout_height="36dp"
+                        android:layout_gravity="start|center_vertical"
+                        android:textAppearance="@style/TextAppearance.InternetDialog"
+                        android:textSize="14sp"
+                        android:background="@drawable/internet_dialog_footer_background"
+                        android:clickable="false"/>
+                </FrameLayout>
+
+                <FrameLayout
+                    android:id="@+id/done_layout"
+                    android:layout_width="wrap_content"
+                    android:layout_height="48dp"
+                    android:layout_marginStart="16dp"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:layout_gravity="end|center_vertical"
+                    android:orientation="vertical">
+                    <Button
+                        android:text="@string/inline_done_button"
+                        android:ellipsize="end"
+                        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+                        android:layout_width="67dp"
+                        android:layout_height="36dp"
+                        android:layout_gravity="end|center_vertical"
+                        android:textAppearance="@style/TextAppearance.InternetDialog"
+                        android:textSize="14sp"
+                        android:background="@drawable/internet_dialog_footer_background"
+                        android:clickable="false"/>
+                </FrameLayout>
             </FrameLayout>
         </LinearLayout>
     </androidx.core.widget.NestedScrollView>
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml
new file mode 100644
index 0000000..6fbc41c
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_ttt_chip.xml
@@ -0,0 +1,64 @@
+<!--
+  ~ 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:orientation="horizontal"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:padding="@dimen/media_ttt_chip_outer_padding"
+    android:background="@drawable/media_ttt_chip_background"
+    android:layout_marginTop="50dp"
+    android:clipToPadding="false"
+    android:gravity="center_vertical"
+    >
+
+    <TextView
+        android:id="@+id/text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="@dimen/media_ttt_text_size"
+        android:textColor="?android:attr/textColorPrimary"
+        />
+
+    <ProgressBar
+        android:id="@+id/loading"
+        android:indeterminate="true"
+        android:layout_width="@dimen/media_ttt_icon_size"
+        android:layout_height="@dimen/media_ttt_icon_size"
+        android:layout_marginStart="12dp"
+        android:indeterminateTint="?androidprv:attr/colorAccentPrimaryVariant"
+        style="?android:attr/progressBarStyleSmall"
+        />
+
+    <TextView
+        android:id="@+id/undo"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/media_transfer_undo"
+        android:textColor="?androidprv:attr/textColorOnAccent"
+        android:layout_marginStart="12dp"
+        android:textSize="@dimen/media_ttt_text_size"
+        android:paddingStart="@dimen/media_ttt_chip_outer_padding"
+        android:paddingEnd="@dimen/media_ttt_chip_outer_padding"
+        android:paddingTop="@dimen/media_ttt_undo_button_vertical_padding"
+        android:paddingBottom="@dimen/media_ttt_undo_button_vertical_padding"
+        android:layout_marginTop="@dimen/media_ttt_undo_button_vertical_negative_margin"
+        android:layout_marginBottom="@dimen/media_ttt_undo_button_vertical_negative_margin"
+        android:background="@drawable/media_ttt_undo_background"
+        />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml
index 91b11fc..3a0df28 100644
--- a/packages/SystemUI/res/layout/qs_user_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml
@@ -37,8 +37,8 @@
             android:layout_height="@dimen/qs_framed_avatar_size"
             android:layout_marginBottom="7dp"
             systemui:frameWidth="6dp"
-            systemui:badgeDiameter="18dp"
-            systemui:badgeMargin="1dp"
+            systemui:badgeDiameter="15dp"
+            systemui:badgeMargin="5dp"
             systemui:framePadding="-1dp"
             systemui:frameColor="@color/qs_user_avatar_frame"/>
 
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index b3bbd87..ff748a9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -98,6 +98,7 @@
          as custom(package/class). Relative class name is supported. -->
     <string-array name="config_quickSettingsAutoAdd" translatable="false">
         <item>accessibility_display_inversion_enabled:inversion</item>
+        <item>one_handed_mode_enabled:onehanded</item>
     </string-array>
 
     <!-- Show indicator for Wifi on but not connected. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a437ae6..0b56264 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -976,6 +976,13 @@
     <dimen name="qs_aa_media_rec_album_margin_vert">4dp</dimen>
     <dimen name="qq_aa_media_rec_header_text_size">16sp</dimen>
 
+    <!-- Media tap-to-transfer chip -->
+    <dimen name="media_ttt_chip_outer_padding">16dp</dimen>
+    <dimen name="media_ttt_text_size">16sp</dimen>
+    <dimen name="media_ttt_icon_size">16dp</dimen>
+    <dimen name="media_ttt_undo_button_vertical_padding">8dp</dimen>
+    <dimen name="media_ttt_undo_button_vertical_negative_margin">-8dp</dimen>
+
     <!-- Window magnification -->
     <dimen name="magnification_border_drag_size">35dp</dimen>
     <dimen name="magnification_outer_border_margin">15dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 300cb2d3..43254aa 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2164,6 +2164,15 @@
     <!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] -->
     <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string>
 
+    <!-- Description for button in media controls. Pressing button starts playback [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_button_play">Play</string>
+    <!-- Description for button in media controls. Pressing button pauses playback [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_button_pause">Pause</string>
+    <!-- Description for button in media controls. Pressing button goes to previous track [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_button_prev">Previous track</string>
+    <!-- Description for button in media controls. Pressing button goes to next track [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_button_next">Next track</string>
+
     <!-- Title for Smartspace recommendation card within media controls. The "Play" means the action to play a media [CHAR_LIMIT=10] -->
     <string name="controls_media_smartspace_rec_title">Play</string>
     <!-- Description for Smartspace recommendation card within media controls [CHAR_LIMIT=NONE] -->
@@ -2173,6 +2182,14 @@
     <!-- Description for Smartspace recommendation's media item which doesn't have artist info, including information for the media's title and the source app [CHAR LIMIT=NONE]-->
     <string name="controls_media_smartspace_rec_item_no_artist_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%2$s</xliff:g></string>
 
+    <!--- ****** Media tap-to-transfer ****** -->
+    <!-- Text for a button to undo the media transfer. [CHAR LIMIT=20] -->
+    <string name="media_transfer_undo">Undo</string>
+    <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to play music on the different device. [CHAR LIMIT=75] -->
+    <string name="media_move_closer_to_transfer">Move closer to play on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
+    <!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] -->
+    <string name="media_transfer_playing">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
+
     <!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] -->
     <string name="controls_error_timeout">Inactive, check app</string>
     <!-- Error message indicating that the control is no longer available in the application [CHAR LIMIT=30] -->
@@ -2347,6 +2364,8 @@
     <string name="to_switch_networks_disconnect_ethernet">To switch networks, disconnect ethernet</string>
     <!-- Message to describe "Wi-Fi scan always available feature" when Wi-Fi is off and Wi-Fi scanning is on. [CHAR LIMIT=NONE] -->
     <string name="wifi_scan_notify_message">To improve device experience, apps and services can still scan for Wi\u2011Fi networks at any time, even when Wi\u2011Fi is off. You can change this in Wi\u2011Fi scanning settings. <annotation id="link">Change</annotation></string>
+    <!-- Provider Model: Description of the airplane mode button. [CHAR LIMIT=60] -->
+    <string name="turn_off_airplane_mode">Turn off airplane mode</string>
 
     <!-- Text for TileService request dialog. This is shown to the user that an app is requesting
          user approval to add the shown tile to Quick Settings [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index 9574101..b611c96 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -16,28 +16,31 @@
 
 package com.android.systemui.flags
 
+import android.annotation.BoolRes
+import android.annotation.IntegerRes
+import android.annotation.StringRes
 import android.os.Parcel
 import android.os.Parcelable
 
-interface Flag<T> : Parcelable {
+interface Flag<T> {
     val id: Int
+}
+
+interface ParcelableFlag<T> : Flag<T>, Parcelable {
     val default: T
-    val resourceOverride: Int
-
     override fun describeContents() = 0
+}
 
-    fun hasResourceOverride(): Boolean {
-        return resourceOverride != -1
-    }
+interface ResourceFlag<T> : Flag<T> {
+    val resourceId: Int
 }
 
 // Consider using the "parcelize" kotlin library.
 
 data class BooleanFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Boolean = false,
-    override val resourceOverride: Int = -1
-) : Flag<Boolean> {
+    override val default: Boolean = false
+) : ParcelableFlag<Boolean> {
 
     companion object {
         @JvmField
@@ -58,11 +61,15 @@
     }
 }
 
+data class ResourceBooleanFlag constructor(
+    override val id: Int,
+    @BoolRes override val resourceId: Int
+) : ResourceFlag<Boolean>
+
 data class StringFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: String = "",
-    override val resourceOverride: Int = -1
-) : Flag<String> {
+    override val default: String = ""
+) : ParcelableFlag<String> {
     companion object {
         @JvmField
         val CREATOR = object : Parcelable.Creator<StringFlag> {
@@ -82,11 +89,15 @@
     }
 }
 
+data class ResourceStringFlag constructor(
+    override val id: Int,
+    @StringRes override val resourceId: Int
+) : ResourceFlag<String>
+
 data class IntFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Int = 0,
-    override val resourceOverride: Int = -1
-) : Flag<Int> {
+    override val default: Int = 0
+) : ParcelableFlag<Int> {
 
     companion object {
         @JvmField
@@ -107,11 +118,15 @@
     }
 }
 
+data class ResourceIntFlag constructor(
+    override val id: Int,
+    @IntegerRes override val resourceId: Int
+) : ResourceFlag<Int>
+
 data class LongFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Long = 0,
-    override val resourceOverride: Int = -1
-) : Flag<Long> {
+    override val default: Long = 0
+) : ParcelableFlag<Long> {
 
     companion object {
         @JvmField
@@ -134,9 +149,8 @@
 
 data class FloatFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Float = 0f,
-    override val resourceOverride: Int = -1
-) : Flag<Float> {
+    override val default: Float = 0f
+) : ParcelableFlag<Float> {
 
     companion object {
         @JvmField
@@ -157,11 +171,15 @@
     }
 }
 
+data class ResourceFloatFlag constructor(
+    override val id: Int,
+    override val resourceId: Int
+) : ResourceFlag<Int>
+
 data class DoubleFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Double = 0.0,
-    override val resourceOverride: Int = -1
-) : Flag<Double> {
+    override val default: Double = 0.0
+) : ParcelableFlag<Double> {
 
     companion object {
         @JvmField
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index e61cb5c..b2ca2d7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -60,7 +60,7 @@
                     object : BroadcastReceiver() {
                         override fun onReceive(context: Context, intent: Intent) {
                             val extras: Bundle? = getResultExtras(false)
-                            val listOfFlags: java.util.ArrayList<Flag<*>>? =
+                            val listOfFlags: java.util.ArrayList<ParcelableFlag<*>>? =
                                 extras?.getParcelableArrayList(FIELD_FLAGS)
                             if (listOfFlags != null) {
                                 completer.set(listOfFlags)
@@ -108,6 +108,10 @@
         }
     }
 
+    override fun isEnabled(flag: ResourceBooleanFlag): Boolean {
+        throw RuntimeException("Not implemented in FlagManager")
+    }
+
     override fun addListener(listener: FlagReader.Listener) {
         synchronized(listeners) {
             val registerNeeded = listeners.isEmpty()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
index 91a3912..26c6a4b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
@@ -24,6 +24,8 @@
         return flag.default
     }
 
+    fun isEnabled(flag: ResourceBooleanFlag): Boolean
+
     /** Returns a boolean value for the given flag.  */
     fun isEnabled(id: Int, def: Boolean): Boolean {
         return def
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index a319b40..3cd090e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -270,7 +270,7 @@
                 mOpeningLeashes.add(openingTasks.get(i).getLeash());
                 // We are receiving new opening tasks, so convert to onTasksAppeared.
                 final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat(
-                        openingTasks.get(i), layer, mInfo, t);
+                        openingTasks.get(i), layer, info, t);
                 mLeashMap.put(mOpeningLeashes.get(i), target.leash.mSurfaceControl);
                 t.reparent(target.leash.mSurfaceControl, mInfo.getRootLeash());
                 t.setLayer(target.leash.mSurfaceControl, layer);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index b6be6ed..e46b6f1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
 import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
 import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
+import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider
 import com.android.systemui.unfold.updates.DeviceFoldStateProvider
 import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
 import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
@@ -62,7 +63,7 @@
         mainExecutor
     )
 
-    return if (config.isHingeAngleEnabled) {
+    val unfoldTransitionProgressProvider = if (config.isHingeAngleEnabled) {
         PhysicsBasedUnfoldTransitionProgressProvider(
             mainHandler,
             foldStateProvider
@@ -70,6 +71,10 @@
     } else {
         FixedTimingTransitionProgressProvider(foldStateProvider)
     }
+    return ScaleAwareTransitionProgressProvider(
+            unfoldTransitionProgressProvider,
+            context.contentResolver
+    )
 }
 
 fun createConfig(context: Context): UnfoldTransitionConfig =
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
index e072d41..58d7dfb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
@@ -2,7 +2,6 @@
 
 import android.content.Context
 import android.os.RemoteException
-import android.util.Log
 import android.view.IRotationWatcher
 import android.view.IWindowManager
 import android.view.Surface
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
new file mode 100644
index 0000000..df9078a
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
@@ -0,0 +1,50 @@
+package com.android.systemui.unfold.util
+
+import android.animation.ValueAnimator
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+
+/** Wraps [UnfoldTransitionProgressProvider] to disable transitions when animations are disabled. */
+class ScaleAwareTransitionProgressProvider(
+    unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+    private val contentResolver: ContentResolver
+) : UnfoldTransitionProgressProvider {
+
+    private val scopedUnfoldTransitionProgressProvider =
+            ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider)
+
+    private val animatorDurationScaleObserver = object : ContentObserver(null) {
+        override fun onChange(selfChange: Boolean) {
+            onAnimatorScaleChanged()
+        }
+    }
+
+    init {
+        contentResolver.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
+                /* notifyForDescendants= */ false,
+                animatorDurationScaleObserver)
+        onAnimatorScaleChanged()
+    }
+
+    private fun onAnimatorScaleChanged() {
+        val animationsEnabled = ValueAnimator.areAnimatorsEnabled()
+        scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(animationsEnabled)
+    }
+
+    override fun addCallback(listener: TransitionProgressListener) {
+        scopedUnfoldTransitionProgressProvider.addCallback(listener)
+    }
+
+    override fun removeCallback(listener: TransitionProgressListener) {
+        scopedUnfoldTransitionProgressProvider.removeCallback(listener)
+    }
+
+    override fun destroy() {
+        contentResolver.unregisterContentObserver(animatorDurationScaleObserver)
+        scopedUnfoldTransitionProgressProvider.destroy()
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java
deleted file mode 100644
index 543232d..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java
+++ /dev/null
@@ -1,142 +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.unfold.util;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Manages progress listeners that can have smaller lifespan than the unfold animation.
- * Allows to limit getting transition updates to only when
- * {@link ScopedUnfoldTransitionProgressProvider#setReadyToHandleTransition} is called
- * with readyToHandleTransition = true
- *
- * If the transition has already started by the moment when the clients are ready to play
- * the transition then it will report transition started callback and current animation progress.
- */
-public final class ScopedUnfoldTransitionProgressProvider implements
-        UnfoldTransitionProgressProvider, TransitionProgressListener {
-
-    private static final float PROGRESS_UNSET = -1f;
-
-    @Nullable
-    private UnfoldTransitionProgressProvider mSource;
-
-    private final List<TransitionProgressListener> mListeners = new ArrayList<>();
-
-    private boolean mIsReadyToHandleTransition;
-    private boolean mIsTransitionRunning;
-    private float mLastTransitionProgress = PROGRESS_UNSET;
-
-    public ScopedUnfoldTransitionProgressProvider() {
-        this(null);
-    }
-
-    public ScopedUnfoldTransitionProgressProvider(
-            @Nullable UnfoldTransitionProgressProvider source) {
-        setSourceProvider(source);
-    }
-
-    /**
-     * Sets the source for the unfold transition progress updates,
-     * it replaces current provider if it is already set
-     * @param provider transition provider that emits transition progress updates
-     */
-    public void setSourceProvider(@Nullable UnfoldTransitionProgressProvider provider) {
-        if (mSource != null) {
-            mSource.removeCallback(this);
-        }
-
-        if (provider != null) {
-            mSource = provider;
-            mSource.addCallback(this);
-        } else {
-            mSource = null;
-        }
-    }
-
-    /**
-     * Allows to notify this provide whether the listeners can play the transition or not.
-     * Call this method with readyToHandleTransition = true when all listeners
-     * are ready to consume the transition progress events.
-     * Call it with readyToHandleTransition = false when listeners can't process the events.
-     */
-    public void setReadyToHandleTransition(boolean isReadyToHandleTransition) {
-        if (mIsTransitionRunning) {
-            if (isReadyToHandleTransition) {
-                mListeners.forEach(TransitionProgressListener::onTransitionStarted);
-
-                if (mLastTransitionProgress != PROGRESS_UNSET) {
-                    mListeners.forEach(listener ->
-                            listener.onTransitionProgress(mLastTransitionProgress));
-                }
-            } else {
-                mIsTransitionRunning = false;
-                mListeners.forEach(TransitionProgressListener::onTransitionFinished);
-            }
-        }
-
-        mIsReadyToHandleTransition = isReadyToHandleTransition;
-    }
-
-    @Override
-    public void addCallback(@NonNull TransitionProgressListener listener) {
-        mListeners.add(listener);
-    }
-
-    @Override
-    public void removeCallback(@NonNull TransitionProgressListener listener) {
-        mListeners.remove(listener);
-    }
-
-    @Override
-    public void destroy() {
-        mSource.removeCallback(this);
-    }
-
-    @Override
-    public void onTransitionStarted() {
-        this.mIsTransitionRunning = true;
-        if (mIsReadyToHandleTransition) {
-            mListeners.forEach(TransitionProgressListener::onTransitionStarted);
-        }
-    }
-
-    @Override
-    public void onTransitionProgress(float progress) {
-        if (mIsReadyToHandleTransition) {
-            mListeners.forEach(listener -> listener.onTransitionProgress(progress));
-        }
-
-        mLastTransitionProgress = progress;
-    }
-
-    @Override
-    public void onTransitionFinished() {
-        if (mIsReadyToHandleTransition) {
-            mListeners.forEach(TransitionProgressListener::onTransitionFinished);
-        }
-
-        mIsTransitionRunning = false;
-        mLastTransitionProgress = PROGRESS_UNSET;
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
new file mode 100644
index 0000000..22698a8
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.unfold.util
+
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+
+/**
+ * Manages progress listeners that can have smaller lifespan than the unfold animation.
+ * Allows to limit getting transition updates to only when
+ * [ScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition] is called
+ * with readyToHandleTransition = true
+ *
+ * If the transition has already started by the moment when the clients are ready to play
+ * the transition then it will report transition started callback and current animation progress.
+ */
+class ScopedUnfoldTransitionProgressProvider @JvmOverloads constructor(
+    source: UnfoldTransitionProgressProvider? = null
+) : UnfoldTransitionProgressProvider, TransitionProgressListener {
+
+    private var source: UnfoldTransitionProgressProvider? = null
+
+    private val listeners: MutableList<TransitionProgressListener> = mutableListOf()
+
+    private var isReadyToHandleTransition = false
+    private var isTransitionRunning = false
+    private var lastTransitionProgress = PROGRESS_UNSET
+
+    init {
+        setSourceProvider(source)
+    }
+    /**
+     * Sets the source for the unfold transition progress updates,
+     * it replaces current provider if it is already set
+     * @param provider transition provider that emits transition progress updates
+     */
+    fun setSourceProvider(provider: UnfoldTransitionProgressProvider?) {
+        source?.removeCallback(this)
+
+        if (provider != null) {
+            source = provider
+            provider.addCallback(this)
+        } else {
+            source = null
+        }
+    }
+
+    /**
+     * Allows to notify this provide whether the listeners can play the transition or not.
+     * Call this method with readyToHandleTransition = true when all listeners
+     * are ready to consume the transition progress events.
+     * Call it with readyToHandleTransition = false when listeners can't process the events.
+     */
+    fun setReadyToHandleTransition(isReadyToHandleTransition: Boolean) {
+        if (isTransitionRunning) {
+            if (isReadyToHandleTransition) {
+                listeners.forEach { it.onTransitionStarted() }
+                if (lastTransitionProgress != PROGRESS_UNSET) {
+                    listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
+                }
+            } else {
+                isTransitionRunning = false
+                listeners.forEach { it.onTransitionFinished() }
+            }
+        }
+        this.isReadyToHandleTransition = isReadyToHandleTransition
+    }
+
+    override fun addCallback(listener: TransitionProgressListener) {
+        listeners += listener
+    }
+
+    override fun removeCallback(listener: TransitionProgressListener) {
+        listeners -= listener
+    }
+
+    override fun destroy() {
+        source?.removeCallback(this)
+    }
+
+    override fun onTransitionStarted() {
+        isTransitionRunning = true
+        if (isReadyToHandleTransition) {
+            listeners.forEach { it.onTransitionStarted() }
+        }
+    }
+
+    override fun onTransitionProgress(progress: Float) {
+        if (isReadyToHandleTransition) {
+            listeners.forEach { it.onTransitionProgress(progress) }
+        }
+        lastTransitionProgress = progress
+    }
+
+    override fun onTransitionFinished() {
+        if (isReadyToHandleTransition) {
+            listeners.forEach { it.onTransitionFinished() }
+        }
+        isTransitionRunning = false
+        lastTransitionProgress = PROGRESS_UNSET
+    }
+
+    companion object {
+        private const val PROGRESS_UNSET = -1f
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
index 3ebfb51..9b72655 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
@@ -22,12 +22,11 @@
 
 import com.android.systemui.communal.conditions.CommunalCondition;
 import com.android.systemui.communal.conditions.CommunalSettingCondition;
-import com.android.systemui.communal.conditions.CommunalTrustedNetworkCondition;
 import com.android.systemui.idle.AmbientLightModeMonitor;
 import com.android.systemui.idle.LightSensorEventsDebounceAlgorithm;
 import com.android.systemui.idle.dagger.IdleViewComponent;
 
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -73,9 +72,7 @@
     @ElementsIntoSet
     @Named(COMMUNAL_CONDITIONS)
     static Set<CommunalCondition> provideCommunalConditions(
-            CommunalSettingCondition communalSettingCondition,
-            CommunalTrustedNetworkCondition communalTrustedNetworkCondition) {
-        return new HashSet<>(
-                Arrays.asList(communalSettingCondition, communalTrustedNetworkCondition));
+            CommunalSettingCondition communalSettingCondition) {
+        return new HashSet<>(Collections.singletonList(communalSettingCondition));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 1d17fd8..c4a58db 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -52,6 +52,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.QsFrameTranslateModule;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -112,6 +113,7 @@
             LogModule.class,
             PeopleHubModule.class,
             PluginModule.class,
+            QsFrameTranslateModule.class,
             ScreenshotModule.class,
             SensorModule.class,
             SettingsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 0ee47a7..3f00b87 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -22,6 +22,7 @@
 import static com.android.systemui.flags.FlagManager.FIELD_ID;
 import static com.android.systemui.flags.FlagManager.FIELD_VALUE;
 
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -30,7 +31,6 @@
 import android.os.Bundle;
 import android.util.Log;
 
-import androidx.annotation.BoolRes;
 import androidx.annotation.NonNull;
 
 import com.android.systemui.Dumpable;
@@ -89,16 +89,18 @@
     public boolean isEnabled(BooleanFlag flag) {
         int id = flag.getId();
         if (!mBooleanFlagCache.containsKey(id)) {
-            boolean def = flag.getDefault();
-            if (flag.hasResourceOverride()) {
-                try {
-                    def = isEnabledInOverlay(flag.getResourceOverride());
-                } catch (Resources.NotFoundException e) {
-                    // no-op
-                }
-            }
+            mBooleanFlagCache.put(id, isEnabled(id, flag.getDefault()));
+        }
 
-            mBooleanFlagCache.put(id, isEnabled(id, def));
+        return mBooleanFlagCache.get(id);
+    }
+
+    @Override
+    public boolean isEnabled(ResourceBooleanFlag flag) {
+        int id = flag.getId();
+        if (!mBooleanFlagCache.containsKey(id)) {
+            mBooleanFlagCache.put(
+                    id, isEnabled(id, mResources.getBoolean(flag.getResourceId())));
         }
 
         return mBooleanFlagCache.get(id);
@@ -111,6 +113,7 @@
         return result == null ? defaultValue : result;
     }
 
+
     /** Returns the stored value or null if not set. */
     private Boolean isEnabledInternal(int id) {
         try {
@@ -121,10 +124,6 @@
         return null;
     }
 
-    private boolean isEnabledInOverlay(@BoolRes int resId) {
-        return mResources.getBoolean(resId);
-    }
-
     /** Set whether a given {@link BooleanFlag} is enabled or not. */
     public void setEnabled(int id, boolean value) {
         Boolean currentValue = isEnabledInternal(id);
@@ -185,9 +184,19 @@
             } else if (ACTION_GET_FLAGS.equals(action)) {
                 Map<Integer, Flag<?>> knownFlagMap = Flags.collectFlags();
                 ArrayList<Flag<?>> flags = new ArrayList<>(knownFlagMap.values());
+
+                // Convert all flags to parcelable flags.
+                ArrayList<ParcelableFlag<?>> pFlags = new ArrayList<>();
+                for (Flag<?> f : flags) {
+                    ParcelableFlag<?> pf = toParcelableFlag(f);
+                    if (pf != null) {
+                        pFlags.add(pf);
+                    }
+                }
+
                 Bundle extras =  getResultExtras(true);
                 if (extras != null) {
-                    extras.putParcelableArrayList(FIELD_FLAGS, flags);
+                    extras.putParcelableArrayList(FIELD_FLAGS, pFlags);
                 }
             }
         }
@@ -215,6 +224,25 @@
                 setEnabled(id, extras.getBoolean(FIELD_VALUE));
             }
         }
+
+        /**
+         * Ensures that the data we send to the app reflects the current state of the flags.
+         *
+         * Also converts an non-parcelable versions of the flags to their parcelable versions.
+         */
+        @Nullable
+        private ParcelableFlag<?> toParcelableFlag(Flag<?> f) {
+            if (f instanceof BooleanFlag) {
+                return new BooleanFlag(f.getId(), isEnabled((BooleanFlag) f));
+            }
+            if (f instanceof ResourceBooleanFlag) {
+                return new BooleanFlag(f.getId(), isEnabled((ResourceBooleanFlag) f));
+            }
+
+            // TODO: add support for other flag types.
+            Log.w(TAG, "Unsupported Flag Type. Please file a bug.");
+            return null;
+        }
     };
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index bd6cb66..5b6404f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -16,12 +16,14 @@
 
 package com.android.systemui.flags;
 
+import android.content.res.Resources;
 import android.util.SparseBooleanArray;
 
 import androidx.annotation.NonNull;
 
 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 java.io.FileDescriptor;
@@ -37,9 +39,11 @@
  */
 @SysUISingleton
 public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
-    SparseBooleanArray mAccessedFlags = new SparseBooleanArray();
+    private final Resources mResources;
+    SparseBooleanArray mFlagCache = new SparseBooleanArray();
     @Inject
-    public FeatureFlagsRelease(DumpManager dumpManager) {
+    public FeatureFlagsRelease(@Main Resources resources, DumpManager dumpManager) {
+        mResources = resources;
         dumpManager.registerDumpable("SysUIFlags", this);
     }
 
@@ -55,18 +59,28 @@
     }
 
     @Override
+    public boolean isEnabled(ResourceBooleanFlag flag) {
+        int cacheIndex = mFlagCache.indexOfKey(flag.getId());
+        if (cacheIndex < 0) {
+            return isEnabled(flag.getId(), mResources.getBoolean(flag.getResourceId()));
+        }
+
+        return mFlagCache.valueAt(cacheIndex);
+    }
+
+    @Override
     public boolean isEnabled(int key, boolean defaultValue) {
-        mAccessedFlags.append(key, defaultValue);
+        mFlagCache.append(key, defaultValue);
         return defaultValue;
     }
 
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("can override: false");
-        int size = mAccessedFlags.size();
+        int size = mFlagCache.size();
         for (int i = 0; i < size; i++) {
-            pw.println("  sysui_flag_" + mAccessedFlags.keyAt(i)
-                    + ": " + mAccessedFlags.valueAt(i));
+            pw.println("  sysui_flag_" + mFlagCache.keyAt(i)
+                    + ": " + mFlagCache.valueAt(i));
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 458cdc1f..97533c9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -61,8 +61,8 @@
     public static final BooleanFlag NEW_UNLOCK_SWIPE_ANIMATION =
             new BooleanFlag(202, true);
 
-    public static final BooleanFlag CHARGING_RIPPLE =
-            new BooleanFlag(203, false, R.bool.flag_charging_ripple);
+    public static final ResourceBooleanFlag CHARGING_RIPPLE =
+            new ResourceBooleanFlag(203, R.bool.flag_charging_ripple);
 
     /***************************************/
     // 300 - power menu
@@ -77,8 +77,8 @@
     public static final BooleanFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
             new BooleanFlag(401, false);
 
-    public static final BooleanFlag SMARTSPACE =
-            new BooleanFlag(402, false, R.bool.flag_smartspace);
+    public static final ResourceBooleanFlag SMARTSPACE =
+            new ResourceBooleanFlag(402, R.bool.flag_smartspace);
 
     /***************************************/
     // 500 - quick settings
@@ -88,11 +88,11 @@
     public static final BooleanFlag COMBINED_QS_HEADERS =
             new BooleanFlag(501, false);
 
-    public static final BooleanFlag PEOPLE_TILE =
-            new BooleanFlag(502, false, R.bool.flag_conversations);
+    public static final ResourceBooleanFlag PEOPLE_TILE =
+            new ResourceBooleanFlag(502, R.bool.flag_conversations);
 
-    public static final BooleanFlag QS_USER_DETAIL_SHORTCUT =
-            new BooleanFlag(503, false, R.bool.flag_lockscreen_qs_user_detail_shortcut);
+    public static final ResourceBooleanFlag QS_USER_DETAIL_SHORTCUT =
+            new ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut);
 
     /***************************************/
     // 600- status bar
@@ -115,12 +115,13 @@
 
     /***************************************/
     // 800 - general visual/theme
-    public static final BooleanFlag MONET =
-            new BooleanFlag(800, true, R.bool.flag_monet);
+    public static final ResourceBooleanFlag MONET =
+            new ResourceBooleanFlag(800, R.bool.flag_monet);
 
     /***************************************/
     // 900 - media
     public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false);
+    public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false);
 
     // Pay no attention to the reflection behind the curtain.
     // ========================== Curtain ==========================
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index b85f1072..e921ad29c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -862,8 +862,23 @@
 
 @VisibleForTesting
 internal object MediaPlayerData {
-    private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null,
-            emptyList(), emptyList(), "INVALID", null, null, null, true, null)
+    private val EMPTY = MediaData(
+            userId = -1,
+            initialized = false,
+            backgroundColor = 0,
+            app = null,
+            appIcon = null,
+            artist = null,
+            song = null,
+            artwork = null,
+            actions = emptyList(),
+            actionsToShowInCompact = emptyList(),
+            packageName = "INVALID",
+            token = null,
+            clickIntent = null,
+            device = null,
+            active = true,
+            resumeAction = null)
     // Whether should prioritize Smartspace card.
     internal var shouldPrioritizeSs: Boolean = false
         private set
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index f66eb5b..63555bb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -62,6 +62,7 @@
 import com.android.systemui.util.time.SystemClock;
 
 import java.net.URISyntaxException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -122,6 +123,8 @@
     private MediaCarouselController mMediaCarouselController;
     private final MediaOutputDialogFactory mMediaOutputDialogFactory;
     private final FalsingManager mFalsingManager;
+    private final MediaFlags mMediaFlags;
+
     // Used for swipe-to-dismiss logging.
     protected boolean mIsImpressed = false;
     private SystemClock mSystemClock;
@@ -138,7 +141,7 @@
             SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
             KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory
             mediaOutputDialogFactory, MediaCarouselController mediaCarouselController,
-            FalsingManager falsingManager, SystemClock systemClock) {
+            FalsingManager falsingManager, MediaFlags mediaFlags, SystemClock systemClock) {
         mContext = context;
         mBackgroundExecutor = backgroundExecutor;
         mActivityStarter = activityStarter;
@@ -149,8 +152,8 @@
         mMediaOutputDialogFactory = mediaOutputDialogFactory;
         mMediaCarouselController = mediaCarouselController;
         mFalsingManager = falsingManager;
+        mMediaFlags = mediaFlags;
         mSystemClock = systemClock;
-
         loadDimens();
 
         mSeekBarViewModel.setLogSmartspaceClick(() -> {
@@ -426,33 +429,61 @@
         deviceName.setText(deviceString);
         seamlessView.setContentDescription(deviceString);
 
-        List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
-        // Media controls
-        int i = 0;
+        // Media action buttons
         List<MediaAction> actionIcons = data.getActions();
+        List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
+
+        if (mMediaFlags.areMediaSessionActionsEnabled() && data.getSemanticActions() != null) {
+            // Use PlaybackState actions instead
+            MediaButton semanticActions = data.getSemanticActions();
+
+            actionIcons = new ArrayList<MediaAction>();
+            actionIcons.add(semanticActions.getStartCustom());
+            actionIcons.add(semanticActions.getPrevOrCustom());
+            actionIcons.add(semanticActions.getPlayOrPause());
+            actionIcons.add(semanticActions.getNextOrCustom());
+            actionIcons.add(semanticActions.getEndCustom());
+
+            actionsWhenCollapsed = new ArrayList<Integer>();
+            actionsWhenCollapsed.add(1);
+            actionsWhenCollapsed.add(2);
+            actionsWhenCollapsed.add(3);
+        }
+
+        int i = 0;
         for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) {
             int actionId = ACTION_IDS[i];
+            boolean visibleInCompat = actionsWhenCollapsed.contains(i);
             final ImageButton button = mPlayerViewHolder.getAction(actionId);
             MediaAction mediaAction = actionIcons.get(i);
-            button.setImageIcon(mediaAction.getIcon());
-            button.setContentDescription(mediaAction.getContentDescription());
-            Runnable action = mediaAction.getAction();
+            if (mediaAction != null) {
+                button.setImageIcon(mediaAction.getIcon());
+                button.setContentDescription(mediaAction.getContentDescription());
+                Runnable action = mediaAction.getAction();
 
-            if (action == null) {
-                button.setEnabled(false);
+                if (action == null) {
+                    button.setEnabled(false);
+                } else {
+                    button.setEnabled(true);
+                    button.setOnClickListener(v -> {
+                        if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                            logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
+                                    /* isRecommendationCard */ false);
+                            action.run();
+                        }
+                    });
+                }
+                setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
+                setVisibleAndAlpha(expandedSet, actionId, true /*visible */);
             } else {
-                button.setEnabled(true);
-                button.setOnClickListener(v -> {
-                    if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                        logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
-                                /* isRecommendationCard */ false);
-                        action.run();
-                    }
-                });
+                button.setImageIcon(null);
+                button.setContentDescription(null);
+                button.setEnabled(false);
+                setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
+                // for expanded layout, set as INVISIBLE so that we still reserve space in the UI
+                expandedSet.setVisibility(actionId, ConstraintSet.INVISIBLE);
+                expandedSet.setAlpha(actionId, 0.0f);
             }
-            boolean visibleInCompat = actionsWhenCollapsed.contains(i);
-            setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
-            setVisibleAndAlpha(expandedSet, actionId, true /*visible */);
         }
 
         // Hide any unused buttons
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index bda07f4..4b8dfde 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -47,7 +47,7 @@
      */
     val artwork: Icon?,
     /**
-     * List of actions that can be performed on the player: prev, next, play, pause, etc.
+     * List of generic action buttons for the media player, based on notification actions
      */
     val actions: List<MediaAction>,
     /**
@@ -55,6 +55,11 @@
      */
     val actionsToShowInCompact: List<Int>,
     /**
+     * Semantic actions buttons, based on the PlaybackState of the media session.
+     * If present, these actions will be preferred in the UI over [actions]
+     */
+    val semanticActions: MediaButton? = null,
+    /**
      * Package name of the app that's posting the media.
      */
     val packageName: String,
@@ -125,6 +130,32 @@
     }
 }
 
+/**
+ * Contains [MediaAction] objects which represent specific buttons in the UI
+ */
+data class MediaButton(
+    /**
+     * Play/pause button
+     */
+    var playOrPause: MediaAction? = null,
+    /**
+     * Next button, or custom action
+     */
+    var nextOrCustom: MediaAction? = null,
+    /**
+     * Previous button, or custom action
+     */
+    var prevOrCustom: MediaAction? = null,
+    /**
+     * First custom action space
+     */
+    var startCustom: MediaAction? = null,
+    /**
+     * Last custom action space
+     */
+    var endCustom: MediaAction? = null
+)
+
 /** State of a media action. */
 data class MediaAction(
     val icon: Icon?,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 7c0f7fc..49a63c3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -38,6 +38,7 @@
 import android.media.MediaMetadata
 import android.media.session.MediaController
 import android.media.session.MediaSession
+import android.media.session.PlaybackState
 import android.net.Uri
 import android.os.Parcelable
 import android.os.UserHandle
@@ -45,6 +46,7 @@
 import android.service.notification.StatusBarNotification
 import android.text.TextUtils
 import android.util.Log
+import androidx.media.utils.MediaConstants
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.Dumpable
 import com.android.systemui.R
@@ -80,8 +82,24 @@
 private const val DEBUG = true
 private const val EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY = "dismiss_intent"
 
-private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
-        emptyList(), emptyList(), "INVALID", null, null, null, true, null)
+private val LOADING = MediaData(
+        userId = -1,
+        initialized = false,
+        backgroundColor = 0,
+        app = null,
+        appIcon = null,
+        artist = null,
+        song = null,
+        artwork = null,
+        actions = emptyList(),
+        actionsToShowInCompact = emptyList(),
+        packageName = "INVALID",
+        token = null,
+        clickIntent = null,
+        device = null,
+        active = true,
+        resumeAction = null)
+
 @VisibleForTesting
 internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData("INVALID", false, false,
     "INVALID", null, emptyList(), null, 0, 0)
@@ -112,7 +130,8 @@
     private var useMediaResumption: Boolean,
     private val useQsMediaPlayer: Boolean,
     private val systemClock: SystemClock,
-    private val tunerService: TunerService
+    private val tunerService: TunerService,
+    private val mediaFlags: MediaFlags,
 ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
 
     companion object {
@@ -127,6 +146,10 @@
         // Maximum number of actions allowed in compact view
         @JvmField
         val MAX_COMPACT_ACTIONS = 3
+
+        /** Maximum number of [PlaybackState.CustomAction] buttons supported */
+        @JvmField
+        val MAX_CUSTOM_ACTIONS = 4
     }
 
     private val themeText = com.android.settingslib.Utils.getColorAttr(context,
@@ -182,12 +205,13 @@
         activityStarter: ActivityStarter,
         smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
         clock: SystemClock,
-        tunerService: TunerService
+        tunerService: TunerService,
+        mediaFlags: MediaFlags
     ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
             broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
             mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter,
             activityStarter, smartspaceMediaDataProvider, Utils.useMediaResumption(context),
-            Utils.useQsMediaPlayer(context), clock, tunerService)
+            Utils.useQsMediaPlayer(context), clock, tunerService, mediaFlags)
 
     private val appChangeReceiver = object : BroadcastReceiver() {
         override fun onReceive(context: Context, intent: Intent) {
@@ -522,7 +546,7 @@
         foregroundExecutor.execute {
             onMediaDataLoaded(packageName, null, MediaData(userId, true, bgColor, appName,
                     null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
-                    packageName, token, appIntent, device = null, active = false,
+                    null, packageName, token, appIntent, device = null, active = false,
                     resumeAction = resumeAction, resumption = true, notificationKey = packageName,
                     hasCheckedForResume = true, lastActive = lastActive))
         }
@@ -594,15 +618,55 @@
         }
 
         // Control buttons
+        // If flag is enabled and controller has a PlaybackState, create actions from session info
+        // Otherwise, use the notification actions
+        var actionIcons: List<MediaAction> = emptyList()
+        var actionsToShowCollapsed: List<Int> = emptyList()
+        var semanticActions: MediaButton? = null
+        if (mediaFlags.areMediaSessionActionsEnabled() && mediaController.playbackState != null) {
+            semanticActions = createActionsFromState(sbn.packageName, mediaController)
+        } else {
+            val actions = createActionsFromNotification(sbn)
+            actionIcons = actions.first
+            actionsToShowCollapsed = actions.second
+        }
+
+        val playbackLocation =
+                if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE
+                else if (mediaController.playbackInfo?.playbackType ==
+                        MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) MediaData.PLAYBACK_LOCAL
+                else MediaData.PLAYBACK_CAST_LOCAL
+        val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null
+        val lastActive = systemClock.elapsedRealtime()
+        foregroundExecutor.execute {
+            val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
+            val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
+            val active = mediaEntries[key]?.active ?: true
+            onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
+                    smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed,
+                    semanticActions, sbn.packageName, token, notif.contentIntent, null,
+                    active, resumeAction = resumeAction, playbackLocation = playbackLocation,
+                    notificationKey = key, hasCheckedForResume = hasCheckedForResume,
+                    isPlaying = isPlaying, isClearable = sbn.isClearable(),
+                    lastActive = lastActive))
+        }
+    }
+
+    /**
+     * Generate action buttons based on notification actions
+     */
+    private fun createActionsFromNotification(sbn: StatusBarNotification):
+            Pair<List<MediaAction>, List<Int>> {
+        val notif = sbn.notification
         val actionIcons: MutableList<MediaAction> = ArrayList()
         val actions = notif.actions
         var actionsToShowCollapsed = notif.extras.getIntArray(
-                Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() ?: mutableListOf<Int>()
+            Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() ?: mutableListOf()
         if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
-            Log.e(TAG, "Too many compact actions for $key, limiting to first $MAX_COMPACT_ACTIONS")
+            Log.e(TAG, "Too many compact actions for ${sbn.key}," +
+                "limiting to first $MAX_COMPACT_ACTIONS")
             actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
         }
-        // TODO: b/153736623 look into creating actions when this isn't a media style notification
 
         if (actions != null) {
             for ((index, action) in actions.withIndex()) {
@@ -631,32 +695,150 @@
                     action.getIcon()
                 }.setTint(themeText)
                 val mediaAction = MediaAction(
-                        mediaActionIcon,
-                        runnable,
-                        action.title)
+                    mediaActionIcon,
+                    runnable,
+                    action.title)
                 actionIcons.add(mediaAction)
             }
         }
+        return Pair(actionIcons, actionsToShowCollapsed)
+    }
 
-        val playbackLocation =
-                if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE
-                else if (mediaController.playbackInfo?.playbackType ==
-                    MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) MediaData.PLAYBACK_LOCAL
-                else MediaData.PLAYBACK_CAST_LOCAL
-        val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null
-        val lastActive = systemClock.elapsedRealtime()
-        foregroundExecutor.execute {
-            val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
-            val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
-            val active = mediaEntries[key]?.active ?: true
-            onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
-                    smallIcon, artist, song, artWorkIcon, actionIcons,
-                    actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null,
-                    active, resumeAction = resumeAction, playbackLocation = playbackLocation,
-                    notificationKey = key, hasCheckedForResume = hasCheckedForResume,
-                    isPlaying = isPlaying, isClearable = sbn.isClearable(),
-                    lastActive = lastActive))
+    /**
+     * Generates action button info for this media session based on the PlaybackState
+     *
+     * @param packageName Package name for the media app
+     * @param controller MediaController for the current session
+     * @return a Pair consisting of a list of media actions, and a list of ints representing which
+     *      of those actions should be shown in the compact player
+     */
+    private fun createActionsFromState(packageName: String, controller: MediaController):
+            MediaButton? {
+        val actions = MediaButton()
+        controller.playbackState?.let { state ->
+            // First, check for standard actions
+            actions.playOrPause = if (isPlayingState(state.state)) {
+                getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
+            } else {
+                getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
+            }
+            val prevButton = getStandardAction(controller, state.actions,
+                    PlaybackState.ACTION_SKIP_TO_PREVIOUS)
+            val nextButton = getStandardAction(controller, state.actions,
+                    PlaybackState.ACTION_SKIP_TO_NEXT)
+
+            // Then, check for custom actions
+            val customActions = MutableList<MediaAction?>(4) { null }
+            var customCount = 0
+            for (i in 0..MAX_CUSTOM_ACTIONS) {
+                getCustomAction(state, packageName, controller, customCount)?.let {
+                    customActions[customCount++] = it
+                }
+            }
+
+            // Finally, assign the remaining button slots: C A play/pause B D
+            // A = previous, else custom action (if not reserved)
+            // B = next, else custom action (if not reserved)
+            // C and D are always custom actions
+            val reservePrev = controller.extras?.getBoolean(
+                    MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV) == true
+            val reserveNext = controller.extras?.getBoolean(
+                    MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT) == true
+            var customIdx = 0
+
+            actions.prevOrCustom = if (prevButton != null) {
+                prevButton
+            } else if (!reservePrev) {
+                customActions[customIdx++]
+            } else {
+                null
+            }
+
+            actions.nextOrCustom = if (nextButton != null) {
+                nextButton
+            } else if (!reserveNext) {
+                customActions[customIdx++]
+            } else {
+                null
+            }
+
+            actions.startCustom = customActions[customIdx++]
+            actions.endCustom = customActions[customIdx++]
         }
+        return actions
+    }
+
+    /**
+     * Get a [MediaAction] representing one of
+     * - [PlaybackState.ACTION_PLAY]
+     * - [PlaybackState.ACTION_PAUSE]
+     * - [PlaybackState.ACTION_SKIP_TO_PREVIOUS]
+     * - [PlaybackState.ACTION_SKIP_TO_NEXT]
+     */
+    private fun getStandardAction(
+        controller: MediaController,
+        stateActions: Long,
+        action: Long
+    ): MediaAction? {
+        if (stateActions and action == 0L) {
+            return null
+        }
+
+        return when (action) {
+            PlaybackState.ACTION_PLAY -> {
+                MediaAction(
+                    Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_play),
+                    { controller.transportControls.play() },
+                    context.getString(R.string.controls_media_button_play)
+                )
+            }
+            PlaybackState.ACTION_PAUSE -> {
+                MediaAction(
+                    Icon.createWithResource(context,
+                        com.android.internal.R.drawable.ic_media_pause),
+                    { controller.transportControls.pause() },
+                    context.getString(R.string.controls_media_button_pause)
+                )
+            }
+            PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
+                MediaAction(
+                    Icon.createWithResource(context,
+                        com.android.internal.R.drawable.ic_media_previous),
+                    { controller.transportControls.skipToPrevious() },
+                    context.getString(R.string.controls_media_button_prev)
+                )
+            }
+            PlaybackState.ACTION_SKIP_TO_NEXT -> {
+                MediaAction(
+                    Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_next),
+                    { controller.transportControls.skipToNext() },
+                    context.getString(R.string.controls_media_button_next)
+                )
+            }
+            else -> null
+        }
+    }
+
+    /**
+     * Get a [MediaAction] representing a [PlaybackState.CustomAction]
+     */
+    private fun getCustomAction(
+        state: PlaybackState,
+        packageName: String,
+        controller: MediaController,
+        index: Int
+    ): MediaAction? {
+        if (state.customActions.size <= index || state.customActions[index] == null) {
+            if (DEBUG) { Log.d(TAG, "not enough actions or action was null at $index") }
+            return null
+        }
+
+        val it = state.customActions[index]
+        return MediaAction(
+            Icon.createWithResource(packageName, it.icon),
+            { controller.transportControls.sendCustomAction(it, it.extras) },
+            it.name
+        )
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
new file mode 100644
index 0000000..b4a4b42
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.media
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+@SysUISingleton
+class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
+    /**
+     * Check whether media control actions should be based on PlaybackState instead of notification
+     */
+    fun areMediaSessionActionsEnabled(): Boolean {
+        return featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 237d077..cf679f0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -83,6 +83,6 @@
         if (!mediaTttFlags.isMediaTttEnabled()) {
             return Optional.empty();
         }
-        return Optional.of(new MediaTttChipController(context, commandRegistry, windowManager));
+        return Optional.of(new MediaTttChipController(commandRegistry, context, windowManager));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
index 85e5b33..af8fc0e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
@@ -19,9 +19,14 @@
 import android.content.Context
 import android.graphics.PixelFormat
 import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
 import android.view.WindowManager
+import android.widget.LinearLayout
 import android.widget.TextView
+import androidx.annotation.StringRes
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -36,8 +41,8 @@
  */
 @SysUISingleton
 class MediaTttChipController @Inject constructor(
-    context: Context,
     commandRegistry: CommandRegistry,
+    private val context: Context,
     private val windowManager: WindowManager,
 ) {
     init {
@@ -46,9 +51,9 @@
     }
 
     private val windowLayoutParams = WindowManager.LayoutParams().apply {
-        width = WindowManager.LayoutParams.MATCH_PARENT
-        height = WindowManager.LayoutParams.MATCH_PARENT
-        gravity = Gravity.CENTER_HORIZONTAL
+        width = WindowManager.LayoutParams.WRAP_CONTENT
+        height = WindowManager.LayoutParams.WRAP_CONTENT
+        gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
         type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
         title = "Media Tap-To-Transfer Chip View"
         flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -57,29 +62,71 @@
         setTrustedOverlay()
     }
 
-    // TODO(b/203800327): Create a layout that matches UX.
-    private val chipView: TextView = TextView(context).apply {
-        text = "Media Tap-To-Transfer Chip"
-    }
+    /** The chip view currently being displayed. Null if the chip is not being displayed. */
+    private var chipView: LinearLayout? = null
 
-    private var chipDisplaying: Boolean = false
+    private fun displayChip(chipType: ChipType, otherDeviceName: String) {
+        val oldChipView = chipView
+        if (chipView == null) {
+            chipView = LayoutInflater
+                .from(context)
+                .inflate(R.layout.media_ttt_chip, null) as LinearLayout
+        }
+        val currentChipView = chipView!!
 
-    private fun addChip() {
-        if (chipDisplaying) { return }
-        windowManager.addView(chipView, windowLayoutParams)
-        chipDisplaying = true
+        // Text
+        currentChipView.requireViewById<TextView>(R.id.text).apply {
+            text = context.getString(chipType.chipText, otherDeviceName)
+        }
+
+        // Loading
+        val showLoading = chipType == ChipType.TRANSFER_INITIATED
+        currentChipView.requireViewById<View>(R.id.loading).visibility =
+            if (showLoading) { View.VISIBLE } else { View.GONE }
+
+        // Undo
+        val showUndo = chipType == ChipType.TRANSFER_SUCCEEDED
+        currentChipView.requireViewById<View>(R.id.undo).visibility =
+            if (showUndo) { View.VISIBLE } else { View.GONE }
+
+        if (oldChipView == null) {
+            windowManager.addView(chipView, windowLayoutParams)
+        }
     }
 
     private fun removeChip() {
-        if (!chipDisplaying) { return }
+        if (chipView == null) { return }
         windowManager.removeView(chipView)
-        chipDisplaying = false
+        chipView = null
+    }
+
+    @VisibleForTesting
+    enum class ChipType(
+        @StringRes internal val chipText: Int
+    ) {
+        MOVE_CLOSER_TO_TRANSFER(R.string.media_move_closer_to_transfer),
+        TRANSFER_INITIATED(R.string.media_transfer_playing),
+        TRANSFER_SUCCEEDED(R.string.media_transfer_playing),
     }
 
     inner class AddChipCommand : Command {
-        override fun execute(pw: PrintWriter, args: List<String>) = addChip()
+        override fun execute(pw: PrintWriter, args: List<String>) {
+            val chipTypeArg = args[1]
+            ChipType.values().forEach {
+                if (it.name == chipTypeArg) {
+                    displayChip(it, otherDeviceName = args[0])
+                    return
+                }
+            }
+
+            pw.println("Chip type must be one of " +
+                    ChipType.values().map { it.name }.reduce { acc, s -> "$acc, $s" })
+        }
+
         override fun help(pw: PrintWriter) {
-            pw.println("Usage: adb shell cmd statusbar $ADD_CHIP_COMMAND_TAG")
+            pw.println(
+                "Usage: adb shell cmd statusbar $ADD_CHIP_COMMAND_TAG <deviceName> <chipType>"
+            )
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 5afefa1..ab48a28 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.navigationbar.gestural;
 
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
+
 import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
 
 import android.app.ActivityManager;
@@ -544,7 +546,8 @@
         layoutParams.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
         layoutParams.windowAnimations = 0;
         layoutParams.privateFlags |=
-                WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+                (WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
+                | PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION);
         layoutParams.setTitle(TAG + mContext.getDisplayId());
         layoutParams.setFitInsetsTypes(0 /* types */);
         layoutParams.setTrustedOverlay();
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index a16b92f..097cf35 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -33,6 +33,7 @@
 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 import static android.util.TypedValue.COMPLEX_UNIT_PX;
 
+import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
 import static com.android.systemui.people.PeopleSpaceUtils.STARRED_CONTACT;
 import static com.android.systemui.people.PeopleSpaceUtils.VALID_CONTACT;
 import static com.android.systemui.people.PeopleSpaceUtils.convertDrawableToBitmap;
@@ -45,8 +46,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
 import android.graphics.ImageDecoder;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
@@ -75,7 +74,6 @@
 import androidx.core.math.MathUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.systemui.R;
 import com.android.systemui.people.widget.LaunchConversationActivity;
 import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
@@ -339,8 +337,9 @@
             views = new RemoteViews(mContext.getPackageName(),
                     R.layout.people_tile_suppressed_layout);
         }
-        Drawable appIcon = mContext.getDrawable(R.drawable.ic_conversation_icon);
-        Bitmap disabledBitmap = convertDrawableToDisabledBitmap(appIcon);
+        Drawable appIcon = mContext.getDrawable(R.drawable.ic_conversation_icon).mutate();
+        appIcon.setColorFilter(getDisabledColorFilter());
+        Bitmap disabledBitmap = convertDrawableToBitmap(appIcon);
         views.setImageViewBitmap(R.id.icon, disabledBitmap);
         return views;
     }
@@ -1262,8 +1261,9 @@
             Context context, PeopleSpaceTile tile, int maxAvatarSize, boolean hasNewStory) {
         Icon icon = tile.getUserIcon();
         if (icon == null) {
-            Drawable placeholder = context.getDrawable(R.drawable.ic_avatar_with_badge);
-            return convertDrawableToDisabledBitmap(placeholder);
+            Drawable placeholder = context.getDrawable(R.drawable.ic_avatar_with_badge).mutate();
+            placeholder.setColorFilter(getDisabledColorFilter());
+            return convertDrawableToBitmap(placeholder);
         }
         PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(context,
                 context.getPackageManager(),
@@ -1276,10 +1276,7 @@
                 hasNewStory);
 
         if (isDndBlockingTileData(tile)) {
-            // If DND is blocking the conversation, then display the icon in grayscale.
-            ColorMatrix colorMatrix = new ColorMatrix();
-            colorMatrix.setSaturation(0);
-            personDrawable.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
+            personDrawable.setColorFilter(getDisabledColorFilter());
         }
 
         return convertDrawableToBitmap(personDrawable);
@@ -1375,11 +1372,4 @@
             mAvatarSize = avatarSize;
         }
     }
-
-    private static Bitmap convertDrawableToDisabledBitmap(Drawable icon) {
-        Bitmap appIconAsBitmap = convertDrawableToBitmap(icon);
-        FastBitmapDrawable drawable = new FastBitmapDrawable(appIconAsBitmap);
-        drawable.setIsDisabled(true);
-        return convertDrawableToBitmap(drawable);
-    }
 }
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 033fe1e..26c89ff 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
@@ -120,10 +120,12 @@
     private ImageView mSignalIcon;
     private TextView mMobileTitleText;
     private TextView mMobileSummaryText;
+    private TextView mAirplaneModeSummaryText;
     private Switch mMobileDataToggle;
     private View mMobileToggleDivider;
     private Switch mWiFiToggle;
     private FrameLayout mDoneLayout;
+    private FrameLayout mAirplaneModeLayout;
     private Drawable mBackgroundOn;
     private Drawable mBackgroundOff = null;
     private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -210,9 +212,11 @@
         mWifiRecyclerView = mDialogView.requireViewById(R.id.wifi_list_layout);
         mSeeAllLayout = mDialogView.requireViewById(R.id.see_all_layout);
         mDoneLayout = mDialogView.requireViewById(R.id.done_layout);
+        mAirplaneModeLayout = mDialogView.requireViewById(R.id.apm_layout);
         mSignalIcon = mDialogView.requireViewById(R.id.signal_icon);
         mMobileTitleText = mDialogView.requireViewById(R.id.mobile_title);
         mMobileSummaryText = mDialogView.requireViewById(R.id.mobile_summary);
+        mAirplaneModeSummaryText = mDialogView.requireViewById(R.id.airplane_mode_summary);
         mMobileToggleDivider = mDialogView.requireViewById(R.id.mobile_toggle_divider);
         mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle);
         mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle);
@@ -230,6 +234,8 @@
 
         setOnClickListener();
         mTurnWifiOnLayout.setBackground(null);
+        mAirplaneModeLayout.setVisibility(
+                mInternetDialogController.isAirplaneModeEnabled() ? View.VISIBLE : View.GONE);
         mWifiRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
         mWifiRecyclerView.setAdapter(mAdapter);
     }
@@ -269,6 +275,7 @@
         mSeeAllLayout.setOnClickListener(null);
         mWiFiToggle.setOnCheckedChangeListener(null);
         mDoneLayout.setOnClickListener(null);
+        mAirplaneModeLayout.setOnClickListener(null);
         mInternetDialogController.onStop();
         mInternetDialogFactory.destroyDialog();
     }
@@ -294,13 +301,17 @@
         }
         if (mInternetDialogController.isAirplaneModeEnabled()) {
             mInternetDialogSubTitle.setVisibility(View.GONE);
+            mAirplaneModeLayout.setVisibility(View.VISIBLE);
         } else {
+            mInternetDialogTitle.setText(getDialogTitleText());
+            mInternetDialogSubTitle.setVisibility(View.VISIBLE);
             mInternetDialogSubTitle.setText(getSubtitleText());
+            mAirplaneModeLayout.setVisibility(View.GONE);
         }
         updateEthernet();
         if (shouldUpdateMobileNetwork) {
-            setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular()
-                    || mInternetDialogController.isCarrierNetworkActive());
+            setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular(),
+                    mInternetDialogController.isCarrierNetworkActive());
         }
 
         if (!mCanConfigWifi) {
@@ -343,6 +354,9 @@
                     mWifiManager.setWifiEnabled(isChecked);
                 });
         mDoneLayout.setOnClickListener(v -> dismiss());
+        mAirplaneModeLayout.setOnClickListener(v -> {
+            mInternetDialogController.setAirplaneModeDisabled();
+        });
     }
 
     @MainThread
@@ -351,42 +365,60 @@
                 mInternetDialogController.hasEthernet() ? View.VISIBLE : View.GONE);
     }
 
-    private void setMobileDataLayout(boolean isCarrierNetworkConnected) {
-        if (mInternetDialogController.isAirplaneModeEnabled()
-                || !mInternetDialogController.hasCarrier()) {
+    private void setMobileDataLayout(boolean activeNetworkIsCellular,
+            boolean isCarrierNetworkActive) {
+        boolean isNetworkConnected = activeNetworkIsCellular || isCarrierNetworkActive;
+        // 1. Mobile network should be gone if airplane mode ON or the list of active
+        //    subscriptionId is null.
+        // 2. Carrier network should be gone if airplane mode ON and Wi-Fi is OFF.
+        if (DEBUG) {
+            Log.d(TAG, "setMobileDataLayout, isCarrierNetworkActive = " + isCarrierNetworkActive);
+        }
+
+        if (!mInternetDialogController.hasActiveSubId()
+                && (!mWifiManager.isWifiEnabled() || !isCarrierNetworkActive)) {
             mMobileNetworkLayout.setVisibility(View.GONE);
         } else {
-            mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled());
             mMobileNetworkLayout.setVisibility(View.VISIBLE);
+            mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled());
             mMobileTitleText.setText(getMobileNetworkTitle());
-            if (!TextUtils.isEmpty(getMobileNetworkSummary())) {
+            String summary = getMobileNetworkSummary();
+            if (!TextUtils.isEmpty(summary)) {
                 mMobileSummaryText.setText(
-                        Html.fromHtml(getMobileNetworkSummary(), Html.FROM_HTML_MODE_LEGACY));
+                        Html.fromHtml(summary, Html.FROM_HTML_MODE_LEGACY));
                 mMobileSummaryText.setVisibility(View.VISIBLE);
             } else {
                 mMobileSummaryText.setVisibility(View.GONE);
             }
-
             mBackgroundExecutor.execute(() -> {
                 Drawable drawable = getSignalStrengthDrawable();
                 mHandler.post(() -> {
                     mSignalIcon.setImageDrawable(drawable);
                 });
             });
-            mMobileTitleText.setTextAppearance(isCarrierNetworkConnected
+            mMobileTitleText.setTextAppearance(isNetworkConnected
                     ? R.style.TextAppearance_InternetDialog_Active
                     : R.style.TextAppearance_InternetDialog);
-            mMobileSummaryText.setTextAppearance(isCarrierNetworkConnected
+            int secondaryRes = isNetworkConnected
                     ? R.style.TextAppearance_InternetDialog_Secondary_Active
-                    : R.style.TextAppearance_InternetDialog_Secondary);
+                    : R.style.TextAppearance_InternetDialog_Secondary;
+            mMobileSummaryText.setTextAppearance(secondaryRes);
+            // Set airplane mode to the summary for carrier network
+            if (mInternetDialogController.isAirplaneModeEnabled()) {
+                mAirplaneModeSummaryText.setVisibility(View.VISIBLE);
+                mAirplaneModeSummaryText.setText(mContext.getText(R.string.airplane_mode));
+                mAirplaneModeSummaryText.setTextAppearance(secondaryRes);
+            } else {
+                mAirplaneModeSummaryText.setVisibility(View.GONE);
+            }
             mMobileNetworkLayout.setBackground(
-                    isCarrierNetworkConnected ? mBackgroundOn : mBackgroundOff);
+                    isNetworkConnected ? 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
+            mMobileToggleDivider.setBackgroundColor(isNetworkConnected
                     ? array.getColor(0, dividerColor) : dividerColor);
             array.recycle();
 
@@ -620,10 +652,13 @@
     @WorkerThread
     public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
             @Nullable WifiEntry connectedEntry) {
+        // Should update the carrier network layout when it is connected under airplane mode ON.
+        boolean shouldUpdateCarrierNetwork = mMobileNetworkLayout.getVisibility() == View.VISIBLE
+                && mInternetDialogController.isAirplaneModeEnabled();
         mHandler.post(() -> {
             mConnectedWifiEntry = connectedEntry;
             mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
-            updateDialog(false /* shouldUpdateMobileNetwork */);
+            updateDialog(shouldUpdateCarrierNetwork /* shouldUpdateMobileNetwork */);
             mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount);
             mAdapter.notifyDataSetChanged();
         });
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 2a7d2c3..6f63a08 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -86,6 +86,7 @@
 import com.android.wifitrackerlib.MergedCarrierEntry;
 import com.android.wifitrackerlib.WifiEntry;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -98,8 +99,10 @@
 
 import javax.inject.Inject;
 
-public class InternetDialogController implements WifiEntry.DisconnectCallback,
-        AccessPointController.AccessPointCallback {
+/**
+ * Controller for Internet Dialog.
+ */
+public class InternetDialogController implements AccessPointController.AccessPointCallback {
 
     private static final String TAG = "InternetDialogController";
     private static final String ACTION_NETWORK_PROVIDER_SETTINGS =
@@ -281,6 +284,10 @@
         return mGlobalSettings.getInt(Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
     }
 
+    void setAirplaneModeDisabled() {
+        mConnectivityManager.setAirplaneMode(false);
+    }
+
     @VisibleForTesting
     protected int getDefaultDataSubscriptionId() {
         return mSubscriptionManager.getDefaultDataSubscriptionId();
@@ -352,7 +359,7 @@
         if (DEBUG) {
             Log.d(TAG, "No Wi-Fi item.");
         }
-        if (!hasCarrier() || (!isVoiceStateInService() && !isDataStateInService())) {
+        if (!hasActiveSubId() || (!isVoiceStateInService() && !isDataStateInService())) {
             if (DEBUG) {
                 Log.d(TAG, "No carrier or service is out of service.");
             }
@@ -403,15 +410,16 @@
                 return drawable;
             }
 
-            if (isDataStateInService() || isVoiceStateInService()) {
+            boolean isCarrierNetworkActive = isCarrierNetworkActive();
+            if (isDataStateInService() || isVoiceStateInService() || isCarrierNetworkActive) {
                 AtomicReference<Drawable> shared = new AtomicReference<>();
-                shared.set(getSignalStrengthDrawableWithLevel());
+                shared.set(getSignalStrengthDrawableWithLevel(isCarrierNetworkActive));
                 drawable = shared.get();
             }
 
             int tintColor = Utils.getColorAttrDefaultColor(mContext,
                     android.R.attr.textColorTertiary);
-            if (activeNetworkIsCellular() || isCarrierNetworkActive()) {
+            if (activeNetworkIsCellular() || isCarrierNetworkActive) {
                 tintColor = mContext.getColor(R.color.connected_network_primary_color);
             }
             drawable.setTint(tintColor);
@@ -426,12 +434,15 @@
      *
      * @return The Drawable which is a signal bar icon with level.
      */
-    Drawable getSignalStrengthDrawableWithLevel() {
+    Drawable getSignalStrengthDrawableWithLevel(boolean isCarrierNetworkActive) {
         final SignalStrength strength = mTelephonyManager.getSignalStrength();
         int level = (strength == null) ? 0 : strength.getLevel();
         int numLevels = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
-        if (mSubscriptionManager != null && shouldInflateSignalStrength(mDefaultDataSubId)) {
-            level += 1;
+        if ((mSubscriptionManager != null && shouldInflateSignalStrength(mDefaultDataSubId))
+                || isCarrierNetworkActive) {
+            level = isCarrierNetworkActive
+                    ? SignalStrength.NUM_SIGNAL_STRENGTH_BINS
+                    : (level + 1);
             numLevels += 1;
         }
         return getSignalStrengthIcon(mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON,
@@ -586,15 +597,18 @@
         if (!isMobileDataEnabled()) {
             return context.getString(R.string.mobile_data_off_summary);
         }
-        if (!isDataStateInService()) {
-            return context.getString(R.string.mobile_data_no_connection);
-        }
+
         String summary = networkTypeDescription;
+        // Set network description for the carrier network when connecting to the carrier network
+        // under the airplane mode ON.
         if (activeNetworkIsCellular() || isCarrierNetworkActive()) {
             summary = context.getString(R.string.preference_summary_default_combination,
                     context.getString(R.string.mobile_data_connection_active),
                     networkTypeDescription);
+        } else if (!isDataStateInService()) {
+            summary = context.getString(R.string.mobile_data_no_connection);
         }
+
         return summary;
     }
 
@@ -678,7 +692,7 @@
     /**
      * @return whether there is the carrier item in the slice.
      */
-    boolean hasCarrier() {
+    boolean hasActiveSubId() {
         if (mSubscriptionManager == null) {
             if (DEBUG) {
                 Log.d(TAG, "SubscriptionManager is null, can not check carrier.");
@@ -870,36 +884,29 @@
             return;
         }
 
-        boolean hasConnectedWifi = false;
-        final int accessPointSize = accessPoints.size();
-        for (int i = 0; i < accessPointSize; i++) {
-            WifiEntry wifiEntry = accessPoints.get(i);
-            if (wifiEntry.isDefaultNetwork() && wifiEntry.hasInternetAccess()) {
-                mConnectedEntry = wifiEntry;
-                hasConnectedWifi = true;
-                break;
-            }
-        }
-        if (!hasConnectedWifi) {
-            mConnectedEntry = null;
-        }
-
         int count = MAX_WIFI_ENTRY_COUNT;
         if (mHasEthernet) {
             count -= 1;
         }
-        if (hasCarrier()) {
+        if (hasActiveSubId()) {
             count -= 1;
         }
-        if (hasConnectedWifi) {
-            count -= 1;
+        if (count > accessPoints.size()) {
+            count = accessPoints.size();
         }
-        final List<WifiEntry> wifiEntries = accessPoints.stream()
-                .filter(wifiEntry -> (!wifiEntry.isDefaultNetwork()
-                        || !wifiEntry.hasInternetAccess()))
-                .limit(count)
-                .collect(Collectors.toList());
-        mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
+
+        WifiEntry connectedEntry = null;
+        final List<WifiEntry> wifiEntries = new ArrayList<>();
+        for (int i = 0; i < count; i++) {
+            WifiEntry entry = accessPoints.get(i);
+            if (connectedEntry == null && entry.isDefaultNetwork() && entry.hasInternetAccess()) {
+                connectedEntry = entry;
+            } else {
+                wifiEntries.add(entry);
+            }
+        }
+        mConnectedEntry = connectedEntry;
+        mWifiEntriesCount = wifiEntries.size();
 
         if (mCallback != null) {
             mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry);
@@ -910,10 +917,6 @@
     public void onSettingsActivityTriggered(Intent settingsIntent) {
     }
 
-    @Override
-    public void onDisconnectResult(int status) {
-    }
-
     private class InternetTelephonyCallback extends TelephonyCallback implements
             TelephonyCallback.DataConnectionStateListener,
             TelephonyCallback.DisplayInfoListener,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
new file mode 100644
index 0000000..2e1762a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import android.view.View;
+
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+/**
+ * Calculates and moves the QS frame vertically.
+ */
+public abstract class QsFrameTranslateController {
+
+    protected StatusBar mStatusBar;
+
+    public QsFrameTranslateController(StatusBar statusBar) {
+        mStatusBar = statusBar;
+    }
+
+    /**
+     * Calculate and translate the QS Frame on the Y-axis.
+     */
+    public abstract void translateQsFrame(View qsFrame, QS qs, float overExpansion,
+            float qsTranslationForFullShadeTransition);
+
+    /**
+     * Calculate the top padding for notifications panel. This could be the supplied
+     * @param expansionHeight or recalculate it for a different value.
+     */
+    public abstract float getNotificationsTopPadding(float expansionHeight,
+            NotificationStackScrollLayoutController controller);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
new file mode 100644
index 0000000..c156797
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.view.View;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+import javax.inject.Inject;
+
+/**
+ * Default implementation of QS Translation. This by default does not do much.
+ */
+@SysUISingleton
+public class QsFrameTranslateImpl extends QsFrameTranslateController {
+
+    @Inject
+    public QsFrameTranslateImpl(StatusBar statusBar) {
+        super(statusBar);
+    }
+
+    @Override
+    public void translateQsFrame(View qsFrame, QS qs, float overExpansion,
+            float qsTranslationForFullShadeTransition) {
+    }
+
+    @Override
+    public float getNotificationsTopPadding(float expansionHeight,
+            NotificationStackScrollLayoutController controller) {
+
+        return expansionHeight;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateModule.java
new file mode 100644
index 0000000..075adfc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateModule.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import dagger.Binds;
+import dagger.Module;
+
+@Module
+public interface QsFrameTranslateModule {
+
+    @Binds
+    @SysUISingleton
+    QsFrameTranslateController bindQsFrameTranslateController(QsFrameTranslateImpl impl);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index 8b0252b..9e5dab1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -31,11 +31,6 @@
     var parent: GroupEntry?,
 
     /**
-     * Identifies the notification order in the entire notification list
-     */
-    var stableIndex: Int = -1,
-
-    /**
      * The section that this ListEntry was sorted into. If the child of the group, this will be the
      * parent's section. Null if not attached to the list.
      */
@@ -53,6 +48,11 @@
     var promoter: NotifPromoter?,
 
     /**
+     * If an entry's group was pruned from the list by NotifPipeline logic, the reason is here.
+     */
+    var groupPruneReason: String?,
+
+    /**
      * If the [VisualStabilityManager] is suppressing group or section changes for this entry,
      * suppressedChanges will contain the new parent or section that we would have assigned to
      * the entry had it not been suppressed by the VisualStabilityManager.
@@ -60,14 +60,23 @@
     var suppressedChanges: SuppressedAttachState
 ) {
 
+    /**
+     * Identifies the notification order in the entire notification list.
+     * NOTE: this property is intentionally excluded from equals calculation (by not making it a
+     *  constructor arg) because its value changes based on the presence of other members in the
+     *  list, rather than anything having to do with this entry's attachment.
+     */
+    var stableIndex: Int = -1
+
     /** Copies the state of another instance. */
     fun clone(other: ListAttachState) {
         parent = other.parent
-        stableIndex = other.stableIndex
         section = other.section
         excludingFilter = other.excludingFilter
         promoter = other.promoter
+        groupPruneReason = other.groupPruneReason
         suppressedChanges.clone(other.suppressedChanges)
+        stableIndex = other.stableIndex
     }
 
     /** Resets back to a "clean" state (the same as created by the factory method) */
@@ -76,20 +85,22 @@
         section = null
         excludingFilter = null
         promoter = null
-        stableIndex = -1
+        groupPruneReason = null
         suppressedChanges.reset()
+        stableIndex = -1
     }
 
     companion object {
         @JvmStatic
         fun create(): ListAttachState {
             return ListAttachState(
-                    null,
-                    -1,
-                    null,
-                    null,
-                    null,
-                SuppressedAttachState.create())
+                null,
+                null,
+                null,
+                null,
+                null,
+                SuppressedAttachState.create()
+            )
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index 37eacad..915057f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -31,8 +31,6 @@
     private final String mKey;
     private final long mCreationTime;
 
-    int mFirstAddedIteration = -1;
-
     private final ListAttachState mPreviousAttachState = ListAttachState.create();
     private final ListAttachState mAttachState = ListAttachState.create();
 
@@ -95,14 +93,6 @@
     }
 
     /**
-     * True if this entry has been attached to the shade at least once in its lifetime (it may not
-     * currently be attached).
-     */
-    public boolean hasBeenAttachedBefore() {
-        return mFirstAddedIteration != -1;
-    }
-
-    /**
      * Stores the current attach state into {@link #getPreviousAttachState()}} and then starts a
      * fresh attach state (all entries will be null/default-initialized).
      */
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 748c69d..120c722 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
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection;
 
+import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkState;
 import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_STARTED;
 import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZE_FILTERING;
@@ -34,6 +36,7 @@
 import android.annotation.Nullable;
 import android.os.Trace;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -50,6 +53,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
 import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState;
 import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.DefaultNotifStabilityManager;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
@@ -72,6 +76,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 
 import javax.inject.Inject;
 
@@ -103,7 +108,7 @@
     private final List<NotifFilter> mNotifFinalizeFilters = new ArrayList<>();
     private final List<NotifComparator> mNotifComparators = new ArrayList<>();
     private final List<NotifSection> mNotifSections = new ArrayList<>();
-    @Nullable private NotifStabilityManager mNotifStabilityManager;
+    private NotifStabilityManager mNotifStabilityManager;
 
     private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners =
             new ArrayList<>();
@@ -228,7 +233,7 @@
         mNotifSections.add(new NotifSection(DEFAULT_SECTIONER, mNotifSections.size()));
     }
 
-    void setNotifStabilityManager(NotifStabilityManager notifStabilityManager) {
+    void setNotifStabilityManager(@NonNull NotifStabilityManager notifStabilityManager) {
         Assert.isMainThread();
         mPipelineState.requireState(STATE_IDLE);
 
@@ -244,6 +249,14 @@
         mNotifStabilityManager.setInvalidationListener(this::onReorderingAllowedInvalidated);
     }
 
+    @NonNull
+    private NotifStabilityManager getStabilityManager() {
+        if (mNotifStabilityManager == null) {
+            return DefaultNotifStabilityManager.INSTANCE;
+        }
+        return mNotifStabilityManager;
+    }
+
     void setComparators(List<NotifComparator> comparators) {
         Assert.isMainThread();
         mPipelineState.requireState(STATE_IDLE);
@@ -460,10 +473,6 @@
 
         for (NotificationEntry entry : mAllEntries) {
             entry.beginNewAttachState();
-
-            if (entry.mFirstAddedIteration == -1) {
-                entry.mFirstAddedIteration = mIterationCount;
-            }
         }
 
         mNotifList.clear();
@@ -519,7 +528,6 @@
                 GroupEntry group = mGroups.get(topLevelKey);
                 if (group == null) {
                     group = new GroupEntry(topLevelKey, mSystemClock.uptimeMillis());
-                    group.mFirstAddedIteration = mIterationCount;
                     mGroups.put(topLevelKey, group);
                 }
                 if (group.getParent() == null) {
@@ -569,7 +577,7 @@
     }
 
     private void stabilizeGroupingNotifs(List<ListEntry> topLevelList) {
-        if (mNotifStabilityManager == null) {
+        if (getStabilityManager().isEveryChangeAllowed()) {
             return;
         }
         Trace.beginSection("ShadeListBuilder.stabilizeGroupingNotifs");
@@ -612,7 +620,7 @@
         final GroupEntry prevParent = entry.getPreviousAttachState().getParent();
         final GroupEntry assignedParent = entry.getParent();
         if (prevParent != assignedParent
-                && !mNotifStabilityManager.isGroupChangeAllowed(entry.getRepresentativeEntry())) {
+                && !getStabilityManager().isGroupChangeAllowed(entry.getRepresentativeEntry())) {
             entry.getAttachState().getSuppressedChanges().setParent(assignedParent);
             entry.setParent(prevParent);
             if (prevParent == ROOT_ENTRY) {
@@ -655,56 +663,60 @@
 
     private void pruneIncompleteGroups(List<ListEntry> shadeList) {
         Trace.beginSection("ShadeListBuilder.pruneIncompleteGroups");
+        // Any group which lost a child on this run to stability is exempt from being pruned or
+        //  having its summary promoted, regardless of how many children it has
+        Set<String> groupsWithChildrenLostToStability =
+                getGroupsWithChildrenLostToStability(shadeList);
         for (int i = 0; i < shadeList.size(); i++) {
             final ListEntry tle = shadeList.get(i);
 
             if (tle instanceof GroupEntry) {
                 final GroupEntry group = (GroupEntry) tle;
                 final List<NotificationEntry> children = group.getRawChildren();
+                final boolean hasSummary = group.getSummary() != null;
 
-                if (group.getSummary() != null && children.size() == 0) {
-                    NotificationEntry summary = group.getSummary();
-                    summary.setParent(ROOT_ENTRY);
-                    // The list may be sorted; replace the group with the summary, in its place
-                    shadeList.set(i, summary);
-
-                    group.setSummary(null);
-                    annulAddition(group, shadeList);
+                if (hasSummary && children.size() == 0) {
+                    if (groupsWithChildrenLostToStability.contains(group.getKey())) {
+                        // This group lost a child on this run to stability, so it is exempt from
+                        //  having its summary promoted to the top level, so prune it.
+                        //  It has no children, so it will just vanish.
+                        pruneGroupAtIndexAndPromoteAnyChildren(shadeList, group, i);
+                    } else {
+                        // For any other summary with no children, promote the summary.
+                        pruneGroupAtIndexAndPromoteSummary(shadeList, group, i);
+                    }
 
                     i--;  // The node we visited is gone, so be sure to visit this index again.
-                } else if (group.getSummary() == null
-                        || children.size() < MIN_CHILDREN_FOR_GROUP) {
+                } else if (!hasSummary) {
+                    // If the group doesn't provide a summary, ignore it and add
+                    //  any children it may have directly to top-level.
+                    pruneGroupAtIndexAndPromoteAnyChildren(shadeList, group, i);
 
-                    if (group.getSummary() != null
-                            && group.wasAttachedInPreviousPass()
-                            && mNotifStabilityManager != null
-                            && !mNotifStabilityManager.isGroupChangeAllowed(group.getSummary())) {
-                        // if this group was previously attached and group changes aren't
-                        // allowed, keep it around until group changes are allowed again
+                    i--;  // The node we visited is gone, so be sure to visit this index again.
+                } else if (children.size() < MIN_CHILDREN_FOR_GROUP) {
+                    // This group has a summary and insufficient, but nonzero children.
+                    checkState(hasSummary, "group must have summary at this point");
+                    checkState(!children.isEmpty(), "empty group should have been promoted");
+
+                    if (groupsWithChildrenLostToStability.contains(group.getKey())) {
+                        // This group lost a child on this run to stability, so it is exempt from
+                        //  the "min children" requirement; keep it around in case more children are
+                        //  added before changes are allowed again.
+                        group.getAttachState().getSuppressedChanges().setWasPruneSuppressed(true);
+                        continue;
+                    }
+                    if (group.wasAttachedInPreviousPass()
+                            && !getStabilityManager().isGroupChangeAllowed(group.getSummary())) {
+                        checkState(!children.isEmpty(), "empty group should have been pruned");
+                        // This group was previously attached and group changes aren't
+                        //  allowed; keep it around until group changes are allowed again.
                         group.getAttachState().getSuppressedChanges().setWasPruneSuppressed(true);
                         continue;
                     }
 
-                    // If the group doesn't provide a summary or is too small, ignore it and add
+                    // The group is too small, ignore it and add
                     // its children (if any) directly to top-level.
-
-                    shadeList.remove(i);
-
-                    if (group.getSummary() != null) {
-                        final NotificationEntry summary = group.getSummary();
-                        group.setSummary(null);
-                        annulAddition(summary, shadeList);
-                    }
-
-                    for (int j = 0; j < children.size(); j++) {
-                        final NotificationEntry child = children.get(j);
-                        child.setParent(ROOT_ENTRY);
-                        // The list may be sorted, so add the children in order where the group was.
-                        shadeList.add(i + j, child);
-                    }
-                    children.clear();
-
-                    annulAddition(group, shadeList);
+                    pruneGroupAtIndexAndPromoteAnyChildren(shadeList, group, i);
 
                     i--;  // The node we visited is gone, so be sure to visit this index again.
                 }
@@ -713,6 +725,99 @@
         Trace.endSection();
     }
 
+    private void pruneGroupAtIndexAndPromoteSummary(List<ListEntry> shadeList,
+            GroupEntry group, int index) {
+        // Validate that the group has no children
+        checkArgument(group.getChildren().isEmpty(), "group should have no children");
+
+        NotificationEntry summary = group.getSummary();
+        summary.setParent(ROOT_ENTRY);
+        // The list may be sorted; replace the group with the summary, in its place
+        ListEntry oldEntry = shadeList.set(index, summary);
+
+        // Validate that the replaced entry was the group entry
+        checkState(oldEntry == group);
+
+        group.setSummary(null);
+        annulAddition(group, shadeList);
+        summary.getAttachState().setGroupPruneReason(
+                "SUMMARY with no children @ " + mPipelineState.getStateName());
+    }
+
+    private void pruneGroupAtIndexAndPromoteAnyChildren(List<ListEntry> shadeList,
+            GroupEntry group, int index) {
+        // REMOVE the GroupEntry at this index
+        ListEntry oldEntry = shadeList.remove(index);
+
+        // Validate that the replaced entry was the group entry
+        checkState(oldEntry == group);
+
+        List<NotificationEntry> children = group.getRawChildren();
+        boolean hasSummary = group.getSummary() != null;
+
+        // Remove the group summary, if present, and leave detached.
+        if (hasSummary) {
+            final NotificationEntry summary = group.getSummary();
+            group.setSummary(null);
+            annulAddition(summary, shadeList);
+            summary.getAttachState().setGroupPruneReason(
+                    "SUMMARY with too few children @ " + mPipelineState.getStateName());
+        }
+
+        // Promote any children
+        if (!children.isEmpty()) {
+            // create the reason we will report on the child for why its group was pruned.
+            String childReason = hasSummary
+                    ? ("CHILD with " + (children.size() - 1) + " siblings @ "
+                        + mPipelineState.getStateName())
+                    : ("CHILD with no summary @ " + mPipelineState.getStateName());
+
+            // Remove children from the group and add them to the shadeList.
+            for (int j = 0; j < children.size(); j++) {
+                final NotificationEntry child = children.get(j);
+                child.setParent(ROOT_ENTRY);
+                child.getAttachState().setGroupPruneReason(requireNonNull(childReason));
+            }
+            // The list may be sorted, so add the children in order where the group was.
+            shadeList.addAll(index, children);
+            children.clear();
+        }
+
+        annulAddition(group, shadeList);
+    }
+
+    /**
+     * Collect the keys of any groups which have already lost a child to stability this run.
+     *
+     * If stability is being enforced, then {@link #stabilizeGroupingNotifs(List)} might have
+     * detached some children from their groups and left them at the top level because the child was
+     * previously attached at the top level.  Doing so would set the
+     * {@link SuppressedAttachState#getParent() suppressed parent} for the current attach state.
+     *
+     * If we've already removed a child from this group, we don't want to remove any more children
+     * from the group (even if that would leave only a single notification in the group) because
+     * that could cascade over multiple runs and allow a large group of notifications all show up as
+     * top level (ungrouped) notifications.
+     */
+    @NonNull
+    private Set<String> getGroupsWithChildrenLostToStability(List<ListEntry> shadeList) {
+        if (getStabilityManager().isEveryChangeAllowed()) {
+            return Collections.emptySet();
+        }
+        ArraySet<String> groupsWithChildrenLostToStability = new ArraySet<>();
+        for (int i = 0; i < shadeList.size(); i++) {
+            final ListEntry tle = shadeList.get(i);
+            final GroupEntry suppressedParent =
+                    tle.getAttachState().getSuppressedChanges().getParent();
+            if (suppressedParent != null) {
+                // This top-level-entry was supposed to be attached to this group,
+                //  so mark the group as having lost a child to stability.
+                groupsWithChildrenLostToStability.add(suppressedParent.getKey());
+            }
+        }
+        return groupsWithChildrenLostToStability;
+    }
+
     /**
      * If a ListEntry was added to the shade list and then later removed (e.g. because it was a
      * group that was broken up), this method will erase any bookkeeping traces of that addition
@@ -727,10 +832,9 @@
         // lot of them), it will put the system into an inconsistent state. So we check all of them
         // here.
 
-        if (entry.getParent() == null || entry.mFirstAddedIteration == -1) {
+        if (entry.getParent() == null) {
             throw new IllegalStateException(
-                    "Cannot nullify addition of " + entry.getKey() + ": no such addition. ("
-                            + entry.getParent() + " " + entry.mFirstAddedIteration + ")");
+                    "Cannot nullify addition of " + entry.getKey() + ": no parent.");
         }
 
         if (entry.getParent() == ROOT_ENTRY) {
@@ -771,9 +875,6 @@
         entry.setParent(null);
         entry.getAttachState().setSection(null);
         entry.getAttachState().setPromoter(null);
-        if (entry.mFirstAddedIteration == mIterationCount) {
-            entry.mFirstAddedIteration = -1;
-        }
     }
 
     private void assignSections() {
@@ -804,12 +905,12 @@
         assignIndexes(mNotifList);
 
         // Check for suppressed order changes
-        if (!mNotifStabilityManager.isEveryChangeAllowed()) {
+        if (!getStabilityManager().isEveryChangeAllowed()) {
             mForceReorderable = true;
             boolean isSorted = isSorted(mNotifList, mTopLevelComparator);
             mForceReorderable = false;
             if (!isSorted) {
-                mNotifStabilityManager.onEntryReorderSuppressed();
+                getStabilityManager().onEntryReorderSuppressed();
             }
         }
         Trace.endSection();
@@ -897,12 +998,26 @@
                         curr.getParent());
             }
 
+            if (curr.getSuppressedChanges().getSection() != null) {
+                mLogger.logSectionChangeSuppressed(
+                        mIterationCount,
+                        curr.getSuppressedChanges().getSection(),
+                        curr.getSection());
+            }
+
             if (curr.getSuppressedChanges().getWasPruneSuppressed()) {
                 mLogger.logGroupPruningSuppressed(
                         mIterationCount,
                         curr.getParent());
             }
 
+            if (!Objects.equals(curr.getGroupPruneReason(), prev.getGroupPruneReason())) {
+                mLogger.logPrunedReasonChanged(
+                        mIterationCount,
+                        prev.getGroupPruneReason(),
+                        curr.getGroupPruneReason());
+            }
+
             if (curr.getExcludingFilter() != prev.getExcludingFilter()) {
                 mLogger.logFilterChanged(
                         mIterationCount,
@@ -927,20 +1042,11 @@
                         prev.getSection(),
                         curr.getSection());
             }
-
-            if (curr.getSuppressedChanges().getSection() != null) {
-                mLogger.logSectionChangeSuppressed(
-                        mIterationCount,
-                        curr.getSuppressedChanges().getSection(),
-                        curr.getSection());
-            }
         }
     }
 
     private void onBeginRun() {
-        if (mNotifStabilityManager != null) {
-            mNotifStabilityManager.onBeginRun();
-        }
+        getStabilityManager().onBeginRun();
     }
 
     private void cleanupPluggables() {
@@ -953,9 +1059,7 @@
             mNotifSections.get(i).getSectioner().onCleanup();
         }
 
-        if (mNotifStabilityManager != null) {
-            callOnCleanup(List.of(mNotifStabilityManager));
-        }
+        callOnCleanup(List.of(getStabilityManager()));
     }
 
     private void callOnCleanup(List<? extends Pluggable<?>> pluggables) {
@@ -1015,7 +1119,7 @@
     private boolean mForceReorderable = false;
 
     private boolean canReorder(ListEntry entry) {
-        return mForceReorderable || mNotifStabilityManager.isEntryReorderingAllowed(entry);
+        return mForceReorderable || getStabilityManager().isEntryReorderingAllowed(entry);
     }
 
     private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
@@ -1064,12 +1168,10 @@
         NotifSection finalSection = newSection;
 
         // have we seen this entry before and are we changing its section?
-        if (mNotifStabilityManager != null
-                && entry.wasAttachedInPreviousPass()
-                && newSection != prevAttachState.getSection()) {
+        if (entry.wasAttachedInPreviousPass() && newSection != prevAttachState.getSection()) {
 
             // are section changes allowed?
-            if (!mNotifStabilityManager.isSectionChangeAllowed(entry.getRepresentativeEntry())) {
+            if (!getStabilityManager().isSectionChangeAllowed(entry.getRepresentativeEntry())) {
                 // record the section that we wanted to change to
                 entry.getAttachState().getSuppressedChanges().setSection(newSection);
 
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 bbb97d1..ec4e039 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
@@ -385,7 +385,7 @@
     }
 
     private boolean shouldWaitForGroupToInflate(GroupEntry group, long now) {
-        if (group == GroupEntry.ROOT_ENTRY || group.hasBeenAttachedBefore()) {
+        if (group == GroupEntry.ROOT_ENTRY || group.wasAttachedInPreviousPass()) {
             return false;
         }
         if (isBeyondGroupInitializationWindow(group, now)) {
@@ -397,11 +397,12 @@
             return true;
         }
         for (NotificationEntry child : group.getChildren()) {
-            if (mInflatingNotifs.contains(child) && !child.hasBeenAttachedBefore()) {
+            if (mInflatingNotifs.contains(child) && !child.wasAttachedInPreviousPass()) {
                 mLogger.logDelayingGroupRelease(group.getKey(), child.getKey());
                 return true;
             }
         }
+        mLogger.logDoneWaitingForGroupInflation(group.getKey());
         return false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
index dd4794f..f835250 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -41,11 +41,19 @@
         })
     }
 
+    fun logDoneWaitingForGroupInflation(groupKey: String) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = groupKey
+        }, {
+            "Finished inflating all members of group $str1, releasing group"
+        })
+    }
+
     fun logGroupInflationTookTooLong(groupKey: String) {
         buffer.log(TAG, LogLevel.WARNING, {
             str1 = groupKey
         }, {
-            "Group inflation took too long far $str1, releasing children early"
+            "Group inflation took too long for $str1, releasing children early"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 75489b1..327876c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -118,7 +118,7 @@
                 }
 
                 @Override
-                public boolean isGroupChangeAllowed(NotificationEntry entry) {
+                public boolean isGroupChangeAllowed(@NonNull NotificationEntry entry) {
                     final boolean isGroupChangeAllowedForEntry =
                             mReorderingAllowed || mHeadsUpManager.isAlerting(entry.getKey());
                     mIsSuppressingGroupChange |= !isGroupChangeAllowedForEntry;
@@ -126,7 +126,7 @@
                 }
 
                 @Override
-                public boolean isSectionChangeAllowed(NotificationEntry entry) {
+                public boolean isSectionChangeAllowed(@NonNull NotificationEntry entry) {
                     final boolean isSectionChangeAllowedForEntry =
                             mReorderingAllowed
                                     || mHeadsUpManager.isAlerting(entry.getKey())
@@ -138,7 +138,7 @@
                 }
 
                 @Override
-                public boolean isEntryReorderingAllowed(ListEntry section) {
+                public boolean isEntryReorderingAllowed(@NonNull ListEntry section) {
                     return mReorderingAllowed;
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
index 798bfe7..feb48da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
@@ -18,6 +18,8 @@
 
 import android.annotation.IntDef;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
 
 import java.lang.annotation.Retention;
@@ -35,6 +37,7 @@
         return state == mState;
     }
 
+    /** Get the current state's integer representation */
     public @StateName int getState() {
         return mState;
     }
@@ -75,6 +78,41 @@
         }
     }
 
+    /** Get the current state's string representation */
+    @NonNull
+    public String getStateName() {
+        return getStateName(mState);
+    }
+
+    /** Get the given state's string representation */
+    @NonNull
+    public static String getStateName(@StateName int state) {
+        switch (state) {
+            case STATE_IDLE:
+                return "STATE_IDLE";
+            case STATE_BUILD_STARTED:
+                return "STATE_BUILD_STARTED";
+            case STATE_RESETTING:
+                return "STATE_RESETTING";
+            case STATE_PRE_GROUP_FILTERING:
+                return "STATE_PRE_GROUP_FILTERING";
+            case STATE_GROUPING:
+                return "STATE_GROUPING";
+            case STATE_TRANSFORMING:
+                return "STATE_TRANSFORMING";
+            case STATE_GROUP_STABILIZING:
+                return "STATE_GROUP_STABILIZING";
+            case STATE_SORTING:
+                return "STATE_SORTING";
+            case STATE_FINALIZE_FILTERING:
+                return "STATE_FINALIZE_FILTERING";
+            case STATE_FINALIZING:
+                return "STATE_FINALIZING";
+            default:
+                return "STATE:" + state;
+        }
+    }
+
     public static final int STATE_IDLE = 0;
     public static final int STATE_BUILD_STARTED = 1;
     public static final int STATE_RESETTING = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 8fff905..ba3e855 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -37,9 +37,9 @@
         })
     }
 
-    fun logEndBuildList(iterationCount: Int, topLevelEntries: Int, numChildren: Int) {
+    fun logEndBuildList(buildId: Int, topLevelEntries: Int, numChildren: Int) {
         buffer.log(TAG, INFO, {
-            long1 = iterationCount.toLong()
+            long1 = buildId.toLong()
             int1 = topLevelEntries
             int2 = numChildren
         }, {
@@ -112,21 +112,21 @@
 
     fun logDuplicateSummary(buildId: Int, groupKey: String, existingKey: String, newKey: String) {
         buffer.log(TAG, WARNING, {
-            int1 = buildId
+            long1 = buildId.toLong()
             str1 = groupKey
             str2 = existingKey
             str3 = newKey
         }, {
-            """(Build $int1) Duplicate summary for group "$str1": "$str2" vs. "$str3""""
+            """(Build $long1) Duplicate summary for group "$str1": "$str2" vs. "$str3""""
         })
     }
 
     fun logDuplicateTopLevelKey(buildId: Int, topLevelKey: String) {
         buffer.log(TAG, WARNING, {
-            int1 = buildId
+            long1 = buildId.toLong()
             str1 = topLevelKey
         }, {
-            "(Build $int1) Duplicate top-level key: $str1"
+            "(Build $long1) Duplicate top-level key: $str1"
         })
     }
 
@@ -137,7 +137,7 @@
         newParent: GroupEntry?
     ) {
         buffer.log(TAG, INFO, {
-            int1 = buildId
+            long1 = buildId.toLong()
             str1 = key
             str2 = prevParent?.key
             str3 = newParent?.key
@@ -153,22 +153,22 @@
                 "MODIFIED (ATTACHED)"
             }
 
-            "(Build $int1) $action {$str1}"
+            "(Build $long1) $action {$str1}"
         })
     }
 
     fun logParentChanged(buildId: Int, prevParent: GroupEntry?, newParent: GroupEntry?) {
         buffer.log(TAG, INFO, {
-            int1 = buildId
+            long1 = buildId.toLong()
             str1 = prevParent?.key
             str2 = newParent?.key
         }, {
             if (str1 == null && str2 != null) {
-                "(Build $int1)     Parent is {$str2}"
+                "(Build $long1)     Parent is {$str2}"
             } else if (str1 != null && str2 == null) {
-                "(Build $int1)     Parent was {$str1}"
+                "(Build $long1)     Parent was {$str1}"
             } else {
-                "(Build $int1)     Reparent: {$str1} -> {$str2}"
+                "(Build $long1)     Reparent: {$str1} -> {$str2}"
             }
         })
     }
@@ -179,7 +179,7 @@
         keepingParent: GroupEntry?
     ) {
         buffer.log(TAG, INFO, {
-            int1 = buildId
+            long1 = buildId.toLong()
             str1 = suppressedParent?.key
             str2 = keepingParent?.key
         }, {
@@ -192,24 +192,38 @@
         keepingParent: GroupEntry?
     ) {
         buffer.log(TAG, INFO, {
-            int1 = buildId
+            long1 = buildId.toLong()
             str1 = keepingParent?.key
         }, {
             "(Build $long1)     Group pruning suppressed; keeping parent '$str1'"
         })
     }
 
+    fun logPrunedReasonChanged(
+        buildId: Int,
+        prevReason: String?,
+        newReason: String?
+    ) {
+        buffer.log(TAG, INFO, {
+            long1 = buildId.toLong()
+            str1 = prevReason
+            str2 = newReason
+        }, {
+            "(Build $long1)     Pruned reason changed: $str1 -> $str2"
+        })
+    }
+
     fun logFilterChanged(
         buildId: Int,
         prevFilter: NotifFilter?,
         newFilter: NotifFilter?
     ) {
         buffer.log(TAG, INFO, {
-            int1 = buildId
+            long1 = buildId.toLong()
             str1 = prevFilter?.name
             str2 = newFilter?.name
         }, {
-            "(Build $int1)     Filter changed: $str1 -> $str2"
+            "(Build $long1)     Filter changed: $str1 -> $str2"
         })
     }
 
@@ -219,11 +233,11 @@
         newPromoter: NotifPromoter?
     ) {
         buffer.log(TAG, INFO, {
-            int1 = buildId
+            long1 = buildId.toLong()
             str1 = prevPromoter?.name
             str2 = newPromoter?.name
         }, {
-            "(Build $int1)     Promoter changed: $str1 -> $str2"
+            "(Build $long1)     Promoter changed: $str1 -> $str2"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java
deleted file mode 100644
index cb2d3cb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
-
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-/**
- * Pluggable for participating in notif stabilization. In particular, suppressing group and
- * section changes.
- *
- * The stability manager should be invalidated when previously suppressed a group or
- * section change is now allowed.
- */
-public abstract class NotifStabilityManager extends Pluggable<NotifStabilityManager> {
-
-    protected NotifStabilityManager(String name) {
-        super(name);
-    }
-
-    /**
-     * Called at the beginning of every pipeline run to perform any necessary cleanup from the
-     * previous run.
-     */
-    public abstract void onBeginRun();
-
-    /**
-     * Returns whether this notification can currently change groups/parents.
-     * Per iteration of the notification pipeline, locally stores this information until the next
-     * run of the pipeline. When this method returns true, it's expected that a group change for
-     * this entry is being suppressed.
-     */
-    public abstract boolean isGroupChangeAllowed(NotificationEntry entry);
-
-    /**
-     * Returns whether this notification entry can currently change sections.
-     * Per iteration of the notification pipeline, locally stores this information until the next
-     * run of the pipeline. When this method returns true, it's expected that a section change is
-     * being suppressed.
-     */
-    public abstract boolean isSectionChangeAllowed(NotificationEntry entry);
-
-    /**
-     *  Is a notification entry is allowed be reordered
-     * @param entry
-     * @return if can re-order
-     */
-    public abstract boolean isEntryReorderingAllowed(ListEntry entry);
-
-    /**
-     * Called by the pipeline to determine if every call to the other stability methods would
-     * return true, regardless of parameters.  This allows the pipeline to skip any pieces of
-     * work related to stability.
-     *
-     * @return true if all other methods will return true for any parameters.
-     */
-    public abstract boolean isEveryChangeAllowed();
-
-    /**
-     * Called by the pipeline to inform the stability manager that an entry reordering was indeed
-     * suppressed as the result of a previous call to {@link #isEntryReorderingAllowed(ListEntry)}.
-     */
-    public abstract void onEntryReorderSuppressed();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt
new file mode 100644
index 0000000..60f557c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable
+
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/**
+ * Pluggable for participating in notif stabilization. In particular, suppressing group and
+ * section changes.
+ *
+ * The stability manager should be invalidated when previously suppressed a group or
+ * section change is now allowed.
+ */
+abstract class NotifStabilityManager protected constructor(name: String) :
+    Pluggable<NotifStabilityManager>(name) {
+    /**
+     * Called at the beginning of every pipeline run to perform any necessary cleanup from the
+     * previous run.
+     */
+    abstract fun onBeginRun()
+
+    /**
+     * Returns whether this notification can currently change groups/parents.
+     * Per iteration of the notification pipeline, locally stores this information until the next
+     * run of the pipeline. When this method returns false, it's expected that a group change for
+     * this entry is being suppressed.
+     */
+    abstract fun isGroupChangeAllowed(entry: NotificationEntry): Boolean
+
+    /**
+     * Returns whether this notification entry can currently change sections.
+     * Per iteration of the notification pipeline, locally stores this information until the next
+     * run of the pipeline. When this method returns false, it's expected that a section change is
+     * being suppressed.
+     */
+    abstract fun isSectionChangeAllowed(entry: NotificationEntry): Boolean
+
+    /**
+     * Returns whether this list entry is allowed to be reordered within its section.
+     * Unlike [isGroupChangeAllowed] or [isSectionChangeAllowed], this method is called on every
+     * entry, so an implementation may not assume that returning false means an order change is
+     * being suppressed. However, if an order change is suppressed, that will be reported to ths
+     * implementation by calling [onEntryReorderSuppressed] after ordering is complete.
+     */
+    abstract fun isEntryReorderingAllowed(entry: ListEntry): Boolean
+
+    /**
+     * Called by the pipeline to determine if every call to the other stability methods would
+     * return true, regardless of parameters.  This allows the pipeline to skip any pieces of
+     * work related to stability.
+     *
+     * @return true if all other methods will return true for any parameters.
+     */
+    abstract fun isEveryChangeAllowed(): Boolean
+
+    /**
+     * Called by the pipeline to inform the stability manager that an entry reordering was indeed
+     * suppressed as the result of a previous call to [.isEntryReorderingAllowed].
+     */
+    abstract fun onEntryReorderSuppressed()
+}
+
+/** The default, no-op instance of the stability manager which always allows all changes */
+object DefaultNotifStabilityManager : NotifStabilityManager("DefaultNotifStabilityManager") {
+    override fun onBeginRun() {}
+    override fun isGroupChangeAllowed(entry: NotificationEntry): Boolean = true
+    override fun isSectionChangeAllowed(entry: NotificationEntry): Boolean = true
+    override fun isEntryReorderingAllowed(entry: ListEntry): Boolean = true
+    override fun isEveryChangeAllowed(): Boolean = true
+    override fun onEntryReorderSuppressed() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index db68ecf1..275074d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -157,6 +157,7 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.QsFrameTranslateController;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -339,6 +340,7 @@
     private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
     @VisibleForTesting QS mQs;
     private FrameLayout mQsFrame;
+    private QsFrameTranslateController mQsFrameTranslateController;
     @Nullable
     private CommunalHostViewController mCommunalViewController;
     private KeyguardStatusViewController mKeyguardStatusViewController;
@@ -775,7 +777,8 @@
             NotificationRemoteInputManager remoteInputManager,
             Optional<SysUIUnfoldComponent> unfoldComponent,
             ControlsComponent controlsComponent,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            QsFrameTranslateController qsFrameTranslateController) {
         super(view,
                 falsingManager,
                 dozeLog,
@@ -900,7 +903,6 @@
 
         mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count);
         mKeyguardUnfoldTransition = unfoldComponent.map(c -> c.getKeyguardUnfoldTransition());
-
         mCommunalSourceCallback = () -> {
             mUiExecutor.execute(() -> setCommunalSource(null /*source*/));
         };
@@ -908,7 +910,7 @@
         mCommunalSourceMonitorCallback = (source) -> {
             mUiExecutor.execute(() -> setCommunalSource(source));
         };
-
+        mQsFrameTranslateController = qsFrameTranslateController;
         updateUserSwitcherFlags();
         onFinishInflate();
     }
@@ -2667,7 +2669,8 @@
                     (float) (mQsMaxExpansionHeight),
                     computeQsExpansionFraction());
         } else {
-            return mQsExpansionHeight;
+            return mQsFrameTranslateController.getNotificationsTopPadding(mQsExpansionHeight,
+                    mNotificationStackScrollLayoutController);
         }
     }
 
@@ -3251,8 +3254,8 @@
     }
 
     private void updateQsFrameTranslation() {
-        float translation = mOverExpansion / 2.0f + mQsTranslationForFullShadeTransition;
-        mQsFrame.setTranslationY(translation);
+        mQsFrameTranslateController.translateQsFrame(mQsFrame, mQs, mOverExpansion,
+                mQsTranslationForFullShadeTransition);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 256b069..ec7e93b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -43,7 +43,6 @@
             val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
             val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
 
-            val viewCenterProvider = StatusBarViewsCenterProvider()
             val viewsToAnimate = arrayOf(
                 statusBarLeftSide,
                 systemIconArea
@@ -52,7 +51,7 @@
             mView.viewTreeObserver.addOnPreDrawListener(object :
                 ViewTreeObserver.OnPreDrawListener {
                 override fun onPreDraw(): Boolean {
-                    animationController.onViewsReady(viewsToAnimate, viewCenterProvider)
+                    animationController.onViewsReady(viewsToAnimate)
                     mView.viewTreeObserver.removeOnPreDrawListener(this)
                     return true
                 }
@@ -82,7 +81,7 @@
         mView.importantForAccessibility = mode
     }
 
-    private class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
+    class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
         override fun getViewCenter(view: View, outPoint: Point) =
             when (view.id) {
                 R.id.status_bar_left_side -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
index 805ddf5..6d033477 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
@@ -18,7 +18,7 @@
 import android.view.View
 import android.view.WindowManager
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
-import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.ViewCenterProvider
+import com.android.systemui.statusbar.phone.PhoneStatusBarViewController.StatusBarViewsCenterProvider
 import com.android.systemui.unfold.SysUIUnfoldScope
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
@@ -27,20 +27,18 @@
 @SysUIUnfoldScope
 class StatusBarMoveFromCenterAnimationController @Inject constructor(
     private val progressProvider: ScopedUnfoldTransitionProgressProvider,
-    private val windowManager: WindowManager
+    windowManager: WindowManager
 ) {
 
     private val transitionListener = TransitionListener()
-    private var moveFromCenterAnimator: UnfoldMoveFromCenterAnimator? = null
+    private val moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(windowManager,
+        viewCenterProvider = StatusBarViewsCenterProvider())
 
-    fun onViewsReady(viewsToAnimate: Array<View>, viewCenterProvider: ViewCenterProvider) {
-        moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(windowManager,
-            viewCenterProvider = viewCenterProvider)
-
-        moveFromCenterAnimator?.updateDisplayProperties()
+    fun onViewsReady(viewsToAnimate: Array<View>) {
+        moveFromCenterAnimator.updateDisplayProperties()
 
         viewsToAnimate.forEach {
-            moveFromCenterAnimator?.registerViewForAnimation(it)
+            moveFromCenterAnimator.registerViewForAnimation(it)
         }
 
         progressProvider.addCallback(transitionListener)
@@ -48,24 +46,23 @@
 
     fun onViewDetached() {
         progressProvider.removeCallback(transitionListener)
-        moveFromCenterAnimator?.clearRegisteredViews()
-        moveFromCenterAnimator = null
+        moveFromCenterAnimator.clearRegisteredViews()
     }
 
     fun onStatusBarWidthChanged() {
-        moveFromCenterAnimator?.updateDisplayProperties()
-        moveFromCenterAnimator?.updateViewPositions()
+        moveFromCenterAnimator.updateDisplayProperties()
+        moveFromCenterAnimator.updateViewPositions()
     }
 
     private inner class TransitionListener : TransitionProgressListener {
         override fun onTransitionProgress(progress: Float) {
-            moveFromCenterAnimator?.onTransitionProgress(progress)
+            moveFromCenterAnimator.onTransitionProgress(progress)
         }
 
         override fun onTransitionFinished() {
             // Reset translations when transition is stopped/cancelled
             // (e.g. the transition could be cancelled mid-way when rotating the screen)
-            moveFromCenterAnimator?.onTransitionProgress(1f)
+            moveFromCenterAnimator.onTransitionProgress(1f)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
deleted file mode 100644
index ac8b47d..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2016 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.policy;
-
-import android.app.StatusBarManager;
-import android.content.Context;
-import android.content.res.Configuration;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.qs.QSFragment;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.StatusBar;
-
-import javax.inject.Inject;
-
-/**
- * Let {@link RemoteInputView} to control the visibility of QuickSetting.
- */
-@SysUISingleton
-public class RemoteInputQuickSettingsDisabler
-        implements ConfigurationController.ConfigurationListener {
-
-    private Context mContext;
-    @VisibleForTesting boolean mRemoteInputActive;
-    @VisibleForTesting boolean misLandscape;
-    private int mLastOrientation;
-    private final CommandQueue mCommandQueue;
-
-    @Inject
-    public RemoteInputQuickSettingsDisabler(Context context,
-            ConfigurationController configController, CommandQueue commandQueue) {
-        mContext = context;
-        mCommandQueue = commandQueue;
-        mLastOrientation = mContext.getResources().getConfiguration().orientation;
-        configController.addCallback(this);
-    }
-
-    public int adjustDisableFlags(int state) {
-        if (mRemoteInputActive && misLandscape) {
-            state |= StatusBarManager.DISABLE2_QUICK_SETTINGS;
-        }
-
-        return state;
-    }
-
-    public void setRemoteInputActive(boolean active){
-        if(mRemoteInputActive != active){
-            mRemoteInputActive = active;
-            recomputeDisableFlags();
-        }
-    }
-
-    @Override
-    public void onConfigChanged(Configuration newConfig) {
-        if (newConfig.orientation != mLastOrientation) {
-            misLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
-            mLastOrientation = newConfig.orientation;
-            recomputeDisableFlags();
-        }
-    }
-
-    /**
-     * Reapplies the disable flags. Then the method adjustDisableFlags in this class will be invoked
-     * in {@link QSFragment#disable(int, int, boolean)} and
-     * {@link StatusBar#disable(int, int, boolean)}
-     * to modify the disable flags according to the status of mRemoteInputActive and misLandscape.
-     */
-    private void recomputeDisableFlags() {
-        mCommandQueue.recomputeDisableFlags(mContext.getDisplayId(), true);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt
new file mode 100644
index 0000000..31ef2f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 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.policy
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.content.res.Configuration
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.Utils
+import javax.inject.Inject
+
+/**
+ * Controls whether the disable flag [StatusBarManager.DISABLE2_QUICK_SETTINGS] should be set.
+ * This would happen when a [RemoteInputView] is active, the device is in landscape and not using
+ * split shade.
+ */
+@SysUISingleton
+class RemoteInputQuickSettingsDisabler @Inject constructor(
+    private val context: Context,
+    private val commandQueue: CommandQueue,
+    configController: ConfigurationController
+) : ConfigurationController.ConfigurationListener {
+
+    private var remoteInputActive = false
+    private var isLandscape: Boolean
+    private var shouldUseSplitNotificationShade: Boolean
+
+    init {
+        isLandscape =
+            context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+        shouldUseSplitNotificationShade = Utils.shouldUseSplitNotificationShade(context.resources)
+        configController.addCallback(this)
+    }
+
+    fun adjustDisableFlags(state: Int): Int {
+        var mutableState = state
+        if (remoteInputActive &&
+            isLandscape &&
+            !shouldUseSplitNotificationShade
+        ) {
+            mutableState = state or StatusBarManager.DISABLE2_QUICK_SETTINGS
+        }
+        return mutableState
+    }
+
+    fun setRemoteInputActive(active: Boolean) {
+        if (remoteInputActive != active) {
+            remoteInputActive = active
+            recomputeDisableFlags()
+        }
+    }
+
+    override fun onConfigChanged(newConfig: Configuration) {
+        var needToRecompute = false
+
+        val newIsLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
+        if (newIsLandscape != isLandscape) {
+            isLandscape = newIsLandscape
+            needToRecompute = true
+        }
+
+        val newSplitShadeFlag = Utils.shouldUseSplitNotificationShade(context.resources)
+        if (newSplitShadeFlag != shouldUseSplitNotificationShade) {
+            shouldUseSplitNotificationShade = newSplitShadeFlag
+            needToRecompute = true
+        }
+        if (needToRecompute) {
+            recomputeDisableFlags()
+        }
+    }
+
+    /**
+     * Called in order to trigger a refresh of the disable flags after a relevant configuration
+     * change or when a [RemoteInputView] has changed its active state. The method
+     * [adjustDisableFlags] will be invoked to modify the disable flags according to
+     * [remoteInputActive], [isLandscape] and [shouldUseSplitNotificationShade].
+     */
+    private fun recomputeDisableFlags() {
+        commandQueue.recomputeDisableFlags(context.displayId, true)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index 8b394bf..97fce51 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -30,8 +30,6 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.UserHandle;
@@ -42,6 +40,7 @@
 import android.widget.TextView;
 
 import com.android.internal.R;
+import com.android.launcher3.icons.BaseIconFactory.IconOptions;
 import com.android.launcher3.icons.IconFactory;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
@@ -280,9 +279,14 @@
         final ApplicationInfo appInfo = appEntry.info;
         UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid);
         IconFactory iconFactory = IconFactory.obtain(context);
-        Bitmap iconBmp = iconFactory.createBadgedIconBitmap(
-                appInfo.loadUnbadgedIcon(packageManager), user, true).icon;
-        return new BitmapDrawable(context.getResources(), iconBmp);
+        try {
+            return iconFactory.createBadgedIconBitmap(
+                        appInfo.loadUnbadgedIcon(packageManager),
+                        new IconOptions().setUser(user))
+                    .newIcon(context);
+        } finally {
+            iconFactory.recycle();
+        }
     }
 
     private static boolean showApplicationIcon(ApplicationInfo appInfo,
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 51de1321..e6fc49f 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.unfold
 
+import android.animation.ValueAnimator
 import android.content.Context
 import android.graphics.PixelFormat
 import android.hardware.devicestate.DeviceStateManager
@@ -111,7 +112,7 @@
         Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn")
         try {
             // Add the view only if we are unfolding and this is the first screen on
-            if (!isFolded && !isUnfoldHandled) {
+            if (!isFolded && !isUnfoldHandled && ValueAnimator.areAnimatorsEnabled()) {
                 addView(onOverlayReady)
                 isUnfoldHandled = true
             } else {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
index fb8efa9..abc2099 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
@@ -16,12 +16,14 @@
 
 package com.android.systemui.communal;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
 import android.app.communal.CommunalManager;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 
@@ -36,7 +38,6 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
 public class CommunalManagerUpdaterTest extends SysuiTestCase {
     private CommunalSourceMonitor mMonitor;
     @Mock
@@ -49,9 +50,16 @@
         MockitoAnnotations.initMocks(this);
         mContext.addMockSystemService(CommunalManager.class, mCommunalManager);
 
+        doAnswer(invocation -> {
+            final CommunalConditionsMonitor.Callback callback = invocation.getArgument(0);
+            callback.onConditionsChanged(true);
+            return null;
+        }).when(mCommunalConditionsMonitor).addCallback(any());
+
         mMonitor = new CommunalSourceMonitor(mCommunalConditionsMonitor);
         final CommunalManagerUpdater updater = new CommunalManagerUpdater(mContext, mMonitor);
         updater.start();
+        clearInvocations(mCommunalManager);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java
index 475dde2..dea42f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java
@@ -18,15 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
 
-import android.content.Context;
+import android.content.res.Resources;
 
 import androidx.test.filters.SmallTest;
 
@@ -51,13 +50,14 @@
 public class FeatureFlagsReleaseTest extends SysuiTestCase {
     FeatureFlagsRelease mFeatureFlagsRelease;
 
+    @Mock private Resources mResources;
     @Mock private DumpManager mDumpManager;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        mFeatureFlagsRelease = new FeatureFlagsRelease(mDumpManager);
+        mFeatureFlagsRelease = new FeatureFlagsRelease(mResources, mDumpManager);
     }
 
     @After
@@ -68,15 +68,34 @@
     }
 
     @Test
+    public void testBooleanResourceFlag() {
+        int flagId = 213;
+        int flagResourceId = 3;
+        ResourceBooleanFlag flag = new ResourceBooleanFlag(flagId, flagResourceId);
+        when(mResources.getBoolean(flagResourceId)).thenReturn(true);
+
+        assertThat(mFeatureFlagsRelease.isEnabled(flag)).isTrue();
+    }
+
+    @Test
     public void testDump() {
+        int flagIdA = 213;
+        int flagIdB = 18;
+        int flagResourceId = 3;
+        BooleanFlag flagA = new BooleanFlag(flagIdA, true);
+        ResourceBooleanFlag flagB = new ResourceBooleanFlag(flagIdB, flagResourceId);
+        when(mResources.getBoolean(flagResourceId)).thenReturn(true);
+
         // WHEN the flags have been accessed
-        assertFalse(mFeatureFlagsRelease.isEnabled(1, false));
-        assertTrue(mFeatureFlagsRelease.isEnabled(2, true));
+        assertThat(mFeatureFlagsRelease.isEnabled(1, false)).isFalse();
+        assertThat(mFeatureFlagsRelease.isEnabled(flagA)).isTrue();
+        assertThat(mFeatureFlagsRelease.isEnabled(flagB)).isTrue();
 
         // THEN the dump contains the flags and the default values
         String dump = dumpToString();
         assertThat(dump).contains(" sysui_flag_1: false\n");
-        assertThat(dump).contains(" sysui_flag_2: true\n");
+        assertThat(dump).contains(" sysui_flag_" + flagIdA + ": true\n");
+        assertThat(dump).contains(" sysui_flag_" + flagIdB + ": true\n");
     }
 
     private String dumpToString() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 41ce941..7cc0172 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -19,6 +19,7 @@
 import android.content.Intent
 import android.graphics.Color
 import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.Icon
 import android.graphics.drawable.RippleDrawable
 import android.media.MediaMetadata
 import android.media.session.MediaSession
@@ -97,6 +98,7 @@
     @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
     @Mock private lateinit var mediaCarouselController: MediaCarouselController
     @Mock private lateinit var falsingManager: FalsingManager
+    @Mock private lateinit var mediaFlags: MediaFlags
     private lateinit var appIcon: ImageView
     private lateinit var albumView: ImageView
     private lateinit var titleText: TextView
@@ -123,6 +125,7 @@
     private lateinit var session: MediaSession
     private val device = MediaDeviceData(true, null, DEVICE_NAME)
     private val disabledDevice = MediaDeviceData(false, null, "Disabled Device")
+    private lateinit var mediaData: MediaData
     private val clock = FakeSystemClock()
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -135,7 +138,7 @@
 
         player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
             seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil,
-            mediaOutputDialogFactory, mediaCarouselController, falsingManager, clock)
+            mediaOutputDialogFactory, mediaCarouselController, falsingManager, mediaFlags, clock)
         whenever(seekBarViewModel.progress).thenReturn(seekBarData)
 
         // Mock out a view holder for the player to attach to.
@@ -165,14 +168,19 @@
         whenever(holder.totalTimeView).thenReturn(totalTimeView)
         action0 = ImageButton(context)
         whenever(holder.action0).thenReturn(action0)
+        whenever(holder.getAction(R.id.action0)).thenReturn(action0)
         action1 = ImageButton(context)
         whenever(holder.action1).thenReturn(action1)
+        whenever(holder.getAction(R.id.action1)).thenReturn(action1)
         action2 = ImageButton(context)
         whenever(holder.action2).thenReturn(action2)
+        whenever(holder.getAction(R.id.action2)).thenReturn(action2)
         action3 = ImageButton(context)
         whenever(holder.action3).thenReturn(action3)
+        whenever(holder.getAction(R.id.action3)).thenReturn(action3)
         action4 = ImageButton(context)
         whenever(holder.action4).thenReturn(action4)
+        whenever(holder.getAction(R.id.action4)).thenReturn(action4)
         whenever(holder.longPressText).thenReturn(longPressText)
         whenever(longPressText.handler).thenReturn(handler)
         settings = View(context)
@@ -200,6 +208,26 @@
             setPlaybackState(playbackBuilder.build())
         }
         session.setActive(true)
+
+        mediaData = MediaData(
+                userId = USER_ID,
+                initialized = true,
+                backgroundColor = BG_COLOR,
+                app = APP,
+                appIcon = null,
+                artist = ARTIST,
+                song = TITLE,
+                artwork = null,
+                actions = emptyList(),
+                actionsToShowInCompact = emptyList(),
+                packageName = PACKAGE,
+                token = session.sessionToken,
+                clickIntent = null,
+                device = device,
+                active = true,
+                resumeAction = null)
+
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(false)
     }
 
     @After
@@ -210,18 +238,50 @@
 
     @Test
     fun bindWhenUnattached() {
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, null, null, device, true, null)
+        val state = mediaData.copy(token = null)
         player.bindPlayer(state, PACKAGE)
         assertThat(player.isPlaying()).isFalse()
     }
 
     @Test
+    fun bindSemanticActions() {
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+        val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
+        val semanticActions = MediaButton(
+                playOrPause = MediaAction(icon, Runnable {}, "play"),
+                nextOrCustom = MediaAction(icon, Runnable {}, "next"),
+                startCustom = MediaAction(icon, null, "custom 1"),
+                endCustom = MediaAction(icon, null, "custom 2")
+        )
+        val state = mediaData.copy(semanticActions = semanticActions)
+
+        player.attachPlayer(holder)
+        player.bindPlayer(state, PACKAGE)
+
+        verify(expandedSet).setVisibility(R.id.action0, ConstraintSet.VISIBLE)
+        assertThat(action0.contentDescription).isEqualTo("custom 1")
+        assertThat(action0.isEnabled()).isFalse()
+
+        verify(expandedSet).setVisibility(R.id.action1, ConstraintSet.INVISIBLE)
+        assertThat(action1.isEnabled()).isFalse()
+
+        verify(expandedSet).setVisibility(R.id.action2, ConstraintSet.VISIBLE)
+        assertThat(action2.isEnabled()).isTrue()
+        assertThat(action2.contentDescription).isEqualTo("play")
+
+        verify(expandedSet).setVisibility(R.id.action3, ConstraintSet.VISIBLE)
+        assertThat(action3.isEnabled()).isTrue()
+        assertThat(action3.contentDescription).isEqualTo("next")
+
+        verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.VISIBLE)
+        assertThat(action4.contentDescription).isEqualTo("custom 2")
+        assertThat(action4.isEnabled()).isFalse()
+    }
+
+    @Test
     fun bindText() {
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
-        player.bindPlayer(state, PACKAGE)
+        player.bindPlayer(mediaData, PACKAGE)
         assertThat(titleText.getText()).isEqualTo(TITLE)
         assertThat(artistText.getText()).isEqualTo(ARTIST)
     }
@@ -229,9 +289,7 @@
     @Test
     fun bindDevice() {
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
-        player.bindPlayer(state, PACKAGE)
+        player.bindPlayer(mediaData, PACKAGE)
         assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
         assertThat(seamless.contentDescription).isEqualTo(DEVICE_NAME)
         assertThat(seamless.isEnabled()).isTrue()
@@ -242,8 +300,7 @@
         seamless.id = 1
         val fallbackString = context.getString(R.string.media_seamless_other_device)
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
+        val state = mediaData.copy(device = disabledDevice)
         player.bindPlayer(state, PACKAGE)
         assertThat(seamless.isEnabled()).isFalse()
         assertThat(seamlessText.getText()).isEqualTo(fallbackString)
@@ -254,8 +311,7 @@
     fun bindNullDevice() {
         val fallbackString = context.getResources().getString(R.string.media_seamless_other_device)
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
+        val state = mediaData.copy(device = null)
         player.bindPlayer(state, PACKAGE)
         assertThat(seamless.isEnabled()).isTrue()
         assertThat(seamlessText.getText()).isEqualTo(fallbackString)
@@ -265,9 +321,7 @@
     @Test
     fun bindDeviceResumptionPlayer() {
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null,
-                resumption = true)
+        val state = mediaData.copy(resumption = true)
         player.bindPlayer(state, PACKAGE)
         assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
         assertThat(seamless.isEnabled()).isFalse()
@@ -323,9 +377,7 @@
     fun dismissButtonClick() {
         val mediaKey = "key for dismissal"
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
-                notificationKey = KEY)
+        val state = mediaData.copy(notificationKey = KEY)
         player.bindPlayer(state, mediaKey)
 
         assertThat(dismiss.isEnabled).isEqualTo(true)
@@ -337,9 +389,7 @@
     fun dismissButtonDisabled() {
         val mediaKey = "key for dismissal"
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
-                isClearable = false, notificationKey = KEY)
+        val state = mediaData.copy(isClearable = false, notificationKey = KEY)
         player.bindPlayer(state, mediaKey)
 
         assertThat(dismiss.isEnabled).isEqualTo(false)
@@ -351,9 +401,7 @@
         whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false)
 
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
-                notificationKey = KEY)
+        val state = mediaData.copy(notificationKey = KEY)
         player.bindPlayer(state, mediaKey)
 
         assertThat(dismiss.isEnabled).isEqualTo(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index 09c83e5..7a487b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -74,8 +74,9 @@
         mManager = new MediaDataCombineLatest();
         mManager.addListener(mListener);
 
-        mMediaData = new MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
-                new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null,
+        mMediaData = new MediaData(
+                USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
+                new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
                 MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L);
         mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index 5a3c43c..6b203bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -96,11 +96,24 @@
         setUser(USER_MAIN)
 
         // Set up test media data
-        dataMain = MediaData(USER_MAIN, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-            emptyList(), PACKAGE, null, null, device, true, null)
-
-        dataGuest = MediaData(USER_GUEST, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
-            emptyList(), emptyList(), PACKAGE, null, null, device, true, null)
+        dataMain = MediaData(
+                userId = USER_MAIN,
+                initialized = true,
+                backgroundColor = BG_COLOR,
+                app = APP,
+                appIcon = null,
+                artist = ARTIST,
+                song = TITLE,
+                artwork = null,
+                actions = emptyList(),
+                actionsToShowInCompact = emptyList(),
+                packageName = PACKAGE,
+                token = null,
+                clickIntent = null,
+                device = device,
+                active = true,
+                resumeAction = null)
+        dataGuest = dataMain.copy(userId = USER_GUEST)
 
         `when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
         `when`(smartspaceData.isActive).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index e2019e0..d0b957c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -11,12 +11,15 @@
 import android.media.MediaMetadata
 import android.media.session.MediaController
 import android.media.session.MediaSession
+import android.media.session.PlaybackState
 import android.os.Bundle
 import android.provider.Settings
 import android.service.notification.StatusBarNotification
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.media.utils.MediaConstants
 import androidx.test.filters.SmallTest
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dump.DumpManager
@@ -67,6 +70,7 @@
     @JvmField @Rule val mockito = MockitoJUnit.rule()
     @Mock lateinit var mediaControllerFactory: MediaControllerFactory
     @Mock lateinit var controller: MediaController
+    @Mock lateinit var transportControls: MediaController.TransportControls
     @Mock lateinit var playbackInfo: MediaController.PlaybackInfo
     lateinit var session: MediaSession
     lateinit var metadataBuilder: MediaMetadata.Builder
@@ -87,6 +91,7 @@
     @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
     @Mock private lateinit var mediaRecommendationItem: SmartspaceAction
     @Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
+    @Mock private lateinit var mediaFlags: MediaFlags
     lateinit var mediaDataManager: MediaDataManager
     lateinit var mediaNotification: StatusBarNotification
     @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
@@ -122,7 +127,8 @@
             useMediaResumption = true,
             useQsMediaPlayer = true,
             systemClock = clock,
-            tunerService = tunerService
+            tunerService = tunerService,
+            mediaFlags = mediaFlags
         )
         verify(tunerService).addTunable(capture(tunableCaptor),
                 eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -140,6 +146,7 @@
             putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
         }
         whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
+        whenever(controller.transportControls).thenReturn(transportControls)
         whenever(controller.playbackInfo).thenReturn(playbackInfo)
         whenever(playbackInfo.playbackType).thenReturn(
                 MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL)
@@ -161,6 +168,7 @@
         whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
         whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf(mediaRecommendationItem))
         whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L)
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(false)
     }
 
     @After
@@ -583,4 +591,157 @@
         assertThat(mediaDataCaptor.value.actionsToShowInCompact.size).isEqualTo(
                 MediaDataManager.MAX_COMPACT_ACTIONS)
     }
+
+    @Test
+    fun testPlaybackActions_noState_usesNotification() {
+        val desc = "Notification Action"
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+        whenever(controller.playbackState).thenReturn(null)
+
+        val notifWithAction = SbnBuilder().run {
+            setPkg(PACKAGE_NAME)
+            modifyNotification(context).also {
+                it.setSmallIcon(android.R.drawable.ic_media_pause)
+                it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                it.addAction(android.R.drawable.ic_media_play, desc, null)
+            }
+            build()
+        }
+        mediaDataManager.onNotificationAdded(KEY, notifWithAction)
+
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+                eq(0))
+
+        assertThat(mediaDataCaptor.value!!.semanticActions).isNull()
+        assertThat(mediaDataCaptor.value!!.actions).hasSize(1)
+        assertThat(mediaDataCaptor.value!!.actions[0]!!.contentDescription).isEqualTo(desc)
+    }
+
+    @Test
+    fun testPlaybackActions_hasPrevNext() {
+        val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+        val stateActions = PlaybackState.ACTION_PLAY or
+                PlaybackState.ACTION_SKIP_TO_PREVIOUS or
+                PlaybackState.ACTION_SKIP_TO_NEXT
+        val stateBuilder = PlaybackState.Builder()
+                .setActions(stateActions)
+        customDesc.forEach {
+            stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
+        }
+        whenever(controller.playbackState).thenReturn(stateBuilder.build())
+
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+                eq(0))
+
+        assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
+        val actions = mediaDataCaptor.value!!.semanticActions!!
+
+        assertThat(actions.playOrPause).isNotNull()
+        assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_play))
+        actions.playOrPause!!.action!!.run()
+        verify(transportControls).play()
+
+        assertThat(actions.prevOrCustom).isNotNull()
+        assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_prev))
+        actions.prevOrCustom!!.action!!.run()
+        verify(transportControls).skipToPrevious()
+
+        assertThat(actions.nextOrCustom).isNotNull()
+        assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_next))
+        actions.nextOrCustom!!.action!!.run()
+        verify(transportControls).skipToNext()
+
+        assertThat(actions.startCustom).isNotNull()
+        assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0])
+
+        assertThat(actions.endCustom).isNotNull()
+        assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1])
+    }
+
+    @Test
+    fun testPlaybackActions_noPrevNext_usesCustom() {
+        val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+        val stateActions = PlaybackState.ACTION_PLAY
+        val stateBuilder = PlaybackState.Builder()
+                .setActions(stateActions)
+        customDesc.forEach {
+            stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
+        }
+        whenever(controller.playbackState).thenReturn(stateBuilder.build())
+
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+                eq(0))
+
+        assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
+        val actions = mediaDataCaptor.value!!.semanticActions!!
+
+        assertThat(actions.playOrPause).isNotNull()
+        assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_play))
+
+        assertThat(actions.prevOrCustom).isNotNull()
+        assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(customDesc[0])
+
+        assertThat(actions.nextOrCustom).isNotNull()
+        assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(customDesc[1])
+
+        assertThat(actions.startCustom).isNotNull()
+        assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[2])
+
+        assertThat(actions.endCustom).isNotNull()
+        assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[3])
+    }
+
+    @Test
+    fun testPlaybackActions_reservedSpace() {
+        val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+        val stateActions = PlaybackState.ACTION_PLAY
+        val stateBuilder = PlaybackState.Builder()
+                .setActions(stateActions)
+        customDesc.forEach {
+            stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
+        }
+        val extras = Bundle().apply {
+            putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
+            putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
+        }
+        whenever(controller.playbackState).thenReturn(stateBuilder.build())
+        whenever(controller.extras).thenReturn(extras)
+
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+                eq(0))
+
+        assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
+        val actions = mediaDataCaptor.value!!.semanticActions!!
+
+        assertThat(actions.playOrPause).isNotNull()
+        assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_play))
+
+        assertThat(actions.prevOrCustom).isNull()
+        assertThat(actions.nextOrCustom).isNull()
+
+        assertThat(actions.startCustom).isNotNull()
+        assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0])
+
+        assertThat(actions.endCustom).isNotNull()
+        assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1])
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index 7dadbad..3d59497 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -102,9 +102,24 @@
         // Create a media sesssion and notification for testing.
         session = MediaSession(context, SESSION_KEY)
 
-        mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
-            emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
-            device = null, active = true, resumeAction = null)
+        mediaData = MediaData(
+                userId = USER_ID,
+                initialized = true,
+                backgroundColor = 0,
+                app = PACKAGE,
+                appIcon = null,
+                artist = null,
+                song = SESSION_TITLE,
+                artwork = null,
+                actions = emptyList(),
+                actionsToShowInCompact = emptyList(),
+                packageName = PACKAGE,
+                token = session.sessionToken,
+                clickIntent = null,
+                device = null,
+                active = true,
+                resumeAction = null)
+
         whenever(controllerFactory.create(session.sessionToken))
                 .thenReturn(controller)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
index 421f9be..ceeb0db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
@@ -163,8 +163,27 @@
         isPlaying: Boolean?,
         location: Int,
         resumption: Boolean
-    ) =
-        MediaData(0, false, 0, app, null, null, null, null, emptyList(), emptyList<Int>(),
-            "package:" + app, null, null, null, true, null, location, resumption, "key:" + app,
-            false, isPlaying)
+    ) = MediaData(
+        userId = 0,
+        initialized = false,
+        backgroundColor = 0,
+        app = app,
+        appIcon = null,
+        artist = null,
+        song = null,
+        artwork = null,
+        actions = emptyList(),
+        actionsToShowInCompact = emptyList(),
+        packageName = "package: $app",
+        token = null,
+        clickIntent = null,
+        device = null,
+        active = true,
+        resumeAction = null,
+        playbackLocation = location,
+        resumption = resumption,
+        notificationKey = "key: $app",
+        hasCheckedForResume = false,
+        isPlaying = isPlaying
+    )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index de2235d..8c2fed5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -95,13 +95,26 @@
             setPlaybackState(playbackBuilder.build())
         }
         session.setActive(true)
-        mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
-            emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
-            device = null, active = true, resumeAction = null)
 
-        resumeData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
-                emptyList(), emptyList(), PACKAGE, null, clickIntent = null,
-                device = null, active = false, resumeAction = null, resumption = true)
+        mediaData = MediaData(
+                userId = USER_ID,
+                initialized = true,
+                backgroundColor = 0,
+                app = PACKAGE,
+                appIcon = null,
+                artist = null,
+                song = SESSION_TITLE,
+                artwork = null,
+                actions = emptyList(),
+                actionsToShowInCompact = emptyList(),
+                packageName = PACKAGE,
+                token = session.sessionToken,
+                clickIntent = null,
+                device = null,
+                active = true,
+                resumeAction = null)
+
+        resumeData = mediaData.copy(token = null, active = false, resumption = true)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt
index efb4931..923f018 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt
@@ -16,14 +16,20 @@
 
 package com.android.systemui.media.taptotransfer
 
+import android.view.View
 import android.view.WindowManager
+import android.widget.LinearLayout
+import android.widget.TextView
 import androidx.test.filters.SmallTest
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
+import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
@@ -48,7 +54,7 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        mediaTttChipController = MediaTttChipController(context, commandRegistry, windowManager)
+        mediaTttChipController = MediaTttChipController(commandRegistry, context, windowManager)
     }
 
     @Test(expected = IllegalStateException::class)
@@ -71,24 +77,24 @@
 
     @Test
     fun addChipCommand_chipAdded() {
-        commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
+        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
 
         verify(windowManager).addView(any(), any())
     }
 
     @Test
     fun addChipCommand_twice_chipNotAddedTwice() {
-        commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
+        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
         reset(windowManager)
 
-        commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
+        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
         verify(windowManager, never()).addView(any(), any())
     }
 
     @Test
     fun removeChipCommand_chipRemoved() {
         // First, add the chip
-        commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
+        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
 
         // Then, remove it
         commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.REMOVE_CHIP_COMMAND_TAG))
@@ -103,6 +109,104 @@
         verify(windowManager, never()).removeView(any())
     }
 
+    @Test
+    fun moveCloserToTransfer_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
+        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+
+        val chipView = getChipView()
+        assertThat(chipView.getChipText()).contains(DEVICE_NAME)
+        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+        assertThat(chipView.getUndoButtonVisibility()).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun transferInitiated_chipTextContainsDeviceName_loadingIcon_noUndo() {
+        commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
+
+        val chipView = getChipView()
+        assertThat(chipView.getChipText()).contains(DEVICE_NAME)
+        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
+        assertThat(chipView.getUndoButtonVisibility()).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun transferSucceeded_chipTextContainsDeviceName_noLoadingIcon_undo() {
+        commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
+
+        val chipView = getChipView()
+        assertThat(chipView.getChipText()).contains(DEVICE_NAME)
+        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+        assertThat(chipView.getUndoButtonVisibility()).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    fun changeFromCloserToTransferToTransferInitiated_loadingIconAppears() {
+        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+        commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
+
+        assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    fun changeFromTransferInitiatedToTransferSucceeded_loadingIconDisappears() {
+        commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
+        commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
+
+        assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun changeFromTransferInitiatedToTransferSucceeded_undoButtonAppears() {
+        commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
+        commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
+
+        assertThat(getChipView().getUndoButtonVisibility()).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    fun changeFromTransferSucceededToMoveCloser_undoButtonDisappears() {
+        commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
+        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+
+        assertThat(getChipView().getUndoButtonVisibility()).isEqualTo(View.GONE)
+    }
+
+    private fun getMoveCloserToTransferCommand(): Array<String> =
+        arrayOf(
+            MediaTttChipController.ADD_CHIP_COMMAND_TAG,
+            DEVICE_NAME,
+            MediaTttChipController.ChipType.MOVE_CLOSER_TO_TRANSFER.name
+        )
+
+    private fun getTransferInitiatedCommand(): Array<String> =
+        arrayOf(
+            MediaTttChipController.ADD_CHIP_COMMAND_TAG,
+            DEVICE_NAME,
+            MediaTttChipController.ChipType.TRANSFER_INITIATED.name
+        )
+
+    private fun getTransferSucceededCommand(): Array<String> =
+        arrayOf(
+            MediaTttChipController.ADD_CHIP_COMMAND_TAG,
+            DEVICE_NAME,
+            MediaTttChipController.ChipType.TRANSFER_SUCCEEDED.name
+        )
+
+    private fun LinearLayout.getChipText(): String =
+        (this.requireViewById<TextView>(R.id.text)).text as String
+
+    private fun LinearLayout.getLoadingIconVisibility(): Int =
+        this.requireViewById<View>(R.id.loading).visibility
+
+    private fun LinearLayout.getUndoButtonVisibility(): Int =
+        this.requireViewById<View>(R.id.undo).visibility
+
+    private fun getChipView(): LinearLayout {
+        val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+        verify(windowManager).addView(viewCaptor.capture(), any())
+        return viewCaptor.value as LinearLayout
+    }
+
     class EmptyCommand : Command {
         override fun execute(pw: PrintWriter, args: List<String>) {
         }
@@ -111,3 +215,5 @@
         }
     }
 }
+
+private const val DEVICE_NAME = "My Tablet"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index c1562c1..6f4e619 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -185,8 +185,8 @@
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         CommandQueue commandQueue = new CommandQueue(context);
         return new QSFragment(
-                new RemoteInputQuickSettingsDisabler(context, mock(ConfigurationController.class),
-                        commandQueue),
+                new RemoteInputQuickSettingsDisabler(context, commandQueue,
+                        mock(ConfigurationController.class)),
                 mock(QSTileHost.class),
                 mock(StatusBarStateController.class),
                 commandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index ca8903b..95e7a98c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -1,5 +1,7 @@
 package com.android.systemui.qs.tiles.dialog;
 
+import static android.provider.Settings.Global.AIRPLANE_MODE_ON;
+
 import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_HORIZONTAL_WEIGHT;
 import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_VERTICAL_WEIGHT;
 
@@ -19,7 +21,6 @@
 import static org.mockito.Mockito.when;
 
 import android.animation.Animator;
-import android.content.Context;
 import android.content.Intent;
 import android.graphics.PixelFormat;
 import android.graphics.drawable.Drawable;
@@ -37,7 +38,6 @@
 import android.view.View;
 import android.view.WindowManager;
 
-import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
@@ -47,7 +47,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.connectivity.AccessPointController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -70,7 +69,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Executor;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -78,8 +76,6 @@
 public class InternetDialogControllerTest extends SysuiTestCase {
 
     private static final int SUB_ID = 1;
-    private static final String CONNECTED_TITLE = "Connected Wi-Fi Title";
-    private static final String CONNECTED_SUMMARY = "Connected Wi-Fi Summary";
 
     //SystemUIToast
     private static final int GRAVITY_FLAGS = Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL;
@@ -142,7 +138,7 @@
     private DialogLaunchAnimator mDialogLaunchAnimator;
 
     private TestableResources mTestableResources;
-    private MockInternetDialogController mInternetDialogController;
+    private InternetDialogController mInternetDialogController;
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
     private List<WifiEntry> mAccessPoints = new ArrayList<>();
     private List<WifiEntry> mWifiEntries = new ArrayList<>();
@@ -170,7 +166,7 @@
         when(mSystemUIToast.getGravity()).thenReturn(GRAVITY_FLAGS);
         when(mSystemUIToast.getInAnimation()).thenReturn(mAnimator);
 
-        mInternetDialogController = new MockInternetDialogController(mContext,
+        mInternetDialogController = new InternetDialogController(mContext,
                 mock(UiEventLogger.class), mock(ActivityStarter.class), mAccessPointController,
                 mSubscriptionManager, mTelephonyManager, mWifiManager,
                 mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher,
@@ -225,7 +221,7 @@
 
     @Test
     public void getDialogTitleText_withAirplaneModeOn_returnAirplaneMode() {
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
 
         assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
                 getResourcesString("airplane_mode")));
@@ -233,7 +229,7 @@
 
     @Test
     public void getDialogTitleText_withAirplaneModeOff_returnInternet() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
 
         assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
                 getResourcesString("quick_settings_internet_label")));
@@ -241,14 +237,14 @@
 
     @Test
     public void getSubtitleText_withAirplaneModeOn_returnNull() {
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
 
         assertThat(mInternetDialogController.getSubtitleText(false)).isNull();
     }
 
     @Test
     public void getSubtitleText_withWifiOff_returnWifiIsOff() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(false);
 
         assertThat(mInternetDialogController.getSubtitleText(false))
@@ -263,7 +259,7 @@
 
     @Test
     public void getSubtitleText_withNoWifiEntry_returnSearchWifi() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
         mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
 
@@ -280,7 +276,7 @@
     @Test
     public void getSubtitleText_withWifiEntry_returnTapToConnect() {
         // The preconditions WiFi Entries is already in setUp()
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
 
         assertThat(mInternetDialogController.getSubtitleText(false))
@@ -295,7 +291,7 @@
 
     @Test
     public void getSubtitleText_deviceLockedWithWifiOn_returnUnlockToViewNetworks() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
         when(mKeyguardStateController.isUnlocked()).thenReturn(false);
 
@@ -305,7 +301,7 @@
 
     @Test
     public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
         mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
 
@@ -319,7 +315,7 @@
 
     @Test
     public void getSubtitleText_withMobileDataDisabled_returnNoOtherAvailable() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
         mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
 
@@ -420,7 +416,7 @@
     @Test
     public void onAccessPointsChanged_oneConnectedEntry_callbackConnectedEntryOnly() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mConnectedEntry);
 
@@ -433,7 +429,7 @@
     @Test
     public void onAccessPointsChanged_noConnectedEntryAndOneOther_callbackWifiEntriesOnly() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mWifiEntry1);
 
@@ -448,7 +444,7 @@
     @Test
     public void onAccessPointsChanged_oneConnectedEntryAndOneOther_callbackCorrectly() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mConnectedEntry);
         mAccessPoints.add(mWifiEntry1);
@@ -463,7 +459,7 @@
     @Test
     public void onAccessPointsChanged_oneConnectedEntryAndTwoOthers_callbackCorrectly() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mConnectedEntry);
         mAccessPoints.add(mWifiEntry1);
@@ -480,7 +476,7 @@
     @Test
     public void onAccessPointsChanged_oneConnectedEntryAndThreeOthers_callbackCutMore() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mConnectedEntry);
         mAccessPoints.add(mWifiEntry1);
@@ -497,7 +493,7 @@
 
         // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
 
         mInternetDialogController.onAccessPointsChanged(mAccessPoints);
 
@@ -508,7 +504,7 @@
     @Test
     public void onAccessPointsChanged_oneConnectedEntryAndFourOthers_callbackCutMore() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mConnectedEntry);
         mAccessPoints.add(mWifiEntry1);
@@ -526,7 +522,7 @@
 
         // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
 
         mInternetDialogController.onAccessPointsChanged(mAccessPoints);
 
@@ -537,7 +533,7 @@
     @Test
     public void onAccessPointsChanged_fourWifiEntries_callbackCutMore() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mWifiEntry1);
         mAccessPoints.add(mWifiEntry2);
@@ -566,7 +562,7 @@
 
         // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
 
         mInternetDialogController.onAccessPointsChanged(mAccessPoints);
 
@@ -576,6 +572,23 @@
     }
 
     @Test
+    public void onAccessPointsChanged_wifiIsDefaultButNoInternetAccess_putIntoWifiEntries() {
+        reset(mInternetDialogCallback);
+        mAccessPoints.clear();
+        when(mWifiEntry1.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED);
+        when(mWifiEntry1.isDefaultNetwork()).thenReturn(true);
+        when(mWifiEntry1.hasInternetAccess()).thenReturn(false);
+        mAccessPoints.add(mWifiEntry1);
+
+        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+        mWifiEntries.clear();
+        mWifiEntries.add(mWifiEntry1);
+        verify(mInternetDialogCallback)
+                .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+    }
+
+    @Test
     public void setMergedCarrierWifiEnabledIfNeed_carrierProvisionsEnabled_doNothing() {
         when(mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(SUB_ID))
                 .thenReturn(true);
@@ -641,38 +654,7 @@
                 mContext.getPackageName());
     }
 
-    private class MockInternetDialogController extends InternetDialogController {
-
-        private GlobalSettings mGlobalSettings;
-        private boolean mIsAirplaneModeOn;
-
-        MockInternetDialogController(Context context, UiEventLogger uiEventLogger,
-                ActivityStarter starter, AccessPointController accessPointController,
-                SubscriptionManager subscriptionManager, TelephonyManager telephonyManager,
-                @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
-                @Main Handler handler, @Main Executor mainExecutor,
-                BroadcastDispatcher broadcastDispatcher,
-                KeyguardUpdateMonitor keyguardUpdateMonitor, GlobalSettings globalSettings,
-                KeyguardStateController keyguardStateController, WindowManager windowManager,
-                ToastFactory toastFactory, Handler workerHandler,
-                CarrierConfigTracker carrierConfigTracker,
-                LocationController locationController,
-                DialogLaunchAnimator dialogLaunchAnimator) {
-            super(context, uiEventLogger, starter, accessPointController, subscriptionManager,
-                    telephonyManager, wifiManager, connectivityManager, handler, mainExecutor,
-                    broadcastDispatcher, keyguardUpdateMonitor, globalSettings,
-                    keyguardStateController, windowManager, toastFactory, workerHandler,
-                    carrierConfigTracker, locationController, dialogLaunchAnimator);
-            mGlobalSettings = globalSettings;
-        }
-
-        @Override
-        boolean isAirplaneModeEnabled() {
-            return mIsAirplaneModeOn;
-        }
-
-        public void setAirplaneModeEnabled(boolean enabled) {
-            mIsAirplaneModeOn = enabled;
-        }
+    private void fakeAirplaneModeEnabled(boolean enabled) {
+        when(mGlobalSettings.getInt(eq(AIRPLANE_MODE_ON), anyInt())).thenReturn(enabled ? 1 : 0);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 339d5bb..0cf063f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -80,6 +80,7 @@
     private RecyclerView mWifiList;
     private LinearLayout mSeeAll;
     private LinearLayout mWifiScanNotify;
+    private TextView mAirplaneModeSummaryText;
 
     @Before
     public void setUp() {
@@ -114,6 +115,7 @@
         mWifiList = mDialogView.requireViewById(R.id.wifi_list_layout);
         mSeeAll = mDialogView.requireViewById(R.id.see_all_layout);
         mWifiScanNotify = mDialogView.requireViewById(R.id.wifi_scan_notify_layout);
+        mAirplaneModeSummaryText = mDialogView.requireViewById(R.id.airplane_mode_summary);
     }
 
     @After
@@ -191,7 +193,41 @@
     }
 
     @Test
-    public void updateDialog_withApmOn_mobileDataLayoutGone() {
+    public void updateDialog_apmOffAndNotCarrierNetwork_mobileDataLayoutGone() {
+        // Mobile network should be gone if the list of active subscriptionId is null.
+        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+        when(mInternetDialogController.hasActiveSubId()).thenReturn(false);
+
+        mInternetDialog.updateDialog(true);
+
+        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void updateDialog_apmOnWithCarrierNetworkAndWifiStatus_mobileDataLayout() {
+        // Carrier network should be gone if airplane mode ON and Wi-Fi is off.
+        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+        when(mWifiManager.isWifiEnabled()).thenReturn(false);
+
+        mInternetDialog.updateDialog(true);
+
+        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+
+        // Carrier network should be visible if airplane mode ON and Wi-Fi is ON.
+        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+
+        mInternetDialog.updateDialog(true);
+
+        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void updateDialog_apmOnAndNoCarrierNetwork_mobileDataLayoutGone() {
+        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
 
         mInternetDialog.updateDialog(true);
@@ -200,6 +236,51 @@
     }
 
     @Test
+    public void updateDialog_apmOnAndWifiOnHasCarrierNetwork_showAirplaneSummary() {
+        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+        mInternetDialog.mConnectedWifiEntry = null;
+        doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
+
+        mInternetDialog.updateDialog(true);
+
+        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void updateDialog_apmOffAndWifiOnHasCarrierNetwork_notShowApmSummary() {
+        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+        mInternetDialog.mConnectedWifiEntry = null;
+        doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
+
+        mInternetDialog.updateDialog(true);
+
+        assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void updateDialog_apmOffAndHasCarrierNetwork_notShowApmSummary() {
+        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+
+        mInternetDialog.updateDialog(true);
+
+        assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void updateDialog_apmOnAndNoCarrierNetwork_notShowApmSummary() {
+        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+
+        mInternetDialog.updateDialog(true);
+
+        assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
     public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
         // The preconditions WiFi ON and Internet WiFi are already in setUp()
         doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 82cd9fa..b832577 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -114,6 +114,7 @@
     private List<NotificationEntry> mEntrySet = new ArrayList<>();
     private List<ListEntry> mBuiltList;
     private TestableStabilityManager mStabilityManager;
+    private TestableNotifFilter mFinalizeFilter;
 
     private Map<String, Integer> mNextIdMap = new ArrayMap<>();
     private int mNextRank = 0;
@@ -136,6 +137,8 @@
 
         mStabilityManager = spy(new TestableStabilityManager());
         mListBuilder.setNotifStabilityManager(mStabilityManager);
+        mFinalizeFilter = spy(new TestableNotifFilter());
+        mListBuilder.addFinalizeFilter(mFinalizeFilter);
 
         Mockito.verify(mNotifCollection).setBuildListener(mBuildListenerCaptor.capture());
         mReadyForBuildListener = Objects.requireNonNull(mBuildListenerCaptor.getValue());
@@ -408,7 +411,6 @@
 
         // THEN the summary has a null parent and an unset firstAddedIteration
         assertNull(mEntrySet.get(1).getParent());
-        assertEquals(-1, mEntrySet.get(1).mFirstAddedIteration);
     }
 
     @Test
@@ -1037,7 +1039,7 @@
     }
 
     @Test
-    public void testStabilizeGroupsDoesNotAllowGrouping() {
+    public void testStabilizeGroupsDoesNotAllowGroupingExistingNotifications() {
         // GIVEN one group child without a summary yet
         addGroupChild(0, PACKAGE_1, GROUP_1);
 
@@ -1056,7 +1058,10 @@
         // because group changes aren't allowed by the stability manager
         verifyBuiltList(
                 notif(0),
-                notif(2)
+                group(
+                        summary(1),
+                        child(2)
+                )
         );
     }
 
@@ -1110,11 +1115,13 @@
 
         dispatchBuild();
 
-        // THEN all notifications are top-level and the summary doesn't show yet
-        // because group changes aren't allowed by the stability manager
+        // THEN first notification stays top-level but the other notifications are grouped.
         verifyBuiltList(
                 notif(0),
-                notif(2),
+                group(
+                        summary(1),
+                        child(2)
+                ),
                 group(
                         summary(3),
                         child(4),
@@ -1209,6 +1216,140 @@
     }
 
     @Test
+    public void testStabilityIsolationAllowsGroupToHaveSingleChild() {
+        // GIVEN a group with only one child was already drawn
+        addGroupSummary(0, PACKAGE_1, GROUP_1);
+        addGroupChild(1, PACKAGE_1, GROUP_1);
+
+        dispatchBuild();
+        // NOTICE that the group is pruned and the child is moved to the top level
+        verifyBuiltList(
+                notif(1)  // group with only one child is promoted
+        );
+
+        // WHEN another child is added while group changes are disabled.
+        mStabilityManager.setAllowGroupChanges(false);
+        addGroupChild(2, PACKAGE_1, GROUP_1);
+
+        dispatchBuild();
+
+        // THEN the new child should be added to the group
+        verifyBuiltList(
+                group(
+                        summary(0),
+                        child(2)
+                ),
+                notif(1)
+        );
+    }
+
+    @Test
+    public void testStabilityIsolationExemptsGroupWithFinalizeFilteredChildFromShowingSummary() {
+        // GIVEN a group with only one child was already drawn
+        addGroupSummary(0, PACKAGE_1, GROUP_1);
+        addGroupChild(1, PACKAGE_1, GROUP_1);
+
+        dispatchBuild();
+        // NOTICE that the group is pruned and the child is moved to the top level
+        verifyBuiltList(
+                notif(1)  // group with only one child is promoted
+        );
+
+        // WHEN another child is added but still filtered while group changes are disabled.
+        mStabilityManager.setAllowGroupChanges(false);
+        mFinalizeFilter.mIndicesToFilter.add(2);
+        addGroupChild(2, PACKAGE_1, GROUP_1);
+
+        dispatchBuild();
+
+        // THEN the new child should be shown without the summary
+        verifyBuiltList(
+                notif(1)  // previously promoted child
+        );
+    }
+
+    @Test
+    public void testStabilityIsolationOfRemovedChildDoesNotExemptGroupFromPrune() {
+        // GIVEN a group with only one child was already drawn
+        addGroupSummary(0, PACKAGE_1, GROUP_1);
+        addGroupChild(1, PACKAGE_1, GROUP_1);
+
+        dispatchBuild();
+        // NOTICE that the group is pruned and the child is moved to the top level
+        verifyBuiltList(
+                notif(1)  // group with only one child is promoted
+        );
+
+        // WHEN a new child is added and the old one gets filtered while group changes are disabled.
+        mStabilityManager.setAllowGroupChanges(false);
+        mFinalizeFilter.mIndicesToFilter.add(1);
+        addGroupChild(2, PACKAGE_1, GROUP_1);
+
+        dispatchBuild();
+
+        // THEN the new child should be shown without a group
+        verifyBuiltList(
+                notif(2)  // previously promoted child
+        );
+    }
+
+    @Test
+    public void testFinalizeFilteredSummaryPromotesChildren() {
+        // GIVEN a group with only one child was already drawn
+        addGroupSummary(0, PACKAGE_1, GROUP_1);
+        addGroupChild(1, PACKAGE_1, GROUP_1);
+        addGroupChild(2, PACKAGE_1, GROUP_1);
+
+        // WHEN the parent is filtered out at the finalize step
+        mFinalizeFilter.mIndicesToFilter.add(0);
+
+        dispatchBuild();
+
+        // THEN the children should be promoted to the top level
+        verifyBuiltList(
+                notif(1),
+                notif(2)
+        );
+    }
+
+    @Test
+    public void testFinalizeFilteredChildrenPromotesSummary() {
+        // GIVEN a group with only one child was already drawn
+        addGroupSummary(0, PACKAGE_1, GROUP_1);
+        addGroupChild(1, PACKAGE_1, GROUP_1);
+        addGroupChild(2, PACKAGE_1, GROUP_1);
+
+        // WHEN the parent is filtered out at the finalize step
+        mFinalizeFilter.mIndicesToFilter.add(1);
+        mFinalizeFilter.mIndicesToFilter.add(2);
+
+        dispatchBuild();
+
+        // THEN the children should be promoted to the top level
+        verifyBuiltList(
+                notif(0)
+        );
+    }
+
+    @Test
+    public void testFinalizeFilteredChildPromotesSibling() {
+        // GIVEN a group with only one child was already drawn
+        addGroupSummary(0, PACKAGE_1, GROUP_1);
+        addGroupChild(1, PACKAGE_1, GROUP_1);
+        addGroupChild(2, PACKAGE_1, GROUP_1);
+
+        // WHEN the parent is filtered out at the finalize step
+        mFinalizeFilter.mIndicesToFilter.add(1);
+
+        dispatchBuild();
+
+        // THEN the children should be promoted to the top level
+        verifyBuiltList(
+                notif(2)
+        );
+    }
+
+    @Test
     public void testBrokenGroupNotificationOrdering() {
         // GIVEN two group children with different sections & without a summary yet
         addGroupChild(0, PACKAGE_2, GROUP_1);
@@ -1228,30 +1369,6 @@
     }
 
     @Test
-    public void testStabilizeGroupsHidesGroupSummary() {
-        // GIVEN one group child with a summary
-        addGroupChild(0, PACKAGE_1, GROUP_1);
-        addGroupSummary(1, PACKAGE_1, GROUP_1);
-
-        dispatchBuild(); // group summary is hidden because it needs at least 2 children to group
-
-        // GIVEN visual stability manager doesn't allow any group changes
-        mStabilityManager.setAllowGroupChanges(false);
-
-        // WHEN we run the pipeline with the addition of a child
-        addGroupChild(2, PACKAGE_1, GROUP_1);
-
-        dispatchBuild();
-
-        // THEN the children notifications are top-level and the summary still doesn't show yet
-        // because group changes aren't allowed by the stability manager
-        verifyBuiltList(
-                notif(0),
-                notif(2)
-        );
-    }
-
-    @Test
     public void testStabilizeGroupsDelayedSummaryRendersAllNotifsTopLevel() {
         // GIVEN group children posted without a summary
         addGroupChild(0, PACKAGE_1, GROUP_1);
@@ -1269,13 +1386,12 @@
 
         dispatchBuild();
 
-        // THEN all entries are top-level since group changes aren't allowed
+        // THEN all entries are top-level, but summary is suppressed
         verifyBuiltList(
                 notif(0),
                 notif(1),
                 notif(2),
-                notif(3),
-                notif(4)
+                notif(3)
         );
 
         // WHEN visual stability manager allows group changes again
@@ -1907,6 +2023,19 @@
         }
     }
 
+    private class TestableNotifFilter extends NotifFilter {
+        ArrayList<Integer> mIndicesToFilter = new ArrayList<>();
+
+        protected TestableNotifFilter() {
+            super("TestFilter");
+        }
+
+        @Override
+        public boolean shouldFilterOut(@NonNull NotificationEntry entry, long now) {
+            return mIndicesToFilter.stream().anyMatch(i -> notif(i).entry == entry);
+        }
+    }
+
     private static class TestableStabilityManager extends NotifStabilityManager {
         boolean mAllowGroupChanges = true;
         boolean mAllowSectionChanges = true;
@@ -1937,17 +2066,17 @@
         }
 
         @Override
-        public boolean isGroupChangeAllowed(NotificationEntry entry) {
+        public boolean isGroupChangeAllowed(@NonNull NotificationEntry entry) {
             return mAllowGroupChanges;
         }
 
         @Override
-        public boolean isSectionChangeAllowed(NotificationEntry entry) {
+        public boolean isSectionChangeAllowed(@NonNull NotificationEntry entry) {
             return mAllowSectionChanges;
         }
 
         @Override
-        public boolean isEntryReorderingAllowed(ListEntry entry) {
+        public boolean isEntryReorderingAllowed(@NonNull ListEntry entry) {
             return mAllowEntryReodering;
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 3ec9629..99800e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -120,6 +120,7 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.QsFrameTranslateController;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -354,6 +355,8 @@
     private InteractionJankMonitor mInteractionJankMonitor;
     @Mock
     private NotificationsQSContainerController mNotificationsQSContainerController;
+    @Mock
+    private QsFrameTranslateController mQsFrameTranslateController;
     private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
     private SysuiStatusBarStateController mStatusBarStateController;
     private NotificationPanelViewController mNotificationPanelViewController;
@@ -530,7 +533,8 @@
                 mNotificationRemoteInputManager,
                 mSysUIUnfoldComponent,
                 mControlsComponent,
-                mInteractionJankMonitor);
+                mInteractionJankMonitor,
+                mQsFrameTranslateController);
         mNotificationPanelViewController.initDependencies(
                 mStatusBar,
                 () -> {},
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index dc32007..7d266e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -102,7 +102,7 @@
         verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture())
         argumentCaptor.value.onPreDraw()
 
-        verify(moveFromCenterAnimation).onViewsReady(any(), any())
+        verify(moveFromCenterAnimation).onViewsReady(any())
     }
 
     private fun createViewMock(): PhoneStatusBarView {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
new file mode 100644
index 0000000..1ce7ff4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
@@ -0,0 +1,104 @@
+package com.android.systemui.statusbar.phone
+
+import android.graphics.Point
+import android.view.Display
+import android.view.Surface
+import android.view.View
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var windowManager: WindowManager
+
+    @Mock
+    private lateinit var display: Display
+
+    private val view: View = View(context)
+    private val progressProvider = TestUnfoldTransitionProvider()
+    private val scopedProvider = ScopedUnfoldTransitionProgressProvider(progressProvider)
+
+    private lateinit var controller: StatusBarMoveFromCenterAnimationController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        `when`(windowManager.defaultDisplay).thenReturn(display)
+        `when`(display.rotation).thenReturn(Surface.ROTATION_0)
+        `when`(display.getSize(any())).thenAnswer {
+            val point = it.arguments[0] as Point
+            point.x = 100
+            point.y = 100
+            Unit
+        }
+
+        scopedProvider.setReadyToHandleTransition(true)
+
+        controller = StatusBarMoveFromCenterAnimationController(scopedProvider, windowManager)
+    }
+
+    @Test
+    fun onTransitionProgressAndFinished_resetsTranslations() {
+        controller.onViewsReady(arrayOf(view))
+
+        progressProvider.onTransitionProgress(0.5f)
+        progressProvider.onTransitionFinished()
+
+        assertThat(view.translationX).isZero()
+    }
+
+    @Test
+    fun onTransitionProgress_updatesTranslations() {
+        controller.onViewsReady(arrayOf(view))
+
+        progressProvider.onTransitionProgress(0.5f)
+
+        assertThat(view.translationX).isNonZero()
+    }
+
+    @Test
+    fun onTransitionProgress_whenDetached_doesNotUpdateTranslations() {
+        controller.onViewsReady(arrayOf(view))
+        controller.onViewDetached()
+
+        progressProvider.onTransitionProgress(0.5f)
+
+        assertThat(view.translationX).isZero()
+    }
+
+    @Test
+    fun detachedAfterProgress_resetsTranslations() {
+        controller.onViewsReady(arrayOf(view))
+        progressProvider.onTransitionProgress(0.5f)
+
+        controller.onViewDetached()
+
+        assertThat(view.translationX).isZero()
+    }
+
+    @Test
+    fun transitionFinished_viewReAttached_noChangesToTranslation() {
+        controller.onViewsReady(arrayOf(view))
+        progressProvider.onTransitionProgress(0.5f)
+        progressProvider.onTransitionFinished()
+        controller.onViewDetached()
+
+        controller.onViewsReady(arrayOf(view))
+        controller.onStatusBarWidthChanged()
+
+        assertThat(view.translationX).isZero()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java
deleted file mode 100644
index b359b9c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import static junit.framework.TestCase.assertTrue;
-
-import static org.junit.Assert.assertFalse;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.content.res.Configuration;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RemoteInputQuickSettingsDisablerTest extends SysuiTestCase {
-
-    @Mock
-    private CommandQueue mCommandQueue;
-    private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mRemoteInputQuickSettingsDisabler = new RemoteInputQuickSettingsDisabler(mContext,
-                mock(ConfigurationController.class), mCommandQueue);
-    }
-
-    @Test
-    public void shouldEnableQuickSetting_afterDeactiviate() {
-        mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.TRUE);
-        mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.FALSE);
-        assertFalse(mRemoteInputQuickSettingsDisabler.mRemoteInputActive);
-        verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
-    }
-
-    @Test
-    public void shouldDisableQuickSetting_afteActiviate() {
-        mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.FALSE);
-        mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.TRUE);
-        assertTrue(mRemoteInputQuickSettingsDisabler.mRemoteInputActive);
-        verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
-    }
-
-    @Test
-    public void testChangeToLandscape() {
-        Configuration c = new Configuration(mContext.getResources().getConfiguration());
-        c.orientation = Configuration.ORIENTATION_PORTRAIT;
-        mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
-        c.orientation = Configuration.ORIENTATION_LANDSCAPE;
-        mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
-        assertTrue(mRemoteInputQuickSettingsDisabler.misLandscape);
-        verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
-    }
-
-    @Test
-    public void testChangeToPortrait() {
-        Configuration c = new Configuration(mContext.getResources().getConfiguration());
-        c.orientation = Configuration.ORIENTATION_LANDSCAPE;
-        mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
-        c.orientation = Configuration.ORIENTATION_PORTRAIT;
-        mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
-        assertFalse(mRemoteInputQuickSettingsDisabler.misLandscape);
-        verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
-    }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
new file mode 100644
index 0000000..1ab0582
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.
+ */
+package com.android.systemui.statusbar.policy
+
+import android.app.StatusBarManager
+import android.content.res.Configuration
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.CommandQueue
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class RemoteInputQuickSettingsDisablerTest : SysuiTestCase() {
+
+    @Mock lateinit var commandQueue: CommandQueue
+    private lateinit var remoteInputQuickSettingsDisabler: RemoteInputQuickSettingsDisabler
+    private lateinit var configuration: Configuration
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        remoteInputQuickSettingsDisabler = RemoteInputQuickSettingsDisabler(
+            mContext,
+            commandQueue, Mockito.mock(ConfigurationController::class.java)
+        )
+        configuration = Configuration(mContext.resources.configuration)
+
+        // Default these conditions to what they need to be to disable QS.
+        mContext.orCreateTestableResources
+            .addOverride(R.bool.config_use_split_notification_shade, /* value= */false)
+        remoteInputQuickSettingsDisabler.setRemoteInputActive(true)
+        configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+        remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+    }
+
+    @Test
+    fun whenRemoteInputActiveAndLandscapeAndNotSplitShade_shouldDisableQs() {
+        assertThat(
+            shouldDisableQs(
+                remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+            )
+        )
+            .isTrue()
+    }
+
+    @Test
+    fun whenRemoteInputNotActive_shouldNotDisableQs() {
+        remoteInputQuickSettingsDisabler.setRemoteInputActive(false)
+
+        assertThat(
+            shouldDisableQs(
+                remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+            )
+        )
+            .isFalse()
+    }
+
+    @Test
+    fun whenSplitShadeEnabled_shouldNotDisableQs() {
+        mContext.orCreateTestableResources
+            .addOverride(R.bool.config_use_split_notification_shade, /* value= */true)
+        remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+
+        assertThat(
+            shouldDisableQs(
+                remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+            )
+        )
+            .isFalse()
+    }
+
+    @Test
+    fun whenPortrait_shouldNotDisableQs() {
+        configuration.orientation = Configuration.ORIENTATION_PORTRAIT
+        remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+
+        assertThat(
+            shouldDisableQs(
+                remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+            )
+        )
+            .isFalse()
+    }
+
+    @Test
+    fun whenRemoteInputChanges_recomputeTriggered() {
+        remoteInputQuickSettingsDisabler.setRemoteInputActive(false)
+
+        verify(commandQueue, atLeastOnce()).recomputeDisableFlags(
+            anyInt(), anyBoolean()
+        )
+    }
+
+    @Test
+    fun whenConfigChanges_recomputeTriggered() {
+        configuration.orientation = Configuration.ORIENTATION_PORTRAIT
+        remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+
+        verify(commandQueue, atLeastOnce()).recomputeDisableFlags(
+            anyInt(), anyBoolean()
+        )
+    }
+
+    private fun shouldDisableQs(state: Int): Boolean {
+        return state and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
new file mode 100644
index 0000000..db7a8516
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.unfold.util
+
+import android.animation.ValueAnimator
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.util.mockito.any
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
+
+    @Mock
+    lateinit var contentResolver: ContentResolver
+
+    @Mock
+    lateinit var sourceProvider: UnfoldTransitionProgressProvider
+
+    @Mock
+    lateinit var sinkProvider: TransitionProgressListener
+
+    lateinit var progressProvider: ScaleAwareTransitionProgressProvider
+
+    private val sourceProviderListenerCaptor =
+            ArgumentCaptor.forClass(TransitionProgressListener::class.java)
+
+    private val animatorDurationScaleListenerCaptor =
+            ArgumentCaptor.forClass(ContentObserver::class.java)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        progressProvider = ScaleAwareTransitionProgressProvider(
+                sourceProvider,
+                contentResolver
+        )
+
+        verify(sourceProvider).addCallback(sourceProviderListenerCaptor.capture())
+        verify(contentResolver).registerContentObserver(any(), any(),
+                animatorDurationScaleListenerCaptor.capture())
+
+        progressProvider.addCallback(sinkProvider)
+    }
+
+    @Test
+    fun onTransitionStarted_animationsEnabled_eventReceived() {
+        setAnimationsEnabled(true)
+
+        source.onTransitionStarted()
+
+        verify(sinkProvider).onTransitionStarted()
+    }
+
+    @Test
+    fun onTransitionStarted_animationsNotEnabled_eventNotReceived() {
+        setAnimationsEnabled(false)
+
+        source.onTransitionStarted()
+
+        verifyNoMoreInteractions(sinkProvider)
+    }
+
+    @Test
+    fun onTransitionEnd_animationsEnabled_eventReceived() {
+        setAnimationsEnabled(true)
+
+        source.onTransitionFinished()
+
+        verify(sinkProvider).onTransitionFinished()
+    }
+
+    @Test
+    fun onTransitionEnd_animationsNotEnabled_eventNotReceived() {
+        setAnimationsEnabled(false)
+
+        source.onTransitionFinished()
+
+        verifyNoMoreInteractions(sinkProvider)
+    }
+
+    @Test
+    fun onTransitionProgress_animationsEnabled_eventReceived() {
+        setAnimationsEnabled(true)
+
+        source.onTransitionProgress(42f)
+
+        verify(sinkProvider).onTransitionProgress(42f)
+    }
+
+    @Test
+    fun onTransitionProgress_animationsNotEnabled_eventNotReceived() {
+        setAnimationsEnabled(false)
+
+        source.onTransitionProgress(42f)
+
+        verifyNoMoreInteractions(sinkProvider)
+    }
+
+    private fun setAnimationsEnabled(enabled: Boolean) {
+        val durationScale = if (enabled) {
+            1f
+        } else {
+            0f
+        }
+        ValueAnimator.setDurationScale(durationScale)
+        animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false)
+    }
+
+    private val source: TransitionProgressListener
+        get() = sourceProviderListenerCaptor.value
+}
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 dda1c4f..cd338a4 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -16,9 +16,9 @@
 
 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.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_DEFAULT;
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
 
@@ -66,14 +66,14 @@
     public @NonNull MagnificationConfig getMagnificationConfig(int displayId) {
         final int mode = getControllingMode(displayId);
         MagnificationConfig.Builder builder = new MagnificationConfig.Builder();
-        if (mode == FULLSCREEN_MODE) {
+        if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
             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) {
+        } else if (mode == MAGNIFICATION_MODE_WINDOW) {
             final WindowMagnificationManager windowMagnificationManager =
                     mController.getWindowMagnificationMgr();
             builder.setMode(mode)
@@ -103,14 +103,14 @@
         }
 
         int configMode = config.getMode();
-        if (configMode == DEFAULT_MODE) {
+        if (configMode == MAGNIFICATION_MODE_DEFAULT) {
             configMode = getControllingMode(displayId);
         }
-        if (configMode == FULLSCREEN_MODE) {
+        if (configMode == MAGNIFICATION_MODE_FULLSCREEN) {
             return setScaleAndCenterForFullScreenMagnification(displayId, config.getScale(),
                     config.getCenterX(), config.getCenterY(),
                     animate, id);
-        } else if (configMode == WINDOW_MODE) {
+        } else if (configMode == MAGNIFICATION_MODE_WINDOW) {
             return mController.getWindowMagnificationMgr().enableWindowMagnification(displayId,
                     config.getScale(), config.getCenterX(), config.getCenterY());
         }
@@ -141,9 +141,9 @@
      */
     public float getScale(int displayId) {
         int mode = getControllingMode(displayId);
-        if (mode == FULLSCREEN_MODE) {
+        if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
             return mController.getFullScreenMagnificationController().getScale(displayId);
-        } else if (mode == WINDOW_MODE) {
+        } else if (mode == MAGNIFICATION_MODE_WINDOW) {
             return mController.getWindowMagnificationMgr().getScale(displayId);
         }
         return 0;
@@ -161,7 +161,7 @@
      */
     public float getCenterX(int displayId, boolean canControlMagnification) {
         int mode = getControllingMode(displayId);
-        if (mode == FULLSCREEN_MODE) {
+        if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
             boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
                     canControlMagnification);
             try {
@@ -171,7 +171,7 @@
                     unregister(displayId);
                 }
             }
-        } else if (mode == WINDOW_MODE) {
+        } else if (mode == MAGNIFICATION_MODE_WINDOW) {
             return mController.getWindowMagnificationMgr().getCenterX(displayId);
         }
         return 0;
@@ -189,7 +189,7 @@
      */
     public float getCenterY(int displayId, boolean canControlMagnification) {
         int mode = getControllingMode(displayId);
-        if (mode == FULLSCREEN_MODE) {
+        if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
             boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
                     canControlMagnification);
             try {
@@ -199,7 +199,7 @@
                     unregister(displayId);
                 }
             }
-        } else if (mode == WINDOW_MODE) {
+        } else if (mode == MAGNIFICATION_MODE_WINDOW) {
             return mController.getWindowMagnificationMgr().getCenterY(displayId);
         }
         return 0;
@@ -221,9 +221,9 @@
     public Region getMagnificationRegion(int displayId, @NonNull Region outRegion,
             boolean canControlMagnification) {
         int mode = getControllingMode(displayId);
-        if (mode == FULLSCREEN_MODE) {
+        if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
             getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification);
-        } else if (mode == WINDOW_MODE) {
+        } else if (mode == MAGNIFICATION_MODE_WINDOW) {
             mController.getWindowMagnificationMgr().getMagnificationSourceBounds(displayId,
                     outRegion);
         }
@@ -268,9 +268,9 @@
      */
     public boolean reset(int displayId, boolean animate) {
         int mode = getControllingMode(displayId);
-        if (mode == FULLSCREEN_MODE) {
+        if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
             return mController.getFullScreenMagnificationController().reset(displayId, animate);
-        } else if (mode == WINDOW_MODE) {
+        } else if (mode == MAGNIFICATION_MODE_WINDOW) {
             return mController.getWindowMagnificationMgr().reset(displayId);
         }
         return false;
@@ -290,9 +290,9 @@
      */
     public boolean isMagnifying(int displayId) {
         int mode = getControllingMode(displayId);
-        if (mode == FULLSCREEN_MODE) {
+        if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
             return mController.getFullScreenMagnificationController().isMagnifying(displayId);
-        } else if (mode == WINDOW_MODE) {
+        } else if (mode == MAGNIFICATION_MODE_WINDOW) {
             return mController.getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId);
         }
         return false;
@@ -308,14 +308,14 @@
     public int getControllingMode(int displayId) {
         if (mController.isActivated(displayId,
                 ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) {
-            return WINDOW_MODE;
+            return MAGNIFICATION_MODE_WINDOW;
         } else if (mController.isActivated(displayId,
                 ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)) {
-            return FULLSCREEN_MODE;
+            return MAGNIFICATION_MODE_FULLSCREEN;
         } else {
             return (mController.getLastActivatedMode() == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)
-                    ? WINDOW_MODE
-                    : FULLSCREEN_MODE;
+                    ? MAGNIFICATION_MODE_WINDOW
+                    : MAGNIFICATION_MODE_FULLSCREEN;
         }
     }
 
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 049018cf..e0cd472 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -406,9 +406,8 @@
 
         @Override
         public List<AssociationInfo> getAssociations(String packageName, int userId) {
-            final int callingUid = getCallingUserId();
             if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) {
-                throw new SecurityException("Caller (uid=" + callingUid + ") does not have "
+                throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
                         + "permissions to get associations for u" + userId + "/" + packageName);
             }
 
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
index 0d38fa3..45097f0 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -154,14 +154,14 @@
     }
 
     static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) {
-        if (getCallingUserId() == SYSTEM_UID) return true;
+        if (getCallingUid() == SYSTEM_UID) return true;
 
         return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
     }
 
     static void enforceCallerCanManagerCompanionDevice(@NonNull Context context,
             @Nullable String message) {
-        if (getCallingUserId() == SYSTEM_UID) return;
+        if (getCallingUid() == SYSTEM_UID) return;
 
         context.enforceCallingPermission(MANAGE_COMPANION_DEVICES, message);
     }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9351415..ba6854b 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -121,6 +121,7 @@
         "java/com/android/server/am/EventLogTags.logtags",
         "java/com/android/server/wm/EventLogTags.logtags",
         "java/com/android/server/policy/EventLogTags.logtags",
+        ":services.connectivity-nsd-sources",
     ],
 
     libs: [
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 1929df8..c7f4b4d 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1598,6 +1598,8 @@
         } else if (vol.type == VolumeInfo.TYPE_STUB) {
             if (vol.disk.isStubVisible()) {
                 vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE;
+            } else {
+                vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_READ;
             }
             vol.mountUserId = mCurrentUserId;
             mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
@@ -3842,14 +3844,15 @@
             final boolean primary = false;
             final boolean removable = false;
             final boolean emulated = true;
+            final boolean stub = false;
             final boolean allowMassStorage = false;
             final long maxFileSize = 0;
             final UserHandle user = new UserHandle(userId);
             final String envState = Environment.MEDIA_MOUNTED_READ_ONLY;
             final String description = mContext.getString(android.R.string.unknownName);
 
-            res.add(new StorageVolume(id, path, path, description, primary, removable,
-                    emulated, allowMassStorage, maxFileSize, user, null /*uuid */, id, envState));
+            res.add(new StorageVolume(id, path, path, description, primary, removable, emulated,
+                    stub, allowMassStorage, maxFileSize, user, null /*uuid */, id, envState));
         }
 
         if (!foundPrimary) {
@@ -3864,6 +3867,7 @@
             final boolean primary = true;
             final boolean removable = primaryPhysical;
             final boolean emulated = !primaryPhysical;
+            final boolean stub = false;
             final boolean allowMassStorage = false;
             final long maxFileSize = 0L;
             final UserHandle owner = new UserHandle(userId);
@@ -3872,7 +3876,7 @@
             final String state = Environment.MEDIA_REMOVED;
 
             res.add(0, new StorageVolume(id, path, path,
-                    description, primary, removable, emulated,
+                    description, primary, removable, emulated, stub,
                     allowMassStorage, maxFileSize, owner, uuid, fsUuid, state));
         }
 
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 03a4d84..5fc11e8 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -130,6 +130,7 @@
 
     private static IBatteryStats sService;
 
+    private final PowerProfile mPowerProfile;
     final BatteryStatsImpl mStats;
     private final BatteryUsageStatsStore mBatteryUsageStatsStore;
     private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider;
@@ -343,13 +344,15 @@
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
 
+        mPowerProfile = new PowerProfile(context);
+
         mStats = new BatteryStatsImpl(systemDir, handler, this,
                 this, mUserManagerUserInfoProvider);
         mWorker = new BatteryExternalStatsWorker(context, mStats);
         mStats.setExternalStatsSyncLocked(mWorker);
         mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
-        mStats.setPowerProfileLocked(new PowerProfile(context));
+        mStats.setPowerProfileLocked(mPowerProfile);
         mStats.startTrackingSystemServerCpuTime();
 
         if (BATTERY_USAGE_STORE_ENABLED) {
@@ -2464,6 +2467,7 @@
                                 BatteryStatsImpl checkinStats = new BatteryStatsImpl(
                                         null, mStats.mHandler, null, null,
                                         mUserManagerUserInfoProvider);
+                                checkinStats.setPowerProfileLocked(mPowerProfile);
                                 checkinStats.readSummaryFromParcel(in);
                                 in.recycle();
                                 checkinStats.dumpProtoLocked(
@@ -2504,6 +2508,7 @@
                                 BatteryStatsImpl checkinStats = new BatteryStatsImpl(
                                         null, mStats.mHandler, null, null,
                                         mUserManagerUserInfoProvider);
+                                checkinStats.setPowerProfileLocked(mPowerProfile);
                                 checkinStats.readSummaryFromParcel(in);
                                 in.recycle();
                                 checkinStats.dumpCheckinLocked(mContext, pw, apps, flags,
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 47e24b1..5a54332 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1500,7 +1500,7 @@
         int schedGroup;
         int procState;
         int cachedAdjSeq;
-        int capability = 0;
+        int capability = cycleReEval ? app.mState.getCurCapability() : 0;
 
         boolean foregroundActivities = false;
         boolean hasVisibleActivities = false;
@@ -1891,10 +1891,6 @@
                     }
 
                     if ((cr.flags & Context.BIND_WAIVE_PRIORITY) == 0) {
-                        if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
-                            continue;
-                        }
-
                         if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
                             capability |= cstate.getCurCapability();
                         }
@@ -1915,6 +1911,10 @@
                             }
                         }
 
+                        if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
+                            continue;
+                        }
+
                         if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
                             // If the other app is cached for any reason, for purposes here
                             // we are going to consider it empty.  The specific cached state
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 6a26bea..b47ea4f 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -551,9 +551,9 @@
                 logd("canBeSpatialized false due to usage:" + attributes.getUsage());
                 return false;
         }
-        AudioDeviceAttributes[] devices =
-                // going through adapter to take advantage of routing cache
-                (AudioDeviceAttributes[]) mASA.getDevicesForAttributes(attributes).toArray();
+        AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1];
+        // going through adapter to take advantage of routing cache
+        mASA.getDevicesForAttributes(attributes).toArray(devices);
         final boolean able = AudioSystem.canBeSpatialized(attributes, format, devices);
         logd("canBeSpatialized returning " + able);
         return able;
diff --git a/services/core/java/com/android/server/audio/TEST_MAPPING b/services/core/java/com/android/server/audio/TEST_MAPPING
index 90246f8..5a6c6a5 100644
--- a/services/core/java/com/android/server/audio/TEST_MAPPING
+++ b/services/core/java/com/android/server/audio/TEST_MAPPING
@@ -1,13 +1,13 @@
 {
     "presubmit-large": [
         {
-            "name": "CtsMediaTestCases",
+            "name": "CtsMediaAudioTestCases",
             "options": [
                 {
-                    "include-filter": "android.media.cts.AudioManagerTest"
+                    "include-filter": "android.media.audio.cts.AudioManagerTest"
                 },
                 {
-                    "include-filter": "android.media.cts.AudioFocusTest"
+                    "include-filter": "android.media.audio.cts.AudioFocusTest"
                 }
             ]
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 9499e51..3c6b096 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2750,7 +2750,7 @@
                 return;
             }
             final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken);
-            if (targetWindow != null && mLastImeTargetWindow != targetWindow) {
+            if (targetWindow != null) {
                 mWindowManagerInternal.updateInputMethodTargetWindow(token, targetWindow);
             }
             mLastImeTargetWindow = targetWindow;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 99fdb2d..f65deeb 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5073,7 +5073,7 @@
             final DumpFilter filter = DumpFilter.parseFromArguments(args);
             final long token = Binder.clearCallingIdentity();
             try {
-                final ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions =
+                final ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions =
                         getAllUsersNotificationPermissions();
                 if (filter.stats) {
                     dumpJson(pw, filter, pkgPermissions);
@@ -5911,17 +5911,18 @@
     // Returns a single map containing that info keyed by (uid, package name) for all users.
     // Because this calls into mPermissionHelper, this method must never be called with a lock held.
     @VisibleForTesting
-    protected ArrayMap<Pair<Integer, String>, Boolean> getAllUsersNotificationPermissions() {
+    protected ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>>
+            getAllUsersNotificationPermissions() {
         // don't bother if migration is not enabled
         if (!mEnableAppSettingMigration) {
             return null;
         }
-        ArrayMap<Pair<Integer, String>, Boolean> allPermissions = new ArrayMap<>();
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> allPermissions = new ArrayMap<>();
         final List<UserInfo> allUsers = mUm.getUsers();
         // for each of these, get the package notification permissions that are associated
         // with this user and add it to the map
         for (UserInfo ui : allUsers) {
-            ArrayMap<Pair<Integer, String>, Boolean> userPermissions =
+            ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> userPermissions =
                     mPermissionHelper.getNotificationPermissionValues(
                             ui.getUserHandle().getIdentifier());
             for (Pair<Integer, String> pair : userPermissions.keySet()) {
@@ -5932,7 +5933,7 @@
     }
 
     private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter,
-            ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+            ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
         JSONObject dump = new JSONObject();
         try {
             dump.put("service", "Notification Manager");
@@ -5956,7 +5957,7 @@
     }
 
     private void dumpProto(FileDescriptor fd, @NonNull DumpFilter filter,
-            ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+            ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
         final ProtoOutputStream proto = new ProtoOutputStream(fd);
         synchronized (mNotificationLock) {
             int N = mNotificationList.size();
@@ -6047,7 +6048,7 @@
     }
 
     void dumpImpl(PrintWriter pw, @NonNull DumpFilter filter,
-            ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+            ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
         pw.print("Current Notification Manager state");
         if (filter.filtered) {
             pw.print(" (filtered to "); pw.print(filter); pw.print(")");
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index f53bb75..e64ec77 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -36,10 +36,8 @@
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -139,15 +137,17 @@
         return granted;
     }
 
+    // Key: (uid, package name); Value: (granted, user set)
     public @NonNull
-    ArrayMap<Pair<Integer, String>, Boolean> getNotificationPermissionValues(
-            int userId) {
+            ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>>
+                    getNotificationPermissionValues(int userId) {
         assertFlag();
-        ArrayMap<Pair<Integer, String>, Boolean> notifPermissions = new ArrayMap<>();
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions = new ArrayMap<>();
         Set<Pair<Integer, String>> allRequestingUids = getAppsRequestingPermission(userId);
         Set<Pair<Integer, String>> allApprovedUids = getAppsGrantedPermission(userId);
         for (Pair<Integer, String> pair : allRequestingUids) {
-            notifPermissions.put(pair, allApprovedUids.contains(pair));
+            notifPermissions.put(pair, new Pair(allApprovedUids.contains(pair),
+                    isPermissionUserSet(pair.second /* package name */, userId)));
         }
         return notifPermissions;
     }
@@ -196,6 +196,18 @@
         return false;
     }
 
+    boolean isPermissionUserSet(String packageName, @UserIdInt int userId) {
+        assertFlag();
+        try {
+            int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
+                    userId);
+            return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Could not reach system server", e);
+        }
+        return false;
+    }
+
     private void assertFlag() {
         if (!mMigrationEnabled) {
             throw new IllegalStateException("Method called without checking flag value");
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 4e1279c..49e223c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -557,7 +557,7 @@
             out.attributeBoolean(null, ATT_HIDE_SILENT, mHideSilentStatusBarIcons);
             out.endTag(null, TAG_STATUS_ICONS);
         }
-        ArrayMap<Pair<Integer, String>, Boolean> notifPermissions = new ArrayMap<>();
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions = new ArrayMap<>();
         if (mPermissionHelper.isMigrationEnabled() && forBackup) {
             notifPermissions = mPermissionHelper.getNotificationPermissionValues(userId);
         }
@@ -573,9 +573,8 @@
                 out.attribute(null, ATT_NAME, r.pkg);
                 if (!notifPermissions.isEmpty()) {
                     Pair<Integer, String> app = new Pair(r.uid, r.pkg);
-                    out.attributeInt(null, ATT_IMPORTANCE, notifPermissions.get(app)
-                            ? IMPORTANCE_DEFAULT
-                            : IMPORTANCE_NONE);
+                    out.attributeInt(null, ATT_IMPORTANCE,
+                            notifPermissions.get(app).first ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
                     notifPermissions.remove(app);
                 } else {
                     if (r.importance != DEFAULT_IMPORTANCE) {
@@ -642,7 +641,7 @@
                 out.startTag(null, TAG_PACKAGE);
                 out.attribute(null, ATT_NAME, app.second);
                 out.attributeInt(null, ATT_IMPORTANCE,
-                        notifPermissions.get(app) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
+                        notifPermissions.get(app).first ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
                 out.endTag(null, TAG_PACKAGE);
             }
         }
@@ -1932,7 +1931,7 @@
 
     public void dump(PrintWriter pw, String prefix,
             @NonNull NotificationManagerService.DumpFilter filter,
-            ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+            ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
         pw.print(prefix);
         pw.println("per-package config version: " + XML_VERSION);
 
@@ -1946,7 +1945,7 @@
 
     public void dump(ProtoOutputStream proto,
             @NonNull NotificationManagerService.DumpFilter filter,
-            ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+            ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
         synchronized (mPackagePreferences) {
             dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS, filter,
                     mPackagePreferences, pkgPermissions);
@@ -1958,7 +1957,7 @@
     private void dumpPackagePreferencesLocked(PrintWriter pw, String prefix,
             @NonNull NotificationManagerService.DumpFilter filter,
             ArrayMap<String, PackagePreferences> packagePreferences,
-            ArrayMap<Pair<Integer, String>, Boolean> packagePermissions) {
+            ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> packagePermissions) {
         // Used for tracking which package preferences we've seen already for notification
         // permission reasons; after handling packages with local preferences, we'll want to dump
         // the ones with notification permissions set but not local prefs.
@@ -1987,8 +1986,10 @@
                     if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
                         pw.print(" importance=");
                         pw.print(NotificationListenerService.Ranking.importanceToString(
-                                packagePermissions.get(key)
+                                packagePermissions.get(key).first
                                         ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
+                        pw.print(" userSet=");
+                        pw.print(packagePermissions.get(key).second);
                         pkgsWithPermissionsToHandle.remove(key);
                     }
                 }
@@ -2042,7 +2043,10 @@
                     pw.print(')');
                     pw.print(" importance=");
                     pw.print(NotificationListenerService.Ranking.importanceToString(
-                            packagePermissions.get(p) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
+                            packagePermissions.get(p).first
+                                    ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
+                    pw.print(" userSet=");
+                    pw.print(packagePermissions.get(p).second);
                     pw.println();
                 }
             }
@@ -2052,7 +2056,7 @@
     private void dumpPackagePreferencesLocked(ProtoOutputStream proto, long fieldId,
             @NonNull NotificationManagerService.DumpFilter filter,
             ArrayMap<String, PackagePreferences> packagePreferences,
-            ArrayMap<Pair<Integer, String>, Boolean> packagePermissions) {
+            ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> packagePermissions) {
         Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null;
         if (packagePermissions != null) {
             pkgsWithPermissionsToHandle = packagePermissions.keySet();
@@ -2071,7 +2075,8 @@
                     Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
                     if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
                         proto.write(RankingHelperProto.RecordProto.IMPORTANCE,
-                                packagePermissions.get(key) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
+                                packagePermissions.get(key).first
+                                        ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
                         pkgsWithPermissionsToHandle.remove(key);
                     }
                 } else {
@@ -2099,7 +2104,8 @@
                     proto.write(RankingHelperProto.RecordProto.PACKAGE, p.second);
                     proto.write(RankingHelperProto.RecordProto.UID, p.first);
                     proto.write(RankingHelperProto.RecordProto.IMPORTANCE,
-                            packagePermissions.get(p) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
+                            packagePermissions.get(p).first
+                                    ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
                     proto.end(fToken);
                 }
             }
@@ -2110,7 +2116,7 @@
      * Fills out {@link PackageNotificationPreferences} proto and wraps it in a {@link StatsEvent}.
      */
     public void pullPackagePreferencesStats(List<StatsEvent> events,
-            ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+            ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
         Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null;
         if (pkgPermissions != null) {
             pkgsWithPermissionsToHandle = pkgPermissions.keySet();
@@ -2134,7 +2140,8 @@
                     int importance = IMPORTANCE_NONE;
                     Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
                     if (pkgPermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
-                        importance = pkgPermissions.get(key) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE;
+                        importance = pkgPermissions.get(key).first
+                                ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE;
                         pkgsWithPermissionsToHandle.remove(key);
                     }
                     event.writeInt(importance);
@@ -2158,7 +2165,7 @@
                         .setAtomId(PACKAGE_NOTIFICATION_PREFERENCES);
                 event.writeInt(p.first);
                 event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
-                event.writeInt(pkgPermissions.get(p) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
+                event.writeInt(pkgPermissions.get(p).first ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
 
                 // fill out the rest of the fields with default values so as not to confuse the
                 // builder
@@ -2236,7 +2243,7 @@
     }
 
     public JSONObject dumpJson(NotificationManagerService.DumpFilter filter,
-            ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+            ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
         JSONObject ranking = new JSONObject();
         JSONArray PackagePreferencess = new JSONArray();
         try {
@@ -2266,7 +2273,7 @@
                                     && pkgsWithPermissionsToHandle.contains(key)) {
                                 PackagePreferences.put("importance",
                                         NotificationListenerService.Ranking.importanceToString(
-                                                pkgPermissions.get(key)
+                                                pkgPermissions.get(key).first
                                                         ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
                                 pkgsWithPermissionsToHandle.remove(key);
                             }
@@ -2316,7 +2323,7 @@
                         PackagePreferences.put("packageName", p.second);
                         PackagePreferences.put("importance",
                                 NotificationListenerService.Ranking.importanceToString(
-                                        pkgPermissions.get(p)
+                                        pkgPermissions.get(p).first
                                                 ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
                     } catch (JSONException e) {
                         // pass
@@ -2344,7 +2351,7 @@
      * @return
      */
     public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter,
-            ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+            ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
         JSONArray bans = new JSONArray();
         Map<Integer, String> packageBans = mPermissionHelper.isMigrationEnabled()
                 ? getPermissionBasedPackageBans(pkgPermissions) : getPackageBans();
@@ -2383,11 +2390,11 @@
     // Same functionality as getPackageBans by extracting the set of packages from the provided
     // map that are disallowed from sending notifications.
     protected Map<Integer, String> getPermissionBasedPackageBans(
-            ArrayMap<Pair<Integer, String>, Boolean> pkgPermissions) {
+            ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
         ArrayMap<Integer, String> packageBans = new ArrayMap<>();
         if (pkgPermissions != null) {
             for (Pair<Integer, String> p : pkgPermissions.keySet()) {
-                if (!pkgPermissions.get(p)) {
+                if (!pkgPermissions.get(p).first) {
                     packageBans.put(p.first, p.second);
                 }
             }
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 37cb8a9..fb77d10 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -1081,6 +1081,10 @@
             } catch (RemoteException e) {
                 throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
                         "apexservice not available");
+            } catch (PackageManagerException e) {
+                // Catching it in order not to fall back to Exception which rethrows the
+                // PackageManagerException with a common error code.
+                throw e;
             } catch (Exception e) {
                 // TODO(b/187864524): is INSTALL_FAILED_INTERNAL_ERROR is the right error code here?
                 throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index b605c93..9b21790 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2347,7 +2347,7 @@
     @Override
     public final boolean filterSharedLibPackage(@Nullable PackageStateInternal ps, int uid,
             int userId, @PackageManager.ComponentInfoFlags long flags) {
-        return filterStaticSharedLibPackage(ps, uid, userId, flags) && filterSdkLibPackage(ps, uid,
+        return filterStaticSharedLibPackage(ps, uid, userId, flags) || filterSdkLibPackage(ps, uid,
                 userId, flags);
     }
 
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 641f24f..e8922eb 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -251,20 +251,29 @@
 
             if (priorUserStates != null) {
                 synchronized (mPm.mLock) {
-                    for (int i = 0; i < allUsers.length; i++) {
-                        TempUserState priorUserState = priorUserStates.get(allUsers[i]);
-                        int enabledState = priorUserState.enabledState;
-                        PackageSetting pkgSetting = mPm.getPackageSettingForMutation(packageName);
-                        pkgSetting.setEnabled(enabledState, allUsers[i],
-                                priorUserState.lastDisableAppCaller);
-
+                    PackageSetting pkgSetting = mPm.getPackageSettingForMutation(packageName);
+                    if (pkgSetting != null) {
                         AndroidPackage aPkg = pkgSetting.getPkg();
                         boolean pkgEnabled = aPkg != null && aPkg.isEnabled();
-                        if (!reEnableStub && priorUserState.installed
-                                && ((enabledState == COMPONENT_ENABLED_STATE_DEFAULT && pkgEnabled)
-                                || enabledState == COMPONENT_ENABLED_STATE_ENABLED)) {
-                            reEnableStub = true;
+                        for (int i = 0; i < allUsers.length; i++) {
+                            TempUserState priorUserState = priorUserStates.get(allUsers[i]);
+                            int enabledState = priorUserState.enabledState;
+                            pkgSetting.setEnabled(enabledState, allUsers[i],
+                                    priorUserState.lastDisableAppCaller);
+                            if (!reEnableStub && priorUserState.installed
+                                    && (
+                                    (enabledState == COMPONENT_ENABLED_STATE_DEFAULT && pkgEnabled)
+                                            || enabledState == COMPONENT_ENABLED_STATE_ENABLED)) {
+                                reEnableStub = true;
+                            }
                         }
+                    } else {
+                        // This should not happen. If priorUserStates != null, we are uninstalling
+                        // an update of a system app. In that case, mPm.mSettings.getPackageLpr()
+                        // should return a non-null value for the target packageName because
+                        // restoreDisabledSystemPackageLIF() is called during deletePackageLIF().
+                        Slog.w(TAG, "Missing PackageSetting after uninstalling the update for"
+                                + " system app: " + packageName + ". This should not happen.");
                     }
                     mPm.mSettings.writeAllUsersPackageRestrictionsLPr();
                 }
diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
index 509702f..dfa6c66 100644
--- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
@@ -126,13 +126,13 @@
         return null;
     }
 
-    public OverlayConfig setUpSystemPackages(
+    public OverlayConfig initPackages(
             WatchedArrayMap<String, PackageSetting> packageSettings, int[] userIds,
             long startTime) {
         PackageParser2 packageParser = mPm.mInjector.getScanningCachingPackageParser();
 
         ExecutorService executorService = ParallelPackageParser.makeExecutorService();
-        // Prepare apex package info before scanning APKs, these information are needed when
+        // Prepare apex package info before scanning APKs, this information is needed when
         // scanning apk in apex.
         mPm.mApexManager.scanApexPackagesTraced(packageParser, executorService);
 
@@ -289,7 +289,7 @@
             long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
         try {
-            mInstallPackageHelper.installSystemPackagesFromDir(scanDir, parseFlags, scanFlags,
+            mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags, scanFlags,
                     currentTime, packageParser, executorService);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index d2087ee..80c2a7e 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3720,7 +3720,7 @@
     }
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
-    public void installSystemPackagesFromDir(File scanDir, int parseFlags, int scanFlags,
+    public void installPackagesFromDir(File scanDir, int parseFlags, int scanFlags,
             long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
         final File[] files = scanDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 28204ea..55a0c96 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -706,10 +706,9 @@
                     if (mCommitted.get()) {
                         mStagingManager.abortCommittedSession(this);
                     }
-                    destroy();
+                    destroyInternal();
                     dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
-                    maybeFinishChildSessions(INSTALL_FAILED_ABORTED,
-                            "Session was abandoned because the parent session is abandoned");
+                    maybeCleanUpChildSessions();
                 };
                 if (mStageDirInUse) {
                     // Pre-reboot verification is ongoing, not safe to clean up the session yet.
@@ -2132,7 +2131,6 @@
             destroy();
             // Dispatch message to remove session from PackageInstallerService.
             dispatchSessionFinished(error, msg, null);
-            maybeFinishChildSessions(error, msg);
         }
     }
 
@@ -3648,19 +3646,20 @@
             throw new SecurityException("Must be sealed to accept permissions");
         }
 
-        PackageInstallerSession root = hasParentSessionId()
-                ? mSessionProvider.getSession(getParentSessionId()) : this;
-
         if (accepted) {
             // Mark and kick off another install pass
             synchronized (mLock) {
                 mPermissionsManuallyAccepted = true;
             }
+
+            PackageInstallerSession root =
+                    (hasParentSessionId())
+                            ? mSessionProvider.getSession(getParentSessionId())
+                            : this;
             root.mHandler.obtainMessage(MSG_INSTALL).sendToTarget();
         } else {
-            root.destroy();
-            root.dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
-            root.maybeFinishChildSessions(INSTALL_FAILED_ABORTED, "User rejected permissions");
+            destroyInternal();
+            dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
         }
     }
 
@@ -3711,12 +3710,27 @@
     }
 
     /**
-     * Calls dispatchSessionFinished() on all child sessions with the given error code and
-     * error message to prevent orphaned child sessions.
+     * Cleans up the relevant stored files and information of all child sessions.
+     * <p>Cleaning up the stored files and session information is necessary for
+     * preventing the orphan children sessions.
+     * <ol>
+     *     <li>To call {@link #destroyInternal()} cleans up the stored files.</li>
+     *     <li>To call {@link #dispatchSessionFinished(int, String, Bundle)} to trigger the
+     *     procedure to clean up the information in PackageInstallerService.</li>
+     * </ol></p>
      */
-    private void maybeFinishChildSessions(int returnCode, String msg) {
-        for (PackageInstallerSession child : getChildSessions()) {
-            child.dispatchSessionFinished(returnCode, msg, null);
+    private void maybeCleanUpChildSessions() {
+        if (!isMultiPackage()) {
+            return;
+        }
+
+        final List<PackageInstallerSession> childSessions = getChildSessions();
+        final int size = childSessions.size();
+        for (int i = 0; i < size; ++i) {
+            final PackageInstallerSession session = childSessions.get(i);
+            session.destroyInternal();
+            session.dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned"
+                            + " because the parent session is abandoned", null);
         }
     }
 
@@ -3728,12 +3742,10 @@
                 if (LOGD) Slog.d(TAG, "Ignoring abandon for staging files are in use");
                 return;
             }
-            mDestroyed = true;
+            destroyInternal();
         }
-        destroy();
         dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
-        maybeFinishChildSessions(INSTALL_FAILED_ABORTED,
-                "Session was abandoned because the parent session is abandoned");
+        maybeCleanUpChildSessions();
     }
 
     private void assertNotChild(String cookie) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index fe5bce13..0c78ab8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1975,7 +1975,7 @@
                     mIsEngBuild, mIsUserDebugBuild, mIncrementalVersion);
 
             final int[] userIds = mUserManager.getUserIds();
-            mOverlayConfig = mInitAndSystemPackageHelper.setUpSystemPackages(packageSettings,
+            mOverlayConfig = mInitAndSystemPackageHelper.initPackages(packageSettings,
                     userIds, startTime);
 
             // Resolve the storage manager.
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index b89017ed..0564e85 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -878,6 +878,9 @@
         if (userId == UserHandle.USER_ALL) {
             getFlags |= PackageManager.MATCH_KNOWN_PACKAGES;
         }
+        if (showSdks) {
+            getFlags |= PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES;
+        }
         final int translatedUserId =
                 translateUserId(userId, UserHandle.USER_SYSTEM, "runListPackages");
         @SuppressWarnings("unchecked")
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index 75f3725..041c4fe 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -431,6 +431,8 @@
                 }
             }
         }
+        boolean wasNonInternal = permission != null && permission.mType != TYPE_CONFIG
+                && !permission.isInternal();
         boolean wasNonRuntime = permission != null && permission.mType != TYPE_CONFIG
                 && !permission.isRuntime();
         if (permission == null) {
@@ -476,10 +478,10 @@
             r.append("DUP:");
             r.append(permissionInfo.name);
         }
-        if ((permission.isInternal() && ownerChanged)
+        if ((permission.isInternal() && (ownerChanged || wasNonInternal))
                 || (permission.isRuntime() && (ownerChanged || wasNonRuntime))) {
             // If this is an internal/runtime permission and the owner has changed, or this wasn't a
-            // runtime permission, then permission state should be cleaned up.
+            // internal/runtime permission, then permission state should be cleaned up.
             permission.mDefinitionChanged = true;
         }
         if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5c15a84..298f102 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -523,6 +523,7 @@
     private boolean mPendingKeyguardOccluded;
     private boolean mKeyguardOccludedChanged;
 
+    private ActivityTaskManagerInternal.SleepTokenAcquirer mScreenOffSleepTokenAcquirer;
     Intent mHomeIntent;
     Intent mCarDockIntent;
     Intent mDeskDockIntent;
@@ -1852,6 +1853,9 @@
                 new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
         mLogger = new MetricsLogger();
 
+        mScreenOffSleepTokenAcquirer = mActivityTaskManagerInternal
+                .createSleepTokenAcquirer("ScreenOff");
+
         Resources res = mContext.getResources();
         mWakeOnDpadKeyPress =
                 res.getBoolean(com.android.internal.R.bool.config_wakeOnDpadKeyPress);
@@ -4521,6 +4525,7 @@
         if (DEBUG_WAKEUP) Slog.i(TAG, "Display" + displayId + " turned off...");
 
         if (displayId == DEFAULT_DISPLAY) {
+            updateScreenOffSleepToken(true);
             mRequestedOrSleepingDefaultDisplay = false;
             mDefaultDisplayPolicy.screenTurnedOff();
             synchronized (mLock) {
@@ -4553,6 +4558,7 @@
         if (displayId == DEFAULT_DISPLAY) {
             Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn",
                     0 /* cookie */);
+            updateScreenOffSleepToken(false);
             mDefaultDisplayPolicy.screenTurnedOn(screenOnListener);
             mBootAnimationDismissable = false;
 
@@ -5073,6 +5079,15 @@
         }
     }
 
+    // TODO (multidisplay): Support multiple displays in WindowManagerPolicy.
+    private void updateScreenOffSleepToken(boolean acquire) {
+        if (acquire) {
+            mScreenOffSleepTokenAcquirer.acquire(DEFAULT_DISPLAY);
+        } else {
+            mScreenOffSleepTokenAcquirer.release(DEFAULT_DISPLAY);
+        }
+    }
+
     /** {@inheritDoc} */
     @Override
     public void enableScreenAfterBoot() {
diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
index 7c0005c..f368698 100644
--- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java
+++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
@@ -16,14 +16,19 @@
 
 package com.android.server.policy;
 
+import static android.hardware.fingerprint.FingerprintStateListener.STATE_BP_AUTH;
 import static android.hardware.fingerprint.FingerprintStateListener.STATE_ENROLLING;
 import static android.hardware.fingerprint.FingerprintStateListener.STATE_IDLE;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlertDialog;
 import android.app.Dialog;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -34,9 +39,11 @@
 import android.view.WindowManager;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
 
 /**
  * Defines behavior for handling interactions between power button events and
@@ -44,68 +51,115 @@
  * lives on the power button.
  */
 public class SideFpsEventHandler {
+
+    private static final int DEBOUNCE_DELAY_MILLIS = 500;
+
     @NonNull private final Context mContext;
     @NonNull private final Handler mHandler;
     @NonNull private final PowerManager mPowerManager;
-    @NonNull private final AtomicBoolean mIsSideFps;
+    @NonNull private final Supplier<AlertDialog.Builder> mDialogSupplier;
     @NonNull private final AtomicBoolean mSideFpsEventHandlerReady;
 
+    @Nullable private Dialog mDialog;
+    @NonNull private final DialogInterface.OnDismissListener mDialogDismissListener = (dialog) -> {
+        if (mDialog == dialog) {
+            mDialog = null;
+        }
+    };
+
     private @FingerprintStateListener.State int mFingerprintState;
 
     SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager) {
+        this(context, handler, powerManager, () -> new AlertDialog.Builder(context));
+    }
+
+    @VisibleForTesting
+    SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager,
+            Supplier<AlertDialog.Builder> dialogSupplier) {
         mContext = context;
         mHandler = handler;
         mPowerManager = powerManager;
+        mDialogSupplier = dialogSupplier;
         mFingerprintState = STATE_IDLE;
-        mIsSideFps = new AtomicBoolean(false);
         mSideFpsEventHandlerReady = new AtomicBoolean(false);
+
+        // ensure dialog is dismissed if screen goes off for unrelated reasons
+        context.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (mDialog != null) {
+                    mDialog.dismiss();
+                    mDialog = null;
+                }
+            }
+        }, new IntentFilter(Intent.ACTION_SCREEN_OFF));
     }
 
     /**
-     * Called from {@link PhoneWindowManager} after power button is pressed. Checks fingerprint
-     * sensor state and if mFingerprintState = STATE_ENROLLING, displays a dialog confirming intent
-     * to turn screen off. If confirmed, the device goes to sleep, and if canceled, the dialog is
-     * dismissed.
+     * Called from {@link PhoneWindowManager} after the power button is pressed and displays a
+     * dialog confirming the user's intent to turn screen off if a fingerprint operation is
+     * active. The device goes to sleep if confirmed otherwise the dialog is dismissed.
+     *
      * @param eventTime powerPress event time
      * @return true if powerPress was consumed, false otherwise
      */
     public boolean onSinglePressDetected(long eventTime) {
-        if (!mSideFpsEventHandlerReady.get() || !mIsSideFps.get()
-                || mFingerprintState != STATE_ENROLLING) {
+        if (!mSideFpsEventHandlerReady.get()) {
             return false;
         }
-        mHandler.post(() -> {
-            Dialog confirmScreenOffDialog = new AlertDialog.Builder(mContext)
-                    .setTitle(R.string.fp_enrollment_powerbutton_intent_title)
-                    .setMessage(R.string.fp_enrollment_powerbutton_intent_message)
-                    .setPositiveButton(
-                            R.string.fp_enrollment_powerbutton_intent_positive_button,
-                            new DialogInterface.OnClickListener() {
-                                @Override
-                                public void onClick(DialogInterface dialog, int which) {
-                                    dialog.dismiss();
-                                    mPowerManager.goToSleep(
-                                            eventTime,
-                                            PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
-                                            0 /* flags */
-                                    );
-                                }
-                            })
-                    .setNegativeButton(
-                            R.string.fp_enrollment_powerbutton_intent_negative_button,
-                            new DialogInterface.OnClickListener() {
-                                @Override
-                                public void onClick(DialogInterface dialog, int which) {
-                                    dialog.dismiss();
-                                }
-                            })
-                    .setCancelable(false)
-                    .create();
-            confirmScreenOffDialog.getWindow().setType(
-                    WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
-            confirmScreenOffDialog.show();
-        });
-        return true;
+
+        switch (mFingerprintState) {
+            case STATE_ENROLLING:
+            case STATE_BP_AUTH:
+                mHandler.post(() -> {
+                    if (mDialog != null) {
+                        mDialog.dismiss();
+                    }
+                    mDialog = showConfirmDialog(mDialogSupplier.get(),
+                            mPowerManager, eventTime, mFingerprintState, mDialogDismissListener);
+                });
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @NonNull
+    private static Dialog showConfirmDialog(@NonNull AlertDialog.Builder dialogBuilder,
+            @NonNull PowerManager powerManager, long eventTime,
+            @FingerprintStateListener.State int fingerprintState,
+            @NonNull DialogInterface.OnDismissListener dismissListener) {
+        final boolean enrolling = fingerprintState == STATE_ENROLLING;
+        final int title = enrolling ? R.string.fp_power_button_enrollment_title
+                : R.string.fp_power_button_bp_title;
+        final int message = enrolling ? R.string.fp_power_button_enrollment_message
+                : R.string.fp_power_button_bp_message;
+        final int positiveText = enrolling ? R.string.fp_power_button_enrollment_positive_button
+                : R.string.fp_power_button_bp_positive_button;
+        final int negativeText = enrolling ? R.string.fp_power_button_enrollment_negative_button
+                : R.string.fp_power_button_bp_negative_button;
+
+        final Dialog confirmScreenOffDialog = dialogBuilder
+                .setTitle(title)
+                .setMessage(message)
+                .setPositiveButton(positiveText,
+                        (dialog, which) -> {
+                            dialog.dismiss();
+                            powerManager.goToSleep(
+                                    eventTime,
+                                    PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
+                                    0 /* flags */
+                            );
+                        })
+                .setNegativeButton(negativeText, (dialog, which) -> dialog.dismiss())
+                .setOnDismissListener(dismissListener)
+                .setCancelable(false)
+                .create();
+        confirmScreenOffDialog.getWindow().setType(
+                WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+        confirmScreenOffDialog.show();
+
+        return confirmScreenOffDialog;
     }
 
     /**
@@ -116,26 +170,44 @@
      */
     public void onFingerprintSensorReady() {
         final PackageManager pm = mContext.getPackageManager();
-        if (!pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) return;
-        FingerprintManager fingerprintManager =
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+            return;
+        }
+
+        final FingerprintManager fingerprintManager =
                 mContext.getSystemService(FingerprintManager.class);
         fingerprintManager.addAuthenticatorsRegisteredCallback(
                 new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
                     @Override
                     public void onAllAuthenticatorsRegistered(
                             List<FingerprintSensorPropertiesInternal> sensors) {
-                        mIsSideFps.set(fingerprintManager.isPowerbuttonFps());
-                        FingerprintStateListener fingerprintStateListener =
-                                new FingerprintStateListener() {
-                            @Override
-                            public void onStateChanged(
-                                    @FingerprintStateListener.State int newState) {
-                                mFingerprintState = newState;
-                            }
-                        };
-                        fingerprintManager.registerFingerprintStateListener(
-                                fingerprintStateListener);
-                        mSideFpsEventHandlerReady.set(true);
+                        if (fingerprintManager.isPowerbuttonFps()) {
+                            fingerprintManager.registerFingerprintStateListener(
+                                    new FingerprintStateListener() {
+                                        @Nullable private Runnable mStateRunnable = null;
+
+                                        @Override
+                                        public void onStateChanged(
+                                                @FingerprintStateListener.State int newState) {
+                                            if (mStateRunnable != null) {
+                                                mHandler.removeCallbacks(mStateRunnable);
+                                                mStateRunnable = null;
+                                            }
+
+                                            // When the user hits the power button the events can
+                                            // arrive in any order (success auth & power). Add a
+                                            // damper when moving to idle in case auth is first
+                                            if (newState == STATE_IDLE) {
+                                                mStateRunnable = () -> mFingerprintState = newState;
+                                                mHandler.postDelayed(mStateRunnable,
+                                                        DEBOUNCE_DELAY_MILLIS);
+                                            } else {
+                                                mFingerprintState = newState;
+                                            }
+                                        }
+                                    });
+                            mSideFpsEventHandlerReady.set(true);
+                        }
                     }
                 });
     }
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index d24a3df..cf0e350 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -66,6 +66,7 @@
             KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE,
             KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE,
             KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
+            KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
     })
     @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
     @Retention(RetentionPolicy.SOURCE)
@@ -156,12 +157,18 @@
             "time_detector_origin_priorities_override";
 
     /**
-     * The key to override the time detector lower bound configuration. The values is the number of
+     * The key to override the time detector lower bound configuration. The value is the number of
      * milliseconds since the beginning of the Unix epoch.
      */
     public static final @DeviceConfigKey String KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE =
             "time_detector_lower_bound_millis_override";
 
+    /**
+     * The key to allow extra metrics / telemetry information to be collected from internal testers.
+     */
+    public static final @DeviceConfigKey String KEY_ENHANCED_METRICS_COLLECTION_ENABLED =
+            "enhanced_metrics_collection_enabled";
+
     @GuardedBy("mListeners")
     private final ArrayMap<ConfigurationChangeListener, Set<String>> mListeners = new ArrayMap<>();
 
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 65f077e..2291777 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -40,6 +40,7 @@
     private final boolean mTelephonyDetectionSupported;
     private final boolean mGeoDetectionSupported;
     private final boolean mTelephonyFallbackSupported;
+    private final boolean mEnhancedMetricsCollectionEnabled;
     private final boolean mAutoDetectionEnabledSetting;
     private final @UserIdInt int mUserId;
     private final boolean mUserConfigAllowed;
@@ -50,6 +51,7 @@
         mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported;
         mGeoDetectionSupported = builder.mGeoDetectionSupported;
         mTelephonyFallbackSupported = builder.mTelephonyFallbackSupported;
+        mEnhancedMetricsCollectionEnabled = builder.mEnhancedMetricsCollectionEnabled;
         mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting;
 
         mUserId = builder.mUserId;
@@ -81,6 +83,15 @@
         return mTelephonyFallbackSupported;
     }
 
+    /**
+     * Returns {@code true} if the device can collect / report extra metrics information for QA
+     * / testers. These metrics might involve logging more expensive or more revealing data that
+     * would not be collected from the set of public users.
+     */
+    public boolean isEnhancedMetricsCollectionEnabled() {
+        return mEnhancedMetricsCollectionEnabled;
+    }
+
     /** Returns the value of the auto time zone detection enabled setting. */
     public boolean getAutoDetectionEnabledSetting() {
         return mAutoDetectionEnabledSetting;
@@ -227,6 +238,7 @@
                 && mTelephonyDetectionSupported == that.mTelephonyDetectionSupported
                 && mGeoDetectionSupported == that.mGeoDetectionSupported
                 && mTelephonyFallbackSupported == that.mTelephonyFallbackSupported
+                && mEnhancedMetricsCollectionEnabled == that.mEnhancedMetricsCollectionEnabled
                 && mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting
                 && mLocationEnabledSetting == that.mLocationEnabledSetting
                 && mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting;
@@ -235,7 +247,8 @@
     @Override
     public int hashCode() {
         return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported,
-                mGeoDetectionSupported, mTelephonyFallbackSupported, mAutoDetectionEnabledSetting,
+                mGeoDetectionSupported, mTelephonyFallbackSupported,
+                mEnhancedMetricsCollectionEnabled, mAutoDetectionEnabledSetting,
                 mLocationEnabledSetting, mGeoDetectionEnabledSetting);
     }
 
@@ -247,6 +260,7 @@
                 + ", mTelephonyDetectionSupported=" + mTelephonyDetectionSupported
                 + ", mGeoDetectionSupported=" + mGeoDetectionSupported
                 + ", mTelephonyFallbackSupported=" + mTelephonyFallbackSupported
+                + ", mEnhancedMetricsCollectionEnabled=" + mEnhancedMetricsCollectionEnabled
                 + ", mAutoDetectionEnabledSetting=" + mAutoDetectionEnabledSetting
                 + ", mLocationEnabledSetting=" + mLocationEnabledSetting
                 + ", mGeoDetectionEnabledSetting=" + mGeoDetectionEnabledSetting
@@ -264,6 +278,7 @@
         private boolean mTelephonyDetectionSupported;
         private boolean mGeoDetectionSupported;
         private boolean mTelephonyFallbackSupported;
+        private boolean mEnhancedMetricsCollectionEnabled;
         private boolean mAutoDetectionEnabledSetting;
         private boolean mLocationEnabledSetting;
         private boolean mGeoDetectionEnabledSetting;
@@ -284,6 +299,7 @@
             this.mTelephonyDetectionSupported = toCopy.mTelephonyDetectionSupported;
             this.mTelephonyFallbackSupported = toCopy.mTelephonyFallbackSupported;
             this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported;
+            this.mEnhancedMetricsCollectionEnabled = toCopy.mEnhancedMetricsCollectionEnabled;
             this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting;
             this.mLocationEnabledSetting = toCopy.mLocationEnabledSetting;
             this.mGeoDetectionEnabledSetting = toCopy.mGeoDetectionEnabledSetting;
@@ -323,6 +339,14 @@
         }
 
         /**
+         * Sets the value for enhanced metrics collection.
+         */
+        public Builder setEnhancedMetricsCollectionEnabled(boolean enabled) {
+            mEnhancedMetricsCollectionEnabled = enabled;
+            return this;
+        }
+
+        /**
          * Sets the value of the automatic time zone detection enabled setting for this device.
          */
         public Builder setAutoDetectionEnabledSetting(boolean enabled) {
diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
index f156f8c..ecac267 100644
--- a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
+++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
@@ -34,11 +34,13 @@
  * A class that provides time zone detector state information for metrics.
  *
  * <p>
- * Regarding time zone ID ordinals:
+ * Regarding the use of time zone ID ordinals in metrics / telemetry:
  * <p>
- * We don't want to leak user location information by reporting time zone IDs. Instead, time zone
- * IDs are consistently identified within a given instance of this class by a numeric ID. This
- * allows comparison of IDs without revealing what those IDs are.
+ * For general metrics, we don't want to leak user location information by reporting time zone
+ * IDs. Instead, time zone IDs are consistently identified within a given instance of this class by
+ * a numeric ID (ordinal). This allows comparison of IDs without revealing what those IDs are.
+ * See {@link #isEnhancedMetricsCollectionEnabled()} for the setting that enables actual IDs to be
+ * collected.
  */
 public final class MetricsTimeZoneDetectorState {
 
@@ -54,6 +56,7 @@
 
     @NonNull private final ConfigurationInternal mConfigurationInternal;
     private final int mDeviceTimeZoneIdOrdinal;
+    @Nullable private final String mDeviceTimeZoneId;
     @Nullable private final MetricsTimeZoneSuggestion mLatestManualSuggestion;
     @Nullable private final MetricsTimeZoneSuggestion mLatestTelephonySuggestion;
     @Nullable private final MetricsTimeZoneSuggestion mLatestGeolocationSuggestion;
@@ -61,11 +64,13 @@
     private MetricsTimeZoneDetectorState(
             @NonNull ConfigurationInternal configurationInternal,
             int deviceTimeZoneIdOrdinal,
+            @Nullable String deviceTimeZoneId,
             @Nullable MetricsTimeZoneSuggestion latestManualSuggestion,
             @Nullable MetricsTimeZoneSuggestion latestTelephonySuggestion,
             @Nullable MetricsTimeZoneSuggestion latestGeolocationSuggestion) {
         mConfigurationInternal = Objects.requireNonNull(configurationInternal);
         mDeviceTimeZoneIdOrdinal = deviceTimeZoneIdOrdinal;
+        mDeviceTimeZoneId = deviceTimeZoneId;
         mLatestManualSuggestion = latestManualSuggestion;
         mLatestTelephonySuggestion = latestTelephonySuggestion;
         mLatestGeolocationSuggestion = latestGeolocationSuggestion;
@@ -83,18 +88,24 @@
             @Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion,
             @Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion) {
 
+        boolean includeZoneIds = configurationInternal.isEnhancedMetricsCollectionEnabled();
+        String metricDeviceTimeZoneId = includeZoneIds ? deviceTimeZoneId : null;
         int deviceTimeZoneIdOrdinal =
                 tzIdOrdinalGenerator.ordinal(Objects.requireNonNull(deviceTimeZoneId));
         MetricsTimeZoneSuggestion latestCanonicalManualSuggestion =
-                createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestManualSuggestion);
+                createMetricsTimeZoneSuggestion(
+                        tzIdOrdinalGenerator, latestManualSuggestion, includeZoneIds);
         MetricsTimeZoneSuggestion latestCanonicalTelephonySuggestion =
-                createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestTelephonySuggestion);
+                createMetricsTimeZoneSuggestion(
+                        tzIdOrdinalGenerator, latestTelephonySuggestion, includeZoneIds);
         MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion =
-                createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestGeolocationSuggestion);
+                createMetricsTimeZoneSuggestion(
+                        tzIdOrdinalGenerator, latestGeolocationSuggestion, includeZoneIds);
 
         return new MetricsTimeZoneDetectorState(
-                configurationInternal, deviceTimeZoneIdOrdinal, latestCanonicalManualSuggestion,
-                latestCanonicalTelephonySuggestion, latestCanonicalGeolocationSuggestion);
+                configurationInternal, deviceTimeZoneIdOrdinal, metricDeviceTimeZoneId,
+                latestCanonicalManualSuggestion, latestCanonicalTelephonySuggestion,
+                latestCanonicalGeolocationSuggestion);
     }
 
     /** Returns true if the device supports telephony time zone detection. */
@@ -112,6 +123,11 @@
         return mConfigurationInternal.isTelephonyFallbackSupported();
     }
 
+    /** Returns true if enhanced metric collection is enabled. */
+    public boolean isEnhancedMetricsCollectionEnabled() {
+        return mConfigurationInternal.isEnhancedMetricsCollectionEnabled();
+    }
+
     /** Returns true if user's location can be used generally. */
     public boolean getUserLocationEnabledSetting() {
         return mConfigurationInternal.getLocationEnabledSetting();
@@ -142,7 +158,7 @@
     }
 
     /**
-     * Returns the ordinal for the device's currently set time zone ID.
+     * Returns the ordinal for the device's current time zone ID.
      * See {@link MetricsTimeZoneDetectorState} for information about ordinals.
      */
     public int getDeviceTimeZoneIdOrdinal() {
@@ -150,6 +166,16 @@
     }
 
     /**
+     * Returns the device's current time zone ID. This will only be populated if {@link
+     * #isEnhancedMetricsCollectionEnabled()} is {@code true}. See {@link
+     * MetricsTimeZoneDetectorState} for details.
+     */
+    @Nullable
+    public String getDeviceTimeZoneId() {
+        return mDeviceTimeZoneId;
+    }
+
+    /**
      * Returns a canonical form of the last manual suggestion received.
      */
     @Nullable
@@ -183,6 +209,7 @@
         }
         MetricsTimeZoneDetectorState that = (MetricsTimeZoneDetectorState) o;
         return mDeviceTimeZoneIdOrdinal == that.mDeviceTimeZoneIdOrdinal
+                && Objects.equals(mDeviceTimeZoneId, that.mDeviceTimeZoneId)
                 && mConfigurationInternal.equals(that.mConfigurationInternal)
                 && Objects.equals(mLatestManualSuggestion, that.mLatestManualSuggestion)
                 && Objects.equals(mLatestTelephonySuggestion, that.mLatestTelephonySuggestion)
@@ -191,7 +218,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mConfigurationInternal, mDeviceTimeZoneIdOrdinal,
+        return Objects.hash(mConfigurationInternal, mDeviceTimeZoneIdOrdinal, mDeviceTimeZoneId,
                 mLatestManualSuggestion, mLatestTelephonySuggestion, mLatestGeolocationSuggestion);
     }
 
@@ -200,6 +227,7 @@
         return "MetricsTimeZoneDetectorState{"
                 + "mConfigurationInternal=" + mConfigurationInternal
                 + ", mDeviceTimeZoneIdOrdinal=" + mDeviceTimeZoneIdOrdinal
+                + ", mDeviceTimeZoneId=" + mDeviceTimeZoneId
                 + ", mLatestManualSuggestion=" + mLatestManualSuggestion
                 + ", mLatestTelephonySuggestion=" + mLatestTelephonySuggestion
                 + ", mLatestGeolocationSuggestion=" + mLatestGeolocationSuggestion
@@ -209,34 +237,40 @@
     @Nullable
     private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
             @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
-            @NonNull ManualTimeZoneSuggestion manualSuggestion) {
+            @NonNull ManualTimeZoneSuggestion manualSuggestion,
+            boolean includeFullZoneIds) {
         if (manualSuggestion == null) {
             return null;
         }
 
-        int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(manualSuggestion.getZoneId());
-        return MetricsTimeZoneSuggestion.createCertain(
-                new int[] { zoneIdOrdinal });
+        String suggestionZoneId = manualSuggestion.getZoneId();
+        String[] metricZoneIds = includeFullZoneIds ? new String[] { suggestionZoneId } : null;
+        int[] zoneIdOrdinals = new int[] { zoneIdOrdinalGenerator.ordinal(suggestionZoneId) };
+        return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals);
     }
 
     @Nullable
     private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
             @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
-            @NonNull TelephonyTimeZoneSuggestion telephonySuggestion) {
+            @NonNull TelephonyTimeZoneSuggestion telephonySuggestion,
+            boolean includeFullZoneIds) {
         if (telephonySuggestion == null) {
             return null;
         }
-        if (telephonySuggestion.getZoneId() == null) {
+        String suggestionZoneId = telephonySuggestion.getZoneId();
+        if (suggestionZoneId == null) {
             return MetricsTimeZoneSuggestion.createUncertain();
         }
-        int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(telephonySuggestion.getZoneId());
-        return MetricsTimeZoneSuggestion.createCertain(new int[] { zoneIdOrdinal });
+        String[] metricZoneIds = includeFullZoneIds ? new String[] { suggestionZoneId } : null;
+        int[] zoneIdOrdinals = new int[] { zoneIdOrdinalGenerator.ordinal(suggestionZoneId) };
+        return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals);
     }
 
     @Nullable
     private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
             @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
-            @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion) {
+            @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion,
+            boolean includeFullZoneIds) {
         if (geolocationSuggestion == null) {
             return null;
         }
@@ -245,7 +279,9 @@
         if (zoneIds == null) {
             return MetricsTimeZoneSuggestion.createUncertain();
         }
-        return MetricsTimeZoneSuggestion.createCertain(zoneIdOrdinalGenerator.ordinals(zoneIds));
+        String[] metricZoneIds = includeFullZoneIds ? zoneIds.toArray(new String[0]) : null;
+        int[] zoneIdOrdinals = zoneIdOrdinalGenerator.ordinals(zoneIds);
+        return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals);
     }
 
     /**
@@ -254,33 +290,49 @@
      * MetricsTimeZoneSuggestion proto definition.
      */
     public static final class MetricsTimeZoneSuggestion {
-        @Nullable
-        private final int[] mZoneIdOrdinals;
+        @Nullable private final String[] mZoneIds;
+        @Nullable private final int[] mZoneIdOrdinals;
 
-        MetricsTimeZoneSuggestion(@Nullable int[] zoneIdOrdinals) {
+        private MetricsTimeZoneSuggestion(
+                @Nullable String[] zoneIds, @Nullable int[] zoneIdOrdinals) {
+            mZoneIds = zoneIds;
             mZoneIdOrdinals = zoneIdOrdinals;
         }
 
         @NonNull
         static MetricsTimeZoneSuggestion createUncertain() {
-            return new MetricsTimeZoneSuggestion(null);
+            return new MetricsTimeZoneSuggestion(null, null);
         }
 
         @NonNull
         static MetricsTimeZoneSuggestion createCertain(
-                @NonNull int[] zoneIdOrdinals) {
-            return new MetricsTimeZoneSuggestion(zoneIdOrdinals);
+                @Nullable String[] zoneIds, @NonNull int[] zoneIdOrdinals) {
+            return new MetricsTimeZoneSuggestion(zoneIds, zoneIdOrdinals);
         }
 
         public boolean isCertain() {
             return mZoneIdOrdinals != null;
         }
 
+        /**
+         * Returns ordinals for the time zone IDs contained in the suggestion.
+         * See {@link MetricsTimeZoneDetectorState} for information about ordinals.
+         */
         @Nullable
         public int[] getZoneIdOrdinals() {
             return mZoneIdOrdinals;
         }
 
+        /**
+         * Returns the time zone IDs contained in the suggestion. This will only be populated if
+         * {@link #isEnhancedMetricsCollectionEnabled()} is {@code true}. See {@link
+         * MetricsTimeZoneDetectorState} for details.
+         */
+        @Nullable
+        public String[] getZoneIds() {
+            return mZoneIds;
+        }
+
         @Override
         public boolean equals(Object o) {
             if (this == o) {
@@ -290,18 +342,22 @@
                 return false;
             }
             MetricsTimeZoneSuggestion that = (MetricsTimeZoneSuggestion) o;
-            return Arrays.equals(mZoneIdOrdinals, that.mZoneIdOrdinals);
+            return Arrays.equals(mZoneIdOrdinals, that.mZoneIdOrdinals)
+                    && Arrays.equals(mZoneIds, that.mZoneIds);
         }
 
         @Override
         public int hashCode() {
-            return Arrays.hashCode(mZoneIdOrdinals);
+            int result = Arrays.hashCode(mZoneIds);
+            result = 31 * result + Arrays.hashCode(mZoneIdOrdinals);
+            return result;
         }
 
         @Override
         public String toString() {
             return "MetricsTimeZoneSuggestion{"
                     + "mZoneIdOrdinals=" + Arrays.toString(mZoneIdOrdinals)
+                    + ", mZoneIds=" + Arrays.toString(mZoneIds)
                     + '}';
         }
     }
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index 02ea433..4612f65 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -62,6 +62,7 @@
     private static final Set<String> CONFIGURATION_INTERNAL_SERVER_FLAGS_KEYS_TO_WATCH =
             Collections.unmodifiableSet(new ArraySet<>(new String[] {
                     ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
+                    ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
                     ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
                     ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
                     ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
@@ -296,6 +297,7 @@
                         isTelephonyTimeZoneDetectionFeatureSupported())
                 .setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported())
                 .setTelephonyFallbackSupported(isTelephonyFallbackSupported())
+                .setEnhancedMetricsCollectionEnabled(isEnhancedMetricsCollectionEnabled())
                 .setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting())
                 .setUserConfigAllowed(isUserConfigAllowed(userId))
                 .setLocationEnabledSetting(getLocationEnabledSetting(userId))
@@ -400,6 +402,17 @@
                 defaultEnabled);
     }
 
+    /**
+     * Returns {@code true} if extra metrics / telemetry information can be collected. Used for
+     * internal testers.
+     */
+    private boolean isEnhancedMetricsCollectionEnabled() {
+        final boolean defaultEnabled = false;
+        return mServerFlags.getBoolean(
+                ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
+                defaultEnabled);
+    }
+
     @Override
     @NonNull
     public synchronized String getPrimaryLocationTimeZoneProviderPackageName() {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 14784cf..f75608e 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -364,6 +364,13 @@
         }
     }
 
+    @NonNull
+    MetricsTimeZoneDetectorState generateMetricsState() {
+        enforceManageTimeZoneDetectorPermission();
+
+        return mTimeZoneDetectorStrategy.generateMetricsState();
+    }
+
     @Override
     protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
             @Nullable String[] args) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index 2b912ad..8535b3d 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.timezonedetector;
 
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_DUMP_METRICS;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
@@ -28,6 +29,7 @@
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE;
 import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
 
+import static com.android.server.timedetector.ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED;
 import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED;
 import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT;
 import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE;
@@ -80,6 +82,8 @@
                 return runSuggestTelephonyTimeZone();
             case SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK:
                 return runEnableTelephonyFallback();
+            case SHELL_COMMAND_DUMP_METRICS:
+                return runDumpMetrics();
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -168,14 +172,22 @@
             pw.println("Suggestion " + suggestion + " injected.");
             return 0;
         } catch (RuntimeException e) {
-            pw.println(e.toString());
+            pw.println(e);
             return 1;
         }
     }
 
     private int runEnableTelephonyFallback() {
         mInterface.enableTelephonyFallback();
-        return 1;
+        return 0;
+    }
+
+    private int runDumpMetrics() {
+        final PrintWriter pw = getOutPrintWriter();
+        MetricsTimeZoneDetectorState metricsState = mInterface.generateMetricsState();
+        pw.println("MetricsTimeZoneDetectorState:");
+        pw.println(metricsState.toString());
+        return 0;
     }
 
     @Override
@@ -208,10 +220,10 @@
         pw.println();
         pw.printf("  %s <geolocation suggestion opts>\n",
                 SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE);
-        pw.printf("  %s <manual suggestion opts>\n",
-                SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE);
-        pw.printf("  %s <telephony suggestion opts>\n",
-                SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE);
+        pw.printf("  %s <manual suggestion opts>\n", SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE);
+        pw.printf("  %s <telephony suggestion opts>\n", SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE);
+        pw.printf("  %s\n", SHELL_COMMAND_DUMP_METRICS);
+        pw.printf("    Dumps the service metrics to stdout for inspection.\n");
         pw.println();
         GeolocationTimeZoneSuggestion.printCommandLineOpts(pw);
         pw.println();
@@ -235,6 +247,8 @@
         pw.printf("  %s\n", KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED);
         pw.printf("    Used to enable / disable support for telephony detection fallback. Also see"
                 + " the %s command.\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK);
+        pw.printf("  %s\n", KEY_ENHANCED_METRICS_COLLECTION_ENABLED);
+        pw.printf("    Used to increase the detail of metrics collected / reported.\n");
         pw.println();
         pw.printf("[*] To be enabled, the user must still have location = on / auto time zone"
                 + " detection = on.\n");
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 59b6a08..7e36f89 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -44,6 +44,7 @@
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.media.PlaybackParams;
+import android.media.tv.AitInfo;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.DvbDeviceInfo;
@@ -1761,6 +1762,26 @@
         }
 
         @Override
+        public void setIAppNotificationEnabled(IBinder sessionToken, boolean enabled, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "setIAppNotificationEnabled");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .setIAppNotificationEnabled(enabled);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in setIAppNotificationEnabled", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
                 int userId) {
             final int callingUid = Binder.getCallingUid();
@@ -3341,7 +3362,23 @@
             }
         }
 
-        // For the recording session only
+        @Override
+        public void onAitInfoUpdated(AitInfo aitInfo) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onAitInfoUpdated(" + aitInfo + ")");
+                }
+                if (mSessionState.session == null || mSessionState.client == null) {
+                    return;
+                }
+                try {
+                    mSessionState.client.onAitInfoUpdated(aitInfo, mSessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onAitInfoUpdated", e);
+                }
+            }
+        }
+
         @Override
         public void onTuned(Uri channelUri) {
             synchronized (mLock) {
@@ -3352,7 +3389,7 @@
                     return;
                 }
                 try {
-                    mSessionState.client.onTuned(mSessionState.seq, channelUri);
+                    mSessionState.client.onTuned(channelUri, mSessionState.seq);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "error in onTuned", e);
                 }
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 c7b6421..f12139d 100644
--- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
@@ -28,6 +28,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
+import android.graphics.Rect;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.interactive.ITvIAppClient;
@@ -39,6 +40,7 @@
 import android.media.tv.interactive.ITvIAppSessionCallback;
 import android.media.tv.interactive.TvIAppInfo;
 import android.media.tv.interactive.TvIAppService;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Process;
@@ -522,11 +524,6 @@
         removeSessionStateLocked(state.mSessionToken, state.mUserId);
     }
 
-    private SessionState getSessionState(IBinder sessionToken) {
-        // TODO: implement user state and get session from it.
-        return null;
-    }
-
     private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
             String methodName) {
         return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
@@ -570,6 +567,11 @@
     }
 
     @GuardedBy("mLock")
+    private ITvIAppSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
+        return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
+    }
+
+    @GuardedBy("mLock")
     private ITvIAppSession getSessionLocked(SessionState sessionState) {
         ITvIAppSession session = sessionState.mSession;
         if (session == null) {
@@ -601,6 +603,32 @@
         }
 
         @Override
+        public void prepare(String tiasId, int type, int userId) {
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, "prepare");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    TvIAppState iAppState = userState.mIAppMap.get(tiasId);
+                    if (iAppState == null) {
+                        Slogf.e(TAG, "failed to prepare TIAS - unknown TIAS id " + tiasId);
+                        return;
+                    }
+                    ComponentName componentName = iAppState.mInfo.getComponent();
+                    ServiceState serviceState = userState.mServiceStateMap.get(componentName);
+                    if (serviceState != null) {
+                        serviceState.mService.prepare(type);
+                    }
+                }
+            } catch (RemoteException e) {
+                Slogf.e(TAG, "error in prepare", e);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void createSession(final ITvIAppClient client, final String iAppServiceId, int type,
                 int seq, int userId) {
             final int callingUid = Binder.getCallingUid();
@@ -683,17 +711,53 @@
         }
 
         @Override
+        public void notifyTuned(IBinder sessionToken, Uri channelUri, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "notifyTuned(sessionToken=" + sessionToken
+                        + ", Uri=" + channelUri + ")");
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "notifyTuned");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyTuned(channelUri);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyTuned", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void startIApp(IBinder sessionToken, int userId) {
             if (DEBUG) {
                 Slogf.d(TAG, "BinderService#start(userId=%d)", userId);
             }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "notifyTuned");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
             try {
-                SessionState sessionState = getSessionState(sessionToken);
-                if (sessionState != null && sessionState.mSession != null) {
-                    sessionState.mSession.startIApp();
+                synchronized (mLock) {
+                    try {
+                        sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).startIApp();
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in start", e);
+                    }
                 }
-            } catch (RemoteException e) {
-                Slogf.e(TAG, "error in start", e);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
             }
         }
 
@@ -802,6 +866,67 @@
                 Binder.restoreCallingIdentity(identity);
             }
         }
+
+        @Override
+        public void createMediaView(IBinder sessionToken, IBinder windowToken, Rect frame,
+                int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "createMediaView");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .createMediaView(windowToken, frame);
+                    } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) {
+                        Slog.e(TAG, "error in createMediaView", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void relayoutMediaView(IBinder sessionToken, Rect frame, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "relayoutMediaView");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .relayoutMediaView(frame);
+                    } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) {
+                        Slog.e(TAG, "error in relayoutMediaView", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void removeMediaView(IBinder sessionToken, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "removeMediaView");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .removeMediaView();
+                    } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) {
+                        Slog.e(TAG, "error in removeMediaView", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
     }
 
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
index 5c1b5ff..1c675c2 100644
--- a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
+++ b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
@@ -46,6 +46,7 @@
     private static final String PARCEL_UUID_KEY = "PARCEL_UUID";
     private static final String BYTE_ARRAY_KEY = "BYTE_ARRAY_KEY";
     private static final String INTEGER_KEY = "INTEGER_KEY";
+    private static final String STRING_KEY = "STRING_KEY";
 
     /**
      * Functional interface to convert an object of the specified type to a PersistableBundle.
@@ -91,6 +92,21 @@
                 return bundle.getInt(INTEGER_KEY);
             };
 
+    /** Serializer to convert s String to a PersistableBundle. */
+    public static final Serializer<String> STRING_SERIALIZER =
+            (i) -> {
+                final PersistableBundle result = new PersistableBundle();
+                result.putString(STRING_KEY, i);
+                return result;
+            };
+
+    /** Deserializer to convert a PersistableBundle to a String. */
+    public static final Deserializer<String> STRING_DESERIALIZER =
+            (bundle) -> {
+                Objects.requireNonNull(bundle, "PersistableBundle is null");
+                return bundle.getString(STRING_KEY);
+            };
+
     /**
      * Converts a ParcelUuid to a PersistableBundle.
      *
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index ddac9cd..1d6e158 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -53,13 +53,13 @@
         IGNORED,
         IGNORED_APP_OPS,
         IGNORED_BACKGROUND,
-        IGNORED_RINGTONE,
         IGNORED_UNKNOWN_VIBRATION,
         IGNORED_UNSUPPORTED,
         IGNORED_FOR_ALARM,
         IGNORED_FOR_EXTERNAL,
         IGNORED_FOR_ONGOING,
         IGNORED_FOR_POWER,
+        IGNORED_FOR_RINGER_MODE,
         IGNORED_FOR_SETTINGS,
         IGNORED_SUPERSEDED,
     }
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index f82f99d..1ee115d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -53,12 +53,44 @@
 import com.android.server.LocalServices;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /** Controls all the system settings related to vibration. */
 final class VibrationSettings {
     private static final String TAG = "VibrationSettings";
 
+    /**
+     * Set of usages allowed for vibrations from background processes.
+     *
+     * <p>Some examples are notification, ringtone or alarm vibrations, that are allowed to vibrate
+     * unexpectedly as they are meant to grab the user's attention. Hardware feedback and physical
+     * emulation are also supported, as the trigger process might still be in the background when
+     * the user interaction wakes the device.
+     */
+    private static final Set<Integer> BACKGROUND_PROCESS_USAGE_ALLOWLIST = new HashSet<>(
+            Arrays.asList(
+                    USAGE_RINGTONE,
+                    USAGE_ALARM,
+                    USAGE_NOTIFICATION,
+                    USAGE_COMMUNICATION_REQUEST,
+                    USAGE_HARDWARE_FEEDBACK,
+                    USAGE_PHYSICAL_EMULATION));
+
+    /**
+     * Set of usages allowed for vibrations in battery saver mode (low power).
+     *
+     * <p>Some examples are ringtone or alarm vibrations, that have high priority and should vibrate
+     * even when the device is saving battery.
+     */
+    private static final Set<Integer> BATTERY_SAVER_USAGE_ALLOWLIST = new HashSet<>(
+            Arrays.asList(
+                    USAGE_RINGTONE,
+                    USAGE_ALARM,
+                    USAGE_COMMUNICATION_REQUEST));
+
     /** Listener for changes on vibration settings. */
     interface OnVibratorSettingsChanged {
         /** Callback triggered when any of the vibrator settings change. */
@@ -94,8 +126,6 @@
     @GuardedBy("mLock")
     private boolean mApplyRampingRinger;
     @GuardedBy("mLock")
-    private int mZenMode;
-    @GuardedBy("mLock")
     private int mHapticFeedbackIntensity;
     @GuardedBy("mLock")
     private int mHardwareFeedbackIntensity;
@@ -104,7 +134,7 @@
     @GuardedBy("mLock")
     private int mRingIntensity;
     @GuardedBy("mLock")
-    private boolean mLowPowerMode;
+    private boolean mBatterySaverMode;
 
     VibrationSettings(Context context, Handler handler) {
         this(context, handler,
@@ -172,8 +202,8 @@
                     public void onLowPowerModeChanged(PowerSaveState result) {
                         boolean shouldNotifyListeners;
                         synchronized (mLock) {
-                            shouldNotifyListeners = result.batterySaverEnabled != mLowPowerMode;
-                            mLowPowerMode = result.batterySaverEnabled;
+                            shouldNotifyListeners = result.batterySaverEnabled != mBatterySaverMode;
+                            mBatterySaverMode = result.batterySaverEnabled;
                         }
                         if (shouldNotifyListeners) {
                             notifyListeners();
@@ -187,7 +217,6 @@
         registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES));
         registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING));
         registerSettingsObserver(Settings.System.getUriFor(Settings.System.APPLY_RAMPING_RINGER));
-        registerSettingsObserver(Settings.Global.getUriFor(Settings.Global.ZEN_MODE));
         registerSettingsObserver(
                 Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_INTENSITY));
         registerSettingsObserver(
@@ -299,71 +328,78 @@
         return mFallbackEffects.get(effectId);
     }
 
-    /**
-     * Return {@code true} if the device should vibrate for current ringer mode.
-     *
-     * <p>This checks the current {@link AudioManager#getRingerModeInternal()} against user settings
-     * for ringtone usage only. All other usages are allowed independently of ringer mode.
-     */
-    public boolean shouldVibrateForRingerMode(int usageHint) {
-        if (usageHint != USAGE_RINGTONE) {
-            return true;
-        }
-        synchronized (mLock) {
-            if (mAudioManager == null) {
-                return false;
-            }
-            int ringerMode = mAudioManager.getRingerModeInternal();
-            if (mVibrateWhenRinging) {
-                return ringerMode != AudioManager.RINGER_MODE_SILENT;
-            } else if (mApplyRampingRinger) {
-                return ringerMode != AudioManager.RINGER_MODE_SILENT;
-            } else {
-                return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
-            }
-        }
-    }
-
-    /**
-     * Returns {@code true} if this vibration is allowed for given {@code uid}.
-     *
-     * <p>This checks if the user is aware of this foreground process, or if the vibration usage is
-     * allowed to play in the background (i.e. it's a notification, ringtone or alarm vibration).
-     */
-    public boolean shouldVibrateForUid(int uid, int usageHint) {
-        return mUidObserver.isUidForeground(uid) || isClassAlarm(usageHint);
-    }
-
-    /**
-     * Returns {@code true} if this vibration is allowed for current power mode state.
-     *
-     * <p>This checks if the device is in battery saver mode, in which case only alarm, ringtone and
-     * {@link VibrationAttributes#USAGE_COMMUNICATION_REQUEST} usages are allowed to vibrate.
-     */
-    public boolean shouldVibrateForPowerMode(int usageHint) {
-        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. */
     public boolean shouldVibrateInputDevices() {
         return mVibrateInputDevices;
     }
 
-    /** Return {@code true} if setting for {@link Settings.Global#ZEN_MODE} is not OFF. */
-    public boolean isInZenMode() {
-        return mZenMode != Settings.Global.ZEN_MODE_OFF;
+    /**
+     * Check if given vibration should be ignored by the service.
+     *
+     * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored,
+     * null otherwise.
+     */
+    @Nullable
+    public Vibration.Status shouldIgnoreVibration(int uid, VibrationAttributes attrs) {
+        final int usage = attrs.getUsage();
+        synchronized (mLock) {
+            if (!mUidObserver.isUidForeground(uid)
+                    && !BACKGROUND_PROCESS_USAGE_ALLOWLIST.contains(usage)) {
+                return Vibration.Status.IGNORED_BACKGROUND;
+            }
+
+            if (mBatterySaverMode && !BATTERY_SAVER_USAGE_ALLOWLIST.contains(usage)) {
+                return Vibration.Status.IGNORED_FOR_POWER;
+            }
+
+            int intensity = getCurrentIntensity(usage);
+            if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+                return Vibration.Status.IGNORED_FOR_SETTINGS;
+            }
+
+            if (!shouldVibrateForRingerModeLocked(usage)) {
+                return Vibration.Status.IGNORED_FOR_RINGER_MODE;
+            }
+        }
+        return null;
     }
 
-    private static boolean isClassAlarm(int usageHint) {
-        return (usageHint & VibrationAttributes.USAGE_CLASS_MASK)
-                == VibrationAttributes.USAGE_CLASS_ALARM;
+    /**
+     * Return {@code true} if the device should vibrate for current ringer mode.
+     *
+     * <p>This checks the current {@link AudioManager#getRingerModeInternal()} against user settings
+     * for touch and ringtone usages only. All other usages are allowed by this method.
+     */
+    @GuardedBy("mLock")
+    private boolean shouldVibrateForRingerModeLocked(int usageHint) {
+        // If audio manager was not loaded yet then assume most restrictive mode.
+        int ringerMode = (mAudioManager == null)
+                ? AudioManager.RINGER_MODE_SILENT
+                : mAudioManager.getRingerModeInternal();
+
+        switch (usageHint) {
+            case USAGE_TOUCH:
+                // Touch feedback disabled when phone is on silent mode.
+                return ringerMode != AudioManager.RINGER_MODE_SILENT;
+            case USAGE_RINGTONE:
+                switch (ringerMode) {
+                    case AudioManager.RINGER_MODE_SILENT:
+                        return false;
+                    case AudioManager.RINGER_MODE_VIBRATE:
+                        return true;
+                    default:
+                        // Ringtone vibrations also depend on 2 other settings:
+                        return mVibrateWhenRinging || mApplyRampingRinger;
+                }
+            default:
+                // All other usages ignore ringer mode settings.
+                return true;
+        }
     }
 
     /** Updates all vibration settings and triggers registered listeners. */
-    public void updateSettings() {
+    @VisibleForTesting
+    void updateSettings() {
         synchronized (mLock) {
             mVibrateWhenRinging = getSystemSetting(Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
             mApplyRampingRinger = getSystemSetting(Settings.System.APPLY_RAMPING_RINGER, 0) != 0;
@@ -378,7 +414,6 @@
             mRingIntensity = getSystemSetting(Settings.System.RING_VIBRATION_INTENSITY,
                     getDefaultIntensity(USAGE_RINGTONE));
             mVibrateInputDevices = getSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
-            mZenMode = getGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
         }
         notifyListeners();
     }
@@ -399,31 +434,33 @@
 
     @Override
     public String toString() {
-        return "VibrationSettings{"
-                + "mVibrateInputDevices=" + mVibrateInputDevices
-                + ", mVibrateWhenRinging=" + mVibrateWhenRinging
-                + ", mApplyRampingRinger=" + mApplyRampingRinger
-                + ", mLowPowerMode=" + mLowPowerMode
-                + ", mZenMode=" + Settings.Global.zenModeToString(mZenMode)
-                + ", mProcStatesCache=" + mUidObserver.mProcStatesCache
-                + ", mHapticChannelMaxVibrationAmplitude=" + getHapticChannelMaxVibrationAmplitude()
-                + ", mRampStepDuration=" + mRampStepDuration
-                + ", mRampDownDuration=" + mRampDownDuration
-                + ", mHardwareHapticFeedbackIntensity="
-                + intensityToString(getCurrentIntensity(USAGE_HARDWARE_FEEDBACK))
-                + ", mHapticFeedbackIntensity="
-                + intensityToString(getCurrentIntensity(USAGE_TOUCH))
-                + ", mHapticFeedbackDefaultIntensity="
-                + intensityToString(getDefaultIntensity(USAGE_TOUCH))
-                + ", mNotificationIntensity="
-                + intensityToString(getCurrentIntensity(USAGE_NOTIFICATION))
-                + ", mNotificationDefaultIntensity="
-                + intensityToString(getDefaultIntensity(USAGE_NOTIFICATION))
-                + ", mRingIntensity="
-                + intensityToString(getCurrentIntensity(USAGE_RINGTONE))
-                + ", mRingDefaultIntensity="
-                + intensityToString(getDefaultIntensity(USAGE_RINGTONE))
-                + '}';
+        synchronized (mLock) {
+            return "VibrationSettings{"
+                    + "mVibrateInputDevices=" + mVibrateInputDevices
+                    + ", mVibrateWhenRinging=" + mVibrateWhenRinging
+                    + ", mApplyRampingRinger=" + mApplyRampingRinger
+                    + ", mBatterySaverMode=" + mBatterySaverMode
+                    + ", mProcStatesCache=" + mUidObserver.mProcStatesCache
+                    + ", mHapticChannelMaxVibrationAmplitude="
+                    + getHapticChannelMaxVibrationAmplitude()
+                    + ", mRampStepDuration=" + mRampStepDuration
+                    + ", mRampDownDuration=" + mRampDownDuration
+                    + ", mHardwareHapticFeedbackIntensity="
+                    + intensityToString(getCurrentIntensity(USAGE_HARDWARE_FEEDBACK))
+                    + ", mHapticFeedbackIntensity="
+                    + intensityToString(getCurrentIntensity(USAGE_TOUCH))
+                    + ", mHapticFeedbackDefaultIntensity="
+                    + intensityToString(getDefaultIntensity(USAGE_TOUCH))
+                    + ", mNotificationIntensity="
+                    + intensityToString(getCurrentIntensity(USAGE_NOTIFICATION))
+                    + ", mNotificationDefaultIntensity="
+                    + intensityToString(getDefaultIntensity(USAGE_NOTIFICATION))
+                    + ", mRingIntensity="
+                    + intensityToString(getCurrentIntensity(USAGE_RINGTONE))
+                    + ", mRingDefaultIntensity="
+                    + intensityToString(getDefaultIntensity(USAGE_RINGTONE))
+                    + '}';
+        }
     }
 
     /** Write current settings into given {@link ProtoOutputStream}. */
@@ -480,10 +517,6 @@
                 settingName, defaultValue, UserHandle.USER_CURRENT);
     }
 
-    private int getGlobalSetting(String settingName, int defaultValue) {
-        return Settings.Global.getInt(mContext.getContentResolver(), settingName, defaultValue);
-    }
-
     private void registerSettingsObserver(Uri settingUri) {
         mContext.getContentResolver().registerContentObserver(
                 settingUri, /* notifyForDescendants= */ true, mSettingObserver,
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 9717201..478e86e 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -46,7 +46,6 @@
 import android.os.Trace;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
 import android.os.VibratorInfo;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.VibrationEffectSegment;
@@ -394,13 +393,13 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Starting vibrate for vibration  " + vib.id);
                 }
-                Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib);
-                if (ignoreStatus != null) {
-                    endVibrationLocked(vib, ignoreStatus);
-                    return vib;
+                Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
+                        vib.uid, vib.opPkg, vib.attrs);
+
+                if (ignoreStatus == null) {
+                    ignoreStatus = shouldIgnoreVibrationForOngoingLocked(vib);
                 }
 
-                ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vib);
                 if (ignoreStatus != null) {
                     endVibrationLocked(vib, ignoreStatus);
                     return vib;
@@ -453,8 +452,7 @@
                             && shouldCancelVibration(
                             mCurrentExternalVibration.externalVibration.getVibrationAttributes(),
                             usageFilter)) {
-                        mCurrentExternalVibration.end(Vibration.Status.CANCELLED);
-                        mVibratorManagerRecords.record(mCurrentExternalVibration);
+                        endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
                         mCurrentExternalVibration.externalVibration.mute();
                         mCurrentExternalVibration = null;
                         setExternalControl(false);
@@ -482,15 +480,76 @@
         }
         try {
             if (isDumpProto) {
-                mVibratorManagerRecords.dumpProto(fd);
+                dumpProto(fd);
             } else {
-                mVibratorManagerRecords.dumpText(pw);
+                dumpText(pw);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
+    private void dumpText(PrintWriter pw) {
+        if (DEBUG) {
+            Slog.d(TAG, "Dumping vibrator manager service to text...");
+        }
+        synchronized (mLock) {
+            pw.println("Vibrator Manager Service:");
+            pw.println("  mVibrationSettings:");
+            pw.println("    " + mVibrationSettings);
+            pw.println();
+            pw.println("  mVibratorControllers:");
+            for (int i = 0; i < mVibrators.size(); i++) {
+                pw.println("    " + mVibrators.valueAt(i));
+            }
+            pw.println();
+            pw.println("  mCurrentVibration:");
+            pw.println("    " + (mCurrentVibration == null
+                    ? null : mCurrentVibration.getVibration().getDebugInfo()));
+            pw.println();
+            pw.println("  mNextVibration:");
+            pw.println("    " + (mNextVibration == null
+                    ? null : mNextVibration.getVibration().getDebugInfo()));
+            pw.println();
+            pw.println("  mCurrentExternalVibration:");
+            pw.println("    " + (mCurrentExternalVibration == null
+                    ? null : mCurrentExternalVibration.getDebugInfo()));
+            pw.println();
+        }
+        mVibratorManagerRecords.dumpText(pw);
+    }
+
+    synchronized void dumpProto(FileDescriptor fd) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+        if (DEBUG) {
+            Slog.d(TAG, "Dumping vibrator manager service to proto...");
+        }
+        synchronized (mLock) {
+            mVibrationSettings.dumpProto(proto);
+            if (mCurrentVibration != null) {
+                mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
+                        VibratorManagerServiceDumpProto.CURRENT_VIBRATION);
+            }
+            if (mCurrentExternalVibration != null) {
+                mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
+                        VibratorManagerServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
+            }
+
+            boolean isVibrating = false;
+            boolean isUnderExternalControl = false;
+            for (int i = 0; i < mVibrators.size(); i++) {
+                proto.write(VibratorManagerServiceDumpProto.VIBRATOR_IDS, mVibrators.keyAt(i));
+                isVibrating |= mVibrators.valueAt(i).isVibrating();
+                isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl();
+            }
+            proto.write(VibratorManagerServiceDumpProto.IS_VIBRATING, isVibrating);
+            proto.write(VibratorManagerServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
+                    isUnderExternalControl);
+        }
+        mVibratorManagerRecords.dumpProto(proto);
+        proto.flush();
+    }
+
     @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
             String[] args, ShellCallback cb, ResultReceiver resultReceiver) {
@@ -515,8 +574,15 @@
                 return;
             }
 
-            if (inputDevicesChanged || !mVibrationSettings.shouldVibrateForPowerMode(
-                    mCurrentVibration.getVibration().attrs.getUsage())) {
+            Vibration vib = mCurrentVibration.getVibration();
+            Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
+                    vib.uid, vib.opPkg, vib.attrs);
+
+            if (inputDevicesChanged || (ignoreStatus != null)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Canceling vibration because settings changed: "
+                            + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
+                }
                 mCurrentVibration.cancel();
             }
         }
@@ -602,15 +668,56 @@
     @GuardedBy("mLock")
     private void endVibrationLocked(Vibration vib, Vibration.Status status) {
         vib.end(status);
+        logVibrationStatus(vib.uid, vib.attrs, status);
         mVibratorManagerRecords.record(vib);
     }
 
     @GuardedBy("mLock")
     private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
         vib.end(status);
+        logVibrationStatus(vib.externalVibration.getUid(),
+                vib.externalVibration.getVibrationAttributes(), status);
         mVibratorManagerRecords.record(vib);
     }
 
+    private void logVibrationStatus(int uid, VibrationAttributes attrs, Vibration.Status status) {
+        switch (status) {
+            case IGNORED_BACKGROUND:
+                Slog.e(TAG, "Ignoring incoming vibration as process with"
+                        + " uid= " + uid + " is background," + " attrs= " + attrs);
+                break;
+            case IGNORED_ERROR_APP_OPS:
+                Slog.w(TAG, "Would be an error: vibrate from uid " + uid);
+                break;
+            case IGNORED_FOR_ALARM:
+                if (DEBUG) {
+                    Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
+                }
+                break;
+            case IGNORED_FOR_EXTERNAL:
+                if (DEBUG) {
+                    Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
+                }
+                break;
+            case IGNORED_FOR_ONGOING:
+                if (DEBUG) {
+                    Slog.d(TAG, "Ignoring incoming vibration in favor of repeating vibration");
+                }
+                break;
+            case IGNORED_FOR_RINGER_MODE:
+                if (DEBUG) {
+                    Slog.d(TAG, "Ignoring incoming vibration because of ringer mode, attrs="
+                            + attrs);
+                }
+                break;
+            default:
+                if (DEBUG) {
+                    Slog.d(TAG, "Vibration for uid=" + uid + " and with attrs=" + attrs
+                            + " ended with status " + status);
+                }
+        }
+    }
+
     @GuardedBy("mLock")
     private void reportFinishedVibrationLocked(Vibration.Status status) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
@@ -651,32 +758,36 @@
     }
 
     /**
-     * Check if given vibration should be ignored in favour of one of the vibrations currently
-     * running on the same vibrators.
+     * Check if given vibration should be ignored by this service because of the ongoing vibration.
      *
-     * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored.
+     * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored, null
+     * otherwise.
      */
     @GuardedBy("mLock")
     @Nullable
-    private Vibration.Status shouldIgnoreVibrationForCurrentLocked(Vibration vibration) {
-        if (vibration.isRepeating()) {
-            // Repeating vibrations always take precedence.
+    private Vibration.Status shouldIgnoreVibrationForOngoingLocked(Vibration vib) {
+        if (mCurrentExternalVibration != null) {
+            // If something has external control of the vibrator, assume that it's more important.
+            return Vibration.Status.IGNORED_FOR_EXTERNAL;
+        }
+
+        if (mCurrentVibration == null || vib.isRepeating()) {
+            // Incoming repeating vibrations always take precedence over ongoing vibrations.
             return null;
         }
-        if (mCurrentVibration != null && !mCurrentVibration.getVibration().hasEnded()) {
-            if (mCurrentVibration.getVibration().attrs.getUsage()
-                    == VibrationAttributes.USAGE_ALARM) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
-                }
-                return Vibration.Status.IGNORED_FOR_ALARM;
-            }
-            if (mCurrentVibration.getVibration().isRepeating()) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Ignoring incoming vibration in favor of repeating vibration");
-                }
-                return Vibration.Status.IGNORED_FOR_ONGOING;
-            }
+
+        Vibration currentVibration = mCurrentVibration.getVibration();
+        if (currentVibration.hasEnded()) {
+            // Current vibration is finishing up, it should not block incoming vibrations.
+            return null;
+        }
+
+        if (currentVibration.attrs.getUsage() == VibrationAttributes.USAGE_ALARM) {
+            return Vibration.Status.IGNORED_FOR_ALARM;
+        }
+
+        if (currentVibration.isRepeating()) {
+            return Vibration.Status.IGNORED_FOR_ONGOING;
         }
         return null;
     }
@@ -684,57 +795,16 @@
     /**
      * Check if given vibration should be ignored by this service.
      *
-     * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored.
-     * @see #shouldIgnoreVibrationLocked(int, String, VibrationAttributes)
-     */
-    @GuardedBy("mLock")
-    @Nullable
-    private Vibration.Status shouldIgnoreVibrationLocked(Vibration vib) {
-        // If something has external control of the vibrator, assume that it's more important.
-        if (mCurrentExternalVibration != null) {
-            if (DEBUG) {
-                Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
-            }
-            return Vibration.Status.IGNORED_FOR_EXTERNAL;
-        }
-
-        if (!mVibrationSettings.shouldVibrateForUid(vib.uid, vib.attrs.getUsage())) {
-            Slog.e(TAG, "Ignoring incoming vibration as process with"
-                    + " uid= " + vib.uid + " is background,"
-                    + " attrs= " + vib.attrs);
-            return Vibration.Status.IGNORED_BACKGROUND;
-        }
-
-        return shouldIgnoreVibrationLocked(vib.uid, vib.opPkg, vib.attrs);
-    }
-
-    /**
-     * Check if a vibration with given {@code uid}, {@code opPkg} and {@code attrs} should be
-     * ignored by this service.
-     *
-     * @param uid   The user id of this vibration
-     * @param opPkg The package name of this vibration
-     * @param attrs The attributes of this vibration
-     * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored.
+     * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored, null
+     * otherwise.
      */
     @GuardedBy("mLock")
     @Nullable
     private Vibration.Status shouldIgnoreVibrationLocked(int uid, String opPkg,
             VibrationAttributes attrs) {
-        if (!mVibrationSettings.shouldVibrateForPowerMode(attrs.getUsage())) {
-            return Vibration.Status.IGNORED_FOR_POWER;
-        }
-
-        int intensity = mVibrationSettings.getCurrentIntensity(attrs.getUsage());
-        if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
-            return Vibration.Status.IGNORED_FOR_SETTINGS;
-        }
-
-        if (!mVibrationSettings.shouldVibrateForRingerMode(attrs.getUsage())) {
-            if (DEBUG) {
-                Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
-            }
-            return Vibration.Status.IGNORED_RINGTONE;
+        Vibration.Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(uid, attrs);
+        if (statusFromSettings != null) {
+            return statusFromSettings;
         }
 
         int mode = checkAppOpModeLocked(uid, opPkg, attrs);
@@ -742,7 +812,6 @@
             if (mode == AppOpsManager.MODE_ERRORED) {
                 // We might be getting calls from within system_server, so we don't actually
                 // want to throw a SecurityException here.
-                Slog.w(TAG, "Would be an error: vibrate from uid " + uid);
                 return Vibration.Status.IGNORED_ERROR_APP_OPS;
             } else {
                 return Vibration.Status.IGNORED_APP_OPS;
@@ -1236,21 +1305,18 @@
     }
 
     /** Keep records of vibrations played and provide debug information for this service. */
-    private final class VibratorManagerRecords {
-        @GuardedBy("mLock")
+    private static final class VibratorManagerRecords {
         private final SparseArray<LinkedList<Vibration.DebugInfo>> mPreviousVibrations =
                 new SparseArray<>();
-        @GuardedBy("mLock")
         private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations =
                 new LinkedList<>();
         private final int mPreviousVibrationsLimit;
 
-        private VibratorManagerRecords(int limit) {
+        VibratorManagerRecords(int limit) {
             mPreviousVibrationsLimit = limit;
         }
 
-        @GuardedBy("mLock")
-        void record(Vibration vib) {
+        synchronized void record(Vibration vib) {
             int usage = vib.attrs.getUsage();
             if (!mPreviousVibrations.contains(usage)) {
                 mPreviousVibrations.put(usage, new LinkedList<>());
@@ -1258,122 +1324,67 @@
             record(mPreviousVibrations.get(usage), vib.getDebugInfo());
         }
 
-        @GuardedBy("mLock")
-        void record(ExternalVibrationHolder vib) {
+        synchronized void record(ExternalVibrationHolder vib) {
             record(mPreviousExternalVibrations, vib.getDebugInfo());
         }
 
-        @GuardedBy("mLock")
-        void record(LinkedList<Vibration.DebugInfo> records, Vibration.DebugInfo info) {
+        synchronized void record(LinkedList<Vibration.DebugInfo> records,
+                Vibration.DebugInfo info) {
             if (records.size() > mPreviousVibrationsLimit) {
                 records.removeFirst();
             }
             records.addLast(info);
         }
 
-        void dumpText(PrintWriter pw) {
-            pw.println("Vibrator Manager Service:");
-            synchronized (mLock) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Dumping vibrator manager service to text...");
-                }
-                pw.println("  mVibrationSettings:");
-                pw.println("    " + mVibrationSettings);
+        synchronized void dumpText(PrintWriter pw) {
+            for (int i = 0; i < mPreviousVibrations.size(); i++) {
                 pw.println();
-                pw.println("  mVibratorControllers:");
-                for (int i = 0; i < mVibrators.size(); i++) {
-                    pw.println("    " + mVibrators.valueAt(i));
-                }
-                pw.println();
-                pw.println("  mCurrentVibration:");
-                pw.println("    " + (mCurrentVibration == null
-                        ? null : mCurrentVibration.getVibration().getDebugInfo()));
-                pw.println();
-                pw.println("  mNextVibration:");
-                pw.println("    " + (mNextVibration == null
-                        ? null : mNextVibration.getVibration().getDebugInfo()));
-                pw.println();
-                pw.println("  mCurrentExternalVibration:");
-                pw.println("    " + (mCurrentExternalVibration == null
-                        ? null : mCurrentExternalVibration.getDebugInfo()));
-                pw.println();
-                for (int i = 0; i < mPreviousVibrations.size(); i++) {
-                    pw.println();
-                    pw.print("  Previous vibrations for usage ");
-                    pw.print(VibrationAttributes.usageToString(mPreviousVibrations.keyAt(i)));
-                    pw.println(":");
-                    for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
-                        pw.println("    " + info);
-                    }
-                }
-
-                pw.println();
-                pw.println("  Previous external vibrations:");
-                for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
+                pw.print("  Previous vibrations for usage ");
+                pw.print(VibrationAttributes.usageToString(mPreviousVibrations.keyAt(i)));
+                pw.println(":");
+                for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
                     pw.println("    " + info);
                 }
             }
+
+            pw.println();
+            pw.println("  Previous external vibrations:");
+            for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
+                pw.println("    " + info);
+            }
         }
 
-        synchronized void dumpProto(FileDescriptor fd) {
-            final ProtoOutputStream proto = new ProtoOutputStream(fd);
-
-            synchronized (mLock) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Dumping vibrator manager service to proto...");
+        synchronized void dumpProto(ProtoOutputStream proto) {
+            for (int i = 0; i < mPreviousVibrations.size(); i++) {
+                long fieldId;
+                switch (mPreviousVibrations.keyAt(i)) {
+                    case VibrationAttributes.USAGE_RINGTONE:
+                        fieldId = VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS;
+                        break;
+                    case VibrationAttributes.USAGE_NOTIFICATION:
+                        fieldId = VibratorManagerServiceDumpProto
+                                .PREVIOUS_NOTIFICATION_VIBRATIONS;
+                        break;
+                    case VibrationAttributes.USAGE_ALARM:
+                        fieldId = VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS;
+                        break;
+                    default:
+                        fieldId = VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS;
                 }
-                mVibrationSettings.dumpProto(proto);
-                if (mCurrentVibration != null) {
-                    mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
-                            VibratorManagerServiceDumpProto.CURRENT_VIBRATION);
-                }
-                if (mCurrentExternalVibration != null) {
-                    mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
-                            VibratorManagerServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
-                }
-
-                boolean isVibrating = false;
-                boolean isUnderExternalControl = false;
-                for (int i = 0; i < mVibrators.size(); i++) {
-                    proto.write(VibratorManagerServiceDumpProto.VIBRATOR_IDS, mVibrators.keyAt(i));
-                    isVibrating |= mVibrators.valueAt(i).isVibrating();
-                    isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl();
-                }
-                proto.write(VibratorManagerServiceDumpProto.IS_VIBRATING, isVibrating);
-                proto.write(VibratorManagerServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
-                        isUnderExternalControl);
-
-                for (int i = 0; i < mPreviousVibrations.size(); i++) {
-                    long fieldId;
-                    switch (mPreviousVibrations.keyAt(i)) {
-                        case VibrationAttributes.USAGE_RINGTONE:
-                            fieldId = VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS;
-                            break;
-                        case VibrationAttributes.USAGE_NOTIFICATION:
-                            fieldId = VibratorManagerServiceDumpProto
-                                    .PREVIOUS_NOTIFICATION_VIBRATIONS;
-                            break;
-                        case VibrationAttributes.USAGE_ALARM:
-                            fieldId = VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS;
-                            break;
-                        default:
-                            fieldId = VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS;
-                    }
-                    for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
-                        info.dumpProto(proto, fieldId);
-                    }
-                }
-
-                for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
-                    info.dumpProto(proto,
-                            VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+                for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
+                    info.dumpProto(proto, fieldId);
                 }
             }
-            proto.flush();
+
+            for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
+                info.dumpProto(proto,
+                        VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+            }
         }
     }
 
     /** Clears mNextVibration if set, ending it cleanly */
+    @GuardedBy("mLock")
     private void clearNextVibrationLocked(Vibration.Status endStatus) {
         if (mNextVibration != null) {
             endVibrationLocked(mNextVibration.getVibration(), endStatus);
@@ -1482,6 +1493,7 @@
             }
         }
 
+        @GuardedBy("mLock")
         private void stopExternalVibrateLocked(Vibration.Status status) {
             Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "stopExternalVibrateLocked");
             try {
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index 00e1f55..d736ede 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
+import android.app.TaskInfo;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ResolveInfo;
@@ -40,6 +41,12 @@
     public abstract @Nullable Intent intercept(ActivityInterceptorInfo info);
 
     /**
+     * Called when an activity is successfully launched.
+     */
+    public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo) {
+    }
+
+    /**
      * The unique id of each interceptor which determines the order it will execute in.
      */
     @IntDef(suffix = { "_ORDERED_ID" }, value = {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 100c44b..2b28478 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -830,6 +830,8 @@
     // SystemUi sets the pinned mode on activity after transition is done.
     boolean mWaitForEnteringPinnedMode;
 
+    private final ActivityRecordInputSink mActivityRecordInputSink;
+
     private final Runnable mPauseTimeoutRunnable = new Runnable() {
         @Override
         public void run() {
@@ -1046,6 +1048,8 @@
                 pw.print(" forceNewConfig="); pw.println(forceNewConfig);
         pw.print(prefix); pw.print("mActivityType=");
                 pw.println(activityTypeToString(getActivityType()));
+        pw.print(prefix); pw.print("mImeInsetsFrozenUntilStartInput=");
+                pw.println(mImeInsetsFrozenUntilStartInput);
         if (requestedVrComponent != null) {
             pw.print(prefix);
             pw.print("requestedVrComponent=");
@@ -1785,6 +1789,8 @@
             createTime = _createTime;
         }
         mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName);
+
+        mActivityRecordInputSink = new ActivityRecordInputSink(this);
     }
 
     /**
@@ -6771,6 +6777,10 @@
             } else if (!show && mLastSurfaceShowing) {
                 getSyncTransaction().hide(mSurfaceControl);
             }
+            if (show) {
+                mActivityRecordInputSink.applyChangesToSurfaceIfChanged(
+                        getSyncTransaction(), mSurfaceControl);
+            }
         }
         if (mThumbnail != null) {
             mThumbnail.setShowing(getPendingTransaction(), show);
@@ -7996,7 +8006,8 @@
         if (mVisibleRequested) {
             // It may toggle the UI for user to restart the size compatibility mode activity.
             display.handleActivitySizeCompatModeIfNeeded(this);
-        } else if (mCompatDisplayInsets != null && !visibleIgnoringKeyguard) {
+        } else if (mCompatDisplayInsets != null && !visibleIgnoringKeyguard
+                && (app == null || !app.hasVisibleActivities())) {
             // visibleIgnoringKeyguard is checked to avoid clearing mCompatDisplayInsets during
             // displays change. Displays are turned off during the change so mVisibleRequested
             // can be false.
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
new file mode 100644
index 0000000..b183281
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -0,0 +1,171 @@
+/*
+ * 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.wm;
+
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
+import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
+
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.os.IBinder;
+import android.os.InputConstants;
+import android.os.Looper;
+import android.util.Slog;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputWindowHandle;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+/**
+ * Creates a InputWindowHandle that catches all touches that would otherwise pass through an
+ * Activity.
+ */
+class ActivityRecordInputSink {
+
+    /**
+     * Feature flag for making Activities consume all touches within their task bounds.
+     */
+    @ChangeId
+    @Disabled
+    static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L;
+
+    private static final String TAG = "ActivityRecordInputSink";
+    private static final int NUMBER_OF_TOUCHES_TO_DISABLE = 3;
+    private static final long TOAST_COOL_DOWN_MILLIS = 3000L;
+
+    private final ActivityRecord mActivityRecord;
+    private final boolean mIsCompatEnabled;
+
+    // Hold on to InputEventReceiver to prevent it from getting GCd.
+    private InputEventReceiver mInputEventReceiver;
+    private InputWindowHandleWrapper mInputWindowHandleWrapper;
+    private final String mName = Integer.toHexString(System.identityHashCode(this))
+            + " ActivityRecordInputSink";
+    private int mRapidTouchCount = 0;
+    private IBinder mToken;
+    private boolean mDisabled = false;
+
+    ActivityRecordInputSink(ActivityRecord activityRecord) {
+        mActivityRecord = activityRecord;
+        mIsCompatEnabled = CompatChanges.isChangeEnabled(ENABLE_TOUCH_OPAQUE_ACTIVITIES,
+                mActivityRecord.getUid());
+    }
+
+    public void applyChangesToSurfaceIfChanged(
+            SurfaceControl.Transaction transaction, SurfaceControl surfaceControl) {
+        InputWindowHandleWrapper inputWindowHandleWrapper = getInputWindowHandleWrapper();
+        if (inputWindowHandleWrapper.isChanged()) {
+            inputWindowHandleWrapper.applyChangesToSurface(transaction, surfaceControl);
+        }
+    }
+
+    private InputWindowHandleWrapper getInputWindowHandleWrapper() {
+        if (mInputWindowHandleWrapper == null) {
+            mInputWindowHandleWrapper = new InputWindowHandleWrapper(createInputWindowHandle());
+            InputChannel inputChannel =
+                    mActivityRecord.mWmService.mInputManager.createInputChannel(mName);
+            mToken = inputChannel.getToken();
+            mInputEventReceiver = createInputEventReceiver(inputChannel);
+        }
+        if (mDisabled || !mIsCompatEnabled || mActivityRecord.isAnimating(TRANSITION | PARENTS,
+                ANIMATION_TYPE_APP_TRANSITION)) {
+            // TODO(b/208662670): Investigate if we can have feature active during animations.
+            mInputWindowHandleWrapper.setToken(null);
+        } else if (mActivityRecord.mStartingData != null) {
+            // TODO(b/208659130): Remove this special case
+            // Don't block touches during splash screen. This is done to not show toasts for
+            // touches passing through splash screens. b/171772640
+            mInputWindowHandleWrapper.setToken(null);
+        } else {
+            mInputWindowHandleWrapper.setToken(mToken);
+        }
+        return mInputWindowHandleWrapper;
+    }
+
+    private InputWindowHandle createInputWindowHandle() {
+        InputWindowHandle inputWindowHandle = new InputWindowHandle(
+                mActivityRecord.getInputApplicationHandle(false),
+                mActivityRecord.getDisplayId());
+        inputWindowHandle.replaceTouchableRegionWithCrop(
+                mActivityRecord.getParentSurfaceControl());
+        inputWindowHandle.name = mName;
+        inputWindowHandle.ownerUid = mActivityRecord.getUid();
+        inputWindowHandle.ownerPid = mActivityRecord.getPid();
+        inputWindowHandle.layoutParamsFlags =
+                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+        inputWindowHandle.dispatchingTimeoutMillis =
+                InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+        return inputWindowHandle;
+    }
+
+    private InputEventReceiver createInputEventReceiver(InputChannel inputChannel) {
+        return new SinkInputEventReceiver(inputChannel,
+                mActivityRecord.mAtmService.mUiHandler.getLooper());
+    }
+
+    private void showAsToastAndLog(String message) {
+        Toast.makeText(mActivityRecord.mAtmService.mUiContext, message,
+                Toast.LENGTH_LONG).show();
+        Slog.wtf(TAG, message + " " + mActivityRecord.mActivityComponent);
+    }
+
+    private class SinkInputEventReceiver extends InputEventReceiver {
+        private long mLastToast = 0;
+
+        SinkInputEventReceiver(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
+        public void onInputEvent(InputEvent event) {
+            if (!(event instanceof MotionEvent)) {
+                Slog.wtf(TAG,
+                        "Received InputEvent that was not a MotionEvent");
+                finishInputEvent(event, true);
+                return;
+            }
+            MotionEvent motionEvent = (MotionEvent) event;
+            if (motionEvent.getAction() != MotionEvent.ACTION_DOWN) {
+                finishInputEvent(event, true);
+                return;
+            }
+
+            if (event.getEventTime() - mLastToast > TOAST_COOL_DOWN_MILLIS) {
+                String message = "go/activity-touch-opaque - "
+                        + mActivityRecord.mActivityComponent.getPackageName()
+                        + " blocked the touch!";
+                showAsToastAndLog(message);
+                mLastToast = event.getEventTime();
+                mRapidTouchCount = 1;
+            } else if (++mRapidTouchCount >= NUMBER_OF_TOUCHES_TO_DISABLE && !mDisabled) {
+                // Disable touch blocking until Activity Record is recreated.
+                String message = "Disabled go/activity-touch-opaque - "
+                        + mActivityRecord.mActivityComponent.getPackageName();
+                showAsToastAndLog(message);
+                mDisabled = true;
+            }
+            finishInputEvent(event, true);
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 223f0be..352a070 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -37,6 +37,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.app.KeyguardManager;
+import android.app.TaskInfo;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.Context;
 import android.content.IIntentSender;
@@ -402,4 +403,16 @@
         mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
         return true;
     }
+
+    /**
+     * Called when an activity is successfully launched.
+     */
+    void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo) {
+        final SparseArray<ActivityInterceptorCallback> callbacks =
+                mService.getActivityInterceptorCallbacks();
+        for (int i = 0; i < callbacks.size(); i++) {
+            final ActivityInterceptorCallback callback = callbacks.valueAt(i);
+            callback.onActivityLaunched(taskInfo, activityInfo);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index bb7434d..471b4ce 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1559,6 +1559,10 @@
             mService.getTaskChangeNotificationController().notifyActivityRestartAttempt(
                     targetTask.getTaskInfo(), homeTaskVisible, clearedTask, visible);
         }
+
+        if (ActivityManager.isStartResultSuccessful(result)) {
+            mInterceptor.onActivityLaunched(targetTask.getTaskInfo(), r.info);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index e38e9c1..7fa9861 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -126,6 +126,35 @@
     }
 
     /**
+     * Sleep tokens cause the activity manager to put the top activity to sleep.
+     * They are used by components such as dreams that may hide and block interaction
+     * with underlying activities.
+     * The Acquirer provides an interface that encapsulates the underlying work, so the user does
+     * not need to handle the token by him/herself.
+     */
+    public interface SleepTokenAcquirer {
+
+        /**
+         * Acquires a sleep token.
+         * @param displayId The display to apply to.
+         */
+        void acquire(int displayId);
+
+        /**
+         * Releases the sleep token.
+         * @param displayId The display to apply to.
+         */
+        void release(int displayId);
+    }
+
+    /**
+     * Creates a sleep token acquirer for the specified display with the specified tag.
+     *
+     * @param tag A string identifying the purpose (eg. "Dream").
+     */
+    public abstract SleepTokenAcquirer createSleepTokenAcquirer(@NonNull String tag);
+
+    /**
      * Returns home activity for the specified user.
      *
      * @param userId ID of the user or {@link android.os.UserHandle#USER_ALL}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index e4ed04de..0a85ba1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4544,25 +4544,17 @@
                 reason);
     }
 
-    /**
-     * Sleep tokens cause the activity manager to put the top activity to sleep.
-     * They are used by components such as dreams that may hide and block interaction
-     * with underlying activities.
-     */
-    final class SleepTokenAcquirer {
+    final class SleepTokenAcquirerImpl implements ActivityTaskManagerInternal.SleepTokenAcquirer {
         private final String mTag;
         private final SparseArray<RootWindowContainer.SleepToken> mSleepTokens =
                 new SparseArray<>();
 
-        SleepTokenAcquirer(@NonNull String tag) {
+        SleepTokenAcquirerImpl(@NonNull String tag) {
             mTag = tag;
         }
 
-        /**
-         * Acquires a sleep token.
-         * @param displayId The display to apply to.
-         */
-        void acquire(int displayId) {
+        @Override
+        public void acquire(int displayId) {
             synchronized (mGlobalLock) {
                 if (!mSleepTokens.contains(displayId)) {
                     mSleepTokens.append(displayId,
@@ -4572,11 +4564,8 @@
             }
         }
 
-        /**
-         * Releases the sleep token.
-         * @param displayId The display to apply to.
-         */
-        void release(int displayId) {
+        @Override
+        public void release(int displayId) {
             synchronized (mGlobalLock) {
                 final RootWindowContainer.SleepToken token = mSleepTokens.get(displayId);
                 if (token != null) {
@@ -5266,6 +5255,12 @@
 
     final class LocalService extends ActivityTaskManagerInternal {
         @Override
+        public SleepTokenAcquirer createSleepTokenAcquirer(@NonNull String tag) {
+            Objects.requireNonNull(tag);
+            return new SleepTokenAcquirerImpl(tag);
+        }
+
+        @Override
         public ComponentName getHomeActivityForUser(int userId) {
             synchronized (mGlobalLock) {
                 final ActivityRecord homeActivity =
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3b4a31b..0956580 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -664,7 +664,7 @@
     /** All tokens used to put activities on this root task to sleep (including mOffToken) */
     final ArrayList<RootWindowContainer.SleepToken> mAllSleepTokens = new ArrayList<>();
     /** The token acquirer to put root tasks on the display to sleep */
-    private final ActivityTaskManagerService.SleepTokenAcquirer mOffTokenAcquirer;
+    private final ActivityTaskManagerInternal.SleepTokenAcquirer mOffTokenAcquirer;
 
     private boolean mSleeping;
 
@@ -1585,8 +1585,9 @@
 
     @Override
     boolean isSyncFinished() {
-        if (mDisplayRotation.isWaitingForRemoteRotation()) return false;
-        return super.isSyncFinished();
+        // Do not consider children because if they are requested to be synced, they should be
+        // added to sync group explicitly.
+        return !mDisplayRotation.isWaitingForRemoteRotation();
     }
 
     /**
@@ -1876,16 +1877,16 @@
         }
     }
 
-    /** Shows the given window which may be hidden for screen frozen. */
-    void finishFadeRotationAnimation(WindowState w) {
+    /** Shows the given window which may be hidden for screen rotation. */
+    void finishFadeRotationAnimation(WindowToken windowToken) {
         final FadeRotationAnimationController controller = mFadeRotationAnimationController;
-        if (controller != null && controller.show(w.mToken)) {
+        if (controller != null && controller.show(windowToken)) {
             mFadeRotationAnimationController = null;
         }
     }
 
-    /** Returns {@code true} if the display should wait for the given window to stop freezing. */
-    boolean waitForUnfreeze(WindowState w) {
+    /** Returns {@code true} if the screen rotation animation needs to wait for the window. */
+    boolean shouldSyncRotationChange(WindowState w) {
         if (w.mForceSeamlesslyRotate) {
             // The window should look no different before and after rotation.
             return false;
@@ -3230,6 +3231,7 @@
                 mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
                 controller.mTransitionMetricsReporter.associate(t,
                         startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
+                startFadeRotationAnimation(false /* shouldDebounce */);
             }
             t.setKnownConfigChanges(this, changes);
         }
@@ -4136,11 +4138,11 @@
      * which controls the visibility and animation of the input method window.
      */
     void updateImeInputAndControlTarget(WindowState target) {
+        if (target != null && target.mActivityRecord != null) {
+            target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false;
+        }
         if (mImeInputTarget != target) {
             ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target);
-            if (target != null && target.mActivityRecord != null) {
-                target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false;
-            }
             setImeInputTarget(target);
             mInsetsStateController.updateAboveInsetsState(mInputMethodWindow, mInsetsStateController
                     .getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
@@ -5479,14 +5481,6 @@
         return mMetricsLogger;
     }
 
-    void acquireScreenOffToken(boolean acquire) {
-        if (acquire) {
-            mOffTokenAcquirer.acquire(mDisplayId);
-        } else {
-            mOffTokenAcquirer.release(mDisplayId);
-        }
-    }
-
     void onDisplayChanged() {
         mDisplay.getRealSize(mTmpDisplaySize);
         setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
@@ -5498,9 +5492,9 @@
         final int displayState = mDisplayInfo.state;
         if (displayId != DEFAULT_DISPLAY) {
             if (displayState == Display.STATE_OFF) {
-                acquireScreenOffToken(true /* acquire */);
+                mOffTokenAcquirer.acquire(mDisplayId);
             } else if (displayState == Display.STATE_ON) {
-                acquireScreenOffToken(false /* acquire */);
+                mOffTokenAcquirer.release(mDisplayId);
             }
             ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
                     "Display %d state is now (%d), so update layer mirroring?",
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 7a2a311..71ab5b6 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -752,10 +752,6 @@
 
     public void setAwake(boolean awake) {
         mAwake = awake;
-        // The screen off token for non-default display is controlled by DisplayContent.
-        if (mDisplayContent.isDefaultDisplay) {
-            mDisplayContent.acquireScreenOffToken(!awake);
-        }
     }
 
     public boolean isAwake() {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 427bbeb..4684cb1 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -508,10 +508,10 @@
         mDisplayContent.setLayoutNeeded();
 
         if (useShellTransitions) {
-            final boolean wasInTransition = mDisplayContent.inTransition();
+            final boolean wasCollecting = mDisplayContent.mTransitionController.isCollecting();
             mDisplayContent.requestChangeTransitionIfNeeded(
                     ActivityInfo.CONFIG_WINDOW_CONFIGURATION);
-            if (wasInTransition) {
+            if (wasCollecting) {
                 // Use remote-rotation infra since the transition has already been requested
                 // TODO(shell-transitions): Remove this once lifecycle management can cover all
                 //                          rotation cases.
@@ -595,12 +595,8 @@
                 // Go through all tasks and collect them before the rotation
                 // TODO(shell-transitions): move collect() to onConfigurationChange once wallpaper
                 //       handling is synchronized.
-                mDisplayContent.forAllTasks(task -> {
-                    if (task.isVisible()) {
-                        mDisplayContent.mTransitionController.collect(task);
-                    }
-                });
-                mDisplayContent.getInsetsStateController().addProvidersToTransition();
+                mDisplayContent.mTransitionController.collectForDisplayChange(mDisplayContent,
+                        null /* use collecting transition */);
             }
             mService.mAtmService.deferWindowLayout();
             try {
diff --git a/services/core/java/com/android/server/wm/FadeAnimationController.java b/services/core/java/com/android/server/wm/FadeAnimationController.java
index 2f3ad40..817b27a 100644
--- a/services/core/java/com/android/server/wm/FadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/FadeAnimationController.java
@@ -36,10 +36,12 @@
  * An animation controller to fade-in/out for a window token.
  */
 public class FadeAnimationController {
+    protected final DisplayContent mDisplayContent;
     protected final Context mContext;
     protected final ArrayMap<WindowToken, Runnable> mDeferredFinishCallbacks = new ArrayMap<>();
 
     public FadeAnimationController(DisplayContent displayContent) {
+        mDisplayContent = displayContent;
         mContext = displayContent.mWmService.mContext;
     }
 
@@ -69,7 +71,9 @@
             return;
         }
 
-        final FadeAnimationAdapter animationAdapter = createAdapter(show, windowToken);
+        final Animation animation = show ? getFadeInAnimation() : getFadeOutAnimation();
+        final FadeAnimationAdapter animationAdapter = animation != null
+                ? createAdapter(createAnimationSpec(animation), show, windowToken) : null;
         if (animationAdapter == null) {
             return;
         }
@@ -86,17 +90,10 @@
                 show /* hidden */, animationType, finishedCallback);
     }
 
-    protected FadeAnimationAdapter createAdapter(boolean show, WindowToken windowToken) {
-        final Animation animation = show ? getFadeInAnimation() : getFadeOutAnimation();
-        if (animation == null) {
-            return null;
-        }
-
-        final LocalAnimationAdapter.AnimationSpec windowAnimationSpec =
-                createAnimationSpec(animation);
-
-        return new FadeAnimationAdapter(
-                windowAnimationSpec, windowToken.getSurfaceAnimationRunner(), show, windowToken);
+    protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec,
+            boolean show, WindowToken windowToken) {
+        return new FadeAnimationAdapter(animationSpec, windowToken.getSurfaceAnimationRunner(),
+                show, windowToken);
     }
 
     protected LocalAnimationAdapter.AnimationSpec createAnimationSpec(
@@ -140,7 +137,7 @@
 
     protected class FadeAnimationAdapter extends LocalAnimationAdapter {
         protected final boolean mShow;
-        private final WindowToken mToken;
+        protected final WindowToken mToken;
 
         FadeAnimationAdapter(AnimationSpec windowAnimationSpec,
                 SurfaceAnimationRunner surfaceAnimationRunner, boolean show,
diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
index 52a7ac7..cf36c85 100644
--- a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
+++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
@@ -18,6 +18,10 @@
 
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
 
+import android.os.HandlerExecutor;
+import android.util.ArrayMap;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
@@ -33,10 +37,11 @@
  */
 public class FadeRotationAnimationController extends FadeAnimationController {
 
-    private final ArrayList<WindowToken> mTargetWindowTokens = new ArrayList<>();
+    /** The map of window token to its animation leash. */
+    private final ArrayMap<WindowToken, SurfaceControl> mTargetWindowTokens = new ArrayMap<>();
     private final WindowManagerService mService;
     /** If non-null, it usually indicates that there will be a screen rotation animation. */
-    private final Runnable mFrozenTimeoutRunnable;
+    private final Runnable mTimeoutRunnable;
     private final WindowToken mNavBarToken;
 
     /** A runnable which gets called when the {@link #show()} is called. */
@@ -45,16 +50,30 @@
     /** Whether to use constant zero alpha animation. */
     private boolean mHideImmediately;
 
+    /** Whether this controller is triggered from shell transition. */
+    private final boolean mIsChangeTransition;
+
+    /** Whether the start transaction of the transition is committed (by shell). */
+    private boolean mIsStartTransactionCommitted;
+
+    /** The list to store the drawn tokens before the rotation animation starts. */
+    private ArrayList<WindowToken> mPendingShowTokens;
+
     public FadeRotationAnimationController(DisplayContent displayContent) {
         super(displayContent);
         mService = displayContent.mWmService;
-        mFrozenTimeoutRunnable = mService.mDisplayFrozen ? () -> {
+        mIsChangeTransition = displayContent.inTransition()
+                && displayContent.mTransitionController.getCollectingTransitionType()
+                == WindowManager.TRANSIT_CHANGE;
+        mIsStartTransactionCommitted = !mIsChangeTransition;
+        mTimeoutRunnable = displayContent.getRotationAnimation() != null
+                || mIsChangeTransition ? () -> {
             synchronized (mService.mGlobalLock) {
                 displayContent.finishFadeRotationAnimationIfPossible();
                 mService.mWindowPlacerLocked.performSurfacePlacement();
             }
         } : null;
-        if (mFrozenTimeoutRunnable != null) {
+        if (mTimeoutRunnable != null) {
             // Hide the windows immediately because screen should have been covered by screenshot.
             mHideImmediately = true;
         }
@@ -68,7 +87,7 @@
             // Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation
             // bar is currently controlled by recents animation.
             if (!displayPolicy.navigationBarCanMove() && !navBarControlledByRecents) {
-                mTargetWindowTokens.add(mNavBarToken);
+                mTargetWindowTokens.put(mNavBarToken, null);
             }
         } else {
             mNavBarToken = null;
@@ -79,7 +98,7 @@
             if (w.mActivityRecord == null && w.mHasSurface && !w.mForceSeamlesslyRotate
                     && !w.mIsWallpaper && !w.mIsImWindow && w != navigationBar
                     && w != notificationShade) {
-                mTargetWindowTokens.add(w.mToken);
+                mTargetWindowTokens.put(w.mToken, null);
             }
         }, true /* traverseTopToBottom */);
     }
@@ -87,12 +106,13 @@
     /** Applies show animation on the previously hidden window tokens. */
     void show() {
         for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
-            final WindowToken windowToken = mTargetWindowTokens.get(i);
+            final WindowToken windowToken = mTargetWindowTokens.keyAt(i);
             fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
         }
         mTargetWindowTokens.clear();
-        if (mFrozenTimeoutRunnable != null) {
-            mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
+        mPendingShowTokens = null;
+        if (mTimeoutRunnable != null) {
+            mService.mH.removeCallbacks(mTimeoutRunnable);
         }
         if (mOnShowRunnable != null) {
             mOnShowRunnable.run();
@@ -105,10 +125,22 @@
      * controller is created for normal rotation.
      */
     boolean show(WindowToken token) {
-        if (mFrozenTimeoutRunnable != null && mTargetWindowTokens.remove(token)) {
+        if (!mIsStartTransactionCommitted) {
+            // The fade-in animation should only start after the screenshot layer is shown by shell.
+            // Otherwise the window will be blinking before the rotation animation starts. So store
+            // to a pending list and animate them until the transaction is committed.
+            if (mTargetWindowTokens.containsKey(token)) {
+                if (mPendingShowTokens == null) {
+                    mPendingShowTokens = new ArrayList<>();
+                }
+                mPendingShowTokens.add(token);
+            }
+            return false;
+        }
+        if (mTimeoutRunnable != null && mTargetWindowTokens.remove(token) != null) {
             fadeWindowToken(true /* show */, token, ANIMATION_TYPE_FIXED_TRANSFORM);
             if (mTargetWindowTokens.isEmpty()) {
-                mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
+                mService.mH.removeCallbacks(mTimeoutRunnable);
                 return true;
             }
         }
@@ -118,11 +150,11 @@
     /** Applies hide animation on the window tokens which may be seamlessly rotated later. */
     void hide() {
         for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
-            final WindowToken windowToken = mTargetWindowTokens.get(i);
+            final WindowToken windowToken = mTargetWindowTokens.keyAt(i);
             fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
         }
-        if (mFrozenTimeoutRunnable != null) {
-            mService.mH.postDelayed(mFrozenTimeoutRunnable,
+        if (mTimeoutRunnable != null) {
+            mService.mH.postDelayed(mTimeoutRunnable,
                     WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
         }
     }
@@ -131,7 +163,6 @@
     void hideImmediately(WindowToken windowToken) {
         final boolean original = mHideImmediately;
         mHideImmediately = true;
-        mTargetWindowTokens.add(windowToken);
         fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
         mHideImmediately = original;
     }
@@ -143,16 +174,43 @@
 
     /** Returns {@code true} if the controller will run fade animations on the window. */
     boolean isTargetToken(WindowToken token) {
-        return mTargetWindowTokens.contains(token);
+        return mTargetWindowTokens.containsKey(token);
     }
 
     void setOnShowRunnable(Runnable onShowRunnable) {
         mOnShowRunnable = onShowRunnable;
     }
 
+    /**
+     * Puts initial operation of leash to the transaction which will be executed when the
+     * transition starts. And associate transaction callback to consume pending animations.
+     */
+    void setupStartTransaction(SurfaceControl.Transaction t) {
+        // Hide the windows immediately because a screenshot layer should cover the screen.
+        for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+            final SurfaceControl leash = mTargetWindowTokens.valueAt(i);
+            if (leash != null) {
+                t.setAlpha(leash, 0f);
+            }
+        }
+        // If there are windows have redrawn in new rotation but the start transaction has not
+        // been applied yet, the fade-in animation will be deferred. So once the transaction is
+        // committed, the fade-in animation can run with screen rotation animation.
+        t.addTransactionCommittedListener(new HandlerExecutor(mService.mH), () -> {
+            synchronized (mService.mGlobalLock) {
+                mIsStartTransactionCommitted = true;
+                if (mPendingShowTokens == null) return;
+                for (int i = mPendingShowTokens.size() - 1; i >= 0; i--) {
+                    mDisplayContent.finishFadeRotationAnimation(mPendingShowTokens.get(i));
+                }
+                mPendingShowTokens = null;
+            }
+        });
+    }
+
     @Override
     public Animation getFadeInAnimation() {
-        if (mFrozenTimeoutRunnable != null) {
+        if (mTimeoutRunnable != null) {
             // Use a shorter animation so it is easier to align with screen rotation animation.
             return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter);
         }
@@ -162,8 +220,28 @@
     @Override
     public Animation getFadeOutAnimation() {
         if (mHideImmediately) {
-            return new AlphaAnimation(0 /* fromAlpha */, 0 /* toAlpha */);
+            // For change transition, the hide transaction needs to be applied with sync transaction
+            // (setupStartTransaction). So keep alpha 1 just to get the animation leash.
+            final float alpha = mIsChangeTransition ? 1 : 0;
+            return new AlphaAnimation(alpha /* fromAlpha */, alpha /* toAlpha */);
         }
         return super.getFadeOutAnimation();
     }
+
+    @Override
+    protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec,
+            boolean show, WindowToken windowToken) {
+        return new FadeAnimationAdapter(animationSpec,  windowToken.getSurfaceAnimationRunner(),
+                show, windowToken) {
+            @Override
+            public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
+                    int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+                // The fade cycle is done when showing, so only need to store the leash when hiding.
+                if (!show) {
+                    mTargetWindowTokens.put(mToken, animationLeash);
+                }
+                super.startAnimation(animationLeash, t, type, finishCallback);
+            }
+        };
+    }
 }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 405a9e5..e33c440 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -249,16 +249,6 @@
         return result;
     }
 
-    public void addProvidersToTransition() {
-        for (int i = mProviders.size() - 1; i >= 0; --i) {
-            final InsetsSourceProvider p = mProviders.valueAt(i);
-            if (p == null) continue;
-            final WindowContainer wc = p.mWin;
-            if (wc == null) continue;
-            mDisplayContent.mTransitionController.collect(wc);
-        }
-    }
-
     /**
      * @return The provider of a specific type.
      */
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index cabe414..baf7f87 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -75,14 +75,14 @@
     private final SparseArray<KeyguardDisplayState> mDisplayStates = new SparseArray<>();
     private final ActivityTaskManagerService mService;
     private RootWindowContainer mRootWindowContainer;
-    private final ActivityTaskManagerService.SleepTokenAcquirer mSleepTokenAcquirer;
+    private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
 
 
     KeyguardController(ActivityTaskManagerService service,
             ActivityTaskSupervisor taskSupervisor) {
         mService = service;
         mTaskSupervisor = taskSupervisor;
-        mSleepTokenAcquirer = mService.new SleepTokenAcquirer(KEYGUARD_SLEEP_TOKEN_TAG);
+        mSleepTokenAcquirer = mService.new SleepTokenAcquirerImpl(KEYGUARD_SLEEP_TOKEN_TAG);
     }
 
     void setWindowManager(WindowManagerService windowManager) {
@@ -518,10 +518,10 @@
 
         private boolean mRequestDismissKeyguard;
         private final ActivityTaskManagerService mService;
-        private final ActivityTaskManagerService.SleepTokenAcquirer mSleepTokenAcquirer;
+        private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
 
         KeyguardDisplayState(ActivityTaskManagerService service, int displayId,
-                ActivityTaskManagerService.SleepTokenAcquirer acquirer) {
+                ActivityTaskManagerInternal.SleepTokenAcquirer acquirer) {
             mService = service;
             mDisplayId = displayId;
             mSleepTokenAcquirer = acquirer;
diff --git a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
index 7abf3b8..af8293a 100644
--- a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
@@ -37,7 +37,6 @@
     private static final Interpolator FADE_OUT_INTERPOLATOR =
             new PathInterpolator(0.2f, 0f, 1f, 1f);
 
-    private DisplayContent mDisplayContent;
     private final WindowState mNavigationBar;
     private Animation mFadeInAnimation;
     private Animation mFadeOutAnimation;
@@ -47,7 +46,6 @@
 
     public NavBarFadeAnimationController(DisplayContent displayContent) {
         super(displayContent);
-        mDisplayContent = displayContent;
         mNavigationBar = displayContent.getDisplayPolicy().getNavigationBar();
         mFadeInAnimation = new AlphaAnimation(0f, 1f);
         mFadeInAnimation.setDuration(FADE_IN_DURATION);
@@ -69,16 +67,10 @@
     }
 
     @Override
-    protected FadeAnimationAdapter createAdapter(boolean show, WindowToken windowToken) {
-        final Animation animation = show ? getFadeInAnimation() : getFadeOutAnimation();
-        if (animation == null) {
-            return null;
-        }
-
-        final LocalAnimationAdapter.AnimationSpec windowAnimationSpec =
-                createAnimationSpec(animation);
+    protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec,
+            boolean show, WindowToken windowToken) {
         return new NavFadeAnimationAdapter(
-                windowAnimationSpec, windowToken.getSurfaceAnimationRunner(), show, windowToken,
+                animationSpec, windowToken.getSurfaceAnimationRunner(), show, windowToken,
                 show ? mFadeInParent : mFadeOutParent);
     }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 117b22a..c7b13eb 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -225,7 +225,7 @@
     private static final String DISPLAY_OFF_SLEEP_TOKEN_TAG = "Display-off";
 
     /** The token acquirer to put root tasks on the displays to sleep */
-    final ActivityTaskManagerService.SleepTokenAcquirer mDisplayOffTokenAcquirer;
+    final ActivityTaskManagerInternal.SleepTokenAcquirer mDisplayOffTokenAcquirer;
 
     /**
      * The modes which affect which tasks are returned when calling
@@ -470,7 +470,7 @@
         mService = service.mAtmService;
         mTaskSupervisor = mService.mTaskSupervisor;
         mTaskSupervisor.mRootWindowContainer = this;
-        mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirer(DISPLAY_OFF_SLEEP_TOKEN_TAG);
+        mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl(DISPLAY_OFF_SLEEP_TOKEN_TAG);
     }
 
     boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b328e4d..f0b55cb 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4600,23 +4600,14 @@
         moveToFront(reason, null);
     }
 
-    /**
-     * @param reason The reason for moving the root task to the front.
-     * @param task If non-null, the task will be moved to the top of the root task.
-     */
     void moveToFront(String reason, Task task) {
-        if (!isAttached()) {
-            return;
-        }
-
-        final TaskDisplayArea taskDisplayArea = getDisplayArea();
-
         if (inSplitScreenSecondaryWindowingMode()) {
             // If the root task is in split-screen secondary mode, we need to make sure we move the
             // primary split-screen root task forward in the case it is currently behind a
             // fullscreen root task so both halves of the split-screen appear on-top and the
             // fullscreen root task isn't cutting between them.
             // TODO(b/70677280): This is a workaround until we can fix as part of b/70677280.
+            final TaskDisplayArea taskDisplayArea = getDisplayArea();
             final Task topFullScreenRootTask =
                     taskDisplayArea.getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN);
             if (topFullScreenRootTask != null) {
@@ -4624,10 +4615,30 @@
                         taskDisplayArea.getRootSplitScreenPrimaryTask();
                 if (primarySplitScreenRootTask != null
                         && topFullScreenRootTask.compareTo(primarySplitScreenRootTask) > 0) {
-                    primarySplitScreenRootTask.moveToFront(reason + " splitScreenToTop");
+                    primarySplitScreenRootTask.moveToFrontInner(reason + " splitScreenToTop",
+                            null /* task */);
                 }
             }
+        } else if (mMoveAdjacentTogether && getAdjacentTaskFragment() != null) {
+            final Task adjacentTask = getAdjacentTaskFragment().asTask();
+            if (adjacentTask != null) {
+                adjacentTask.moveToFrontInner(reason + " adjacentTaskToTop", null /* task */);
+            }
         }
+        moveToFrontInner(reason, task);
+    }
+
+    /**
+     * @param reason The reason for moving the root task to the front.
+     * @param task If non-null, the task will be moved to the top of the root task.
+     */
+    @VisibleForTesting
+    void moveToFrontInner(String reason, Task task) {
+        if (!isAttached()) {
+            return;
+        }
+
+        final TaskDisplayArea taskDisplayArea = getDisplayArea();
 
         if (!isActivityTypeHome() && returnsToHomeRootTask()) {
             // Make sure the root home task is behind this root task since that is where we
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index e497b53..59a5cdf 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -168,6 +168,14 @@
     private TaskFragment mAdjacentTaskFragment;
 
     /**
+     * Whether to move adjacent task fragment together when re-positioning.
+     *
+     * @see #mAdjacentTaskFragment
+     */
+    // TODO(b/207185041): Remove this once having a single-top root for split screen.
+    boolean mMoveAdjacentTogether;
+
+    /**
      * Prevents duplicate calls to onTaskAppeared.
      */
     boolean mTaskFragmentAppearedSent;
@@ -309,14 +317,15 @@
         return service.mWindowOrganizerController.getTaskFragment(token);
     }
 
-    void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment) {
+    void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment, boolean moveTogether) {
         if (mAdjacentTaskFragment == taskFragment) {
             return;
         }
         resetAdjacentTaskFragment();
         if (taskFragment != null) {
             mAdjacentTaskFragment = taskFragment;
-            taskFragment.setAdjacentTaskFragment(this);
+            mMoveAdjacentTogether = moveTogether;
+            taskFragment.setAdjacentTaskFragment(this, moveTogether);
         }
     }
 
@@ -325,9 +334,11 @@
         if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) {
             mAdjacentTaskFragment.mAdjacentTaskFragment = null;
             mAdjacentTaskFragment.mDelayLastActivityRemoval = false;
+            mAdjacentTaskFragment.mMoveAdjacentTogether = false;
         }
         mAdjacentTaskFragment = null;
         mDelayLastActivityRemoval = false;
+        mMoveAdjacentTogether = false;
     }
 
     void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
@@ -1942,7 +1953,15 @@
 
             if (inOutConfig.smallestScreenWidthDp
                     == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
-                if (WindowConfiguration.isFloating(windowingMode)) {
+                // When entering to or exiting from Pip, the PipTaskOrganizer will set the
+                // windowing mode of the activity in the task to WINDOWING_MODE_FULLSCREEN and
+                // temporarily set the bounds of the task to fullscreen size for transitioning.
+                // It will get the wrong value if the calculation is based on this temporary
+                // fullscreen bounds.
+                // We should just inherit the value from parent for this temporary state.
+                final boolean inPipTransition = windowingMode == WINDOWING_MODE_PINNED
+                        && !mTmpFullBounds.isEmpty() && mTmpFullBounds.equals(parentBounds);
+                if (WindowConfiguration.isFloating(windowingMode) && !inPipTransition) {
                     // For floating tasks, calculate the smallest width from the bounds of the task
                     inOutConfig.smallestScreenWidthDp = (int) (
                             Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 7349594..3974747 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -596,6 +596,12 @@
             }
         }
 
+        // This is non-null only if display has changes. It handles the visible windows that don't
+        // need to be participated in the transition.
+        final FadeRotationAnimationController controller = dc.getFadeRotationAnimationController();
+        if (controller != null) {
+            controller.setupStartTransaction(transaction);
+        }
         mStartTransaction = transaction;
         mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
         buildFinishTransaction(mFinishTransaction, info.getRootLeash());
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index e054570..99dfe13 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -337,6 +337,29 @@
         mCollectingTransition.collectExistenceChange(wc);
     }
 
+    /**
+     * Collects the window containers which need to be synced with the changing display (e.g.
+     * rotating) to the given transition or the current collecting transition.
+     */
+    void collectForDisplayChange(@NonNull DisplayContent dc, @Nullable Transition incoming) {
+        if (incoming == null) incoming = mCollectingTransition;
+        if (incoming == null) return;
+        final Transition transition = incoming;
+        // Collect all visible tasks.
+        dc.forAllLeafTasks(task -> {
+            if (task.isVisible()) {
+                transition.collect(task);
+            }
+        }, true /* traverseTopToBottom */);
+        // Collect all visible non-app windows which need to be drawn before the animation starts.
+        dc.forAllWindows(w -> {
+            if (w.mActivityRecord == null && w.isVisible() && !inTransition(w.mToken)
+                    && dc.shouldSyncRotationChange(w)) {
+                transition.collect(w.mToken);
+            }
+        }, true /* traverseTopToBottom */);
+    }
+
     /** @see Transition#setOverrideAnimation */
     void setOverrideAnimation(TransitionInfo.AnimationOptions options,
             @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0649b25..525d84be 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -335,10 +335,7 @@
                     // Go through all tasks and collect them before the rotation
                     // TODO(shell-transitions): move collect() to onConfigurationChange once
                     //       wallpaper handling is synchronized.
-                    dc.forAllTasks(task -> {
-                        if (task.isVisible()) transition.collect(task);
-                    });
-                    dc.getInsetsStateController().addProvidersToTransition();
+                    dc.mTransitionController.collectForDisplayChange(dc, transition);
                     dc.sendNewConfiguration();
                     effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 }
@@ -664,7 +661,7 @@
                     sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
                     break;
                 }
-                tf1.setAdjacentTaskFragment(tf2);
+                tf1.setAdjacentTaskFragment(tf2, false /* moveAdjacentTogether */);
                 effects |= TRANSACT_EFFECTS_LIFECYCLE;
 
                 final Bundle bundle = hop.getLaunchOptions();
@@ -977,7 +974,7 @@
             throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
                     + " organizer root1=" + root1 + " root2=" + root2);
         }
-        root1.setAdjacentTaskFragment(root2);
+        root1.setAdjacentTaskFragment(root2, hop.getMoveAdjacentTogether());
         return TRANSACT_EFFECTS_LIFECYCLE;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index bae5465..0b91742 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1473,12 +1473,12 @@
         if (changing) {
             mLastFreezeDuration = 0;
             if (mWmService.mRoot.mOrientationChangeComplete
-                    && mDisplayContent.waitForUnfreeze(this)) {
+                    && mDisplayContent.shouldSyncRotationChange(this)) {
                 mWmService.mRoot.mOrientationChangeComplete = false;
             }
         } else {
             // The orientation change is completed. If it was hidden by the animation, reshow it.
-            mDisplayContent.finishFadeRotationAnimation(this);
+            mDisplayContent.finishFadeRotationAnimation(mToken);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index b147455..316051e 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -566,7 +566,7 @@
 
         if (w.getOrientationChanging()) {
             if (!w.isDrawn()) {
-                if (w.mDisplayContent.waitForUnfreeze(w)) {
+                if (w.mDisplayContent.shouldSyncRotationChange(w)) {
                     w.mWmService.mRoot.mOrientationChangeComplete = false;
                     mAnimator.mLastWindowFreezeSource = w;
                 }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 318ad06..e5a3b7a 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -693,11 +693,8 @@
     @Override
     public String toString() {
         if (stringName == null) {
-            StringBuilder sb = new StringBuilder();
-            sb.append("WindowToken{");
-            sb.append(Integer.toHexString(System.identityHashCode(this)));
-            sb.append(" "); sb.append(token); sb.append('}');
-            stringName = sb.toString();
+            stringName = "WindowToken{" + Integer.toHexString(System.identityHashCode(this))
+                    + " type=" + windowType + " " + token + "}";
         }
         return stringName;
     }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 978eb1d..f72f2cc 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -81,6 +81,7 @@
 
     header_libs: [
         "bionic_libc_platform_headers",
+        "bpf_connectivity_headers",
     ],
 }
 
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 2ccef9a..4504853 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -55,6 +55,7 @@
 #include "gnss/GnssAntennaInfoCallback.h"
 #include "gnss/GnssBatching.h"
 #include "gnss/GnssConfiguration.h"
+#include "gnss/GnssGeofence.h"
 #include "gnss/GnssMeasurement.h"
 #include "gnss/GnssNavigationMessage.h"
 #include "gnss/Utils.h"
@@ -79,12 +80,6 @@
 static jmethodID method_requestRefLocation;
 static jmethodID method_requestSetID;
 static jmethodID method_requestUtcTime;
-static jmethodID method_reportGeofenceTransition;
-static jmethodID method_reportGeofenceStatus;
-static jmethodID method_reportGeofenceAddStatus;
-static jmethodID method_reportGeofenceRemoveStatus;
-static jmethodID method_reportGeofencePauseStatus;
-static jmethodID method_reportGeofenceResumeStatus;
 static jmethodID method_reportGnssServiceDied;
 static jmethodID method_reportGnssPowerStats;
 static jmethodID method_setSubHalMeasurementCorrectionsCapabilities;
@@ -133,8 +128,6 @@
 
 using android::hardware::gnss::V1_0::GnssLocationFlags;
 using android::hardware::gnss::V1_0::IAGnssRilCallback;
-using android::hardware::gnss::V1_0::IGnssGeofenceCallback;
-using android::hardware::gnss::V1_0::IGnssGeofencing;
 using android::hardware::gnss::V1_0::IGnssNavigationMessage;
 using android::hardware::gnss::V1_0::IGnssNavigationMessageCallback;
 using android::hardware::gnss::V1_0::IGnssNi;
@@ -192,8 +185,6 @@
 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;
@@ -220,12 +211,10 @@
 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;
 sp<IAGnssRil_V2_0> agnssRilIface_V2_0 = nullptr;
-sp<IGnssGeofencing> gnssGeofencingIface = nullptr;
 sp<IAGnss_V1_0> agnssIface = nullptr;
 sp<IAGnss_V2_0> agnssIface_V2_0 = nullptr;
 sp<IGnssDebug_V1_0> gnssDebugIface = nullptr;
@@ -241,6 +230,7 @@
 std::unique_ptr<android::gnss::GnssMeasurementInterface> gnssMeasurementIface = nullptr;
 std::unique_ptr<android::gnss::GnssNavigationMessageInterface> gnssNavigationMessageIface = nullptr;
 std::unique_ptr<android::gnss::GnssBatchingInterface> gnssBatchingIface = nullptr;
+std::unique_ptr<android::gnss::GnssGeofenceInterface> gnssGeofencingIface = nullptr;
 
 #define WAKE_LOCK_NAME  "GPS"
 
@@ -714,199 +704,6 @@
     return Void();
 }
 
-/** 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;
-};
-
-template <class T>
-void GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(int geofenceId, const T& location,
-                                                        int transition, int64_t timestamp) {
-    JNIEnv* env = getJniEnv();
-
-    jobject jLocation = translateGnssLocation(env, location);
-
-    env->CallVoidMethod(mCallbacksObj,
-                        method_reportGeofenceTransition,
-                        geofenceId,
-                        jLocation,
-                        transition,
-                        timestamp);
-
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    env->DeleteLocalRef(jLocation);
-}
-
-template <class T>
-void GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(int availability, const T& lastLocation) {
-    JNIEnv* env = getJniEnv();
-
-    jobject jLocation = translateGnssLocation(env, lastLocation);
-
-    env->CallVoidMethod(mCallbacksObj, method_reportGeofenceStatus, availability, jLocation);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    env->DeleteLocalRef(jLocation);
-}
-
-void GnssGeofenceCallbackUtil::gnssGeofenceAddCb(int geofenceId, int status) {
-    JNIEnv* env = getJniEnv();
-    if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
-        ALOGE("%s: Error in adding a Geofence: %d\n", __func__, status);
-    }
-
-    env->CallVoidMethod(mCallbacksObj,
-                        method_reportGeofenceAddStatus,
-                        geofenceId,
-                        status);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
-void GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(int geofenceId, int status) {
-    JNIEnv* env = getJniEnv();
-    if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
-        ALOGE("%s: Error in removing a Geofence: %d\n", __func__, status);
-    }
-
-    env->CallVoidMethod(mCallbacksObj,
-                        method_reportGeofenceRemoveStatus,
-                        geofenceId, status);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
-void GnssGeofenceCallbackUtil::gnssGeofencePauseCb(int geofenceId, int status) {
-    JNIEnv* env = getJniEnv();
-    if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
-        ALOGE("%s: Error in pausing Geofence: %d\n", __func__, status);
-    }
-
-    env->CallVoidMethod(mCallbacksObj,
-                        method_reportGeofencePauseStatus,
-                        geofenceId, status);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
-void GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(int geofenceId, int status) {
-    JNIEnv* env = getJniEnv();
-    if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
-        ALOGE("%s: Error in resuming Geofence: %d\n", __func__, status);
-    }
-
-    env->CallVoidMethod(mCallbacksObj,
-                        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();
-}
-
 /*
  * MeasurementCorrectionsCallback implements callback methods of interface
  * IMeasurementCorrectionsCallback.hal.
@@ -1206,18 +1003,6 @@
     method_requestRefLocation = env->GetMethodID(clazz, "requestRefLocation", "()V");
     method_requestSetID = env->GetMethodID(clazz, "requestSetID", "(I)V");
     method_requestUtcTime = env->GetMethodID(clazz, "requestUtcTime", "()V");
-    method_reportGeofenceTransition = env->GetMethodID(clazz, "reportGeofenceTransition",
-            "(ILandroid/location/Location;IJ)V");
-    method_reportGeofenceStatus = env->GetMethodID(clazz, "reportGeofenceStatus",
-            "(ILandroid/location/Location;)V");
-    method_reportGeofenceAddStatus = env->GetMethodID(clazz, "reportGeofenceAddStatus",
-            "(II)V");
-    method_reportGeofenceRemoveStatus = env->GetMethodID(clazz, "reportGeofenceRemoveStatus",
-            "(II)V");
-    method_reportGeofenceResumeStatus = env->GetMethodID(clazz, "reportGeofenceResumeStatus",
-            "(II)V");
-    method_reportGeofencePauseStatus = env->GetMethodID(clazz, "reportGeofencePauseStatus",
-            "(II)V");
     method_reportGnssServiceDied = env->GetMethodID(clazz, "reportGnssServiceDied", "()V");
     method_reportNfwNotification = env->GetMethodID(clazz, "reportNfwNotification",
             "(Ljava/lang/String;BLjava/lang/String;BLjava/lang/String;BZZ)V");
@@ -1522,27 +1307,27 @@
         if (checkHidlReturn(gnssConfiguration,
                             "Unable to get a handle to GnssConfiguration_V1_1")) {
             gnssConfigurationIface =
-                    std::make_unique<android::gnss::GnssConfiguration_V1_1>(gnssConfiguration);
+                    std::make_unique<gnss::GnssConfiguration_V1_1>(gnssConfiguration);
         }
     } else {
         auto gnssConfiguration = gnssHal->getExtensionGnssConfiguration();
         if (checkHidlReturn(gnssConfiguration,
                             "Unable to get a handle to GnssConfiguration_V1_0")) {
             gnssConfigurationIface =
-                    std::make_unique<android::gnss::GnssConfiguration_V1_0>(gnssConfiguration);
+                    std::make_unique<gnss::GnssConfiguration_V1_0>(gnssConfiguration);
         }
     }
 
     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;
+        sp<hardware::gnss::IGnssGeofence> gnssGeofence;
+        auto status = gnssHalAidl->getExtensionGnssGeofence(&gnssGeofence);
+        if (checkAidlStatus(status, "Unable to get a handle to GnssGeofence AIDL interface.")) {
+            gnssGeofencingIface = std::make_unique<gnss::GnssGeofenceAidl>(gnssGeofence);
         }
     } else if (gnssHal != nullptr) {
         auto gnssGeofencing = gnssHal->getExtensionGnssGeofencing();
         if (checkHidlReturn(gnssGeofencing, "Unable to get a handle to GnssGeofencing")) {
-            gnssGeofencingIface = gnssGeofencing;
+            gnssGeofencingIface = std::make_unique<gnss::GnssGeofenceHidl>(gnssGeofencing);
         }
     }
 
@@ -1680,19 +1465,9 @@
         ALOGI("Unable to initialize IAGnss interface.");
     }
 
-    // Set IGnssGeofencing.hal callback.
-    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);
-        if (!checkHidlReturn(status, "IGnssGeofencing setCallback() failed.")) {
-            gnssGeofencingIface = nullptr;
-        }
+    // Set GnssGeofence callback.
+    if (gnssGeofencingIface != nullptr) {
+        gnssGeofencingIface->setCallback(std::make_unique<gnss::GnssGeofenceCallback>());
     } else {
         ALOGI("Unable to initialize IGnssGeofencing interface.");
     }
@@ -2239,7 +2014,7 @@
 
 static jboolean android_location_gnss_hal_GnssNative_is_geofence_supported(JNIEnv* /* env */,
                                                                            jclass) {
-    if (gnssGeofencingIface == nullptr && gnssGeofenceAidlIface == nullptr) {
+    if (gnssGeofencingIface == nullptr) {
         return JNI_FALSE;
     }
     return JNI_TRUE;
@@ -2249,75 +2024,41 @@
         JNIEnv* /* env */, jclass, jint geofenceId, jdouble latitude, jdouble longitude,
         jdouble radius, jint last_transition, jint monitor_transition,
         jint notification_responsiveness, jint unknown_timer) {
-    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.");
+    if (gnssGeofencingIface == nullptr) {
+        ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+        return JNI_FALSE;
     }
-
-    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;
+    return gnssGeofencingIface->addGeofence(geofenceId, latitude, longitude, radius,
+                                            last_transition, monitor_transition,
+                                            notification_responsiveness, unknown_timer);
 }
 
 static jboolean android_location_gnss_hal_GnssNative_remove_geofence(JNIEnv* /* env */, jclass,
                                                                      jint geofenceId) {
-    if (gnssGeofenceAidlIface != nullptr) {
-        auto status = gnssGeofenceAidlIface->removeGeofence(geofenceId);
-        return checkAidlStatus(status, "IGnssGeofenceAidl removeGeofence() failed.");
+    if (gnssGeofencingIface == nullptr) {
+        ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+        return JNI_FALSE;
     }
-
-    if (gnssGeofencingIface != nullptr) {
-        auto result = gnssGeofencingIface->removeGeofence(geofenceId);
-        return checkHidlReturn(result, "IGnssGeofencing removeGeofence() failed.");
-    }
-
-    ALOGE("%s: IGnssGeofencing interface not available.", __func__);
-    return JNI_FALSE;
+    return gnssGeofencingIface->removeGeofence(geofenceId);
 }
 
 static jboolean android_location_gnss_hal_GnssNative_pause_geofence(JNIEnv* /* env */, jclass,
                                                                     jint geofenceId) {
-    if (gnssGeofenceAidlIface != nullptr) {
-        auto status = gnssGeofenceAidlIface->pauseGeofence(geofenceId);
-        return checkAidlStatus(status, "IGnssGeofenceAidl pauseGeofence() failed.");
+    if (gnssGeofencingIface == nullptr) {
+        ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+        return JNI_FALSE;
     }
-
-    if (gnssGeofencingIface != nullptr) {
-        auto result = gnssGeofencingIface->pauseGeofence(geofenceId);
-        return checkHidlReturn(result, "IGnssGeofencing pauseGeofence() failed.");
-    }
-
-    ALOGE("%s: IGnssGeofencing interface not available.", __func__);
-    return JNI_FALSE;
+    return gnssGeofencingIface->pauseGeofence(geofenceId);
 }
 
 static jboolean android_location_gnss_hal_GnssNative_resume_geofence(JNIEnv* /* env */, jclass,
                                                                      jint geofenceId,
                                                                      jint monitor_transition) {
-    if (gnssGeofenceAidlIface != nullptr) {
-        auto status = gnssGeofenceAidlIface->resumeGeofence(geofenceId, monitor_transition);
-        return checkAidlStatus(status, "IGnssGeofenceAidl resumeGeofence() failed.");
+    if (gnssGeofencingIface == nullptr) {
+        ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+        return JNI_FALSE;
     }
-
-    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;
+    return gnssGeofencingIface->resumeGeofence(geofenceId, monitor_transition);
 }
 
 static jboolean android_location_gnss_hal_GnssNative_is_antenna_info_supported(JNIEnv* env,
diff --git a/services/core/jni/gnss/Android.bp b/services/core/jni/gnss/Android.bp
index 6c6b304..ac50bfa 100644
--- a/services/core/jni/gnss/Android.bp
+++ b/services/core/jni/gnss/Android.bp
@@ -27,6 +27,8 @@
         "GnssBatching.cpp",
         "GnssBatchingCallback.cpp",
         "GnssConfiguration.cpp",
+        "GnssGeofence.cpp",
+        "GnssGeofenceCallback.cpp",
         "GnssMeasurement.cpp",
         "GnssMeasurementCallback.cpp",
         "GnssNavigationMessage.cpp",
diff --git a/services/core/jni/gnss/GnssGeofence.cpp b/services/core/jni/gnss/GnssGeofence.cpp
new file mode 100644
index 0000000..01d134d
--- /dev/null
+++ b/services/core/jni/gnss/GnssGeofence.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Define LOG_TAG before <log/log.h> to overwrite the default value.
+#define LOG_TAG "GnssGeofenceJni"
+
+#include "GnssGeofence.h"
+
+#include "Utils.h"
+
+using android::hardware::hidl_bitfield;
+using GeofenceTransition = android::hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceTransition;
+using IGnssGeofenceAidl = android::hardware::gnss::IGnssGeofence;
+using IGnssGeofenceHidl = android::hardware::gnss::V1_0::IGnssGeofencing;
+
+namespace android::gnss {
+
+// Implementation of GnssGeofence (AIDL HAL)
+
+GnssGeofenceAidl::GnssGeofenceAidl(const sp<IGnssGeofenceAidl>& iGnssGeofence)
+      : mIGnssGeofenceAidl(iGnssGeofence) {
+    assert(mIGnssGeofenceAidl != nullptr);
+}
+
+jboolean GnssGeofenceAidl::setCallback(const std::unique_ptr<GnssGeofenceCallback>& callback) {
+    auto status = mIGnssGeofenceAidl->setCallback(callback->getAidl());
+    return checkAidlStatus(status, "IGnssGeofenceAidl init() failed.");
+}
+
+jboolean GnssGeofenceAidl::addGeofence(int geofenceId, double latitudeDegrees,
+                                       double longitudeDegrees, double radiusMeters,
+                                       int lastTransition, int monitorTransitions,
+                                       int notificationResponsivenessMs, int unknownTimerMs) {
+    auto status = mIGnssGeofenceAidl->addGeofence(geofenceId, latitudeDegrees, longitudeDegrees,
+                                                  radiusMeters, lastTransition, monitorTransitions,
+                                                  notificationResponsivenessMs, unknownTimerMs);
+    return checkAidlStatus(status, "IGnssGeofenceAidl addGeofence() failed");
+}
+
+jboolean GnssGeofenceAidl::removeGeofence(int geofenceId) {
+    auto status = mIGnssGeofenceAidl->removeGeofence(geofenceId);
+    return checkAidlStatus(status, "IGnssGeofenceAidl removeGeofence() failed.");
+}
+
+jboolean GnssGeofenceAidl::pauseGeofence(int geofenceId) {
+    auto status = mIGnssGeofenceAidl->pauseGeofence(geofenceId);
+    return checkAidlStatus(status, "IGnssGeofenceAidl pauseGeofence() failed.");
+}
+
+jboolean GnssGeofenceAidl::resumeGeofence(int geofenceId, int monitorTransitions) {
+    auto status = mIGnssGeofenceAidl->resumeGeofence(geofenceId, monitorTransitions);
+    return checkAidlStatus(status, "IGnssGeofenceAidl resumeGeofence() failed.");
+}
+
+// Implementation of GnssGeofenceHidl
+
+GnssGeofenceHidl::GnssGeofenceHidl(const sp<IGnssGeofenceHidl>& iGnssGeofence)
+      : mIGnssGeofenceHidl(iGnssGeofence) {
+    assert(mIGnssGeofenceHidl != nullptr);
+}
+
+jboolean GnssGeofenceHidl::setCallback(const std::unique_ptr<GnssGeofenceCallback>& callback) {
+    auto result = mIGnssGeofenceHidl->setCallback(callback->getHidl());
+    return checkHidlReturn(result, "IGnssGeofenceHidl setCallback() failed.");
+}
+
+jboolean GnssGeofenceHidl::addGeofence(int geofenceId, double latitudeDegrees,
+                                       double longitudeDegrees, double radiusMeters,
+                                       int lastTransition, int monitorTransitions,
+                                       int notificationResponsivenessMs, int unknownTimerMs) {
+    auto result = mIGnssGeofenceHidl->addGeofence(geofenceId, latitudeDegrees, longitudeDegrees,
+                                                  radiusMeters,
+                                                  static_cast<GeofenceTransition>(lastTransition),
+                                                  static_cast<hidl_bitfield<GeofenceTransition>>(
+                                                          monitorTransitions),
+                                                  notificationResponsivenessMs, unknownTimerMs);
+    return checkHidlReturn(result, "IGnssGeofence addGeofence() failed.");
+}
+
+jboolean GnssGeofenceHidl::removeGeofence(int geofenceId) {
+    auto result = mIGnssGeofenceHidl->removeGeofence(geofenceId);
+    return checkHidlReturn(result, "IGnssGeofence removeGeofence() failed.");
+}
+
+jboolean GnssGeofenceHidl::pauseGeofence(int geofenceId) {
+    auto result = mIGnssGeofenceHidl->pauseGeofence(geofenceId);
+    return checkHidlReturn(result, "IGnssGeofence pauseGeofence() failed.");
+}
+
+jboolean GnssGeofenceHidl::resumeGeofence(int geofenceId, int monitorTransitions) {
+    auto result = mIGnssGeofenceHidl->resumeGeofence(geofenceId, monitorTransitions);
+    return checkHidlReturn(result, "IGnssGeofence resumeGeofence() failed.");
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/GnssGeofence.h b/services/core/jni/gnss/GnssGeofence.h
new file mode 100644
index 0000000..31478ea
--- /dev/null
+++ b/services/core/jni/gnss/GnssGeofence.h
@@ -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.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_GNSSGEOFENCE_H
+#define _ANDROID_SERVER_GNSS_GNSSGEOFENCE_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/1.0/IGnssGeofencing.h>
+#include <android/hardware/gnss/BnGnssGeofence.h>
+#include <log/log.h>
+
+#include "GnssGeofenceCallback.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+class GnssGeofenceInterface {
+public:
+    virtual ~GnssGeofenceInterface() {}
+    virtual jboolean setCallback(const std::unique_ptr<GnssGeofenceCallback>& callback);
+    virtual jboolean addGeofence(int geofenceId, double latitudeDegrees, double longitudeDegrees,
+                                 double radiusMeters, int lastTransition, int monitorTransitions,
+                                 int notificationResponsivenessMs, int unknownTimerMs);
+    virtual jboolean pauseGeofence(int geofenceId);
+    virtual jboolean resumeGeofence(int geofenceId, int monitorTransitions);
+    virtual jboolean removeGeofence(int geofenceId);
+};
+
+class GnssGeofenceAidl : public GnssGeofenceInterface {
+public:
+    GnssGeofenceAidl(const sp<android::hardware::gnss::IGnssGeofence>& iGnssGeofence);
+    jboolean setCallback(const std::unique_ptr<GnssGeofenceCallback>& callback) override;
+    jboolean addGeofence(int geofenceId, double latitudeDegrees, double longitudeDegrees,
+                         double radiusMeters, int lastTransition, int monitorTransitions,
+                         int notificationResponsivenessMs, int unknownTimerMs) override;
+    jboolean pauseGeofence(int geofenceId) override;
+    jboolean resumeGeofence(int geofenceId, int monitorTransitions) override;
+    jboolean removeGeofence(int geofenceId) override;
+
+private:
+    const sp<android::hardware::gnss::IGnssGeofence> mIGnssGeofenceAidl;
+};
+
+class GnssGeofenceHidl : public GnssGeofenceInterface {
+public:
+    GnssGeofenceHidl(const sp<android::hardware::gnss::V1_0::IGnssGeofencing>& iGnssGeofence);
+    jboolean setCallback(const std::unique_ptr<GnssGeofenceCallback>& callback) override;
+    jboolean addGeofence(int geofenceId, double latitudeDegrees, double longitudeDegrees,
+                         double radiusMeters, int lastTransition, int monitorTransitions,
+                         int notificationResponsivenessMs, int unknownTimerMs) override;
+    jboolean pauseGeofence(int geofenceId) override;
+    jboolean resumeGeofence(int geofenceId, int monitorTransitions) override;
+    jboolean removeGeofence(int geofenceId) override;
+
+private:
+    const sp<android::hardware::gnss::V1_0::IGnssGeofencing> mIGnssGeofenceHidl;
+};
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_GNSSGEOFENCE_H
diff --git a/services/core/jni/gnss/GnssGeofenceCallback.cpp b/services/core/jni/gnss/GnssGeofenceCallback.cpp
new file mode 100644
index 0000000..2cdf973
--- /dev/null
+++ b/services/core/jni/gnss/GnssGeofenceCallback.cpp
@@ -0,0 +1,171 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "GnssGeofenceCbJni"
+
+#include "GnssGeofenceCallback.h"
+
+namespace android::gnss {
+
+namespace {
+
+jmethodID method_reportGeofenceTransition;
+jmethodID method_reportGeofenceStatus;
+jmethodID method_reportGeofenceAddStatus;
+jmethodID method_reportGeofenceRemoveStatus;
+jmethodID method_reportGeofencePauseStatus;
+jmethodID method_reportGeofenceResumeStatus;
+
+} // anonymous namespace
+
+using binder::Status;
+using hardware::Return;
+using hardware::Void;
+using GeofenceAvailability =
+        android::hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceAvailability;
+using GeofenceStatus = android::hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceStatus;
+using GeofenceTransition = android::hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceTransition;
+
+using GnssLocationAidl = android::hardware::gnss::GnssLocation;
+using GnssLocation_V1_0 = android::hardware::gnss::V1_0::GnssLocation;
+
+void GnssGeofence_class_init_once(JNIEnv* env, jclass clazz) {
+    method_reportGeofenceTransition = env->GetMethodID(clazz, "reportGeofenceTransition",
+                                                       "(ILandroid/location/Location;IJ)V");
+    method_reportGeofenceStatus =
+            env->GetMethodID(clazz, "reportGeofenceStatus", "(ILandroid/location/Location;)V");
+    method_reportGeofenceAddStatus = env->GetMethodID(clazz, "reportGeofenceAddStatus", "(II)V");
+    method_reportGeofenceRemoveStatus =
+            env->GetMethodID(clazz, "reportGeofenceRemoveStatus", "(II)V");
+    method_reportGeofenceResumeStatus =
+            env->GetMethodID(clazz, "reportGeofenceResumeStatus", "(II)V");
+    method_reportGeofencePauseStatus =
+            env->GetMethodID(clazz, "reportGeofencePauseStatus", "(II)V");
+}
+
+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();
+}
+
+Return<void> GnssGeofenceCallbackHidl::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> GnssGeofenceCallbackHidl::gnssGeofenceStatusCb(GeofenceAvailability availability,
+                                                            const GnssLocation_V1_0& location) {
+    GnssGeofenceCallbackUtil::gnssGeofenceStatusCb((int)availability, location);
+    return Void();
+}
+
+Return<void> GnssGeofenceCallbackHidl::gnssGeofenceAddCb(int32_t geofenceId,
+                                                         GeofenceStatus status) {
+    GnssGeofenceCallbackUtil::gnssGeofenceAddCb(geofenceId, (int)status);
+    return Void();
+}
+
+Return<void> GnssGeofenceCallbackHidl::gnssGeofenceRemoveCb(int32_t geofenceId,
+                                                            GeofenceStatus status) {
+    GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(geofenceId, (int)status);
+    return Void();
+}
+
+Return<void> GnssGeofenceCallbackHidl::gnssGeofencePauseCb(int32_t geofenceId,
+                                                           GeofenceStatus status) {
+    GnssGeofenceCallbackUtil::gnssGeofencePauseCb(geofenceId, (int)status);
+    return Void();
+}
+
+Return<void> GnssGeofenceCallbackHidl::gnssGeofenceResumeCb(int32_t geofenceId,
+                                                            GeofenceStatus status) {
+    GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(geofenceId, (int)status);
+    return Void();
+}
+
+void GnssGeofenceCallbackUtil::gnssGeofenceAddCb(int geofenceId, int status) {
+    JNIEnv* env = getJniEnv();
+    if (status != hardware::gnss::IGnssGeofenceCallback::OPERATION_SUCCESS) {
+        ALOGE("%s: Error in adding a Geofence: %d\n", __func__, status);
+    }
+
+    env->CallVoidMethod(mCallbacksObj, method_reportGeofenceAddStatus, geofenceId, status);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+void GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(int geofenceId, int status) {
+    JNIEnv* env = getJniEnv();
+    if (status != hardware::gnss::IGnssGeofenceCallback::OPERATION_SUCCESS) {
+        ALOGE("%s: Error in removing a Geofence: %d\n", __func__, status);
+    }
+
+    env->CallVoidMethod(mCallbacksObj, method_reportGeofenceRemoveStatus, geofenceId, status);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+void GnssGeofenceCallbackUtil::gnssGeofencePauseCb(int geofenceId, int status) {
+    JNIEnv* env = getJniEnv();
+    if (status != hardware::gnss::IGnssGeofenceCallback::OPERATION_SUCCESS) {
+        ALOGE("%s: Error in pausing Geofence: %d\n", __func__, status);
+    }
+
+    env->CallVoidMethod(mCallbacksObj, method_reportGeofencePauseStatus, geofenceId, status);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+void GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(int geofenceId, int status) {
+    JNIEnv* env = getJniEnv();
+    if (status != hardware::gnss::IGnssGeofenceCallback::OPERATION_SUCCESS) {
+        ALOGE("%s: Error in resuming Geofence: %d\n", __func__, status);
+    }
+
+    env->CallVoidMethod(mCallbacksObj, method_reportGeofenceResumeStatus, geofenceId, status);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/GnssGeofenceCallback.h b/services/core/jni/gnss/GnssGeofenceCallback.h
new file mode 100644
index 0000000..b6a8a36
--- /dev/null
+++ b/services/core/jni/gnss/GnssGeofenceCallback.h
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_GNSSGEOFENCECALLBACK_H
+#define _ANDROID_SERVER_GNSS_GNSSGEOFENCECALLBACK_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/1.0/IGnssGeofencing.h>
+#include <android/hardware/gnss/BnGnssGeofenceCallback.h>
+#include <log/log.h>
+
+#include <vector>
+
+#include "Utils.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+namespace {
+extern jmethodID method_reportGeofenceTransition;
+extern jmethodID method_reportGeofenceStatus;
+extern jmethodID method_reportGeofenceAddStatus;
+extern jmethodID method_reportGeofenceRemoveStatus;
+extern jmethodID method_reportGeofencePauseStatus;
+extern jmethodID method_reportGeofenceResumeStatus;
+} // anonymous namespace
+
+void GnssGeofence_class_init_once(JNIEnv* env, jclass clazz);
+
+class GnssGeofenceCallbackAidl : public hardware::gnss::BnGnssGeofenceCallback {
+public:
+    GnssGeofenceCallbackAidl() {}
+    binder::Status gnssGeofenceTransitionCb(int geofenceId,
+                                            const hardware::gnss::GnssLocation& location,
+                                            int transition, int64_t timestampMillis) override;
+    binder::Status gnssGeofenceStatusCb(int availability,
+                                        const hardware::gnss::GnssLocation& lastLocation) override;
+    binder::Status gnssGeofenceAddCb(int geofenceId, int status) override;
+    binder::Status gnssGeofenceRemoveCb(int geofenceId, int status) override;
+    binder::Status gnssGeofencePauseCb(int geofenceId, int status) override;
+    binder::Status gnssGeofenceResumeCb(int geofenceId, int status) override;
+};
+
+class GnssGeofenceCallbackHidl : public hardware::gnss::V1_0::IGnssGeofenceCallback {
+public:
+    GnssGeofenceCallbackHidl() {}
+    hardware::Return<void> gnssGeofenceTransitionCb(
+            int32_t geofenceId, const hardware::gnss::V1_0::GnssLocation& location,
+            hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceTransition transition,
+            hardware::gnss::V1_0::GnssUtcTime timestamp) override;
+    hardware::Return<void> gnssGeofenceStatusCb(
+            hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceAvailability status,
+            const hardware::gnss::V1_0::GnssLocation& location) override;
+    hardware::Return<void> gnssGeofenceAddCb(
+            int32_t geofenceId,
+            hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceStatus status) override;
+    hardware::Return<void> gnssGeofenceRemoveCb(
+            int32_t geofenceId,
+            hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceStatus status) override;
+    hardware::Return<void> gnssGeofencePauseCb(
+            int32_t geofenceId,
+            hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceStatus status) override;
+    hardware::Return<void> gnssGeofenceResumeCb(
+            int32_t geofenceId,
+            hardware::gnss::V1_0::IGnssGeofenceCallback::GeofenceStatus status) override;
+};
+
+class GnssGeofenceCallback {
+public:
+    GnssGeofenceCallback() {}
+    sp<GnssGeofenceCallbackAidl> getAidl() {
+        if (callbackAidl == nullptr) {
+            callbackAidl = sp<GnssGeofenceCallbackAidl>::make();
+        }
+        return callbackAidl;
+    }
+
+    sp<GnssGeofenceCallbackHidl> getHidl() {
+        if (callbackHidl == nullptr) {
+            callbackHidl = sp<GnssGeofenceCallbackHidl>::make();
+        }
+        return callbackHidl;
+    }
+
+private:
+    sp<GnssGeofenceCallbackAidl> callbackAidl;
+    sp<GnssGeofenceCallbackHidl> callbackHidl;
+};
+
+/** 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;
+};
+
+template <class T>
+void GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(int geofenceId, const T& location,
+                                                        int transition, int64_t timestamp) {
+    JNIEnv* env = getJniEnv();
+
+    jobject jLocation = translateGnssLocation(env, location);
+
+    env->CallVoidMethod(mCallbacksObj, method_reportGeofenceTransition, geofenceId, jLocation,
+                        transition, timestamp);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    env->DeleteLocalRef(jLocation);
+}
+
+template <class T>
+void GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(int availability, const T& lastLocation) {
+    JNIEnv* env = getJniEnv();
+
+    jobject jLocation = translateGnssLocation(env, lastLocation);
+
+    env->CallVoidMethod(mCallbacksObj, method_reportGeofenceStatus, availability, jLocation);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    env->DeleteLocalRef(jLocation);
+}
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_GNSSGEOFENCECALLBACK_H
\ No newline at end of file
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f605fe8..98a7b5e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1833,6 +1833,8 @@
         mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
 
         loadOwners();
+
+        performPolicyVersionUpgrade();
     }
 
     /**
@@ -3159,7 +3161,6 @@
         synchronized (getLockObject()) {
             migrateUserRestrictionsIfNecessaryLocked();
             fixupAutoTimeRestrictionDuringOrganizationOwnedDeviceMigration();
-            performPolicyVersionUpgrade();
         }
         getUserData(UserHandle.USER_SYSTEM);
         cleanUpOldUsers();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 1ef9d13..50a0a68 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
@@ -1853,6 +1854,36 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoAll_BoundByPersService_Cycle_Branch_Capability() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+        ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+                MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+        bindService(app, client, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class));
+        ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+                MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+        bindService(client, client2, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class));
+        bindService(client2, app, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class));
+        ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
+                MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
+        client3.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+        bindService(app, client3, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class));
+        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
+        lru.clear();
+        lru.add(app);
+        lru.add(client);
+        lru.add(client2);
+        lru.add(client3);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+
+        assertEquals(PROCESS_CAPABILITY_ALL, client.mState.getSetCapability());
+        assertEquals(PROCESS_CAPABILITY_ALL, client2.mState.getSetCapability());
+        assertEquals(PROCESS_CAPABILITY_ALL, app.mState.getSetCapability());
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_DoAll_Provider_Cycle_Branch_2() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
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 205c3da..74dd291 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
@@ -16,8 +16,8 @@
 
 package com.android.server.accessibility;
 
-import static android.accessibilityservice.MagnificationConfig.FULLSCREEN_MODE;
-import static android.accessibilityservice.MagnificationConfig.WINDOW_MODE;
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -82,7 +82,7 @@
     @Test
     public void getScale_fullscreenMode_expectedValue() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
-                .setMode(FULLSCREEN_MODE)
+                .setMode(MAGNIFICATION_MODE_FULLSCREEN)
                 .setScale(TEST_SCALE).build();
         setMagnificationActivated(TEST_DISPLAY, config);
 
@@ -94,7 +94,7 @@
     @Test
     public void getScale_windowMode_expectedValue() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
-                .setMode(WINDOW_MODE)
+                .setMode(MAGNIFICATION_MODE_WINDOW)
                 .setScale(TEST_SCALE).build();
         setMagnificationActivated(TEST_DISPLAY, config);
 
@@ -106,7 +106,7 @@
     @Test
     public void getCenterX_canControlFullscreenMagnification_returnCenterX() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
-                .setMode(FULLSCREEN_MODE)
+                .setMode(MAGNIFICATION_MODE_FULLSCREEN)
                 .setCenterX(TEST_CENTER_X).build();
         setMagnificationActivated(TEST_DISPLAY, config);
 
@@ -119,7 +119,7 @@
     @Test
     public void getCenterX_canControlWindowMagnification_returnCenterX() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
-                .setMode(WINDOW_MODE)
+                .setMode(MAGNIFICATION_MODE_WINDOW)
                 .setCenterX(TEST_CENTER_X).build();
         setMagnificationActivated(TEST_DISPLAY, config);
 
@@ -132,7 +132,7 @@
     @Test
     public void getCenterY_canControlFullscreenMagnification_returnCenterY() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
-                .setMode(FULLSCREEN_MODE)
+                .setMode(MAGNIFICATION_MODE_FULLSCREEN)
                 .setCenterY(TEST_CENTER_Y).build();
         setMagnificationActivated(TEST_DISPLAY, config);
 
@@ -145,7 +145,7 @@
     @Test
     public void getCenterY_canControlWindowMagnification_returnCenterY() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
-                .setMode(WINDOW_MODE)
+                .setMode(MAGNIFICATION_MODE_WINDOW)
                 .setCenterY(TEST_CENTER_Y).build();
         setMagnificationActivated(TEST_DISPLAY, config);
 
@@ -158,7 +158,7 @@
     @Test
     public void getMagnificationRegion_canControlFullscreenMagnification_returnRegion() {
         final Region region = new Region(10, 20, 100, 200);
-        setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE);
+        setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
         mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
                 region,  /* canControlMagnification= */true);
 
@@ -169,7 +169,7 @@
     @Test
     public void getMagnificationRegion_canControlWindowMagnification_returnRegion() {
         final Region region = new Region(10, 20, 100, 200);
-        setMagnificationActivated(TEST_DISPLAY, WINDOW_MODE);
+        setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW);
         mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
                 region,  /* canControlMagnification= */true);
 
@@ -180,7 +180,7 @@
     @Test
     public void getMagnificationRegion_fullscreenModeNotRegistered_shouldRegisterThenUnregister() {
         final Region region = new Region(10, 20, 100, 200);
-        setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE);
+        setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
         doAnswer((invocation) -> {
             ((Region) invocation.getArguments()[1]).set(region);
             return null;
@@ -198,7 +198,7 @@
     @Test
     public void getMagnificationCenterX_fullscreenModeNotRegistered_shouldRegisterThenUnregister() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
-                .setMode(FULLSCREEN_MODE)
+                .setMode(MAGNIFICATION_MODE_FULLSCREEN)
                 .setCenterX(TEST_CENTER_X).build();
         setMagnificationActivated(TEST_DISPLAY, config);
 
@@ -212,7 +212,7 @@
     @Test
     public void getMagnificationCenterY_fullscreenModeNotRegistered_shouldRegisterThenUnregister() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
-                .setMode(FULLSCREEN_MODE)
+                .setMode(MAGNIFICATION_MODE_FULLSCREEN)
                 .setCenterY(TEST_CENTER_Y).build();
         setMagnificationActivated(TEST_DISPLAY, config);
 
@@ -225,17 +225,17 @@
 
     @Test
     public void getCurrentMode_configDefaultMode_returnActivatedMode() {
-        final int targetMode = WINDOW_MODE;
+        final int targetMode = MAGNIFICATION_MODE_WINDOW;
         setMagnificationActivated(TEST_DISPLAY, targetMode);
 
         int currentMode = mMagnificationProcessor.getControllingMode(TEST_DISPLAY);
 
-        assertEquals(WINDOW_MODE, currentMode);
+        assertEquals(MAGNIFICATION_MODE_WINDOW, currentMode);
     }
 
     @Test
     public void reset_fullscreenMagnificationActivated() {
-        setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE);
+        setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
 
         mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false);
 
@@ -244,7 +244,7 @@
 
     @Test
     public void reset_windowMagnificationActivated() {
-        setMagnificationActivated(TEST_DISPLAY, WINDOW_MODE);
+        setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW);
 
         mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false);
 
@@ -254,7 +254,7 @@
     @Test
     public void setMagnificationConfig_fullscreenModeNotRegistered_shouldRegister() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
-                .setMode(FULLSCREEN_MODE)
+                .setMode(MAGNIFICATION_MODE_FULLSCREEN)
                 .setScale(TEST_SCALE)
                 .setCenterX(TEST_CENTER_X)
                 .setCenterY(TEST_CENTER_Y).build();
@@ -269,7 +269,7 @@
     @Test
     public void setMagnificationConfig_windowMode_enableMagnification() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
-                .setMode(WINDOW_MODE)
+                .setMode(MAGNIFICATION_MODE_WINDOW)
                 .setScale(TEST_SCALE)
                 .setCenterX(TEST_CENTER_X)
                 .setCenterY(TEST_CENTER_Y).build();
@@ -284,7 +284,7 @@
     @Test
     public void setMagnificationConfig_fullscreenEnabled_expectedConfigValues() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
-                .setMode(FULLSCREEN_MODE)
+                .setMode(MAGNIFICATION_MODE_FULLSCREEN)
                 .setScale(TEST_SCALE)
                 .setCenterX(TEST_CENTER_X)
                 .setCenterY(TEST_CENTER_Y).build();
@@ -302,7 +302,7 @@
     @Test
     public void setMagnificationConfig_windowEnabled_expectedConfigValues() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
-                .setMode(WINDOW_MODE)
+                .setMode(MAGNIFICATION_MODE_WINDOW)
                 .setScale(TEST_SCALE)
                 .setCenterX(TEST_CENTER_X)
                 .setCenterY(TEST_CENTER_Y).build();
@@ -319,8 +319,8 @@
 
     @Test
     public void setMagnificationConfig_controllingModeChangeAndAnimating_transitionConfigMode() {
-        final int currentActivatedMode = WINDOW_MODE;
-        final int targetMode = FULLSCREEN_MODE;
+        final int currentActivatedMode = MAGNIFICATION_MODE_WINDOW;
+        final int targetMode = MAGNIFICATION_MODE_FULLSCREEN;
         final MagnificationConfig oldConfig = new MagnificationConfig.Builder()
                 .setMode(currentActivatedMode)
                 .setScale(TEST_SCALE)
@@ -354,15 +354,15 @@
         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);
+        if (config.getMode() == MAGNIFICATION_MODE_FULLSCREEN) {
+            when(mMockMagnificationController.isActivated(displayId,
+                    MAGNIFICATION_MODE_WINDOW)).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);
+        } else if (config.getMode() == MAGNIFICATION_MODE_WINDOW) {
+            when(mMockMagnificationController.isActivated(displayId,
+                    MAGNIFICATION_MODE_FULLSCREEN)).thenReturn(false);
             mWindowMagnificationManagerStub.resetAndStubMethods();
             mMockWindowMagnificationManager.enableWindowMagnification(displayId, config.getScale(),
                     config.getCenterX(), config.getCenterY());
diff --git a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
new file mode 100644
index 0000000..41c7e31
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.policy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlertDialog;
+import android.content.pm.PackageManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintStateListener;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.view.Window;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.List;
+
+/**
+ * Unit tests for {@link SideFpsEventHandler}.
+ * <p/>
+ * Run with <code>atest SideFpsEventHandlerTest</code>.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class SideFpsEventHandlerTest {
+
+    private static final List<Integer> sAllStates = List.of(
+            FingerprintStateListener.STATE_IDLE,
+            FingerprintStateListener.STATE_ENROLLING,
+            FingerprintStateListener.STATE_KEYGUARD_AUTH,
+            FingerprintStateListener.STATE_BP_AUTH,
+            FingerprintStateListener.STATE_AUTH_OTHER);
+
+    @Rule
+    public TestableContext mContext =
+            new TestableContext(InstrumentationRegistry.getContext(), null);
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private FingerprintManager mFingerprintManager;
+    @Spy
+    private AlertDialog.Builder mDialogBuilder = new AlertDialog.Builder(mContext);
+    @Mock
+    private AlertDialog mAlertDialog;
+    @Mock
+    private Window mWindow;
+
+    private TestLooper mLooper = new TestLooper();
+    private SideFpsEventHandler mEventHandler;
+    private FingerprintStateListener mFingerprintStateListener;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext.addMockSystemService(PackageManager.class, mPackageManager);
+        mContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
+
+        when(mDialogBuilder.create()).thenReturn(mAlertDialog);
+        when(mAlertDialog.getWindow()).thenReturn(mWindow);
+
+        mEventHandler = new SideFpsEventHandler(
+                mContext, new Handler(mLooper.getLooper()),
+                mContext.getSystemService(PowerManager.class), () -> mDialogBuilder);
+    }
+
+    @Test
+    public void ignoresWithoutFingerprintFeature() {
+        when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT)))
+                .thenReturn(false);
+
+        assertThat(mEventHandler.onSinglePressDetected(60L)).isFalse();
+
+        mLooper.dispatchAll();
+        verify(mAlertDialog, never()).show();
+    }
+
+    @Test
+    public void ignoresWithoutSfps() throws Exception {
+        setupWithSensor(false /* hasSfps */, true /* initialized */);
+
+        for (int state : sAllStates) {
+            setFingerprintState(state);
+            assertThat(mEventHandler.onSinglePressDetected(200L)).isFalse();
+
+            mLooper.dispatchAll();
+            verify(mAlertDialog, never()).show();
+        }
+    }
+
+    @Test
+    public void ignoresWhileWaitingForSfps() throws Exception {
+        setupWithSensor(true /* hasSfps */, false /* initialized */);
+
+        for (int state : sAllStates) {
+            setFingerprintState(state);
+            assertThat(mEventHandler.onSinglePressDetected(400L)).isFalse();
+
+            mLooper.dispatchAll();
+            verify(mAlertDialog, never()).show();
+        }
+    }
+
+    @Test
+    public void ignoresWhenIdleOrUnknown() throws Exception {
+        setupWithSensor(true /* hasSfps */, true /* initialized */);
+
+        setFingerprintState(FingerprintStateListener.STATE_IDLE);
+        assertThat(mEventHandler.onSinglePressDetected(80000L)).isFalse();
+
+        setFingerprintState(FingerprintStateListener.STATE_AUTH_OTHER);
+        assertThat(mEventHandler.onSinglePressDetected(90000L)).isFalse();
+
+        mLooper.dispatchAll();
+        verify(mAlertDialog, never()).show();
+    }
+
+    @Test
+    public void ignoresOnKeyguard() throws Exception {
+        setupWithSensor(true /* hasSfps */, true /* initialized */);
+
+        setFingerprintState(FingerprintStateListener.STATE_KEYGUARD_AUTH);
+        assertThat(mEventHandler.onSinglePressDetected(80000L)).isFalse();
+
+        mLooper.dispatchAll();
+        verify(mAlertDialog, never()).show();
+    }
+
+    @Test
+    public void promptsWhenBPisActive() throws Exception {
+        setupWithSensor(true /* hasSfps */, true /* initialized */);
+
+        setFingerprintState(FingerprintStateListener.STATE_BP_AUTH);
+        assertThat(mEventHandler.onSinglePressDetected(80000L)).isTrue();
+
+        mLooper.dispatchAll();
+        verify(mAlertDialog).show();
+    }
+
+    @Test
+    public void promptsWhenEnrolling() throws Exception {
+        setupWithSensor(true /* hasSfps */, true /* initialized */);
+
+        setFingerprintState(FingerprintStateListener.STATE_ENROLLING);
+        assertThat(mEventHandler.onSinglePressDetected(80000L)).isTrue();
+
+        mLooper.dispatchAll();
+        verify(mAlertDialog).show();
+    }
+
+    private void setFingerprintState(@FingerprintStateListener.State int newState) {
+        if (mFingerprintStateListener != null) {
+            mFingerprintStateListener.onStateChanged(newState);
+            mLooper.dispatchAll();
+        }
+    }
+
+    private void setupWithSensor(boolean hasSfps, boolean initialized) throws Exception {
+        when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT)))
+                .thenReturn(true);
+        when(mFingerprintManager.isPowerbuttonFps()).thenReturn(hasSfps);
+        mEventHandler.onFingerprintSensorReady();
+
+        ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fpCallbackCaptor =
+                ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class);
+        verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(fpCallbackCaptor.capture());
+        if (initialized) {
+            fpCallbackCaptor.getValue().onAllAuthenticatorsRegistered(
+                    List.of(mock(FingerprintSensorPropertiesInternal.class)));
+            if (hasSfps) {
+                ArgumentCaptor<FingerprintStateListener> captor = ArgumentCaptor.forClass(
+                        FingerprintStateListener.class);
+                verify(mFingerprintManager).registerFingerprintStateListener(captor.capture());
+                mFingerprintStateListener = captor.getValue();
+            }
+        }
+    }
+}
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 6ee6020c..a963785 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -49,6 +49,7 @@
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setAutoDetectionEnabledSetting(true)
                 .setLocationEnabledSetting(true)
                 .setGeoDetectionEnabledSetting(true)
@@ -112,6 +113,7 @@
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setAutoDetectionEnabledSetting(true)
                 .setLocationEnabledSetting(true)
                 .setGeoDetectionEnabledSetting(true)
@@ -177,6 +179,7 @@
                 .setTelephonyDetectionFeatureSupported(false)
                 .setGeoDetectionFeatureSupported(false)
                 .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setAutoDetectionEnabledSetting(true)
                 .setLocationEnabledSetting(true)
                 .setGeoDetectionEnabledSetting(true)
@@ -240,6 +243,7 @@
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(false)
                 .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setAutoDetectionEnabledSetting(true)
                 .setLocationEnabledSetting(true)
                 .setGeoDetectionEnabledSetting(true)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
new file mode 100644
index 0000000..9029ac5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.timezonedetector;
+
+import static com.android.server.timezonedetector.MetricsTimeZoneDetectorState.DETECTION_MODE_GEO;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.UserIdInt;
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+
+import com.android.server.timezonedetector.MetricsTimeZoneDetectorState.MetricsTimeZoneSuggestion;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.function.Function;
+
+/** Tests for {@link MetricsTimeZoneDetectorState}. */
+public class MetricsTimeZoneDetectorStateTest {
+
+    private static final @UserIdInt int ARBITRARY_USER_ID = 1;
+    private static final @ElapsedRealtimeLong long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
+    private static final String DEVICE_TIME_ZONE_ID = "DeviceTimeZoneId";
+
+    private static final ManualTimeZoneSuggestion MANUAL_TIME_ZONE_SUGGESTION =
+            new ManualTimeZoneSuggestion("ManualTimeZoneId");
+
+    private static final TelephonyTimeZoneSuggestion TELEPHONY_TIME_ZONE_SUGGESTION =
+            new TelephonyTimeZoneSuggestion.Builder(0)
+                    .setZoneId("TelephonyZoneId")
+                    .setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY)
+                    .setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE)
+                    .build();
+
+    private static final GeolocationTimeZoneSuggestion GEOLOCATION_TIME_ZONE_SUGGESTION =
+            GeolocationTimeZoneSuggestion.createCertainSuggestion(
+                    ARBITRARY_ELAPSED_REALTIME_MILLIS,
+                    Arrays.asList("GeoTimeZoneId1", "GeoTimeZoneId2"));
+
+    private final OrdinalGenerator<String> mOrdinalGenerator =
+            new OrdinalGenerator<>(Function.identity());
+
+    @Test
+    public void enhancedMetricsCollectionEnabled() {
+        final boolean enhancedMetricsCollectionEnabled = true;
+        ConfigurationInternal configurationInternal =
+                createConfigurationInternal(enhancedMetricsCollectionEnabled);
+
+        // Create the object.
+        MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
+                MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
+                        DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
+                        TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+
+        // Assert the content.
+        assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
+
+        assertEquals(DEVICE_TIME_ZONE_ID, metricsTimeZoneDetectorState.getDeviceTimeZoneId());
+        MetricsTimeZoneSuggestion expectedManualSuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        new String[] { MANUAL_TIME_ZONE_SUGGESTION.getZoneId() },
+                        new int[] { 1 });
+        assertEquals(expectedManualSuggestion,
+                metricsTimeZoneDetectorState.getLatestManualSuggestion());
+
+        MetricsTimeZoneSuggestion expectedTelephonySuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        new String[] { TELEPHONY_TIME_ZONE_SUGGESTION.getZoneId() },
+                        new int[] { 2 });
+        assertEquals(expectedTelephonySuggestion,
+                metricsTimeZoneDetectorState.getLatestTelephonySuggestion());
+
+        MetricsTimeZoneSuggestion expectedGeoSuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        GEOLOCATION_TIME_ZONE_SUGGESTION.getZoneIds().toArray(new String[0]),
+                        new int[] { 3, 4 });
+        assertEquals(expectedGeoSuggestion,
+                metricsTimeZoneDetectorState.getLatestGeolocationSuggestion());
+    }
+
+    @Test
+    public void enhancedMetricsCollectionDisabled() {
+        final boolean enhancedMetricsCollectionEnabled = false;
+        ConfigurationInternal configurationInternal =
+                createConfigurationInternal(enhancedMetricsCollectionEnabled);
+
+        // Create the object.
+        MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
+                MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
+                        DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
+                        TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+
+        // Assert the content.
+        assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
+
+        // When enhancedMetricsCollectionEnabled == false, no time zone IDs should be included.
+        assertNull(metricsTimeZoneDetectorState.getDeviceTimeZoneId());
+        final String[] omittedZoneIds = null;
+
+        MetricsTimeZoneSuggestion expectedManualSuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        omittedZoneIds,
+                        new int[] { 1 });
+        assertEquals(expectedManualSuggestion,
+                metricsTimeZoneDetectorState.getLatestManualSuggestion());
+
+        MetricsTimeZoneSuggestion expectedTelephonySuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        omittedZoneIds,
+                        new int[] { 2 });
+        assertEquals(expectedTelephonySuggestion,
+                metricsTimeZoneDetectorState.getLatestTelephonySuggestion());
+
+        MetricsTimeZoneSuggestion expectedGeoSuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        omittedZoneIds,
+                        new int[] { 3, 4 });
+        assertEquals(expectedGeoSuggestion,
+                metricsTimeZoneDetectorState.getLatestGeolocationSuggestion());
+    }
+
+    private static void assertCommonConfiguration(ConfigurationInternal configurationInternal,
+            MetricsTimeZoneDetectorState metricsTimeZoneDetectorState) {
+        assertEquals(configurationInternal.isTelephonyDetectionSupported(),
+                metricsTimeZoneDetectorState.isTelephonyDetectionSupported());
+        assertEquals(configurationInternal.isGeoDetectionSupported(),
+                metricsTimeZoneDetectorState.isGeoDetectionSupported());
+        assertEquals(configurationInternal.getAutoDetectionEnabledSetting(),
+                metricsTimeZoneDetectorState.getAutoDetectionEnabledSetting());
+        assertEquals(configurationInternal.getLocationEnabledSetting(),
+                metricsTimeZoneDetectorState.getUserLocationEnabledSetting());
+        assertEquals(configurationInternal.getGeoDetectionEnabledSetting(),
+                metricsTimeZoneDetectorState.getGeoDetectionEnabledSetting());
+        assertEquals(configurationInternal.isEnhancedMetricsCollectionEnabled(),
+                metricsTimeZoneDetectorState.isEnhancedMetricsCollectionEnabled());
+        assertEquals(0, metricsTimeZoneDetectorState.getDeviceTimeZoneIdOrdinal());
+        assertEquals(DETECTION_MODE_GEO, metricsTimeZoneDetectorState.getDetectionMode());
+    }
+
+    private static ConfigurationInternal createConfigurationInternal(
+            boolean enhancedMetricsCollectionEnabled) {
+        return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+                .setUserConfigAllowed(true)
+                .setTelephonyDetectionFeatureSupported(true)
+                .setGeoDetectionFeatureSupported(true)
+                .setEnhancedMetricsCollectionEnabled(enhancedMetricsCollectionEnabled)
+                .setAutoDetectionEnabledSetting(true)
+                .setLocationEnabledSetting(true)
+                .setGeoDetectionEnabledSetting(true)
+                .build();
+    }
+}
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 193b2e3..e0e5ba0 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -379,6 +379,7 @@
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setUserConfigAllowed(true)
                 .setAutoDetectionEnabledSetting(autoDetectionEnabled)
                 .setLocationEnabledSetting(geoDetectionEnabled)
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 ef1b4f5..27f7814 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -87,10 +87,11 @@
 
     private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED =
             new ConfigurationInternal.Builder(USER_ID)
-                    .setUserConfigAllowed(false)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(false)
                     .setAutoDetectionEnabledSetting(false)
                     .setLocationEnabledSetting(true)
                     .setGeoDetectionEnabledSetting(false)
@@ -98,10 +99,11 @@
 
     private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
             new ConfigurationInternal.Builder(USER_ID)
-                    .setUserConfigAllowed(false)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(false)
                     .setAutoDetectionEnabledSetting(true)
                     .setLocationEnabledSetting(true)
                     .setGeoDetectionEnabledSetting(true)
@@ -109,10 +111,11 @@
 
     private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
             new ConfigurationInternal.Builder(USER_ID)
-                    .setUserConfigAllowed(true)
                     .setTelephonyDetectionFeatureSupported(false)
                     .setGeoDetectionFeatureSupported(false)
                     .setTelephonyFallbackSupported(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(true)
                     .setAutoDetectionEnabledSetting(false)
                     .setLocationEnabledSetting(true)
                     .setGeoDetectionEnabledSetting(false)
@@ -120,10 +123,11 @@
 
     private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
             new ConfigurationInternal.Builder(USER_ID)
-                    .setUserConfigAllowed(true)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(true)
                     .setAutoDetectionEnabledSetting(false)
                     .setLocationEnabledSetting(true)
                     .setGeoDetectionEnabledSetting(false)
@@ -134,6 +138,7 @@
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
                     .setUserConfigAllowed(true)
                     .setAutoDetectionEnabledSetting(true)
                     .setLocationEnabledSetting(true)
@@ -145,6 +150,7 @@
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
                     .setUserConfigAllowed(true)
                     .setAutoDetectionEnabledSetting(true)
                     .setLocationEnabledSetting(true)
@@ -955,8 +961,20 @@
     }
 
     @Test
-    public void testGenerateMetricsState() {
-        ConfigurationInternal expectedInternalConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
+    public void testGenerateMetricsState_enhancedMetricsCollection() {
+        testGenerateMetricsState(true);
+    }
+
+    @Test
+    public void testGenerateMetricsState_notEnhancedMetricsCollection() {
+        testGenerateMetricsState(false);
+    }
+
+    private void testGenerateMetricsState(boolean enhancedMetricsCollection) {
+        ConfigurationInternal expectedInternalConfig =
+                new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED_GEO_DISABLED)
+                        .setEnhancedMetricsCollectionEnabled(enhancedMetricsCollection)
+                        .build();
         String expectedDeviceTimeZoneId = "InitialZoneId";
 
         Script script = new Script()
@@ -1028,7 +1046,7 @@
                         tzIdOrdinalGenerator, expectedInternalConfig, expectedDeviceTimeZoneId,
                         expectedManualSuggestion, expectedTelephonySuggestion,
                         expectedGeolocationTimeZoneSuggestion);
-        // Rely on MetricsTimeZoneDetectorState.equals() for time zone ID ordinal comparisons.
+        // Rely on MetricsTimeZoneDetectorState.equals() for time zone ID / ID ordinal comparisons.
         assertEquals(expectedState, actualState);
     }
 
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 a2df3130..9d01310 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
@@ -47,6 +47,7 @@
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setAutoDetectionEnabledSetting(true)
                 .setLocationEnabledSetting(true)
                 .setGeoDetectionEnabledSetting(geoDetectionEnabledSetting)
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 beee2a7..ab9fbb5 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -32,6 +32,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -52,6 +53,7 @@
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.UserHandle;
+import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
@@ -124,10 +126,11 @@
                 new Handler(mTestLooper.getLooper()));
         mVibrationSettings.onSystemReady();
 
+        // Simulate System defaults.
         setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0);
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
         setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
-        setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
     }
 
     @After
@@ -142,13 +145,12 @@
         setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
         setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
-        setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_ALARMS);
         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(8)).onChange();
+        verify(mListenerMock, times(7)).onChange();
     }
 
     @Test
@@ -173,126 +175,242 @@
 
         verifyNoMoreInteractions(mListenerMock);
         setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_ALARMS);
     }
 
     @Test
-    public void shouldVibrateForRingerMode_beforeSystemReady_returnsFalseOnlyForRingtone() {
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
-        setRingerMode(AudioManager.RINGER_MODE_MAX);
-        VibrationSettings vibrationSettings = new VibrationSettings(mContextSpy,
-                new Handler(mTestLooper.getLooper()));
-
-        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(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() {
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
-
-        setRingerMode(AudioManager.RINGER_MODE_SILENT);
-        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
-        setRingerMode(AudioManager.RINGER_MODE_MAX);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
-        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
-        setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-    }
-
-    @Test
-    public void shouldVibrateForRingerMode_withApplyRampingRinger_ignoreSettingsForSilentMode() {
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
-        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
-
-        setRingerMode(AudioManager.RINGER_MODE_SILENT);
-        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
-        setRingerMode(AudioManager.RINGER_MODE_MAX);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
-        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
-        setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-    }
-
-    @Test
-    public void shouldVibrateForRingerMode_withAllSettingsOff_onlyVibratesForVibrateMode() {
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
-        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
-
-        setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
-        setRingerMode(AudioManager.RINGER_MODE_SILENT);
-        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
-        setRingerMode(AudioManager.RINGER_MODE_MAX);
-        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-
-        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
-    }
-
-    @Test
-    public void shouldVibrateForUid_withForegroundOnlyUsage_returnsTrueWhInForeground() {
-        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_TOUCH));
+    public void shouldIgnoreVibration_fromBackground_doesNotIgnoreUsagesFromAllowlist() {
+        int[] expectedAllowedVibrations = new int[] {
+                USAGE_RINGTONE,
+                USAGE_ALARM,
+                USAGE_NOTIFICATION,
+                USAGE_COMMUNICATION_REQUEST,
+                USAGE_HARDWARE_FEEDBACK,
+                USAGE_PHYSICAL_EMULATION,
+        };
 
         mVibrationSettings.mUidObserver.onUidStateChanged(
                 UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
-        assertFalse(mVibrationSettings.shouldVibrateForUid(UID, USAGE_TOUCH));
+
+        for (int usage : expectedAllowedVibrations) {
+            assertNull("Error for usage " + VibrationAttributes.usageToString(usage),
+                    mVibrationSettings.shouldIgnoreVibration(UID,
+                            VibrationAttributes.createForUsage(usage)));
+        }
     }
 
     @Test
-    public void shouldVibrateForUid_withBackgroundAllowedUsage_returnTrue() {
+    public void shouldIgnoreVibration_fromBackground_ignoresUsagesNotInAllowlist() {
+        int[] expectedIgnoredVibrations = new int[] {
+                USAGE_TOUCH,
+                USAGE_UNKNOWN,
+        };
+
         mVibrationSettings.mUidObserver.onUidStateChanged(
                 UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
 
-        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));
+        for (int usage : expectedIgnoredVibrations) {
+            assertEquals("Error for usage " + VibrationAttributes.usageToString(usage),
+                    Vibration.Status.IGNORED_BACKGROUND,
+                    mVibrationSettings.shouldIgnoreVibration(UID,
+                            VibrationAttributes.createForUsage(usage)));
+        }
     }
 
     @Test
-    public void shouldVibrateForPowerMode_withLowPowerAndAllowedUsage_returnTrue() {
+    public void shouldIgnoreVibration_fromForeground_allowsAnyUsage() {
+        mVibrationSettings.mUidObserver.onUidStateChanged(
+                UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
+
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_TOUCH)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_ALARM)));
+    }
+
+    @Test
+    public void shouldIgnoreVibration_inBatterySaverMode_doesNotIgnoreUsagesFromAllowlist() {
+        int[] expectedAllowedVibrations = new int[] {
+                USAGE_RINGTONE,
+                USAGE_ALARM,
+                USAGE_COMMUNICATION_REQUEST,
+        };
+
         mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
 
-        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_ALARM));
-        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_RINGTONE));
-        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_COMMUNICATION_REQUEST));
+        for (int usage : expectedAllowedVibrations) {
+            assertNull("Error for usage " + VibrationAttributes.usageToString(usage),
+                    mVibrationSettings.shouldIgnoreVibration(UID,
+                            VibrationAttributes.createForUsage(usage)));
+        }
     }
 
     @Test
-    public void shouldVibrateForPowerMode_withRestrictedUsage_returnsFalseWhileInLowPowerMode() {
+    public void shouldIgnoreVibration_inBatterySaverMode_ignoresUsagesNotInAllowlist() {
+        int[] expectedIgnoredVibrations = new int[] {
+                USAGE_NOTIFICATION,
+                USAGE_HARDWARE_FEEDBACK,
+                USAGE_PHYSICAL_EMULATION,
+                USAGE_TOUCH,
+                USAGE_UNKNOWN,
+        };
+
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+        for (int usage : expectedIgnoredVibrations) {
+            assertEquals("Error for usage " + VibrationAttributes.usageToString(usage),
+                    Vibration.Status.IGNORED_FOR_POWER,
+                    mVibrationSettings.shouldIgnoreVibration(UID,
+                            VibrationAttributes.createForUsage(usage)));
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_notInBatterySaverMode_allowsAnyUsage() {
         mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
 
-        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_TOUCH));
-        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_NOTIFICATION));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_TOUCH)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_COMMUNICATION_REQUEST)));
+    }
 
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+    @Test
+    public void shouldIgnoreVibration_withRingerModeSilent_ignoresRingtoneAndTouch() {
+        // Vibrating settings on are overruled by ringer mode.
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
+        setRingerMode(AudioManager.RINGER_MODE_SILENT);
 
-        assertFalse(mVibrationSettings.shouldVibrateForPowerMode(USAGE_TOUCH));
-        assertFalse(mVibrationSettings.shouldVibrateForPowerMode(USAGE_NOTIFICATION));
+        assertEquals(Vibration.Status.IGNORED_FOR_RINGER_MODE,
+                mVibrationSettings.shouldIgnoreVibration(UID,
+                        VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+        assertEquals(Vibration.Status.IGNORED_FOR_RINGER_MODE,
+                mVibrationSettings.shouldIgnoreVibration(UID,
+                        VibrationAttributes.createForUsage(USAGE_TOUCH)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_COMMUNICATION_REQUEST)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_HARDWARE_FEEDBACK)));
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withRingerModeVibrate_allowsAllVibrations() {
+        // Vibrating settings off are overruled by ringer mode.
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
+        setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
+
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_TOUCH)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_PHYSICAL_EMULATION)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withRingerModeNormalAndRingSettingsOff_ignoresRingtoneOnly() {
+        // Vibrating settings off are respected for normal ringer mode.
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+
+        assertEquals(Vibration.Status.IGNORED_FOR_RINGER_MODE,
+                mVibrationSettings.shouldIgnoreVibration(UID,
+                        VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_TOUCH)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_NOTIFICATION)));
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withRingerModeNormalAndRingSettingsOn_allowsAllVibrations() {
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_TOUCH)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_ALARM)));
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withRingerModeNormalAndRampingRingerOn_allowsAllVibrations() {
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_TOUCH)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_COMMUNICATION_REQUEST)));
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withHapticFeedbackSettingsOff_ignoresTouchVibration() {
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+
+        assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS,
+                mVibrationSettings.shouldIgnoreVibration(UID,
+                        VibrationAttributes.createForUsage(USAGE_TOUCH)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_HARDWARE_FEEDBACK)));
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withHardwareFeedbackSettingsOff_ignoresHardwareVibrations() {
+        setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+
+        assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS,
+                mVibrationSettings.shouldIgnoreVibration(UID,
+                        VibrationAttributes.createForUsage(USAGE_HARDWARE_FEEDBACK)));
+        assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS,
+                mVibrationSettings.shouldIgnoreVibration(UID,
+                        VibrationAttributes.createForUsage(USAGE_PHYSICAL_EMULATION)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_TOUCH)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_NOTIFICATION)));
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withNotificationSettingsOff_ignoresNotificationVibrations() {
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+
+        assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS,
+                mVibrationSettings.shouldIgnoreVibration(UID,
+                        VibrationAttributes.createForUsage(USAGE_NOTIFICATION)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_ALARM)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withRingSettingsOff_ignoresRingtoneVibrations() {
+        // Vibrating settings on are overruled by ring intensity setting.
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
+        setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+
+        assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS,
+                mVibrationSettings.shouldIgnoreVibration(UID,
+                        VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_NOTIFICATION)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_ALARM)));
+        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
+                VibrationAttributes.createForUsage(USAGE_TOUCH)));
     }
 
     @Test
@@ -305,24 +423,6 @@
     }
 
     @Test
-    public void isInZenMode_returnsSettingsValue() {
-        setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
-        assertFalse(mVibrationSettings.isInZenMode());
-
-        setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_NO_INTERRUPTIONS);
-        assertTrue(mVibrationSettings.isInZenMode());
-        setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_ALARMS);
-        assertTrue(mVibrationSettings.isInZenMode());
-
-        setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
-        assertFalse(mVibrationSettings.isInZenMode());
-
-        setGlobalSetting(Settings.Global.ZEN_MODE,
-                Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-        assertTrue(mVibrationSettings.isInZenMode());
-    }
-
-    @Test
     public void getDefaultIntensity_beforeSystemReady_returnsMediumToAllExceptAlarm() {
         mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_HIGH);
         mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_HIGH);
@@ -464,12 +564,6 @@
         mVibrationSettings.updateSettings();
     }
 
-    private void setGlobalSetting(String settingName, int value) {
-        Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value);
-        // FakeSettingsProvider don't support testing triggering ContentObserver yet.
-        mVibrationSettings.updateSettings();
-    }
-
     private void setRingerMode(int ringerMode) {
         mAudioManager.setRingerModeInternal(ringerMode);
         assertEquals(ringerMode, mAudioManager.getRingerModeInternal());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
index 2e5cf3c..a9fd3c9 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -840,21 +840,25 @@
         when(mUm.getUsers()).thenReturn(userInfos);
 
         // construct the permissions for each of them
-        ArrayMap<Pair<Integer, String>, Boolean> permissions0 = new ArrayMap<>(),
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> permissions0 = new ArrayMap<>(),
                 permissions1 = new ArrayMap<>();
-        permissions0.put(new Pair<>(10, "package1"), true);
-        permissions0.put(new Pair<>(20, "package2"), false);
-        permissions1.put(new Pair<>(11, "package1"), false);
-        permissions1.put(new Pair<>(21, "package2"), true);
+        permissions0.put(new Pair<>(10, "package1"), new Pair<>(true, false));
+        permissions0.put(new Pair<>(20, "package2"), new Pair<>(false, true));
+        permissions1.put(new Pair<>(11, "package1"), new Pair<>(false, false));
+        permissions1.put(new Pair<>(21, "package2"), new Pair<>(true, true));
         when(mPermissionHelper.getNotificationPermissionValues(0)).thenReturn(permissions0);
         when(mPermissionHelper.getNotificationPermissionValues(1)).thenReturn(permissions1);
         when(mPermissionHelper.getNotificationPermissionValues(2)).thenReturn(new ArrayMap<>());
 
-        ArrayMap<Pair<Integer, String>, Boolean> combinedPermissions =
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> combinedPermissions =
                 mService.getAllUsersNotificationPermissions();
-        assertTrue(combinedPermissions.get(new Pair<>(10, "package1")));
-        assertFalse(combinedPermissions.get(new Pair<>(20, "package2")));
-        assertFalse(combinedPermissions.get(new Pair<>(11, "package1")));
-        assertTrue(combinedPermissions.get(new Pair<>(21, "package2")));
+        assertTrue(combinedPermissions.get(new Pair<>(10, "package1")).first);
+        assertFalse(combinedPermissions.get(new Pair<>(10, "package1")).second);
+        assertFalse(combinedPermissions.get(new Pair<>(20, "package2")).first);
+        assertTrue(combinedPermissions.get(new Pair<>(20, "package2")).second);
+        assertFalse(combinedPermissions.get(new Pair<>(11, "package1")).first);
+        assertFalse(combinedPermissions.get(new Pair<>(11, "package1")).second);
+        assertTrue(combinedPermissions.get(new Pair<>(21, "package2")).first);
+        assertTrue(combinedPermissions.get(new Pair<>(21, "package2")).second);
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index 5800400..bd3ba04 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -313,11 +313,22 @@
         when(mPackageManager.getInstalledPackages(eq((long) GET_PERMISSIONS), anyInt()))
                 .thenReturn(requesting);
 
-        Map<Pair<Integer, String>, Boolean> expected = ImmutableMap.of(new Pair(1, "first"), true,
-                new Pair(2, "second"), true,
-                new Pair(3, "third"), false);
+        // 2 and 3 are user-set permissions
+        when(mPermManager.getPermissionFlags(
+                "first", Manifest.permission.POST_NOTIFICATIONS, userId)).thenReturn(0);
+        when(mPermManager.getPermissionFlags(
+                "second", Manifest.permission.POST_NOTIFICATIONS, userId))
+                .thenReturn(FLAG_PERMISSION_USER_SET);
+        when(mPermManager.getPermissionFlags(
+                "third", Manifest.permission.POST_NOTIFICATIONS, userId))
+                .thenReturn(FLAG_PERMISSION_USER_SET);
 
-        Map<Pair<Integer, String>, Boolean> actual =
+        Map<Pair<Integer, String>, Pair<Boolean, Boolean>> expected =
+                ImmutableMap.of(new Pair(1, "first"), new Pair(true, false),
+                    new Pair(2, "second"), new Pair(true, true),
+                    new Pair(3, "third"), new Pair(false, true));
+
+        Map<Pair<Integer, String>, Pair<Boolean, Boolean>> actual =
                 mPermissionHelper.getNotificationPermissionValues(userId);
 
         assertThat(actual).containsExactlyEntriesIn(expected);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index dd6d469..c85e876 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -707,12 +707,12 @@
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
 
-        ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), true);
-        appPermissions.put(new Pair(3, "third"), false);
-        appPermissions.put(new Pair(UID_P, PKG_P), true);
-        appPermissions.put(new Pair(UID_O, PKG_O), false);
-        appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), true);
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair(1, "first"), new Pair(true, false));
+        appPermissions.put(new Pair(3, "third"), new Pair(false, false));
+        appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
+        appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
 
         when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM))
                 .thenReturn(appPermissions);
@@ -788,12 +788,12 @@
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
 
-        ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), true);
-        appPermissions.put(new Pair(3, "third"), false);
-        appPermissions.put(new Pair(UID_P, PKG_P), true);
-        appPermissions.put(new Pair(UID_O, PKG_O), false);
-        appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), true);
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair(1, "first"), new Pair(true, false));
+        appPermissions.put(new Pair(3, "third"), new Pair(false, false));
+        appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
+        appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
 
         when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM))
                 .thenReturn(appPermissions);
@@ -875,9 +875,9 @@
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
 
-        ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(UID_P, PKG_P), true);
-        appPermissions.put(new Pair(UID_O, PKG_O), false);
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
         when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM))
                 .thenReturn(appPermissions);
 
@@ -955,12 +955,12 @@
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
 
-        ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), true);
-        appPermissions.put(new Pair(3, "third"), false);
-        appPermissions.put(new Pair(UID_P, PKG_P), true);
-        appPermissions.put(new Pair(UID_O, PKG_O), false);
-        appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), true);
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair(1, "first"), new Pair(true, false));
+        appPermissions.put(new Pair(3, "third"), new Pair(false, false));
+        appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
+        appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
 
         when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM))
                 .thenReturn(appPermissions);
@@ -2556,11 +2556,11 @@
         //     know about, those are ignored if migration is not enabled
 
         // package permissions map to be passed in
-        ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), true);    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), false);   // not in local prefs
-        appPermissions.put(new Pair(UID_P, PKG_P), true);  // in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
+        appPermissions.put(new Pair(3, "third"), new Pair(false, false));   // not in local prefs
+        appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));  // in local prefs
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
 
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
@@ -2618,11 +2618,11 @@
         //     know about, those should still be included
 
         // package permissions map to be passed in
-        ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), true);    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), false);   // not in local prefs
-        appPermissions.put(new Pair(UID_P, PKG_P), true);  // in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
+        appPermissions.put(new Pair(3, "third"), new Pair(false, false));   // not in local prefs
+        appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));  // in local prefs
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
 
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
@@ -2701,10 +2701,10 @@
         // not from the passed-in permissions map
         when(mPermissionHelper.isMigrationEnabled()).thenReturn(false);
 
-        ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), true);    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), false);   // not in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
+        appPermissions.put(new Pair(3, "third"), new Pair(false, false));   // not in local prefs
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
 
         // package preferences: only PKG_P is banned
         mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
@@ -2726,10 +2726,10 @@
         // have their permission set to false, and not based on PackagePreferences importance
         when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
 
-        ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), true);    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), false);   // not in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
+        appPermissions.put(new Pair(3, "third"), new Pair(false, false));   // not in local prefs
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
 
         // package preferences: PKG_O not banned based on local importance, and PKG_P is
         mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
@@ -2770,10 +2770,10 @@
         // confirm that the string resulting from dumpImpl contains only info from package prefs
         when(mPermissionHelper.isMigrationEnabled()).thenReturn(false);
 
-        ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), true);    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), false);   // not in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
+        appPermissions.put(new Pair(3, "third"), new Pair(false, true));    // not in local prefs
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
 
         // local package preferences: PKG_O is not banned even though the permissions would
         // indicate so
@@ -2796,6 +2796,7 @@
         ArrayList<String> notExpected = new ArrayList<>();
         notExpected.add("first (1) importance=DEFAULT");
         notExpected.add("third (3) importance=NONE");
+        notExpected.add("userSet=");  // no user-set information pre migration
 
         for (String exp : expected) {
             assertTrue(actual.contains(exp));
@@ -2819,10 +2820,10 @@
         // confirm that the string resulting from dumpImpl contains only importances from permission
         when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
 
-        ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), true);    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), false);   // not in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
+        appPermissions.put(new Pair(3, "third"), new Pair(false, true));   // not in local prefs
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
 
         // local package preferences
         mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
@@ -2837,9 +2838,9 @@
 
         // expected (substring) output for each preference via permissions
         ArrayList<String> expected = new ArrayList<>();
-        expected.add("first (1) importance=DEFAULT");
-        expected.add("third (3) importance=NONE");
-        expected.add(PKG_O + " (" + UID_O + ") importance=NONE");
+        expected.add("first (1) importance=DEFAULT userSet=false");
+        expected.add("third (3) importance=NONE userSet=true");
+        expected.add(PKG_O + " (" + UID_O + ") importance=NONE userSet=false");
         expected.add(PKG_P + " (" + UID_P + ")");
 
         // make sure we don't have package preference info
@@ -2881,10 +2882,10 @@
         // test that dumping to proto gets the importances from the right place
         when(mPermissionHelper.isMigrationEnabled()).thenReturn(false);
 
-        ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), true);    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), false);   // not in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
+        appPermissions.put(new Pair(3, "third"), new Pair(false, false));   // not in local prefs
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
 
         // local package preferences
         mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
@@ -2921,10 +2922,10 @@
         when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
 
         // permissions -- these should take precedence
-        ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), true);    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), false);   // not in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
+        appPermissions.put(new Pair(3, "third"), new Pair(false, false));   // not in local prefs
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
 
         // local package preferences
         mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
@@ -4993,10 +4994,10 @@
         when(mPermissionHelper.isMigrationEnabled()).thenReturn(false);
 
         // build a collection of app permissions that should be passed in but ignored
-        ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), true);    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), false);   // not in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
+        appPermissions.put(new Pair(3, "third"), new Pair(false, false));   // not in local prefs
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
 
         // package preferences: PKG_O not banned based on local importance, and PKG_P is
         mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
@@ -5036,10 +5037,10 @@
         when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
 
         // build a collection of app permissions that should be passed in but ignored
-        ArrayMap<Pair<Integer, String>, Boolean> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), true);    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), false);   // not in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), false); // in local prefs
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+        appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
+        appPermissions.put(new Pair(3, "third"), new Pair(false, false));   // not in local prefs
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
 
         // package preferences: PKG_O not banned based on local importance, and PKG_P is
         mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 7a133ea..4a8e121 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -39,6 +39,7 @@
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.os.Process.NOBODY_UID;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.InsetsState.ITYPE_IME;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -128,6 +129,8 @@
 import android.view.IRemoteAnimationRunner.Stub;
 import android.view.IWindowManager;
 import android.view.IWindowSession;
+import android.view.InsetsSource;
+import android.view.InsetsState;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationTarget;
 import android.view.Surface;
@@ -3024,6 +3027,41 @@
         assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
     }
 
+    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @Test
+    public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+
+        InsetsSource imeSource = new InsetsSource(ITYPE_IME);
+        app.getInsetsState().addSource(imeSource);
+        mDisplayContent.setImeLayeringTarget(app);
+        mDisplayContent.updateImeInputAndControlTarget(app);
+
+        InsetsState state = mDisplayContent.getInsetsPolicy().getInsetsForWindow(app);
+        assertFalse(state.getSource(ITYPE_IME).isVisible());
+        assertTrue(state.getSource(ITYPE_IME).getFrame().isEmpty());
+
+        // Simulate app is closing and expect IME insets is frozen.
+        mDisplayContent.mOpeningApps.clear();
+        app.mActivityRecord.commitVisibility(false, false);
+        app.mActivityRecord.onWindowsGone();
+        assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+        // Simulate app re-start input or turning screen off/on then unlocked by un-secure
+        // keyguard to back to the app, expect IME insets is not frozen
+        imeSource.setFrame(new Rect(100, 400, 500, 500));
+        app.getInsetsState().addSource(imeSource);
+        app.getInsetsState().setSourceVisible(ITYPE_IME, true);
+        mDisplayContent.updateImeInputAndControlTarget(app);
+        assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+        // Verify when IME is visible and the app can receive the right IME insets from policy.
+        makeWindowVisibleAndDrawn(app, mImeWindow);
+        state = mDisplayContent.getInsetsPolicy().getInsetsForWindow(app);
+        assertTrue(state.getSource(ITYPE_IME).isVisible());
+        assertEquals(state.getSource(ITYPE_IME).getFrame(), imeSource.getFrame());
+    }
+
     @Test
     public void testInClosingAnimation_doNotHideSurface() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index c103bc6..3e617d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -19,6 +19,10 @@
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
 import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
@@ -327,4 +331,16 @@
         assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
         assertEquals("android.test.second", mInterceptor.mIntent.getAction());
     }
+
+    @Test
+    public void testActivityLaunchedCallback_singleCallback() {
+        addMockInterceptorCallback(null);
+
+        assertEquals(1, mActivityInterceptorCallbacks.size());
+        final ActivityInterceptorCallback callback = mActivityInterceptorCallbacks.valueAt(0);
+        spyOn(callback);
+        mInterceptor.onActivityLaunched(null, null);
+
+        verify(callback, times(1)).onActivityLaunched(any(), any());
+    }
 }
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 d7a0ab3..dc0e028 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1336,7 +1336,6 @@
         spyOn(rotationAnim);
         // Assume that the display rotation is changed so it is frozen in preparation for animation.
         doReturn(true).when(rotationAnim).hasScreenshot();
-        mWm.mDisplayFrozen = true;
         displayContent.getDisplayRotation().setRotation((displayContent.getRotation() + 1) % 4);
         displayContent.setRotationAnimation(rotationAnim);
         // The fade rotation animation also starts to hide some non-app windows.
@@ -1347,9 +1346,9 @@
             w.setOrientationChanging(true);
         }
         // The display only waits for the app window to unfreeze.
-        assertFalse(displayContent.waitForUnfreeze(statusBar));
-        assertFalse(displayContent.waitForUnfreeze(navBar));
-        assertTrue(displayContent.waitForUnfreeze(app));
+        assertFalse(displayContent.shouldSyncRotationChange(statusBar));
+        assertFalse(displayContent.shouldSyncRotationChange(navBar));
+        assertTrue(displayContent.shouldSyncRotationChange(app));
         // If all windows animated by fade rotation animation have done the orientation change,
         // the animation controller should be cleared.
         statusBar.setOrientationChanging(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 2954d78..645d804 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -535,6 +535,7 @@
         mActivity.mVisibleRequested = false;
         mActivity.visibleIgnoringKeyguard = false;
         mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
+        mActivity.app.computeProcessActivityState();
 
         // Simulate the display changes orientation.
         final Configuration rotatedConfig = rotateDisplay(display, ROTATION_90);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index d68edba..cdf6b59 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -84,7 +84,7 @@
                 mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         adjacentRootTask.mCreatedByOrganizer = true;
         final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
-        adjacentRootTask.setAdjacentTaskFragment(rootTask);
+        adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */);
 
         taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask);
         Task actualRootTask = taskDisplayArea.getLaunchRootTask(
@@ -110,7 +110,7 @@
         final Task adjacentRootTask = createTask(
                 mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         adjacentRootTask.mCreatedByOrganizer = true;
-        adjacentRootTask.setAdjacentTaskFragment(rootTask);
+        adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */);
 
         taskDisplayArea.setLaunchRootTask(rootTask,
                 new int[]{WINDOWING_MODE_MULTI_WINDOW}, new int[]{ACTIVITY_TYPE_STANDARD});
@@ -131,7 +131,7 @@
                 mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         adjacentRootTask.mCreatedByOrganizer = true;
         final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
-        adjacentRootTask.setAdjacentTaskFragment(rootTask);
+        adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */);
 
         taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask);
         final Task actualRootTask = taskDisplayArea.getLaunchRootTask(
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index a5c6dc0..9ad8c5b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -330,7 +330,7 @@
 
         // Throw exception if the transaction is trying to change a window that is not organized by
         // the organizer.
-        mTransaction.setAdjacentRoots(mFragmentWindowToken, token2);
+        mTransaction.setAdjacentRoots(mFragmentWindowToken, token2, false /* moveTogether */);
 
         assertThrows(SecurityException.class, () -> {
             try {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 5fde7eb..a7a374b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -59,6 +59,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.clearInvocations;
@@ -1365,6 +1366,25 @@
         assertNotNull(activity.getTask().getDimmer());
     }
 
+    @Test
+    public void testMoveToFront_moveAdjacentTask() {
+        final Task task1 =
+                createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+        final Task task2 =
+                createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+        spyOn(task2);
+
+        task1.setAdjacentTaskFragment(task2, false /* moveTogether */);
+        task1.moveToFront("" /* reason */);
+        verify(task2, never()).moveToFrontInner(anyString(), isNull());
+
+        // Reset adjacent tasks to move together.
+        task1.setAdjacentTaskFragment(null, false /* moveTogether */);
+        task1.setAdjacentTaskFragment(task2, true /* moveTogether */);
+        task1.moveToFront("" /* reason */);
+        verify(task2).moveToFrontInner(anyString(), isNull());
+    }
+
     private Task getTestTask() {
         final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
         return task.getBottomMostTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index d9a166a..b7417c4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -19,6 +19,9 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
@@ -32,7 +35,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -44,6 +49,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.view.SurfaceControl;
+import android.view.TransactionCommittedListener;
 import android.window.ITaskOrganizer;
 import android.window.ITransitionPlayer;
 import android.window.TransitionInfo;
@@ -52,6 +58,7 @@
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -469,6 +476,54 @@
     }
 
     @Test
+    public void testDisplayRotationChange() {
+        final Task task = createActivityRecord(mDisplayContent).getTask();
+        final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
+        final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
+        final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
+        final WindowState[] windows = { statusBar, navBar, ime };
+        makeWindowVisible(windows);
+        mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs);
+        mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
+        final TestTransitionPlayer player = registerTestTransitionPlayer();
+
+        mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
+        mDisplayContent.requestChangeTransitionIfNeeded(1 /* any changes */);
+        final FadeRotationAnimationController fadeController =
+                mDisplayContent.getFadeRotationAnimationController();
+        assertNotNull(fadeController);
+        for (WindowState w : windows) {
+            w.setOrientationChanging(true);
+        }
+        player.startTransition();
+
+        assertFalse(statusBar.mToken.inTransition());
+        assertTrue(ime.mToken.inTransition());
+        assertTrue(task.inTransition());
+
+        // Status bar finishes drawing before the start transaction. Its fade-in animation will be
+        // executed until the transaction is committed, so it is still in target tokens.
+        statusBar.setOrientationChanging(false);
+        assertTrue(fadeController.isTargetToken(statusBar.mToken));
+
+        final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
+        final ArgumentCaptor<TransactionCommittedListener> listenerCaptor =
+                ArgumentCaptor.forClass(TransactionCommittedListener.class);
+        player.onTransactionReady(startTransaction);
+
+        verify(startTransaction).addTransactionCommittedListener(any(), listenerCaptor.capture());
+        // The transaction is committed, so fade-in animation for status bar is consumed.
+        listenerCaptor.getValue().onTransactionCommitted();
+        assertFalse(fadeController.isTargetToken(statusBar.mToken));
+
+        // Status bar finishes drawing after the start transaction, so its fade-in animation can
+        // execute directly.
+        navBar.setOrientationChanging(false);
+        assertFalse(fadeController.isTargetToken(navBar.mToken));
+        assertNull(mDisplayContent.getFadeRotationAnimationController());
+    }
+
+    @Test
     public void testIntermediateVisibility() {
         final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);
         final TransitionController controller = new TransitionController(mAtm, snapshotController);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 1eed79f1..75a87ba 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -554,7 +554,7 @@
         final RunningTaskInfo info2 = task2.getTaskInfo();
 
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.setAdjacentRoots(info1.token, info2.token);
+        wct.setAdjacentRoots(info1.token, info2.token, false /* moveTogether */);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         assertEquals(task1.getAdjacentTaskFragment(), task2);
         assertEquals(task2.getAdjacentTaskFragment(), task1);
@@ -564,8 +564,8 @@
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, task1);
 
-        task1.setAdjacentTaskFragment(null);
-        task2.setAdjacentTaskFragment(null);
+        task1.setAdjacentTaskFragment(null, false /* moveTogether */);
+        task2.setAdjacentTaskFragment(null, false /* moveTogether */);
         wct = new WindowContainerTransaction();
         wct.clearLaunchAdjacentFlagRoot(info1.token);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 92fd682..a985de5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1660,10 +1660,17 @@
             mLastRequest = request;
         }
 
-        public void start() {
+        void startTransition() {
             mOrganizer.startTransition(mLastRequest.getType(), mLastTransit, null);
-            mLastTransit.onTransactionReady(mLastTransit.getSyncId(),
-                    mock(SurfaceControl.Transaction.class));
+        }
+
+        void onTransactionReady(SurfaceControl.Transaction t) {
+            mLastTransit.onTransactionReady(mLastTransit.getSyncId(), t);
+        }
+
+        void start() {
+            startTransition();
+            onTransactionReady(mock(SurfaceControl.Transaction.class));
         }
 
         public void finish() {
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 e5dc557..049966c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -489,8 +489,8 @@
                 createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         final WindowState splitWindow2 =
                 createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2");
-        splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2);
-        splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1);
+        splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2, true /* moveTogether */);
+        splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1, true /* moveTogether */);
 
         final Task aboveTask =
                 createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
@@ -528,8 +528,8 @@
                 createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         final WindowState splitWindow2 =
                 createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2");
-        splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2);
-        splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1);
+        splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2, true /* moveTogether */);
+        splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1, true /* moveTogether */);
 
         mDisplayContent.assignChildLayers(mTransaction);
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index a32bd2d..997c883 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -41,6 +41,7 @@
 import android.app.IUidObserver;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.usage.AppLaunchEstimateInfo;
 import android.app.usage.AppStandbyInfo;
 import android.app.usage.ConfigurationStats;
 import android.app.usage.EventStats;
@@ -1554,6 +1555,52 @@
         }
     }
 
+    private void setEstimatedLaunchTime(int userId, String packageName,
+            @CurrentTimeMillisLong long estimatedLaunchTime) {
+        final long now = System.currentTimeMillis();
+        if (estimatedLaunchTime <= now) {
+            if (DEBUG) {
+                Slog.w(TAG, "Ignoring new estimate for "
+                        + userId + ":" + packageName + " because it's old");
+            }
+            return;
+        }
+        final long oldEstimatedLaunchTime = mAppStandby.getEstimatedLaunchTime(packageName, userId);
+        if (estimatedLaunchTime != oldEstimatedLaunchTime) {
+            mAppStandby.setEstimatedLaunchTime(packageName, userId, estimatedLaunchTime);
+            mHandler.obtainMessage(
+                    MSG_NOTIFY_ESTIMATED_LAUNCH_TIME_CHANGED, userId, 0, packageName)
+                    .sendToTarget();
+        }
+    }
+
+    private void setEstimatedLaunchTimes(int userId, List<AppLaunchEstimateInfo> launchEstimates) {
+        final ArraySet<String> changedTimes = new ArraySet<>();
+        final long now = System.currentTimeMillis();
+        for (int i = launchEstimates.size() - 1; i >= 0; --i) {
+            AppLaunchEstimateInfo estimate = launchEstimates.get(i);
+            if (estimate.estimatedLaunchTime <= now) {
+                if (DEBUG) {
+                    Slog.w(TAG, "Ignoring new estimate for "
+                            + userId + ":" + estimate.packageName + " because it's old");
+                }
+                continue;
+            }
+            final long oldEstimatedLaunchTime =
+                    mAppStandby.getEstimatedLaunchTime(estimate.packageName, userId);
+            if (estimate.estimatedLaunchTime != oldEstimatedLaunchTime) {
+                mAppStandby.setEstimatedLaunchTime(
+                        estimate.packageName, userId, estimate.estimatedLaunchTime);
+                changedTimes.add(estimate.packageName);
+            }
+        }
+        if (changedTimes.size() > 0) {
+            mHandler.obtainMessage(
+                    MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED, userId, 0, changedTimes)
+                    .sendToTarget();
+        }
+    }
+
     /**
      * Called via the local interface.
      */
@@ -2293,6 +2340,37 @@
         }
 
         @Override
+        public void setEstimatedLaunchTime(String packageName, long estimatedLaunchTime,
+                int userId) {
+            getContext().enforceCallingPermission(
+                    Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE,
+                    "No permission to change app launch estimates");
+
+            final long token = Binder.clearCallingIdentity();
+            try {
+                UsageStatsService.this
+                        .setEstimatedLaunchTime(userId, packageName, estimatedLaunchTime);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setEstimatedLaunchTimes(ParceledListSlice estimatedLaunchTimes, int userId) {
+            getContext().enforceCallingPermission(
+                    Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE,
+                    "No permission to change app launch estimates");
+
+            final long token = Binder.clearCallingIdentity();
+            try {
+                UsageStatsService.this
+                        .setEstimatedLaunchTimes(userId, estimatedLaunchTimes.getList());
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void onCarrierPrivilegedAppsChanged() {
             if (DEBUG) {
                 Slog.i(TAG, "Carrier privileged apps changed");
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 3c799e0..8c78f74 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -101,6 +101,25 @@
         }
     }
 
+    /**
+     * Convenience method for running the provided action in the provided
+     * executor enclosed in
+     * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity}
+     *
+     * Any exception thrown by the given action will need to be handled by caller.
+     *
+     */
+    public static void runWithCleanCallingIdentity(
+            @NonNull Runnable action, @NonNull Executor executor) {
+        if (action != null) {
+            if (executor != null) {
+                executor.execute(() -> runWithCleanCallingIdentity(action));
+            } else {
+                runWithCleanCallingIdentity(action);
+            }
+        }
+    }
+
 
     /**
      * Convenience method for running the provided action enclosed in
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ae2facd..d120f5a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -12620,16 +12620,19 @@
      * <p>If this object has been created with {@link #createForSubscriptionId}, applies
      *      to the given subId. Otherwise, applies to
      * {@link SubscriptionManager#getDefaultDataSubscriptionId()}
-     *
      * @param reason the reason the data enable change is taking place
      * @return whether data is enabled for a reason.
      * <p>Requires Permission:
+     * The calling app has carrier privileges (see {@link #hasCarrierPrivileges}) or
      * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} or
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}
+     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} or
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE}
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
-            android.Manifest.permission.READ_PHONE_STATE})
+            android.Manifest.permission.READ_PHONE_STATE,
+            android.Manifest.permission.MODIFY_PHONE_STATE
+    })
     public boolean isDataEnabledForReason(@DataEnabledReason int reason) {
         return isDataEnabledForReason(getSubId(), reason);
     }
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 097d363..8c02ffe 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -328,8 +328,6 @@
     @SystemApi
     public static final String TYPE_XCAP_STRING = "xcap";
 
-
-
     /**
      * APN type for Virtual SIM service.
      *
@@ -506,27 +504,21 @@
     private final int mRoamingProtocol;
     private final int mMtuV4;
     private final int mMtuV6;
-
     private final boolean mCarrierEnabled;
-
-    private final int mNetworkTypeBitmask;
-
+    private final @TelephonyManager.NetworkTypeBitMask int mNetworkTypeBitmask;
+    private final @TelephonyManager.NetworkTypeBitMask long mLingeringNetworkTypeBitmask;
     private final int mProfileId;
-
     private final boolean mPersistent;
     private final int mMaxConns;
     private final int mWaitTime;
     private final int mMaxConnsTime;
-
     private final int mMvnoType;
     private final String mMvnoMatchData;
-
     private final int mApnSetId;
-
     private boolean mPermanentFailed = false;
     private final int mCarrierId;
-
     private final int mSkip464Xlat;
+    private final boolean mAlwaysOn;
 
     /**
      * Returns the MTU size of the IPv4 mobile interface to which the APN connected. Note this value
@@ -843,20 +835,37 @@
     }
 
     /**
-     * Returns a bitmask describing the Radio Technologies(Network Types) which this APN may use.
+     * Returns a bitmask describing the Radio Technologies (Network Types) which this APN may use.
      *
      * NetworkType bitmask is calculated from NETWORK_TYPE defined in {@link TelephonyManager}.
      *
      * Examples of Network Types include {@link TelephonyManager#NETWORK_TYPE_UNKNOWN},
      * {@link TelephonyManager#NETWORK_TYPE_GPRS}, {@link TelephonyManager#NETWORK_TYPE_EDGE}.
      *
-     * @return a bitmask describing the Radio Technologies(Network Types)
+     * @return a bitmask describing the Radio Technologies (Network Types) or 0 if it is undefined.
      */
     public int getNetworkTypeBitmask() {
         return mNetworkTypeBitmask;
     }
 
     /**
+     * Returns a bitmask describing the Radio Technologies (Network Types) that should not be torn
+     * down if it exists or brought up if it already exists for this APN.
+     *
+     * NetworkType bitmask is calculated from NETWORK_TYPE defined in {@link TelephonyManager}.
+     *
+     * Examples of Network Types include {@link TelephonyManager#NETWORK_TYPE_UNKNOWN},
+     * {@link TelephonyManager#NETWORK_TYPE_GPRS}, {@link TelephonyManager#NETWORK_TYPE_EDGE}.
+     *
+     * @return a bitmask describing the Radio Technologies (Network Types) that should linger
+     *         or 0 if it is undefined.
+     * @hide
+     */
+    public @TelephonyManager.NetworkTypeBitMask long getLingeringNetworkTypeBitmask() {
+        return mLingeringNetworkTypeBitmask;
+    }
+
+    /**
      * Returns the MVNO match type for this APN.
      *
      * @see Builder#setMvnoType(int)
@@ -888,6 +897,18 @@
         return mSkip464Xlat;
     }
 
+    /**
+     * Returns whether User Plane resources have to be activated during every transition from
+     * CM-IDLE mode to CM-CONNECTED state for this APN
+     * See 3GPP TS 23.501 section 5.6.13
+     *
+     * @return True if the PDU session for this APN should always be on and false otherwise
+     * @hide
+     */
+    public boolean isAlwaysOn() {
+        return mAlwaysOn;
+    }
+
     private ApnSetting(Builder builder) {
         this.mEntryName = builder.mEntryName;
         this.mApnName = builder.mApnName;
@@ -912,6 +933,7 @@
         this.mMtuV6 = builder.mMtuV6;
         this.mCarrierEnabled = builder.mCarrierEnabled;
         this.mNetworkTypeBitmask = builder.mNetworkTypeBitmask;
+        this.mLingeringNetworkTypeBitmask = builder.mLingeringNetworkTypeBitmask;
         this.mProfileId = builder.mProfileId;
         this.mPersistent = builder.mModemCognitive;
         this.mMaxConns = builder.mMaxConns;
@@ -922,6 +944,7 @@
         this.mApnSetId = builder.mApnSetId;
         this.mCarrierId = builder.mCarrierId;
         this.mSkip464Xlat = builder.mSkip464Xlat;
+        this.mAlwaysOn = builder.mAlwaysOn;
     }
 
     /**
@@ -938,6 +961,10 @@
             networkTypeBitmask =
                 ServiceState.convertBearerBitmaskToNetworkTypeBitmask(bearerBitmask);
         }
+        int mtuV4 = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU_V4));
+        if (mtuV4 == -1) {
+            mtuV4 = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU));
+        }
 
         return new Builder()
                 .setId(cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)))
@@ -972,6 +999,8 @@
                 .setCarrierEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                         Telephony.Carriers.CARRIER_ENABLED)) == 1)
                 .setNetworkTypeBitmask(networkTypeBitmask)
+                .setLingeringNetworkTypeBitmask(cursor.getInt(cursor.getColumnIndexOrThrow(
+                        Carriers.LINGERING_NETWORK_TYPE_BITMASK)))
                 .setProfileId(cursor.getInt(
                         cursor.getColumnIndexOrThrow(Telephony.Carriers.PROFILE_ID)))
                 .setModemCognitive(cursor.getInt(cursor.getColumnIndexOrThrow(
@@ -982,8 +1011,8 @@
                         cursor.getColumnIndexOrThrow(Telephony.Carriers.WAIT_TIME_RETRY)))
                 .setMaxConnsTime(cursor.getInt(cursor.getColumnIndexOrThrow(
                         Telephony.Carriers.TIME_LIMIT_FOR_MAX_CONNECTIONS)))
-                .setMtuV4(cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU)))
-                .setMtuV6(UNSET_MTU) // TODO: Add corresponding support in telephony provider
+                .setMtuV4(mtuV4)
+                .setMtuV6(cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU_V6)))
                 .setMvnoType(getMvnoTypeIntFromString(
                         cursor.getString(cursor.getColumnIndexOrThrow(
                                 Telephony.Carriers.MVNO_TYPE))))
@@ -994,6 +1023,7 @@
                 .setCarrierId(cursor.getInt(
                         cursor.getColumnIndexOrThrow(Telephony.Carriers.CARRIER_ID)))
                 .setSkip464Xlat(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.SKIP_464XLAT)))
+                .setAlwaysOn(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.ALWAYS_ON)) == 1)
                 .buildWithoutCheck();
     }
 
@@ -1019,6 +1049,7 @@
                 .setRoamingProtocol(apn.mRoamingProtocol)
                 .setCarrierEnabled(apn.mCarrierEnabled)
                 .setNetworkTypeBitmask(apn.mNetworkTypeBitmask)
+                .setLingeringNetworkTypeBitmask(apn.mLingeringNetworkTypeBitmask)
                 .setProfileId(apn.mProfileId)
                 .setModemCognitive(apn.mPersistent)
                 .setMaxConns(apn.mMaxConns)
@@ -1031,6 +1062,7 @@
                 .setApnSetId(apn.mApnSetId)
                 .setCarrierId(apn.mCarrierId)
                 .setSkip464Xlat(apn.mSkip464Xlat)
+                .setAlwaysOn(apn.mAlwaysOn)
                 .buildWithoutCheck();
     }
 
@@ -1069,9 +1101,11 @@
         sb.append(", ").append(mMvnoMatchData);
         sb.append(", ").append(mPermanentFailed);
         sb.append(", ").append(mNetworkTypeBitmask);
+        sb.append(", ").append(mLingeringNetworkTypeBitmask);
         sb.append(", ").append(mApnSetId);
         sb.append(", ").append(mCarrierId);
         sb.append(", ").append(mSkip464Xlat);
+        sb.append(", ").append(mAlwaysOn);
         return sb.toString();
     }
 
@@ -1136,8 +1170,9 @@
         return Objects.hash(mApnName, mProxyAddress, mProxyPort, mMmsc, mMmsProxyAddress,
                 mMmsProxyPort, mUser, mPassword, mAuthType, mApnTypeBitmask, mId, mOperatorNumeric,
                 mProtocol, mRoamingProtocol, mMtuV4, mMtuV6, mCarrierEnabled, mNetworkTypeBitmask,
-                mProfileId, mPersistent, mMaxConns, mWaitTime, mMaxConnsTime, mMvnoType,
-                mMvnoMatchData, mApnSetId, mCarrierId, mSkip464Xlat);
+                mLingeringNetworkTypeBitmask, mProfileId, mPersistent, mMaxConns, mWaitTime,
+                mMaxConnsTime, mMvnoType, mMvnoMatchData, mApnSetId, mCarrierId, mSkip464Xlat,
+                mAlwaysOn);
     }
 
     @Override
@@ -1174,9 +1209,11 @@
                 && Objects.equals(mMvnoType, other.mMvnoType)
                 && Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
                 && Objects.equals(mNetworkTypeBitmask, other.mNetworkTypeBitmask)
+                && Objects.equals(mLingeringNetworkTypeBitmask, other.mLingeringNetworkTypeBitmask)
                 && Objects.equals(mApnSetId, other.mApnSetId)
                 && Objects.equals(mCarrierId, other.mCarrierId)
-                && Objects.equals(mSkip464Xlat, other.mSkip464Xlat);
+                && Objects.equals(mSkip464Xlat, other.mSkip464Xlat)
+                && Objects.equals(mAlwaysOn, other.mAlwaysOn);
     }
 
     /**
@@ -1210,6 +1247,7 @@
                 && Objects.equals(mPassword, other.mPassword)
                 && Objects.equals(mAuthType, other.mAuthType)
                 && Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask)
+                && Objects.equals(mLingeringNetworkTypeBitmask, other.mLingeringNetworkTypeBitmask)
                 && (isDataRoaming || Objects.equals(mProtocol, other.mProtocol))
                 && (!isDataRoaming || Objects.equals(mRoamingProtocol, other.mRoamingProtocol))
                 && Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
@@ -1224,7 +1262,8 @@
                 && Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
                 && Objects.equals(mApnSetId, other.mApnSetId)
                 && Objects.equals(mCarrierId, other.mCarrierId)
-                && Objects.equals(mSkip464Xlat, other.mSkip464Xlat);
+                && Objects.equals(mSkip464Xlat, other.mSkip464Xlat)
+                && Objects.equals(mAlwaysOn, other.mAlwaysOn);
     }
 
     /**
@@ -1304,9 +1343,13 @@
         apnValue.put(Telephony.Carriers.CARRIER_ENABLED, mCarrierEnabled);
         apnValue.put(Telephony.Carriers.MVNO_TYPE, getMvnoTypeStringFromInt(mMvnoType));
         apnValue.put(Telephony.Carriers.NETWORK_TYPE_BITMASK, mNetworkTypeBitmask);
+        apnValue.put(Telephony.Carriers.LINGERING_NETWORK_TYPE_BITMASK,
+                mLingeringNetworkTypeBitmask);
+        apnValue.put(Telephony.Carriers.MTU_V4, mMtuV4);
+        apnValue.put(Telephony.Carriers.MTU_V6, mMtuV6);
         apnValue.put(Telephony.Carriers.CARRIER_ID, mCarrierId);
         apnValue.put(Telephony.Carriers.SKIP_464XLAT, mSkip464Xlat);
-
+        apnValue.put(Telephony.Carriers.ALWAYS_ON, mAlwaysOn);
         return apnValue;
     }
 
@@ -1510,6 +1553,31 @@
         return ServiceState.bitmaskHasTech(mNetworkTypeBitmask, networkType);
     }
 
+    /**
+     * Check if this APN setting can support the given lingering network
+     *
+     * @param networkType The lingering network type
+     * @return {@code true} if this APN setting can support the given lingering network.
+     *
+     * @hide
+     */
+    public boolean canSupportLingeringNetworkType(@NetworkType int networkType) {
+        if (networkType == 0) {
+            return canSupportNetworkType(networkType);
+        }
+        // Do a special checking for GSM. In reality, GSM is a voice only network type and can never
+        // be used for data. We allow it here because in some DSDS corner cases, on the non-DDS
+        // sub, modem reports data rat unknown. In that case if voice is GSM and this APN supports
+        // GPRS or EDGE, this APN setting should be selected.
+        if (networkType == TelephonyManager.NETWORK_TYPE_GSM
+                && (mLingeringNetworkTypeBitmask & (TelephonyManager.NETWORK_TYPE_BITMASK_GPRS
+                | TelephonyManager.NETWORK_TYPE_BITMASK_EDGE)) != 0) {
+            return true;
+        }
+
+        return ServiceState.bitmaskHasTech((int) mLingeringNetworkTypeBitmask, networkType);
+    }
+
     // Implement Parcelable.
     @Override
     /** @hide */
@@ -1537,6 +1605,7 @@
         dest.writeInt(mRoamingProtocol);
         dest.writeBoolean(mCarrierEnabled);
         dest.writeInt(mNetworkTypeBitmask);
+        dest.writeLong(mLingeringNetworkTypeBitmask);
         dest.writeInt(mProfileId);
         dest.writeBoolean(mPersistent);
         dest.writeInt(mMaxConns);
@@ -1549,6 +1618,7 @@
         dest.writeInt(mApnSetId);
         dest.writeInt(mCarrierId);
         dest.writeInt(mSkip464Xlat);
+        dest.writeBoolean(mAlwaysOn);
     }
 
     private static ApnSetting readFromParcel(Parcel in) {
@@ -1570,6 +1640,7 @@
                 .setRoamingProtocol(in.readInt())
                 .setCarrierEnabled(in.readBoolean())
                 .setNetworkTypeBitmask(in.readInt())
+                .setLingeringNetworkTypeBitmask(in.readLong())
                 .setProfileId(in.readInt())
                 .setModemCognitive(in.readBoolean())
                 .setMaxConns(in.readInt())
@@ -1582,6 +1653,7 @@
                 .setApnSetId(in.readInt())
                 .setCarrierId(in.readInt())
                 .setSkip464Xlat(in.readInt())
+                .setAlwaysOn(in.readBoolean())
                 .buildWithoutCheck();
     }
 
@@ -1649,7 +1721,8 @@
         private int mRoamingProtocol = UNSPECIFIED_INT;
         private int mMtuV4;
         private int mMtuV6;
-        private int mNetworkTypeBitmask;
+        private @TelephonyManager.NetworkTypeBitMask int mNetworkTypeBitmask;
+        private @TelephonyManager.NetworkTypeBitMask long mLingeringNetworkTypeBitmask;
         private boolean mCarrierEnabled;
         private int mProfileId;
         private boolean mModemCognitive;
@@ -1661,6 +1734,7 @@
         private int mApnSetId;
         private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
         private int mSkip464Xlat = Carriers.SKIP_464XLAT_DEFAULT;
+        private boolean mAlwaysOn;
 
         /**
          * Default constructor for Builder.
@@ -2012,6 +2086,19 @@
         }
 
         /**
+         * Sets lingering Radio Technology (Network Type) for this APN.
+         *
+         * @param lingeringNetworkTypeBitmask the Radio Technology (Network Type) that should linger
+         * @hide
+         */
+        @NonNull
+        public Builder setLingeringNetworkTypeBitmask(@TelephonyManager.NetworkTypeBitMask
+                long lingeringNetworkTypeBitmask) {
+            this.mLingeringNetworkTypeBitmask = lingeringNetworkTypeBitmask;
+            return this;
+        }
+
+        /**
          * Sets the MVNO match type for this APN.
          *
          * @param mvnoType the MVNO match type to set for this APN
@@ -2048,6 +2135,18 @@
         }
 
         /**
+         * Sets whether the PDU session brought up by this APN should always be on.
+         * See 3GPP TS 23.501 section 5.6.13
+         *
+         * @param alwaysOn the always on status to set for this APN
+         * @hide
+         */
+        public Builder setAlwaysOn(boolean alwaysOn) {
+            this.mAlwaysOn = alwaysOn;
+            return this;
+        }
+
+        /**
          * Builds {@link ApnSetting} from this builder.
          *
          * @return {@code null} if {@link #setApnName(String)} or {@link #setEntryName(String)}
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
index dfe5e6c9..6569de6 100755
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -27,10 +27,12 @@
 
 import com.android.ims.internal.IImsCallSession;
 import com.android.ims.internal.IImsVideoCallProvider;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  * Provides the call initiation/termination, and media exchange between two IMS endpoints.
@@ -522,6 +524,7 @@
     private final IImsCallSession miSession;
     private boolean mClosed = false;
     private Listener mListener;
+    private Executor mListenerExecutor = Runnable::run;
 
     /** @hide */
     public ImsCallSession(IImsCallSession iSession) {
@@ -538,9 +541,9 @@
     }
 
     /** @hide */
-    public ImsCallSession(IImsCallSession iSession, Listener listener) {
+    public ImsCallSession(IImsCallSession iSession, Listener listener, Executor executor) {
         this(iSession);
-        setListener(listener);
+        setListener(listener, executor);
     }
 
     /**
@@ -738,10 +741,14 @@
      * override the previous listener.
      *
      * @param listener to listen to the session events of this object
+     * @param executor an Executor that will execute callbacks
      * @hide
      */
-    public void setListener(Listener listener) {
+    public void setListener(Listener listener, Executor executor) {
         mListener = listener;
+        if (executor != null) {
+            mListenerExecutor = executor;
+        }
     }
 
     /**
@@ -1206,42 +1213,48 @@
         @Override
         public void callSessionInitiating(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionInitiating(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionInitiating(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionProgressing(ImsStreamMediaProfile profile) {
             if (mListener != null) {
-                mListener.callSessionProgressing(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionProgressing(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionInitiated(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionStarted(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStarted(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionInitiatingFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStartFailed(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStartFailed(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionTerminated(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionTerminated(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionTerminated(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
@@ -1251,42 +1264,49 @@
         @Override
         public void callSessionHeld(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionHeld(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHeld(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionHoldFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionHoldFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHoldFailed(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionHoldReceived(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionHoldReceived(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHoldReceived(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionResumed(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionResumed(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionResumed(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionResumeFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionResumeFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionResumeFailed(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionResumeReceived(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionResumeReceived(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionResumeReceived(ImsCallSession.this, profile),
+                        mListenerExecutor);
             }
         }
 
@@ -1311,13 +1331,15 @@
         @Override
         public void callSessionMergeComplete(IImsCallSession newSession) {
             if (mListener != null) {
-                if (newSession != null) {
-                    // New session created after conference
-                    mListener.callSessionMergeComplete(new ImsCallSession(newSession));
-               } else {
-                   // Session already exists. Hence no need to pass
-                   mListener.callSessionMergeComplete(null);
-               }
+                TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                    if (newSession != null) {
+                        // New session created after conference
+                        mListener.callSessionMergeComplete(new ImsCallSession(newSession));
+                    } else {
+                        // Session already exists. Hence no need to pass
+                        mListener.callSessionMergeComplete(null);
+                    }
+                }, mListenerExecutor);
             }
         }
 
@@ -1329,7 +1351,9 @@
         @Override
         public void callSessionMergeFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo),
+                        mListenerExecutor);
             }
         }
 
@@ -1339,21 +1363,27 @@
         @Override
         public void callSessionUpdated(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionUpdated(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionUpdated(ImsCallSession.this, profile),
+                        mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionUpdateFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo),
+                        mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionUpdateReceived(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionUpdateReceived(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionUpdateReceived(ImsCallSession.this, profile),
+                        mListenerExecutor);
             }
         }
 
@@ -1364,15 +1394,18 @@
         public void callSessionConferenceExtended(IImsCallSession newSession,
                 ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionConferenceExtended(ImsCallSession.this,
-                        new ImsCallSession(newSession), profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionConferenceExtended(ImsCallSession.this,
+                        new ImsCallSession(newSession), profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionConferenceExtendFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionConferenceExtendFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionConferenceExtendFailed(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
@@ -1380,8 +1413,9 @@
         public void callSessionConferenceExtendReceived(IImsCallSession newSession,
                 ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionConferenceExtendReceived(ImsCallSession.this,
-                        new ImsCallSession(newSession), profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionConferenceExtendReceived(ImsCallSession.this,
+                        new ImsCallSession(newSession), profile), mListenerExecutor);
             }
         }
 
@@ -1392,30 +1426,36 @@
         @Override
         public void callSessionInviteParticipantsRequestDelivered() {
             if (mListener != null) {
-                mListener.callSessionInviteParticipantsRequestDelivered(ImsCallSession.this);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionInviteParticipantsRequestDelivered(
+                        ImsCallSession.this), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionInviteParticipantsRequestFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this,
-                        reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this,
+                        reasonInfo), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionRemoveParticipantsRequestDelivered() {
             if (mListener != null) {
-                mListener.callSessionRemoveParticipantsRequestDelivered(ImsCallSession.this);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRemoveParticipantsRequestDelivered(
+                        ImsCallSession.this), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionRemoveParticipantsRequestFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this,
-                        reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this,
+                        reasonInfo), mListenerExecutor);
             }
         }
 
@@ -1425,7 +1465,9 @@
         @Override
         public void callSessionConferenceStateUpdated(ImsConferenceState state) {
             if (mListener != null) {
-                mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state),
+                        mListenerExecutor);
             }
         }
 
@@ -1435,7 +1477,9 @@
         @Override
         public void callSessionUssdMessageReceived(int mode, String ussdMessage) {
             if (mListener != null) {
-                mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode, ussdMessage);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode,
+                        ussdMessage), mListenerExecutor);
             }
         }
 
@@ -1453,8 +1497,9 @@
         @Override
         public void callSessionMayHandover(int srcNetworkType, int targetNetworkType) {
             if (mListener != null) {
-                mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType,
-                        targetNetworkType);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType,
+                        targetNetworkType), mListenerExecutor);
             }
         }
 
@@ -1465,8 +1510,9 @@
         public void callSessionHandover(int srcNetworkType, int targetNetworkType,
                 ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionHandover(ImsCallSession.this, srcNetworkType,
-                        targetNetworkType, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionHandover(ImsCallSession.this, srcNetworkType,
+                        targetNetworkType, reasonInfo), mListenerExecutor);
             }
         }
 
@@ -1477,8 +1523,9 @@
         public void callSessionHandoverFailed(int srcNetworkType, int targetNetworkType,
                 ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType,
-                        targetNetworkType, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType,
+                        targetNetworkType, reasonInfo), mListenerExecutor);
             }
         }
 
@@ -1488,7 +1535,9 @@
         @Override
         public void callSessionTtyModeReceived(int mode) {
             if (mListener != null) {
-                mListener.callSessionTtyModeReceived(ImsCallSession.this, mode);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionTtyModeReceived(ImsCallSession.this, mode),
+                        mListenerExecutor);
             }
         }
 
@@ -1500,14 +1549,18 @@
          */
         public void callSessionMultipartyStateChanged(boolean isMultiParty) {
             if (mListener != null) {
-                mListener.callSessionMultipartyStateChanged(ImsCallSession.this, isMultiParty);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionMultipartyStateChanged(ImsCallSession.this,
+                        isMultiParty), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionSuppServiceReceived(ImsSuppServiceNotification suppServiceInfo ) {
             if (mListener != null) {
-                mListener.callSessionSuppServiceReceived(ImsCallSession.this, suppServiceInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionSuppServiceReceived(ImsCallSession.this,
+                        suppServiceInfo), mListenerExecutor);
             }
         }
 
@@ -1517,7 +1570,9 @@
         @Override
         public void callSessionRttModifyRequestReceived(ImsCallProfile callProfile) {
             if (mListener != null) {
-                mListener.callSessionRttModifyRequestReceived(ImsCallSession.this, callProfile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRttModifyRequestReceived(ImsCallSession.this,
+                        callProfile), mListenerExecutor);
             }
         }
 
@@ -1527,7 +1582,9 @@
         @Override
         public void callSessionRttModifyResponseReceived(int status) {
             if (mListener != null) {
-                mListener.callSessionRttModifyResponseReceived(status);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRttModifyResponseReceived(status),
+                        mListenerExecutor);
             }
         }
 
@@ -1537,7 +1594,8 @@
         @Override
         public void callSessionRttMessageReceived(String rttMessage) {
             if (mListener != null) {
-                mListener.callSessionRttMessageReceived(rttMessage);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRttMessageReceived(rttMessage), mListenerExecutor);
             }
         }
 
@@ -1547,21 +1605,26 @@
         @Override
         public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) {
             if (mListener != null) {
-                mListener.callSessionRttAudioIndicatorChanged(profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRttAudioIndicatorChanged(profile),
+                        mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionTransferred() {
             if (mListener != null) {
-                mListener.callSessionTransferred(ImsCallSession.this);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionTransferred(ImsCallSession.this), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionTransferFailed(@Nullable ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo),
+                        mListenerExecutor);
             }
         }
 
@@ -1572,7 +1635,8 @@
         @Override
         public void callSessionDtmfReceived(char dtmf) {
             if (mListener != null) {
-                mListener.callSessionDtmfReceived(dtmf);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionDtmfReceived(
+                        dtmf), mListenerExecutor);
             }
         }
 
@@ -1582,7 +1646,8 @@
         @Override
         public void callQualityChanged(CallQuality callQuality) {
             if (mListener != null) {
-                mListener.callQualityChanged(callQuality);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callQualityChanged(
+                        callQuality), mListenerExecutor);
             }
         }
 
@@ -1594,8 +1659,9 @@
         public void callSessionRtpHeaderExtensionsReceived(
                 @NonNull List<RtpHeaderExtension> extensions) {
             if (mListener != null) {
-                mListener.callSessionRtpHeaderExtensionsReceived(
-                        new ArraySet<RtpHeaderExtension>(extensions));
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRtpHeaderExtensionsReceived(
+                        new ArraySet<RtpHeaderExtension>(extensions)), mListenerExecutor);
             }
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 3e89cab..5d0d718 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -91,16 +91,14 @@
         }
 
     /**
-     * Checks that the nav bar layer starts visible, becomes invisible during unlocking animation
-     * and becomes visible at the end
+     * Checks that the nav bar layer starts invisible, becomes visible during unlocking animation
+     * and remains visible at the end
      */
-    @FlakyTest
+    @Postsubmit
     @Test
     fun navBarLayerVisibilityChanges() {
         testSpec.assertLayers {
-            this.isVisible(FlickerComponentName.NAV_BAR)
-                .then()
-                .isInvisible(FlickerComponentName.NAV_BAR)
+            this.isInvisible(FlickerComponentName.NAV_BAR)
                 .then()
                 .isVisible(FlickerComponentName.NAV_BAR)
         }
@@ -153,16 +151,14 @@
     }
 
     /**
-     * Checks that the nav bar starts the transition visible, then becomes invisible during
-     * then unlocking animation and becomes visible at the end of the transition
+     * Checks that the nav bar starts the transition invisible, then becomes visible during
+     * the unlocking animation and remains visible at the end of the transition
      */
-    @FlakyTest
+    @Postsubmit
     @Test
     fun navBarWindowsVisibilityChanges() {
         testSpec.assertWm {
-            this.isAboveAppWindowVisible(FlickerComponentName.NAV_BAR)
-                .then()
-                .isNonAppWindowInvisible(FlickerComponentName.NAV_BAR)
+            this.isNonAppWindowInvisible(FlickerComponentName.NAV_BAR)
                 .then()
                 .isAboveAppWindowVisible(FlickerComponentName.NAV_BAR)
         }
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 6e65350..de9bbb6 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -19,6 +19,7 @@
         "androidx.test.ext.junit",
         "androidx.test.rules",
         "services.core.unboosted",
+        "testables",
         "truth-prebuilt",
         "ub-uiautomator",
     ],
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 3eeba7d..1d65cc3 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -19,7 +19,11 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.filters.MediumTest
 
+import android.app.ActivityManager
+import android.app.ApplicationExitInfo
 import android.graphics.Rect
+import android.os.Build
+import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS
 import android.os.SystemClock
 import android.provider.Settings
 import android.provider.Settings.Global.HIDE_ERROR_DIALOGS
@@ -27,10 +31,13 @@
 import android.support.test.uiautomator.UiDevice
 import android.support.test.uiautomator.UiObject2
 import android.support.test.uiautomator.Until
+import android.testing.PollingCheck
 import android.view.InputDevice
 import android.view.MotionEvent
 
 import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
 import org.junit.Assert.fail
 import org.junit.Before
 import org.junit.Test
@@ -51,22 +58,28 @@
 class AnrTest {
     companion object {
         private const val TAG = "AnrTest"
+        private const val ALL_PIDS = 0
+        private const val NO_MAX = 0
     }
 
-    val mInstrumentation = InstrumentationRegistry.getInstrumentation()
-    var mHideErrorDialogs = 0
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+    private var hideErrorDialogs = 0
+    private lateinit var PACKAGE_NAME: String
+    private val DISPATCHING_TIMEOUT = (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
+            Build.HW_TIMEOUT_MULTIPLIER)
 
     @Before
     fun setUp() {
-        val contentResolver = mInstrumentation.targetContext.contentResolver
-        mHideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
+        val contentResolver = instrumentation.targetContext.contentResolver
+        hideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
         Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
+        PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage().getName()
     }
 
     @After
     fun tearDown() {
-        val contentResolver = mInstrumentation.targetContext.contentResolver
-        Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, mHideErrorDialogs)
+        val contentResolver = instrumentation.targetContext.contentResolver
+        Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, hideErrorDialogs)
     }
 
     @Test
@@ -86,19 +99,28 @@
 
     private fun clickCloseAppOnAnrDialog() {
         // Find anr dialog and kill app
-        val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
+        val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
         val closeAppButton: UiObject2? =
                 uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
         if (closeAppButton == null) {
             fail("Could not find anr dialog")
             return
         }
+        val initialReasons = getExitReasons()
         closeAppButton.click()
+        /**
+         * We must wait for the app to be fully closed before exiting this test. This is because
+         * another test may again invoke 'am start' for the same activity.
+         * If the 1st process that got ANRd isn't killed by the time second 'am start' runs,
+         * the killing logic will apply to the newly launched 'am start' instance, and the second
+         * test will fail because the unresponsive activity will never be launched.
+         */
+        waitForNewExitReason(initialReasons[0].timestamp)
     }
 
     private fun clickWaitOnAnrDialog() {
         // Find anr dialog and tap on wait
-        val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
+        val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
         val waitButton: UiObject2? =
                 uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000)
         if (waitButton == null) {
@@ -108,9 +130,27 @@
         waitButton.click()
     }
 
+    private fun getExitReasons(): List<ApplicationExitInfo> {
+        lateinit var infos: List<ApplicationExitInfo>
+        instrumentation.runOnMainSync {
+            val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)
+            infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, ALL_PIDS, NO_MAX)
+        }
+        return infos
+    }
+
+    private fun waitForNewExitReason(previousExitTimestamp: Long) {
+        PollingCheck.waitFor {
+            getExitReasons()[0].timestamp > previousExitTimestamp
+        }
+        val reasons = getExitReasons()
+        assertTrue(reasons[0].timestamp > previousExitTimestamp)
+        assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason)
+    }
+
     private fun triggerAnr() {
         startUnresponsiveActivity()
-        val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
+        val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
         val obj: UiObject2? = uiDevice.wait(Until.findObject(
                 By.text("Unresponsive gesture monitor")), 10000)
 
@@ -125,15 +165,14 @@
                 MotionEvent.ACTION_DOWN, rect.left.toFloat(), rect.top.toFloat(), 0 /* metaState */)
         downEvent.source = InputDevice.SOURCE_TOUCHSCREEN
 
-        mInstrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/)
+        instrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/)
 
-        // Todo: replace using timeout from android.hardware.input.IInputManager
-        SystemClock.sleep(5000) // default ANR timeout for gesture monitors
+        SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors
     }
 
     private fun startUnresponsiveActivity() {
         val flags = " -W -n "
-        val startCmd = "am start $flags com.android.test.input/.UnresponsiveGestureMonitorActivity"
-        mInstrumentation.uiAutomation.executeShellCommand(startCmd)
+        val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity"
+        instrumentation.uiAutomation.executeShellCommand(startCmd)
     }
-}
\ No newline at end of file
+}
diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
index da5468e..b21d7b5 100644
--- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@@ -44,6 +44,7 @@
 import com.android.cts.install.lib.Install;
 import com.android.cts.install.lib.InstallUtils;
 import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
 
 import org.junit.After;
 import org.junit.Before;
@@ -108,6 +109,7 @@
     @Test
     public void cleanUp() throws Exception {
         Files.deleteIfExists(mTestStateFile.toPath());
+        Uninstall.packages(TestApp.A, TestApp.B);
     }
 
     @Test
diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java
new file mode 100644
index 0000000..f7d36970
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.net.vcn;
+
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class VcnCellUnderlyingNetworkPriorityTest {
+    private static final Set<String> ALLOWED_PLMN_IDS = new HashSet<>();
+    private static final Set<Integer> ALLOWED_CARRIER_IDS = new HashSet<>();
+
+    // Package private for use in VcnGatewayConnectionConfigTest
+    static VcnCellUnderlyingNetworkPriority getTestNetworkPriority() {
+        return new VcnCellUnderlyingNetworkPriority.Builder()
+                .setNetworkQuality(NETWORK_QUALITY_OK)
+                .setAllowMetered(true /* allowMetered */)
+                .setAllowedPlmnIds(ALLOWED_PLMN_IDS)
+                .setAllowedSpecificCarrierIds(ALLOWED_CARRIER_IDS)
+                .setAllowRoaming(true /* allowRoaming */)
+                .setRequireOpportunistic(true /* requireOpportunistic */)
+                .build();
+    }
+
+    @Test
+    public void testBuilderAndGetters() {
+        final VcnCellUnderlyingNetworkPriority networkPriority = getTestNetworkPriority();
+        assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
+        assertTrue(networkPriority.allowMetered());
+        assertEquals(ALLOWED_PLMN_IDS, networkPriority.getAllowedPlmnIds());
+        assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getAllowedSpecificCarrierIds());
+        assertTrue(networkPriority.allowRoaming());
+        assertTrue(networkPriority.requireOpportunistic());
+    }
+
+    @Test
+    public void testBuilderAndGettersForDefaultValues() {
+        final VcnCellUnderlyingNetworkPriority networkPriority =
+                new VcnCellUnderlyingNetworkPriority.Builder().build();
+        assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
+        assertFalse(networkPriority.allowMetered());
+        assertEquals(new HashSet<String>(), networkPriority.getAllowedPlmnIds());
+        assertEquals(new HashSet<Integer>(), networkPriority.getAllowedSpecificCarrierIds());
+        assertFalse(networkPriority.allowRoaming());
+        assertFalse(networkPriority.requireOpportunistic());
+    }
+
+    @Test
+    public void testPersistableBundle() {
+        final VcnCellUnderlyingNetworkPriority networkPriority = getTestNetworkPriority();
+        assertEquals(
+                networkPriority,
+                VcnUnderlyingNetworkPriority.fromPersistableBundle(
+                        networkPriority.toPersistableBundle()));
+    }
+}
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index dc338ae..724c33f 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -38,6 +38,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
+import java.util.LinkedHashSet;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
@@ -50,9 +51,17 @@
             };
     public static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN};
 
+    private static final LinkedHashSet<VcnUnderlyingNetworkPriority> UNDERLYING_NETWORK_PRIORITIES =
+            new LinkedHashSet();
+
     static {
         Arrays.sort(EXPOSED_CAPS);
         Arrays.sort(UNDERLYING_CAPS);
+
+        UNDERLYING_NETWORK_PRIORITIES.add(
+                VcnCellUnderlyingNetworkPriorityTest.getTestNetworkPriority());
+        UNDERLYING_NETWORK_PRIORITIES.add(
+                VcnWifiUnderlyingNetworkPriorityTest.getTestNetworkPriority());
     }
 
     public static final long[] RETRY_INTERVALS_MS =
@@ -82,7 +91,10 @@
 
     // Public for use in VcnGatewayConnectionTest
     public static VcnGatewayConnectionConfig buildTestConfig() {
-        return buildTestConfigWithExposedCaps(EXPOSED_CAPS);
+        final VcnGatewayConnectionConfig.Builder builder =
+                newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_PRIORITIES);
+
+        return buildTestConfigWithExposedCaps(builder, EXPOSED_CAPS);
     }
 
     private static VcnGatewayConnectionConfig.Builder newBuilder() {
@@ -159,6 +171,15 @@
     }
 
     @Test
+    public void testBuilderRequiresNonNullNetworkPriorities() {
+        try {
+            newBuilder().setVcnUnderlyingNetworkPriorities(null);
+            fail("Expected exception due to invalid underlyingNetworkPriorities");
+        } catch (NullPointerException e) {
+        }
+    }
+
+    @Test
     public void testBuilderRequiresNonNullRetryInterval() {
         try {
             newBuilder().setRetryIntervalsMillis(null);
@@ -195,6 +216,7 @@
         Arrays.sort(exposedCaps);
         assertArrayEquals(EXPOSED_CAPS, exposedCaps);
 
+        assertEquals(UNDERLYING_NETWORK_PRIORITIES, config.getVcnUnderlyingNetworkPriorities());
         assertEquals(TUNNEL_CONNECTION_PARAMS, config.getTunnelConnectionParams());
 
         assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis());
diff --git a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkPriorityTest.java b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkPriorityTest.java
new file mode 100644
index 0000000..dd272cb
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkPriorityTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.net.vcn;
+
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+public class VcnWifiUnderlyingNetworkPriorityTest {
+    private static final String SSID = "TestWifi";
+    private static final int INVALID_NETWORK_QUALITY = -1;
+
+    // Package private for use in VcnGatewayConnectionConfigTest
+    static VcnWifiUnderlyingNetworkPriority getTestNetworkPriority() {
+        return new VcnWifiUnderlyingNetworkPriority.Builder()
+                .setNetworkQuality(NETWORK_QUALITY_OK)
+                .setAllowMetered(true /* allowMetered */)
+                .setSsid(SSID)
+                .build();
+    }
+
+    @Test
+    public void testBuilderAndGetters() {
+        final VcnWifiUnderlyingNetworkPriority networkPriority = getTestNetworkPriority();
+        assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
+        assertTrue(networkPriority.allowMetered());
+        assertEquals(SSID, networkPriority.getSsid());
+    }
+
+    @Test
+    public void testBuilderAndGettersForDefaultValues() {
+        final VcnWifiUnderlyingNetworkPriority networkPriority =
+                new VcnWifiUnderlyingNetworkPriority.Builder().build();
+        assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
+        assertFalse(networkPriority.allowMetered());
+        assertNull(SSID, networkPriority.getSsid());
+    }
+
+    @Test
+    public void testBuildWithInvalidNetworkQuality() {
+        try {
+            new VcnWifiUnderlyingNetworkPriority.Builder()
+                    .setNetworkQuality(INVALID_NETWORK_QUALITY);
+            fail("Expected to fail due to the invalid network quality");
+        } catch (Exception expected) {
+        }
+    }
+
+    @Test
+    public void testPersistableBundle() {
+        final VcnWifiUnderlyingNetworkPriority networkPriority = getTestNetworkPriority();
+        assertEquals(
+                networkPriority,
+                VcnUnderlyingNetworkPriority.fromPersistableBundle(
+                        networkPriority.toPersistableBundle()));
+    }
+}