Snap for 7964896 from 0f642cfedfe5d4f9873974cf7d838e4c8d9b0f58 to tm-release

Change-Id: I2a421603bc92099c47aef68f6bd3b53a6a0b4262
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 56b6800..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";
@@ -13223,6 +13240,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.SharedLibraryInfo> CREATOR;
     field public static final int TYPE_BUILTIN = 0; // 0x0
     field public static final int TYPE_DYNAMIC = 1; // 0x1
+    field public static final int TYPE_SDK = 3; // 0x3
     field public static final int TYPE_STATIC = 2; // 0x2
     field public static final int VERSION_UNDEFINED = -1; // 0xffffffff
   }
@@ -16442,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();
@@ -17659,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();
@@ -21597,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();
@@ -21604,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);
   }
 
@@ -31789,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);
@@ -43119,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..96a7434 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";
@@ -1923,6 +1924,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);
@@ -9762,7 +9765,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/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/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/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c777bf5..a7f3801 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -697,7 +697,7 @@
             MATCH_DISABLED_COMPONENTS,
             MATCH_DISABLED_UNTIL_USED_COMPONENTS,
             MATCH_INSTANT,
-            MATCH_STATIC_SHARED_LIBRARIES,
+            MATCH_STATIC_SHARED_AND_SDK_LIBRARIES,
             GET_DISABLED_UNTIL_USED_COMPONENTS,
             GET_UNINSTALLED_PACKAGES,
             MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
@@ -721,7 +721,7 @@
             MATCH_SYSTEM_ONLY,
             MATCH_UNINSTALLED_PACKAGES,
             MATCH_INSTANT,
-            MATCH_STATIC_SHARED_LIBRARIES,
+            MATCH_STATIC_SHARED_AND_SDK_LIBRARIES,
             GET_DISABLED_COMPONENTS,
             GET_DISABLED_UNTIL_USED_COMPONENTS,
             GET_UNINSTALLED_PACKAGES,
@@ -1038,14 +1038,14 @@
     public static final int MATCH_EXPLICITLY_VISIBLE_ONLY = 0x02000000;
 
     /**
-     * Internal {@link PackageInfo} flag: include static shared libraries.
-     * Apps that depend on static shared libs can always access the version
+     * Internal {@link PackageInfo} flag: include static shared and SDK libraries.
+     * Apps that depend on static shared/SDK libs can always access the version
      * of the lib they depend on. System/shell/root can access all shared
      * libs regardless of dependency but need to explicitly ask for them
      * via this flag.
      * @hide
      */
-    public static final int MATCH_STATIC_SHARED_LIBRARIES = 0x04000000;
+    public static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 0x04000000;
 
     /**
      * {@link PackageInfo} flag: return the signing certificates associated with
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index 7abb694..4ba2ee6 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -70,6 +70,13 @@
     public static final int TYPE_STATIC = 2;
 
     /**
+     * SDK library type: this library is <strong>not</strong> backwards
+     * -compatible, can be updated and updates can be uninstalled. Clients
+     * depend on a specific version of the library.
+     */
+    public static final int TYPE_SDK = 3;
+
+    /**
      * Constant for referring to an undefined version.
      */
     public static final int VERSION_UNDEFINED = -1;
@@ -289,6 +296,13 @@
     }
 
     /**
+     * @hide
+     */
+    public boolean isSdk() {
+        return mType == TYPE_SDK;
+    }
+
+    /**
      * Gets the package that declares the library.
      *
      * @return The package declaring the library.
@@ -351,6 +365,9 @@
             case TYPE_STATIC: {
                 return "static";
             }
+            case TYPE_SDK: {
+                return "sdk";
+            }
             default: {
                 return "unknown";
             }
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 056f99f..63332e7 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -103,11 +103,11 @@
 
     ParsingPackage addUsesOptionalNativeLibrary(String libraryName);
 
-    ParsingPackage addUsesStaticLibrary(String libraryName);
+    ParsingPackage addUsesSdkLibrary(String libraryName, long versionMajor,
+            String[] certSha256Digests);
 
-    ParsingPackage addUsesStaticLibraryCertDigests(String[] certSha256Digests);
-
-    ParsingPackage addUsesStaticLibraryVersion(long version);
+    ParsingPackage addUsesStaticLibrary(String libraryName, long version,
+            String[] certSha256Digests);
 
     ParsingPackage addQueriesIntent(Intent intent);
 
@@ -212,6 +212,12 @@
 
     ParsingPackage setRestoreAnyVersion(boolean restoreAnyVersion);
 
+    ParsingPackage setSdkLibName(String sdkLibName);
+
+    ParsingPackage setSdkLibVersionMajor(int sdkLibVersionMajor);
+
+    ParsingPackage setSdkLibrary(boolean sdkLibrary);
+
     ParsingPackage setSplitHasCode(int splitIndex, boolean splitHasCode);
 
     ParsingPackage setStaticSharedLibrary(boolean staticSharedLibrary);
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index d5957a2..19a8ce9 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -179,6 +179,10 @@
 
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
+    private String sdkLibName;
+    private int sdkLibVersionMajor;
+    @Nullable
+    @DataClass.ParcelWith(ForInternedString.class)
     private String staticSharedLibName;
     private long staticSharedLibVersion;
     @NonNull
@@ -203,10 +207,17 @@
     private List<String> usesStaticLibraries = emptyList();
     @Nullable
     private long[] usesStaticLibrariesVersions;
-
     @Nullable
     private String[][] usesStaticLibrariesCertDigests;
 
+    @NonNull
+    @DataClass.ParcelWith(ForInternedStringList.class)
+    private List<String> usesSdkLibraries = emptyList();
+    @Nullable
+    private long[] usesSdkLibrariesVersionsMajor;
+    @Nullable
+    private String[][] usesSdkLibrariesCertDigests;
+
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
     private String sharedUserId;
@@ -518,6 +529,7 @@
         private static final long REQUEST_FOREGROUND_SERVICE_EXEMPTION = 1L << 46;
         private static final long ATTRIBUTIONS_ARE_USER_VISIBLE = 1L << 47;
         private static final long RESET_ENABLED_SETTINGS_ON_APP_DATA_CLEARED = 1L << 48;
+        private static final long SDK_LIBRARY = 1L << 49;
     }
 
     private ParsingPackageImpl setBoolean(@Booleans.Values long flag, boolean value) {
@@ -828,21 +840,24 @@
     }
 
     @Override
-    public ParsingPackageImpl addUsesStaticLibrary(String libraryName) {
+    public ParsingPackageImpl addUsesSdkLibrary(String libraryName, long versionMajor,
+            String[] certSha256Digests) {
+        this.usesSdkLibraries = CollectionUtils.add(this.usesSdkLibraries,
+                TextUtils.safeIntern(libraryName));
+        this.usesSdkLibrariesVersionsMajor = ArrayUtils.appendLong(
+                this.usesSdkLibrariesVersionsMajor, versionMajor, true);
+        this.usesSdkLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
+                this.usesSdkLibrariesCertDigests, certSha256Digests, true);
+        return this;
+    }
+
+    @Override
+    public ParsingPackageImpl addUsesStaticLibrary(String libraryName, long version,
+            String[] certSha256Digests) {
         this.usesStaticLibraries = CollectionUtils.add(this.usesStaticLibraries,
                 TextUtils.safeIntern(libraryName));
-        return this;
-    }
-
-    @Override
-    public ParsingPackageImpl addUsesStaticLibraryVersion(long version) {
         this.usesStaticLibrariesVersions = ArrayUtils.appendLong(this.usesStaticLibrariesVersions,
                 version, true);
-        return this;
-    }
-
-    @Override
-    public ParsingPackageImpl addUsesStaticLibraryCertDigests(String[] certSha256Digests) {
         this.usesStaticLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
                 this.usesStaticLibrariesCertDigests, certSha256Digests, true);
         return this;
@@ -1136,6 +1151,8 @@
         dest.writeString(this.overlayCategory);
         dest.writeInt(this.overlayPriority);
         sForInternedStringValueMap.parcel(this.overlayables, dest, flags);
+        sForInternedString.parcel(this.sdkLibName, dest, flags);
+        dest.writeInt(this.sdkLibVersionMajor);
         sForInternedString.parcel(this.staticSharedLibName, dest, flags);
         dest.writeLong(this.staticSharedLibVersion);
         sForInternedStringList.parcel(this.libraryNames, dest, flags);
@@ -1143,9 +1160,9 @@
         sForInternedStringList.parcel(this.usesOptionalLibraries, dest, flags);
         sForInternedStringList.parcel(this.usesNativeLibraries, dest, flags);
         sForInternedStringList.parcel(this.usesOptionalNativeLibraries, dest, flags);
+
         sForInternedStringList.parcel(this.usesStaticLibraries, dest, flags);
         dest.writeLongArray(this.usesStaticLibrariesVersions);
-
         if (this.usesStaticLibrariesCertDigests == null) {
             dest.writeInt(-1);
         } else {
@@ -1155,6 +1172,17 @@
             }
         }
 
+        sForInternedStringList.parcel(this.usesSdkLibraries, dest, flags);
+        dest.writeLongArray(this.usesSdkLibrariesVersionsMajor);
+        if (this.usesSdkLibrariesCertDigests == null) {
+            dest.writeInt(-1);
+        } else {
+            dest.writeInt(this.usesSdkLibrariesCertDigests.length);
+            for (int index = 0; index < this.usesSdkLibrariesCertDigests.length; index++) {
+                dest.writeStringArray(this.usesSdkLibrariesCertDigests[index]);
+            }
+        }
+
         sForInternedString.parcel(this.sharedUserId, dest, flags);
         dest.writeInt(this.sharedUserLabel);
         dest.writeTypedList(this.configPreferences);
@@ -1259,6 +1287,8 @@
         this.overlayCategory = in.readString();
         this.overlayPriority = in.readInt();
         this.overlayables = sForInternedStringValueMap.unparcel(in);
+        this.sdkLibName = sForInternedString.unparcel(in);
+        this.sdkLibVersionMajor = in.readInt();
         this.staticSharedLibName = sForInternedString.unparcel(in);
         this.staticSharedLibVersion = in.readLong();
         this.libraryNames = sForInternedStringList.unparcel(in);
@@ -1266,14 +1296,29 @@
         this.usesOptionalLibraries = sForInternedStringList.unparcel(in);
         this.usesNativeLibraries = sForInternedStringList.unparcel(in);
         this.usesOptionalNativeLibraries = sForInternedStringList.unparcel(in);
+
         this.usesStaticLibraries = sForInternedStringList.unparcel(in);
         this.usesStaticLibrariesVersions = in.createLongArray();
+        {
+            int digestsSize = in.readInt();
+            if (digestsSize >= 0) {
+                this.usesStaticLibrariesCertDigests = new String[digestsSize][];
+                for (int index = 0; index < digestsSize; index++) {
+                    this.usesStaticLibrariesCertDigests[index] = sForInternedStringArray.unparcel(
+                            in);
+                }
+            }
+        }
 
-        int digestsSize = in.readInt();
-        if (digestsSize >= 0) {
-            this.usesStaticLibrariesCertDigests = new String[digestsSize][];
-            for (int index = 0; index < digestsSize; index++) {
-                this.usesStaticLibrariesCertDigests[index] = sForInternedStringArray.unparcel(in);
+        this.usesSdkLibraries = sForInternedStringList.unparcel(in);
+        this.usesSdkLibrariesVersionsMajor = in.createLongArray();
+        {
+            int digestsSize = in.readInt();
+            if (digestsSize >= 0) {
+                this.usesSdkLibrariesCertDigests = new String[digestsSize][];
+                for (int index = 0; index < digestsSize; index++) {
+                    this.usesSdkLibrariesCertDigests[index] = sForInternedStringArray.unparcel(in);
+                }
             }
         }
 
@@ -1479,6 +1524,17 @@
 
     @Nullable
     @Override
+    public String getSdkLibName() {
+        return sdkLibName;
+    }
+
+    @Override
+    public int getSdkLibVersionMajor() {
+        return sdkLibVersionMajor;
+    }
+
+    @Nullable
+    @Override
     public String getStaticSharedLibName() {
         return staticSharedLibName;
     }
@@ -1536,6 +1592,18 @@
         return usesStaticLibrariesCertDigests;
     }
 
+    @NonNull
+    @Override
+    public List<String> getUsesSdkLibraries() { return usesSdkLibraries; }
+
+    @Nullable
+    @Override
+    public long[] getUsesSdkLibrariesVersionsMajor() { return usesSdkLibrariesVersionsMajor; }
+
+    @Nullable
+    @Override
+    public String[][] getUsesSdkLibrariesCertDigests() { return usesSdkLibrariesCertDigests; }
+
     @Nullable
     @Override
     public String getSharedUserId() {
@@ -2083,6 +2151,11 @@
     }
 
     @Override
+    public boolean isSdkLibrary() {
+        return getBoolean(Booleans.SDK_LIBRARY);
+    }
+
+    @Override
     public boolean isOverlay() {
         return getBoolean(Booleans.OVERLAY);
     }
@@ -2558,6 +2631,23 @@
     }
 
     @Override
+    public ParsingPackageImpl setSdkLibName(String sdkLibName) {
+        this.sdkLibName = TextUtils.safeIntern(sdkLibName);
+        return this;
+    }
+
+    @Override
+    public ParsingPackageImpl setSdkLibVersionMajor(int sdkLibVersionMajor) {
+        this.sdkLibVersionMajor = sdkLibVersionMajor;
+        return this;
+    }
+
+    @Override
+    public ParsingPackageImpl setSdkLibrary(boolean value) {
+        return setBoolean(Booleans.SDK_LIBRARY, value);
+    }
+
+    @Override
     public ParsingPackageImpl setStaticSharedLibrary(boolean value) {
         return setBoolean(Booleans.STATIC_SHARED_LIBRARY, value);
     }
diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java
index 2933f95..49b3b08 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageRead.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java
@@ -196,6 +196,17 @@
     int[] getSplitFlags();
 
     /**
+     * @see R.styleable#AndroidManifestSdkLibrary_name
+     */
+    @Nullable
+    String getSdkLibName();
+
+    /**
+     * @see R.styleable#AndroidManifestSdkLibrary_versionMajor
+     */
+    int getSdkLibVersionMajor();
+
+    /**
      * @see R.styleable#AndroidManifestStaticLibrary_name
      */
     @Nullable
@@ -267,6 +278,26 @@
     @Nullable
     long[] getUsesStaticLibrariesVersions();
 
+    /**
+     * TODO(b/135203078): Move SDK library stuff to an inner data class
+     *
+     * @see R.styleable#AndroidManifestUsesSdkLibrary
+     */
+    @NonNull
+    List<String> getUsesSdkLibraries();
+
+    /**
+     * @see R.styleable#AndroidManifestUsesSdkLibrary_certDigest
+     */
+    @Nullable
+    String[][] getUsesSdkLibrariesCertDigests();
+
+    /**
+     * @see R.styleable#AndroidManifestUsesSdkLibrary_versionMajor
+     */
+    @Nullable
+    long[] getUsesSdkLibrariesVersionsMajor();
+
     boolean hasPreserveLegacyExternalStorage();
 
     /**
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index d2ac8739..3e537c8 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -117,6 +117,7 @@
 
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
+import libcore.util.HexEncoding;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -848,6 +849,8 @@
                     pkg.addProperty(propertyResult.getResult());
                 }
                 return propertyResult;
+            case "uses-sdk-library":
+                return parseUsesSdkLibrary(input, pkg, res, parser);
             case "uses-static-library":
                 return parseUsesStaticLibrary(input, pkg, res, parser);
             case "uses-library":
@@ -2212,7 +2215,8 @@
             }
         }
 
-        if (TextUtils.isEmpty(pkg.getStaticSharedLibName())) {
+        if (TextUtils.isEmpty(pkg.getStaticSharedLibName()) && TextUtils.isEmpty(
+                pkg.getSdkLibName())) {
             // Add a hidden app detail activity to normal apps which forwards user to App Details
             // page.
             ParseResult<ParsedActivity> a = generateAppDetailsHiddenActivity(input, pkg);
@@ -2351,10 +2355,14 @@
                     pkg.addProperty(propertyResult.getResult());
                 }
                 return propertyResult;
+            case "sdk-library":
+                return parseSdkLibrary(pkg, res, parser, input);
             case "static-library":
                 return parseStaticLibrary(pkg, res, parser, input);
             case "library":
                 return parseLibrary(pkg, res, parser, input);
+            case "uses-sdk-library":
+                return parseUsesSdkLibrary(input, pkg, res, parser);
             case "uses-static-library":
                 return parseUsesStaticLibrary(input, pkg, res, parser);
             case "uses-library":
@@ -2375,6 +2383,41 @@
     }
 
     @NonNull
+    private static ParseResult<ParsingPackage> parseSdkLibrary(
+            ParsingPackage pkg, Resources res,
+            XmlResourceParser parser, ParseInput input) {
+        TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestSdkLibrary);
+        try {
+            // Note: don't allow this value to be a reference to a resource that may change.
+            String lname = sa.getNonResourceString(
+                    R.styleable.AndroidManifestSdkLibrary_name);
+            final int versionMajor = sa.getInt(
+                    R.styleable.AndroidManifestSdkLibrary_versionMajor,
+                    -1);
+
+            // Fail if malformed.
+            if (lname == null || versionMajor < 0) {
+                return input.error("Bad sdk-library declaration name: " + lname
+                        + " version: " + versionMajor);
+            } else if (pkg.getSharedUserId() != null) {
+                return input.error(
+                        PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID,
+                        "sharedUserId not allowed in SDK library"
+                );
+            } else if (pkg.getSdkLibName() != null) {
+                return input.error("Multiple SDKs for package "
+                        + pkg.getPackageName());
+            }
+
+            return input.success(pkg.setSdkLibName(lname.intern())
+                    .setSdkLibVersionMajor(versionMajor)
+                    .setSdkLibrary(true));
+        } finally {
+            sa.recycle();
+        }
+    }
+
+    @NonNull
     private static ParseResult<ParsingPackage> parseStaticLibrary(
             ParsingPackage pkg, Resources res,
             XmlResourceParser parser, ParseInput input) {
@@ -2436,6 +2479,68 @@
     }
 
     @NonNull
+    private static ParseResult<ParsingPackage> parseUsesSdkLibrary(ParseInput input,
+            ParsingPackage pkg, Resources res, XmlResourceParser parser)
+            throws XmlPullParserException, IOException {
+        TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdkLibrary);
+        try {
+            // Note: don't allow this value to be a reference to a resource that may change.
+            String lname = sa.getNonResourceString(
+                    R.styleable.AndroidManifestUsesSdkLibrary_name);
+            final int versionMajor = sa.getInt(
+                    R.styleable.AndroidManifestUsesSdkLibrary_versionMajor, -1);
+            String certSha256Digest = sa.getNonResourceString(R.styleable
+                    .AndroidManifestUsesSdkLibrary_certDigest);
+
+            // Since an APK providing a static shared lib can only provide the lib - fail if
+            // malformed
+            if (lname == null || versionMajor < 0 || certSha256Digest == null) {
+                return input.error("Bad uses-sdk-library declaration name: " + lname
+                        + " version: " + versionMajor + " certDigest" + certSha256Digest);
+            }
+
+            // Can depend only on one version of the same library
+            List<String> usesSdkLibraries = pkg.getUsesSdkLibraries();
+            if (usesSdkLibraries.contains(lname)) {
+                return input.error(
+                        "Depending on multiple versions of SDK library " + lname);
+            }
+
+            lname = lname.intern();
+            // We allow ":" delimiters in the SHA declaration as this is the format
+            // emitted by the certtool making it easy for developers to copy/paste.
+            certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();
+
+            if ("".equals(certSha256Digest)) {
+                // Test-only uses-sdk-library empty certificate digest override.
+                certSha256Digest = SystemProperties.get(
+                        "debug.pm.uses_sdk_library_default_cert_digest", "");
+                // Validate the overridden digest.
+                try {
+                    HexEncoding.decode(certSha256Digest, false);
+                } catch (IllegalArgumentException e) {
+                    certSha256Digest = "";
+                }
+            }
+
+            ParseResult<String[]> certResult = parseAdditionalCertificates(input, res, parser);
+            if (certResult.isError()) {
+                return input.error(certResult);
+            }
+            String[] additionalCertSha256Digests = certResult.getResult();
+
+            final String[] certSha256Digests = new String[additionalCertSha256Digests.length + 1];
+            certSha256Digests[0] = certSha256Digest;
+            System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
+                    1, additionalCertSha256Digests.length);
+
+            return input.success(pkg.addUsesSdkLibrary(lname, versionMajor, certSha256Digests));
+        } finally {
+            sa.recycle();
+        }
+    }
+
+    @NonNull
     private static ParseResult<ParsingPackage> parseUsesStaticLibrary(ParseInput input,
             ParsingPackage pkg, Resources res, XmlResourceParser parser)
             throws XmlPullParserException, IOException {
@@ -2483,9 +2588,7 @@
             System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
                     1, additionalCertSha256Digests.length);
 
-            return input.success(pkg.addUsesStaticLibrary(lname)
-                    .addUsesStaticLibraryVersion(version)
-                    .addUsesStaticLibraryCertDigests(certSha256Digests));
+            return input.success(pkg.addUsesStaticLibrary(lname, version, certSha256Digests));
         } finally {
             sa.recycle();
         }
diff --git a/core/java/android/content/pm/parsing/PkgWithoutStateAppInfo.java b/core/java/android/content/pm/parsing/PkgWithoutStateAppInfo.java
index fcad10c..625b9d1 100644
--- a/core/java/android/content/pm/parsing/PkgWithoutStateAppInfo.java
+++ b/core/java/android/content/pm/parsing/PkgWithoutStateAppInfo.java
@@ -21,8 +21,6 @@
 import android.content.pm.ApplicationInfo;
 import android.util.SparseArray;
 
-import com.android.internal.R;
-
 /**
  * Container for fields that are eventually exposed through {@link ApplicationInfo}.
  * <p>
@@ -576,6 +574,11 @@
     boolean isStaticSharedLibrary();
 
     /**
+     * True means that this package/app contains an SDK library.
+     */
+    boolean isSdkLibrary();
+
+    /**
      * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
      * android.os.Build.VERSION_CODES#GINGERBREAD}.
      *
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/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/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 3d57db9..b8e50fc 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1663,6 +1663,9 @@
     @CriticalNative
     private static native void nativeScale(long nativePtr, float scale);
 
+    @CriticalNative
+    private static native int nativeGetSurfaceRotation(long nativePtr);
+
     private MotionEvent() {
     }
 
@@ -3805,17 +3808,39 @@
     }
 
     /**
-     * Gets a rotation matrix that (when applied to a motionevent) will rotate that motion event
-     * such that the result coordinates end up in the same physical location on a display whose
-     * coordinates are rotated by `rotation`.
+     * Gets the rotation value of the transform for this MotionEvent.
      *
-     * For example, rotating 0,0 by 90 degrees will move a point from the physical top-left to
-     * the bottom-left of the 90-degree-rotated display.
+     * This MotionEvent's rotation can be changed by passing a rotation matrix to
+     * {@link #transform(Matrix)} to change the coordinate space of this event.
+     *
+     * @return the rotation value, or -1 if unknown or invalid.
+     * @see Surface.Rotation
+     * @see #createRotateMatrix(int, int, int)
      *
      * @hide
      */
+    public @Surface.Rotation int getSurfaceRotation() {
+        return nativeGetSurfaceRotation(mNativePtr);
+    }
+
+    /**
+     * Gets a rotation matrix that (when applied to a MotionEvent) will rotate that motion event
+     * such that the result coordinates end up in the same physical location on a frame whose
+     * coordinates are rotated by `rotation`.
+     *
+     * For example, rotating (0,0) by 90 degrees will move a point from the physical top-left to
+     * the bottom-left of the 90-degree-rotated frame.
+     *
+     * @param rotation the surface rotation of the output matrix
+     * @param rotatedFrameWidth the width of the rotated frame
+     * @param rotatedFrameHeight the height of the rotated frame
+     *
+     * @see #transform(Matrix)
+     * @see #getSurfaceRotation()
+     * @hide
+     */
     public static Matrix createRotateMatrix(
-            @Surface.Rotation int rotation, int displayW, int displayH) {
+            @Surface.Rotation int rotation, int rotatedFrameWidth, int rotatedFrameHeight) {
         if (rotation == Surface.ROTATION_0) {
             return new Matrix(Matrix.IDENTITY_MATRIX);
         }
@@ -3823,14 +3848,14 @@
         float[] values = null;
         if (rotation == Surface.ROTATION_90) {
             values = new float[]{0, 1, 0,
-                    -1, 0, displayH,
+                    -1, 0, rotatedFrameHeight,
                     0, 0, 1};
         } else if (rotation == Surface.ROTATION_180) {
-            values = new float[]{-1, 0, displayW,
-                    0, -1, displayH,
+            values = new float[]{-1, 0, rotatedFrameWidth,
+                    0, -1, rotatedFrameHeight,
                     0, 0, 1};
         } else if (rotation == Surface.ROTATION_270) {
-            values = new float[]{0, -1, displayW,
+            values = new float[]{0, -1, rotatedFrameWidth,
                     1, 0, 0,
                     0, 0, 1};
         }
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 62585c1a..a8bf50e 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1848,7 +1848,7 @@
         if (mHasPendingRestartInputForSetText) {
             final InputMethodManager imm = getInputMethodManager();
             if (imm != null) {
-                imm.restartInput(mTextView);
+                imm.invalidateInput(mTextView);
             }
             mHasPendingRestartInputForSetText = false;
         }
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/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 292f305..07e1a6c 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -769,6 +769,11 @@
     event->scale(scale);
 }
 
+static jint android_view_MotionEvent_nativeGetSurfaceRotation(jlong nativePtr) {
+    MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+    return jint(event->getSurfaceRotation());
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gMotionEventMethods[] = {
@@ -845,6 +850,8 @@
         {"nativeFindPointerIndex", "(JI)I", (void*)android_view_MotionEvent_nativeFindPointerIndex},
         {"nativeGetHistorySize", "(J)I", (void*)android_view_MotionEvent_nativeGetHistorySize},
         {"nativeScale", "(JF)V", (void*)android_view_MotionEvent_nativeScale},
+        {"nativeGetSurfaceRotation", "(J)I",
+         (void*)android_view_MotionEvent_nativeGetSurfaceRotation},
 };
 
 int register_android_view_MotionEvent(JNIEnv* env) {
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/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 94717b1..fe58114 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2303,6 +2303,36 @@
         <attr name="authorities" />
     </declare-styleable>
 
+    <!-- The <code>sdk-library</code> tag declares that this apk is providing itself
+    as an SDK library for other applications to use. Any app can declare an SDK library and there
+    can be only one SDK library per package. These SDK libraries are updatable, multiple major
+    versions can be installed at the same time, and an app depends on a specific version.
+    Other apks can link to it with the {@link #AndroidManifestUsesSdkLibrary uses-sdk-library} tag.
+
+    <p>This appears as a child tag of the {@link #AndroidManifestApplication application} tag. -->
+    <declare-styleable name="AndroidManifestSdkLibrary" parent="AndroidManifestApplication">
+        <!-- Required public name of the SDK library, which other components and packages will use
+        when referring to this SDK library. This is a string using Java-style scoping to ensure
+        it is unique.
+        Both name and version should typically form the apk's package name: name_versionMajor. -->
+        <attr name="name" />
+        <!-- Required major version of the SDK library. -->
+        <attr name="versionMajor" format="integer" />
+    </declare-styleable>
+
+
+    <!-- The <code>uses-sdk-library</code> specifies a shared <strong>SDK</strong> library that this
+    package requires to be present on the device.
+
+    <p>This appears as a child tag of the {@link #AndroidManifestApplication application} tag. -->
+    <declare-styleable name="AndroidManifestUsesSdkLibrary" parent="AndroidManifestApplication">
+        <!-- Required name of the SDK library you use. -->
+        <attr name="name" />
+        <!-- Specify which major version of the SDK library you use. -->
+        <attr name="versionMajor" format="integer" />
+        <!-- The SHA-256 digest of the SDK library signing certificate. -->
+        <attr name="certDigest" format="string" />
+    </declare-styleable>
 
     <!-- The <code>static-library</code> tag declares that this apk is providing itself
        as a static shared library for other applications to use. Any app can declare such
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/view/MotionEventTest.java b/core/tests/coretests/src/android/view/MotionEventTest.java
index 7f7a7aa..78a8f7b 100644
--- a/core/tests/coretests/src/android/view/MotionEventTest.java
+++ b/core/tests/coretests/src/android/view/MotionEventTest.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 
+import android.graphics.Matrix;
 import android.platform.test.annotations.Presubmit;
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
@@ -174,22 +175,43 @@
 
     @Test
     public void testEventRotation() {
+        // The un-rotated frame size.
+        final int width = 600;
+        final int height = 1000;
         final MotionEvent event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */,
-                    ACTION_DOWN, 30 /* x */, 50 /* y */, 0 /* metaState */);
+                ACTION_DOWN, 30 /* x */, 50 /* y */, 0 /* metaState */);
         event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+        assertEquals(Surface.ROTATION_0, event.getSurfaceRotation());
+
         MotionEvent rot90 = MotionEvent.obtain(event);
-        rot90.transform(MotionEvent.createRotateMatrix(/* 90 deg */1, 1000, 600));
+        rot90.transform(MotionEvent.createRotateMatrix(Surface.ROTATION_90, height, width));
         assertEquals(50, (int) rot90.getX());
         assertEquals(570, (int) rot90.getY());
+        assertEquals(Surface.ROTATION_90, rot90.getSurfaceRotation());
 
         MotionEvent rot180 = MotionEvent.obtain(event);
-        rot180.transform(MotionEvent.createRotateMatrix(/* 180 deg */2, 1000, 600));
-        assertEquals(970, (int) rot180.getX());
-        assertEquals(550, (int) rot180.getY());
+        rot180.transform(MotionEvent.createRotateMatrix(Surface.ROTATION_180, width, height));
+        assertEquals(570, (int) rot180.getX());
+        assertEquals(950, (int) rot180.getY());
+        assertEquals(Surface.ROTATION_180, rot180.getSurfaceRotation());
 
         MotionEvent rot270 = MotionEvent.obtain(event);
-        rot270.transform(MotionEvent.createRotateMatrix(/* 270 deg */3, 1000, 600));
+        rot270.transform(MotionEvent.createRotateMatrix(Surface.ROTATION_270, height, width));
         assertEquals(950, (int) rot270.getX());
         assertEquals(30, (int) rot270.getY());
+        assertEquals(Surface.ROTATION_270, rot270.getSurfaceRotation());
+
+        MotionEvent compoundRot = MotionEvent.obtain(event);
+        compoundRot.transform(MotionEvent.createRotateMatrix(Surface.ROTATION_90, height, width));
+        compoundRot.transform(MotionEvent.createRotateMatrix(Surface.ROTATION_180, height, width));
+        assertEquals(950, (int) compoundRot.getX());
+        assertEquals(30, (int) compoundRot.getY());
+        assertEquals(Surface.ROTATION_270, compoundRot.getSurfaceRotation());
+
+        MotionEvent rotInvalid = MotionEvent.obtain(event);
+        Matrix mat = new Matrix();
+        mat.setValues(new float[]{1, 2, 3, -4, -5, -6, 0, 0, 1});
+        rotInvalid.transform(mat);
+        assertEquals(-1, rotInvalid.getSurfaceRotation());
     }
 }
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/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/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/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/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 100%
rename from services/core/java/com/android/server/NativeDaemonConnector.java
rename to packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
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 100%
rename from services/core/java/com/android/server/NativeDaemonEvent.java
rename to packages/Nsd/service/src/com/android/server/NativeDaemonEvent.java
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 100%
rename from services/core/java/com/android/server/NsdService.java
rename to packages/Nsd/service/src/com/android/server/NsdService.java
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/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..4790412 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2173,6 +2173,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 +2355,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/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/CommunalManagerUpdater.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalManagerUpdater.java
new file mode 100644
index 0000000..ebe804a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalManagerUpdater.java
@@ -0,0 +1,67 @@
+/*
+ * 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.communal;
+
+import android.app.communal.CommunalManager;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.SysUISingleton;
+
+import java.lang.ref.WeakReference;
+
+import javax.inject.Inject;
+
+/**
+ * The {@link CommunalManagerUpdater} is responsible for forwarding state from SystemUI to
+ * the {@link CommunalManager} system service.
+ */
+@SysUISingleton
+public class CommunalManagerUpdater extends CoreStartable {
+    private static final String TAG = "CommunalManagerUpdater";
+
+    private final CommunalManager mCommunalManager;
+    private final CommunalSourceMonitor mMonitor;
+
+    private final CommunalSourceMonitor.Callback mSourceCallback =
+            new CommunalSourceMonitor.Callback() {
+                @Override
+                public void onSourceAvailable(WeakReference<CommunalSource> source) {
+                    try {
+                        mCommunalManager.setCommunalViewShowing(
+                                source != null && source.get() != null);
+                    } catch (RuntimeException e) {
+                        Log.e(TAG, "Error updating communal manager service state", e);
+                    }
+                }
+            };
+
+    @Inject
+    public CommunalManagerUpdater(Context context, CommunalSourceMonitor monitor) {
+        super(context);
+        mCommunalManager = context.getSystemService(CommunalManager.class);
+        mMonitor = monitor;
+    }
+
+    @Override
+    public void start() {
+        if (mCommunalManager != null) {
+            mMonitor.addCallback(mSourceCallback);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java
index 2244532..d3018e3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java
@@ -16,17 +16,11 @@
 
 package com.android.systemui.communal;
 
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.Log;
 
-import androidx.annotation.MainThread;
-
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.util.settings.SecureSettings;
 
 import com.google.android.collect.Lists;
 
@@ -46,10 +40,15 @@
 
     // A list of {@link Callback} that have registered to receive updates.
     private final ArrayList<WeakReference<Callback>> mCallbacks = Lists.newArrayList();
-    private final SecureSettings mSecureSettings;
+    private final CommunalConditionsMonitor mConditionsMonitor;
 
     private CommunalSource mCurrentSource;
-    private boolean mCommunalEnabled;
+
+    // Whether all conditions for communal mode to show have been met.
+    private boolean mAllCommunalConditionsMet = false;
+
+    // Whether the class is currently listening for condition changes.
+    private boolean mListeningForConditions = false;
 
     private CommunalSource.Callback mSourceCallback = new CommunalSource.Callback() {
         @Override
@@ -59,24 +58,20 @@
         }
     };
 
+    private final CommunalConditionsMonitor.Callback mConditionsCallback =
+            allConditionsMet -> {
+                if (mAllCommunalConditionsMet != allConditionsMet) {
+                    if (DEBUG) Log.d(TAG, "communal conditions changed: " + allConditionsMet);
+
+                    mAllCommunalConditionsMet = allConditionsMet;
+                    executeOnSourceAvailableCallbacks();
+                }
+            };
+
     @VisibleForTesting
     @Inject
-    public CommunalSourceMonitor(
-            @MainThread Handler mainThreadHandler,
-            SecureSettings secureSettings) {
-        mSecureSettings = secureSettings;
-
-        ContentObserver settingsObserver = new ContentObserver(mainThreadHandler) {
-            @Override
-            public void onChange(boolean selfChange) {
-                reloadSettings();
-            }
-        };
-        mSecureSettings.registerContentObserverForUser(
-                Settings.Secure.COMMUNAL_MODE_ENABLED,
-                /* notifyForDescendants= */false,
-                settingsObserver, UserHandle.USER_SYSTEM);
-        reloadSettings();
+    public CommunalSourceMonitor(CommunalConditionsMonitor communalConditionsMonitor) {
+        mConditionsMonitor = communalConditionsMonitor;
     }
 
     /**
@@ -92,7 +87,7 @@
 
         mCurrentSource = source;
 
-        if (mCommunalEnabled) {
+        if (mAllCommunalConditionsMet) {
             executeOnSourceAvailableCallbacks();
         }
 
@@ -111,7 +106,7 @@
                 itr.remove();
             } else {
                 cb.onSourceAvailable(
-                        (mCommunalEnabled && mCurrentSource != null) ? new WeakReference<>(
+                        (mAllCommunalConditionsMet && mCurrentSource != null) ? new WeakReference<>(
                                 mCurrentSource) : null);
             }
         }
@@ -126,9 +121,14 @@
         mCallbacks.add(new WeakReference<>(callback));
 
         // Inform the callback of any already present CommunalSource.
-        if (mCommunalEnabled && mCurrentSource != null) {
+        if (mAllCommunalConditionsMet && mCurrentSource != null) {
             callback.onSourceAvailable(new WeakReference<>(mCurrentSource));
         }
+
+        if (!mListeningForConditions) {
+            mConditionsMonitor.addCallback(mConditionsCallback);
+            mListeningForConditions = true;
+        }
     }
 
     /**
@@ -138,21 +138,10 @@
      */
     public void removeCallback(Callback callback) {
         mCallbacks.removeIf(el -> el.get() == callback);
-    }
 
-    private void reloadSettings() {
-        boolean newCommunalEnabled = mSecureSettings.getIntForUser(
-                Settings.Secure.COMMUNAL_MODE_ENABLED,
-                1,
-                UserHandle.USER_SYSTEM) == 1;
-
-        if (DEBUG) {
-            Log.d(TAG, "communal mode settings reloaded with value:" + newCommunalEnabled);
-        }
-
-        if (mCommunalEnabled != newCommunalEnabled) {
-            mCommunalEnabled = newCommunalEnabled;
-            executeOnSourceAvailableCallbacks();
+        if (mCallbacks.isEmpty() && mListeningForConditions) {
+            mConditionsMonitor.removeCallback(mConditionsCallback);
+            mListeningForConditions = false;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalStateController.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalStateController.java
index 7be8ecc..c72f542 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalStateController.java
@@ -17,9 +17,6 @@
 package com.android.systemui.communal;
 
 import android.annotation.NonNull;
-import android.app.communal.CommunalManager;
-import android.content.Context;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.SysUISingleton;
@@ -36,9 +33,7 @@
 @SysUISingleton
 public class CommunalStateController implements
         CallbackController<CommunalStateController.Callback> {
-    private static final String TAG = CommunalStateController.class.getSimpleName();
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
-    private final CommunalManager mCommunalManager;
     private boolean mCommunalViewOccluded;
     private boolean mCommunalViewShowing;
 
@@ -61,8 +56,7 @@
 
     @VisibleForTesting
     @Inject
-    public CommunalStateController(Context context) {
-        mCommunalManager = context.getSystemService(CommunalManager.class);
+    public CommunalStateController() {
     }
 
     /**
@@ -76,12 +70,6 @@
 
         mCommunalViewShowing = communalViewShowing;
 
-        try {
-            mCommunalManager.setCommunalViewShowing(communalViewShowing);
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Error updating communal manager service state", e);
-        }
-
         final ArrayList<Callback> callbacks = new ArrayList<>(mCallbacks);
         for (Callback callback : callbacks) {
             callback.onCommunalViewShowingChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalCondition.java b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalCondition.java
new file mode 100644
index 0000000..734ab63
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalCondition.java
@@ -0,0 +1,130 @@
+/*
+ * 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.communal.conditions;
+
+import android.util.Log;
+
+import com.android.systemui.statusbar.policy.CallbackController;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Base class for a condition that needs to be fulfilled in order for Communal Mode to display.
+ */
+public abstract class CommunalCondition implements CallbackController<CommunalCondition.Callback> {
+    private final String mTag = getClass().getSimpleName();
+
+    private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
+    private boolean mIsConditionMet = false;
+    private boolean mStarted = false;
+
+    /**
+     * Starts monitoring the condition.
+     */
+    protected abstract void start();
+
+    /**
+     * Stops monitoring the condition.
+     */
+    protected abstract void stop();
+
+    /**
+     * Registers a callback to receive updates once started. This should be called before
+     * {@link #start()}. Also triggers the callback immediately if already started.
+     */
+    @Override
+    public void addCallback(@NotNull Callback callback) {
+        if (shouldLog()) Log.d(mTag, "adding callback");
+        mCallbacks.add(new WeakReference<>(callback));
+
+        if (mStarted) {
+            callback.onConditionChanged(this, mIsConditionMet);
+            return;
+        }
+
+        start();
+        mStarted = true;
+    }
+
+    /**
+     * Removes the provided callback from further receiving updates.
+     */
+    @Override
+    public void removeCallback(@NotNull Callback callback) {
+        if (shouldLog()) Log.d(mTag, "removing callback");
+        final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
+        while (iterator.hasNext()) {
+            final Callback cb = iterator.next().get();
+            if (cb == null || cb == callback) {
+                iterator.remove();
+            }
+        }
+
+        if (!mCallbacks.isEmpty() || !mStarted) {
+            return;
+        }
+
+        stop();
+        mStarted = false;
+    }
+
+    /**
+     * Updates the value for whether the condition has been fulfilled, and sends an update if the
+     * value changes and any callback is registered.
+     *
+     * @param isConditionMet True if the condition has been fulfilled. False otherwise.
+     */
+    protected void updateCondition(boolean isConditionMet) {
+        if (mIsConditionMet == isConditionMet) {
+            return;
+        }
+
+        if (shouldLog()) Log.d(mTag, "updating condition to " + isConditionMet);
+        mIsConditionMet = isConditionMet;
+
+        final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
+        while (iterator.hasNext()) {
+            final Callback cb = iterator.next().get();
+            if (cb == null) {
+                iterator.remove();
+            } else {
+                cb.onConditionChanged(this, mIsConditionMet);
+            }
+        }
+    }
+
+    private boolean shouldLog() {
+        return Log.isLoggable(mTag, Log.DEBUG);
+    }
+
+    /**
+     * Callback that receives updates about whether the condition has been fulfilled.
+     */
+    public interface Callback {
+        /**
+         * Called when the fulfillment of the condition changes.
+         *
+         * @param condition The condition in question.
+         * @param isConditionMet True if the condition has been fulfilled. False otherwise.
+         */
+        void onConditionChanged(CommunalCondition condition, boolean isConditionMet);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java
new file mode 100644
index 0000000..4f772da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java
@@ -0,0 +1,154 @@
+/*
+ * 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.communal.conditions;
+
+import static com.android.systemui.communal.dagger.CommunalModule.COMMUNAL_CONDITIONS;
+
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.policy.CallbackController;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link CommunalConditionsMonitor} takes in a set of conditions, monitors whether all of them have
+ * been fulfilled, and informs any registered listeners.
+ */
+@SysUISingleton
+public class CommunalConditionsMonitor implements
+        CallbackController<CommunalConditionsMonitor.Callback> {
+    private final String mTag = getClass().getSimpleName();
+
+    private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
+
+    // Set of all conditions that need to be monitored.
+    private final Set<CommunalCondition> mConditions;
+
+    // Map of values of each condition.
+    private final HashMap<CommunalCondition, Boolean> mConditionsMap = new HashMap<>();
+
+    // Whether all conditions have been met.
+    private boolean mAllConditionsMet = false;
+
+    // Whether the monitor has started listening for all the conditions.
+    private boolean mHaveConditionsStarted = false;
+
+    // Callback for when each condition has been updated.
+    private final CommunalCondition.Callback mConditionCallback = (condition, isConditionMet) -> {
+        mConditionsMap.put(condition, isConditionMet);
+
+        final boolean newAllConditionsMet = !mConditionsMap.containsValue(false);
+
+        if (newAllConditionsMet == mAllConditionsMet) {
+            return;
+        }
+
+        if (shouldLog()) Log.d(mTag, "all conditions met: " + newAllConditionsMet);
+        mAllConditionsMet = newAllConditionsMet;
+
+        // Updates all callbacks.
+        final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
+        while (iterator.hasNext()) {
+            final Callback callback = iterator.next().get();
+            if (callback == null) {
+                iterator.remove();
+            } else {
+                callback.onConditionsChanged(mAllConditionsMet);
+            }
+        }
+    };
+
+    @Inject
+    public CommunalConditionsMonitor(
+            @Named(COMMUNAL_CONDITIONS) Set<CommunalCondition> communalConditions) {
+        mConditions = communalConditions;
+
+        // Initializes the conditions map and registers a callback for each condition.
+        mConditions.forEach((condition -> mConditionsMap.put(condition, false)));
+    }
+
+    @Override
+    public void addCallback(@NotNull Callback callback) {
+        if (shouldLog()) Log.d(mTag, "adding callback");
+        mCallbacks.add(new WeakReference<>(callback));
+
+        // Updates the callback immediately.
+        callback.onConditionsChanged(mAllConditionsMet);
+
+        if (!mHaveConditionsStarted) {
+            if (shouldLog()) Log.d(mTag, "starting all conditions");
+            mConditions.forEach(condition -> condition.addCallback(mConditionCallback));
+            mHaveConditionsStarted = true;
+        }
+    }
+
+    @Override
+    public void removeCallback(@NotNull Callback callback) {
+        if (shouldLog()) Log.d(mTag, "removing callback");
+        final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
+        while (iterator.hasNext()) {
+            final Callback cb = iterator.next().get();
+            if (cb == null || cb == callback) {
+                iterator.remove();
+            }
+        }
+
+        if (mCallbacks.isEmpty() && mHaveConditionsStarted) {
+            if (shouldLog()) Log.d(mTag, "stopping all conditions");
+            mConditions.forEach(condition -> condition.removeCallback(mConditionCallback));
+
+            mAllConditionsMet = false;
+            mHaveConditionsStarted = false;
+        }
+    }
+
+    /**
+     * Force updates each condition to the value provided.
+     */
+    @VisibleForTesting
+    public void overrideAllConditionsMet(boolean value) {
+        mConditions.forEach(condition -> condition.updateCondition(value));
+    }
+
+    private boolean shouldLog() {
+        return Log.isLoggable(mTag, Log.DEBUG);
+    }
+
+    /**
+     * Callback that receives updates of whether all conditions have been fulfilled.
+     */
+    public interface Callback {
+        /**
+         * Triggered when the fulfillment of all conditions have been met.
+         *
+         * @param allConditionsMet True if all conditions have been fulfilled. False if none or
+         *                         only partial conditions have been fulfilled.
+         */
+        void onConditionsChanged(boolean allConditionsMet);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalSettingCondition.java b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalSettingCondition.java
new file mode 100644
index 0000000..1616b18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalSettingCondition.java
@@ -0,0 +1,66 @@
+/*
+ * 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.communal.conditions;
+
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.annotation.MainThread;
+
+import com.android.systemui.util.settings.SecureSettings;
+
+import javax.inject.Inject;
+
+/**
+ * Monitors the communal setting, and informs any listeners with updates.
+ */
+public class CommunalSettingCondition extends CommunalCondition {
+    private final SecureSettings mSecureSettings;
+    private final ContentObserver mCommunalSettingContentObserver;
+
+    @Inject
+    public CommunalSettingCondition(@MainThread Handler mainHandler,
+            SecureSettings secureSettings) {
+        mSecureSettings = secureSettings;
+
+        mCommunalSettingContentObserver = new ContentObserver(mainHandler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                final boolean communalSettingEnabled = mSecureSettings.getIntForUser(
+                        Settings.Secure.COMMUNAL_MODE_ENABLED, 0, UserHandle.USER_SYSTEM) == 1;
+                updateCondition(communalSettingEnabled);
+            }
+        };
+    }
+
+    @Override
+    protected void start() {
+        mSecureSettings.registerContentObserverForUser(Settings.Secure.COMMUNAL_MODE_ENABLED,
+                false /*notifyForDescendants*/, mCommunalSettingContentObserver,
+                UserHandle.USER_SYSTEM);
+
+        // Fetches setting immediately.
+        mCommunalSettingContentObserver.onChange(false);
+    }
+
+    @Override
+    protected void stop() {
+        mSecureSettings.unregisterContentObserver(mCommunalSettingContentObserver);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java
new file mode 100644
index 0000000..e4692db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java
@@ -0,0 +1,189 @@
+/*
+ * 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.communal.conditions;
+
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.WifiInfo;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import javax.inject.Inject;
+
+/**
+ * Monitors Wi-Fi connections and triggers callback, if any, when the device is connected to and
+ * disconnected from a trusted network.
+ */
+public class CommunalTrustedNetworkCondition extends CommunalCondition {
+    private final String mTag = getClass().getSimpleName();
+    private final ConnectivityManager mConnectivityManager;
+    private final ContentObserver mTrustedNetworksObserver;
+    private final SecureSettings mSecureSettings;
+
+    // The SSID of the connected Wi-Fi network. Null if not connected to Wi-Fi.
+    private String mWifiSSID;
+
+    // Set of SSIDs of trusted networks.
+    private final HashSet<String> mTrustedNetworks = new HashSet<>();
+
+    /**
+     * The deliminator used to separate trusted network keys saved as a string in secure settings.
+     */
+    public static final String SETTINGS_STRING_DELIMINATOR = ",/";
+
+    @Inject
+    public CommunalTrustedNetworkCondition(@Main Handler handler,
+            ConnectivityManager connectivityManager, SecureSettings secureSettings) {
+        mConnectivityManager = connectivityManager;
+        mSecureSettings = secureSettings;
+
+        mTrustedNetworksObserver = new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                fetchTrustedNetworks();
+            }
+        };
+    }
+
+    /**
+     * Starts monitoring for trusted network connection. Ignores if already started.
+     */
+    @Override
+    protected void start() {
+        if (shouldLog()) Log.d(mTag, "start listening for wifi connections");
+
+        fetchTrustedNetworks();
+
+        final NetworkRequest wifiNetworkRequest = new NetworkRequest.Builder().addTransportType(
+                NetworkCapabilities.TRANSPORT_WIFI).build();
+        mConnectivityManager.registerNetworkCallback(wifiNetworkRequest, mNetworkCallback);
+        mSecureSettings.registerContentObserverForUser(
+                Settings.Secure.COMMUNAL_MODE_TRUSTED_NETWORKS, false, mTrustedNetworksObserver,
+                UserHandle.USER_SYSTEM);
+    }
+
+    /**
+     * Stops monitoring for trusted network connection.
+     */
+    @Override
+    protected void stop() {
+        if (shouldLog()) Log.d(mTag, "stop listening for wifi connections");
+
+        mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+        mSecureSettings.unregisterContentObserver(mTrustedNetworksObserver);
+    }
+
+    private void updateWifiInfo(WifiInfo wifiInfo) {
+        if (wifiInfo == null) {
+            mWifiSSID = null;
+        } else {
+            // Remove the wrapping quotes around the SSID.
+            mWifiSSID = wifiInfo.getSSID().replace("\"", "");
+        }
+
+        checkIfConnectedToTrustedNetwork();
+    }
+
+    private void fetchTrustedNetworks() {
+        final String trustedNetworksString = mSecureSettings.getStringForUser(
+                Settings.Secure.COMMUNAL_MODE_TRUSTED_NETWORKS, UserHandle.USER_SYSTEM);
+        mTrustedNetworks.clear();
+
+        if (shouldLog()) Log.d(mTag, "fetched trusted networks: " + trustedNetworksString);
+
+        if (TextUtils.isEmpty(trustedNetworksString)) {
+            return;
+        }
+
+        mTrustedNetworks.addAll(
+                Arrays.asList(trustedNetworksString.split(SETTINGS_STRING_DELIMINATOR)));
+
+        checkIfConnectedToTrustedNetwork();
+    }
+
+    private void checkIfConnectedToTrustedNetwork() {
+        final boolean connectedToTrustedNetwork = mWifiSSID != null && mTrustedNetworks.contains(
+                mWifiSSID);
+
+        if (shouldLog()) {
+            Log.d(mTag, (connectedToTrustedNetwork ? "connected to" : "disconnected from")
+                    + " a trusted network");
+        }
+
+        updateCondition(connectedToTrustedNetwork);
+    }
+
+    private final ConnectivityManager.NetworkCallback mNetworkCallback =
+            new ConnectivityManager.NetworkCallback(
+                    ConnectivityManager.NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) {
+                private boolean mIsConnected = false;
+                private WifiInfo mWifiInfo;
+
+                @Override
+                public void onAvailable(@NonNull Network network) {
+                    super.onAvailable(network);
+
+                    if (shouldLog()) Log.d(mTag, "connected to wifi");
+
+                    mIsConnected = true;
+                    if (mWifiInfo != null) {
+                        updateWifiInfo(mWifiInfo);
+                    }
+                }
+
+                @Override
+                public void onLost(@NonNull Network network) {
+                    super.onLost(network);
+
+                    if (shouldLog()) Log.d(mTag, "disconnected from wifi");
+
+                    mIsConnected = false;
+                    mWifiInfo = null;
+                    updateWifiInfo(null);
+                }
+
+                @Override
+                public void onCapabilitiesChanged(@NonNull Network network,
+                        @NonNull NetworkCapabilities networkCapabilities) {
+                    super.onCapabilitiesChanged(network, networkCapabilities);
+
+                    mWifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
+
+                    if (mIsConnected) {
+                        updateWifiInfo(mWifiInfo);
+                    }
+                }
+            };
+
+    private boolean shouldLog() {
+        return Log.isLoggable(mTag, Log.DEBUG);
+    }
+}
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 31dd82d..3ebfb51 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
@@ -20,15 +20,23 @@
 import android.view.View;
 import android.widget.FrameLayout;
 
+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.HashSet;
+import java.util.Set;
+
 import javax.inject.Named;
 
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
+import dagger.multibindings.ElementsIntoSet;
 
 /**
  * Dagger Module providing Communal-related functionality.
@@ -39,6 +47,7 @@
 })
 public interface CommunalModule {
     String IDLE_VIEW = "idle_view";
+    String COMMUNAL_CONDITIONS = "communal_conditions";
 
     /** */
     @Provides
@@ -56,4 +65,17 @@
     @Binds
     AmbientLightModeMonitor.DebounceAlgorithm ambientLightDebounceAlgorithm(
             LightSensorEventsDebounceAlgorithm algorithm);
+
+    /**
+     * Provides a set of conditions that need to be fulfilled in order for Communal Mode to display.
+     */
+    @Provides
+    @ElementsIntoSet
+    @Named(COMMUNAL_CONDITIONS)
+    static Set<CommunalCondition> provideCommunalConditions(
+            CommunalSettingCondition communalSettingCondition,
+            CommunalTrustedNetworkCondition communalTrustedNetworkCondition) {
+        return new HashSet<>(
+                Arrays.asList(communalSettingCondition, communalTrustedNetworkCondition));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index e5c6ab5..3426148 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -23,6 +23,7 @@
 import com.android.systemui.accessibility.SystemActions;
 import com.android.systemui.accessibility.WindowMagnification;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.communal.CommunalManagerUpdater;
 import com.android.systemui.dreams.DreamOverlayRegistrant;
 import com.android.systemui.dreams.appwidgets.AppWidgetOverlayPrimer;
 import com.android.systemui.globalactions.GlobalActionsComponent;
@@ -204,4 +205,11 @@
     @ClassKey(AppWidgetOverlayPrimer.class)
     public abstract CoreStartable bindAppWidgetOverlayPrimer(
             AppWidgetOverlayPrimer appWidgetOverlayPrimer);
+
+    /** Inject into CommunalManagerUpdater. */
+    @Binds
+    @IntoMap
+    @ClassKey(CommunalManagerUpdater.class)
+    public abstract CoreStartable bindCommunalManagerUpdater(
+            CommunalManagerUpdater communalManagerUpdater);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index a424674..d73d9cd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
+import android.os.SystemClock;
 import android.text.TextUtils;
 
 import androidx.annotation.IntDef;
@@ -40,13 +41,24 @@
 import java.util.Map;
 
 /**
- * Rotates through messages to show on the keyguard bottom area on the lock screen
- * NOTE: This controller should not be used on AoD to avoid waking up the AP too often.
+ * Animates through messages to show on the keyguard bottom area on the lock screen.
+ * Utilizes a {@link KeyguardIndicationTextView} for animations. This class handles the rotating
+ * nature of the messages including:
+ *  - ensuring a message is shown for its minimum amount of time. Minimum time is determined by
+ *  {@link KeyguardIndication#getMinVisibilityMillis()}
+ *  - showing the next message after a default of 3.5 seconds before animating to the next
+ *  - statically showing a single message if there is only one message to show
+ *  - showing certain messages immediately, assuming te current message has been shown for
+ *  at least {@link KeyguardIndication#getMinVisibilityMillis()}. For example, transient and
+ *  biometric messages are meant to be shown immediately.
+ *  - ending animations when dozing begins, and resuming when dozing ends. Rotating messages on
+ *  AoD is undesirable since it wakes up the AP too often.
  */
 public class KeyguardIndicationRotateTextViewController extends
         ViewController<KeyguardIndicationTextView> implements Dumpable {
     public static String TAG = "KgIndicationRotatingCtrl";
     private static final long DEFAULT_INDICATION_SHOW_LENGTH = 3500; // milliseconds
+    public static final long IMPORTANT_MSG_MIN_DURATION = 2000L + 600L; // 2000ms + [Y in duration]
 
     private final StatusBarStateController mStatusBarStateController;
     private final float mMaxAlpha;
@@ -62,6 +74,8 @@
     // List of indication types to show. The next indication to show is always at index 0
     private final List<Integer> mIndicationQueue = new LinkedList<>();
     private @IndicationType int mCurrIndicationType = INDICATION_TYPE_NONE;
+    private CharSequence mCurrMessage;
+    private long mLastIndicationSwitch;
 
     private boolean mIsDozing;
 
@@ -94,17 +108,19 @@
      * Update the indication type with the given String.
      * @param type of indication
      * @param newIndication message to associate with this indication type
-     * @param showImmediately if true: shows this indication message immediately. Else, the text
-     *                        associated with this type is updated and will show when its turn in
-     *                        the IndicationQueue comes around.
+     * @param showAsap if true: shows this indication message as soon as possible. If false,
+     *                   the text associated with this type is updated and will show when its turn
+     *                   in the IndicationQueue comes around.
      */
     public void updateIndication(@IndicationType int type, KeyguardIndication newIndication,
-            boolean updateImmediately) {
+            boolean showAsap) {
         if (type == INDICATION_TYPE_REVERSE_CHARGING) {
             // temporarily don't show here, instead use AmbientContainer b/181049781
             return;
         }
-        final boolean hasPreviousIndication = mIndicationMessages.get(type) != null;
+        long minShowDuration = getMinVisibilityMillis(mIndicationMessages.get(mCurrIndicationType));
+        final boolean hasPreviousIndication = mIndicationMessages.get(type) != null
+                && !TextUtils.isEmpty(mIndicationMessages.get(type).getMessage());
         final boolean hasNewIndication = newIndication != null;
         if (!hasNewIndication) {
             mIndicationMessages.remove(type);
@@ -121,25 +137,46 @@
             return;
         }
 
-        final boolean showNow = updateImmediately
-                || mCurrIndicationType == INDICATION_TYPE_NONE
-                || mCurrIndicationType == type;
+        long currTime = SystemClock.uptimeMillis();
+        long timeSinceLastIndicationSwitch = currTime - mLastIndicationSwitch;
+        boolean currMsgShownForMinTime = timeSinceLastIndicationSwitch >= minShowDuration;
         if (hasNewIndication) {
-            if (showNow) {
+            if (mCurrIndicationType == INDICATION_TYPE_NONE || mCurrIndicationType == type) {
                 showIndication(type);
+            } else if (showAsap) {
+                if (currMsgShownForMinTime) {
+                    showIndication(type);
+                } else {
+                    mIndicationQueue.removeIf(x -> x == type);
+                    mIndicationQueue.add(0 /* index */, type /* type */);
+                    scheduleShowNextIndication(minShowDuration - timeSinceLastIndicationSwitch);
+                }
             } else if (!isNextIndicationScheduled()) {
-                scheduleShowNextIndication();
+                long nextShowTime = Math.max(
+                        getMinVisibilityMillis(mIndicationMessages.get(type)),
+                        DEFAULT_INDICATION_SHOW_LENGTH);
+                if (timeSinceLastIndicationSwitch >= nextShowTime) {
+                    showIndication(type);
+                } else {
+                    scheduleShowNextIndication(
+                            nextShowTime - timeSinceLastIndicationSwitch);
+                }
             }
             return;
         }
 
+        // current indication is updated to empty
         if (mCurrIndicationType == type
                 && !hasNewIndication
-                && updateImmediately) {
-            if (mShowNextIndicationRunnable != null) {
-                mShowNextIndicationRunnable.runImmediately();
+                && showAsap) {
+            if (currMsgShownForMinTime) {
+                if (mShowNextIndicationRunnable != null) {
+                    mShowNextIndicationRunnable.runImmediately();
+                } else {
+                    showIndication(INDICATION_TYPE_NONE);
+                }
             } else {
-                showIndication(INDICATION_TYPE_NONE);
+                scheduleShowNextIndication(minShowDuration - timeSinceLastIndicationSwitch);
             }
         }
     }
@@ -164,11 +201,10 @@
      * - will continue to be in the rotation of messages shown until hideTransient is called.
      */
     public void showTransient(CharSequence newIndication) {
-        final long inAnimationDuration = 600L; // see KeyguardIndicationTextView.getYInDuration
         updateIndication(INDICATION_TYPE_TRANSIENT,
                 new KeyguardIndication.Builder()
                         .setMessage(newIndication)
-                        .setMinVisibilityMillis(2000L + inAnimationDuration)
+                        .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION)
                         .setTextColor(mInitialTextColorState)
                         .build(),
                 /* showImmediately */true);
@@ -189,6 +225,15 @@
     }
 
     /**
+     * Clears all messages in the queue and sets the current message to an empty string.
+     */
+    public void clearMessages() {
+        mCurrIndicationType = INDICATION_TYPE_NONE;
+        mIndicationQueue.clear();
+        mView.clearMessages();
+    }
+
+    /**
      * Immediately show the passed indication type and schedule the next indication to show.
      * Will re-add this indication to be re-shown after all other indications have been
      * rotated through.
@@ -196,27 +241,52 @@
     private void showIndication(@IndicationType int type) {
         cancelScheduledIndication();
 
+        final CharSequence previousMessage = mCurrMessage;
+        final @IndicationType int previousIndicationType = mCurrIndicationType;
         mCurrIndicationType = type;
+        mCurrMessage = mIndicationMessages.get(type) != null
+                ? mIndicationMessages.get(type).getMessage()
+                : null;
+
         mIndicationQueue.removeIf(x -> x == type);
         if (mCurrIndicationType != INDICATION_TYPE_NONE) {
             mIndicationQueue.add(type); // re-add to show later
         }
 
-        mView.switchIndication(mIndicationMessages.get(type));
+        mLastIndicationSwitch = SystemClock.uptimeMillis();
+        if (!TextUtils.equals(previousMessage, mCurrMessage)
+                || previousIndicationType != mCurrIndicationType) {
+            mView.switchIndication(mIndicationMessages.get(type));
+        }
 
         // only schedule next indication if there's more than just this indication in the queue
         if (mCurrIndicationType != INDICATION_TYPE_NONE && mIndicationQueue.size() > 1) {
-            scheduleShowNextIndication();
+            scheduleShowNextIndication(Math.max(
+                    getMinVisibilityMillis(mIndicationMessages.get(type)),
+                    DEFAULT_INDICATION_SHOW_LENGTH));
         }
     }
 
+    private long getMinVisibilityMillis(KeyguardIndication indication) {
+        if (indication == null) {
+            return 0;
+        }
+
+        if (indication.getMinVisibilityMillis() == null) {
+            return 0;
+        }
+
+        return indication.getMinVisibilityMillis();
+    }
+
     protected boolean isNextIndicationScheduled() {
         return mShowNextIndicationRunnable != null;
     }
 
-    private void scheduleShowNextIndication() {
+
+    private void scheduleShowNextIndication(long msUntilShowNextMsg) {
         cancelScheduledIndication();
-        mShowNextIndicationRunnable = new ShowNextIndication(DEFAULT_INDICATION_SHOW_LENGTH);
+        mShowNextIndicationRunnable = new ShowNextIndication(msUntilShowNextMsg);
     }
 
     private void cancelScheduledIndication() {
@@ -292,7 +362,9 @@
         }
     }
 
+    // only used locally to stop showing any messages & stop the rotating messages
     static final int INDICATION_TYPE_NONE = -1;
+
     public static final int INDICATION_TYPE_OWNER_INFO = 0;
     public static final int INDICATION_TYPE_DISCLOSURE = 1;
     public static final int INDICATION_TYPE_LOGOUT = 2;
@@ -303,6 +375,7 @@
     public static final int INDICATION_TYPE_RESTING = 7;
     public static final int INDICATION_TYPE_USER_LOCKED = 8;
     public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
+    public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE = 11;
 
     @IntDef({
             INDICATION_TYPE_NONE,
@@ -316,6 +389,7 @@
             INDICATION_TYPE_RESTING,
             INDICATION_TYPE_USER_LOCKED,
             INDICATION_TYPE_REVERSE_CHARGING,
+            INDICATION_TYPE_BIOMETRIC_MESSAGE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface IndicationType{}
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/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 25337b6..42b7cc3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -109,6 +109,7 @@
             UserTracker userTracker,
             DumpManager dumpManager) {
         mContext = context;
+        mContentResolver = mContext.getContentResolver();
         mAccessibilityManager = accessibilityManager;
         mAssistManagerLazy = assistManagerLazy;
         mStatusBarOptionalLazy = statusBarOptionalLazy;
@@ -124,7 +125,6 @@
     }
 
     public void init() {
-        mContentResolver = mContext.getContentResolver();
         mContentResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
                 false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL);
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..706f423 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
@@ -281,6 +281,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 +356,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 +407,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 +431,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 +594,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 +689,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.");
@@ -888,7 +899,7 @@
         if (mHasEthernet) {
             count -= 1;
         }
-        if (hasCarrier()) {
+        if (hasActiveSubId()) {
             count -= 1;
         }
         if (hasConnectedWifi) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index f23a7ca..f43d9c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -21,8 +21,10 @@
 import static android.view.View.VISIBLE;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.IMPORTANT_MSG_MIN_DURATION;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
@@ -94,6 +96,15 @@
 
 /**
  * Controls the indications and error messages shown on the Keyguard
+ *
+ * On AoD, only one message shows with the following priorities:
+ *   1. Biometric
+ *   2. Transient
+ *   3. Charging alignment
+ *   4. Battery information
+ *
+ * On the lock screen, message rotate through different message types.
+ *   See {@link KeyguardIndicationRotateTextViewController.IndicationType} for the list of types.
  */
 @SysUISingleton
 public class KeyguardIndicationController {
@@ -103,6 +114,7 @@
 
     private static final int MSG_HIDE_TRANSIENT = 1;
     private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2;
+    private static final int MSG_HIDE_BIOMETRIC_MESSAGE = 3;
     private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
     private static final float BOUNCE_ANIMATION_FINAL_Y = 0f;
 
@@ -132,9 +144,9 @@
     private String mRestingIndication;
     private String mAlignmentIndication;
     private CharSequence mTransientIndication;
+    private CharSequence mBiometricMessage;
     protected ColorStateList mInitialTextColorState;
     private boolean mVisible;
-    private boolean mHideTransientMessageOnScreenOff;
 
     private boolean mPowerPluggedIn;
     private boolean mPowerPluggedInWired;
@@ -277,13 +289,15 @@
     }
 
     /**
-     * Doesn't include disclosure which gets triggered separately.
+     * Doesn't include disclosure (also a persistent indication) which gets triggered separately.
+     *
+     * This method also doesn't update transient messages like biometrics since those messages
+     * are also updated separately.
      */
-    private void updateIndications(boolean animate, int userId) {
+    private void updatePersistentIndications(boolean animate, int userId) {
         updateOwnerInfo();
         updateBattery(animate);
         updateUserLocked(userId);
-        updateTransient();
         updateTrust(userId, getTrustGrantedIndication(), getTrustManagedIndication());
         updateAlignment();
         updateLogoutView();
@@ -383,12 +397,36 @@
         }
     }
 
+    private void updateBiometricMessage() {
+        if (!TextUtils.isEmpty(mBiometricMessage)) {
+            mRotateTextViewController.updateIndication(
+                    INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                    new KeyguardIndication.Builder()
+                            .setMessage(mBiometricMessage)
+                            .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION)
+                            .setTextColor(mInitialTextColorState)
+                            .build(),
+                    true
+            );
+        } else {
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+        }
+
+        if (mDozing) {
+            updateIndication(false);
+        }
+    }
+
     private void updateTransient() {
         if (!TextUtils.isEmpty(mTransientIndication)) {
             mRotateTextViewController.showTransient(mTransientIndication);
         } else {
             mRotateTextViewController.hideTransient();
         }
+
+        if (mDozing) {
+            updateIndication(false);
+        }
     }
 
     private void updateTrust(int userId, CharSequence trustGrantedIndication,
@@ -577,6 +615,14 @@
     }
 
     /**
+     * Hides biometric indication in {@param delayMs}.
+     */
+    public void hideBiometricMessageDelayed(long delayMs) {
+        mHandler.sendMessageDelayed(
+                mHandler.obtainMessage(MSG_HIDE_BIOMETRIC_MESSAGE), delayMs);
+    }
+
+    /**
      * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
      */
     public void showTransientIndication(int transientIndication) {
@@ -586,23 +632,40 @@
     /**
      * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
      */
-    public void showTransientIndication(CharSequence transientIndication) {
-        showTransientIndication(transientIndication, false /* isError */,
-                false /* hideOnScreenOff */);
+    private void showTransientIndication(CharSequence transientIndication) {
+        mTransientIndication = transientIndication;
+        mHandler.removeMessages(MSG_HIDE_TRANSIENT);
+        hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
+
+        updateTransient();
     }
 
     /**
-     * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
+     * Shows {@param biometricMessage} until it is hidden by {@link #hideBiometricMessage}.
      */
-    private void showTransientIndication(CharSequence transientIndication,
-            boolean isError, boolean hideOnScreenOff) {
-        mTransientIndication = transientIndication;
-        mHideTransientMessageOnScreenOff = hideOnScreenOff && transientIndication != null;
-        mHandler.removeMessages(MSG_HIDE_TRANSIENT);
-        mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
-        hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
+    public void showBiometricMessage(int biometricMessage) {
+        showBiometricMessage(mContext.getResources().getString(biometricMessage));
+    }
 
-        updateIndication(false);
+    /**
+     * Shows {@param biometricMessage} until it is hidden by {@link #hideBiometricMessage}.
+     */
+    private void showBiometricMessage(CharSequence biometricMessage) {
+        mBiometricMessage = biometricMessage;
+
+        mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
+        mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE);
+        hideBiometricMessageDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
+
+        updateBiometricMessage();
+    }
+
+    private void hideBiometricMessage() {
+        if (mBiometricMessage != null) {
+            mBiometricMessage = null;
+            mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE);
+            updateBiometricMessage();
+        }
     }
 
     /**
@@ -611,10 +674,8 @@
     public void hideTransientIndication() {
         if (mTransientIndication != null) {
             mTransientIndication = null;
-            mHideTransientMessageOnScreenOff = false;
             mHandler.removeMessages(MSG_HIDE_TRANSIENT);
-            mRotateTextViewController.hideTransient();
-            updateIndication(false);
+            updateTransient();
         }
     }
 
@@ -635,7 +696,11 @@
             // When dozing we ignore any text color and use white instead, because
             // colors can be hard to read in low brightness.
             mTopIndicationView.setTextColor(Color.WHITE);
-            if (!TextUtils.isEmpty(mTransientIndication)) {
+            if (!TextUtils.isEmpty(mBiometricMessage)) {
+                mWakeLock.setAcquired(true);
+                mTopIndicationView.switchIndication(mBiometricMessage, null,
+                        true, () -> mWakeLock.setAcquired(false));
+            } else if (!TextUtils.isEmpty(mTransientIndication)) {
                 mWakeLock.setAcquired(true);
                 mTopIndicationView.switchIndication(mTransientIndication, null,
                         true, () -> mWakeLock.setAcquired(false));
@@ -669,7 +734,7 @@
         mTopIndicationView.setVisibility(GONE);
         mTopIndicationView.setText(null);
         mLockScreenIndicationView.setVisibility(View.VISIBLE);
-        updateIndications(animate, KeyguardUpdateMonitor.getCurrentUser());
+        updatePersistentIndications(animate, KeyguardUpdateMonitor.getCurrentUser());
     }
 
     // animates textView - textView moves up and bounces down
@@ -798,6 +863,8 @@
                 hideTransientIndication();
             } else if (msg.what == MSG_SHOW_ACTION_TO_UNLOCK) {
                 showActionToUnlock();
+            } else if (msg.what == MSG_HIDE_BIOMETRIC_MESSAGE) {
+                hideBiometricMessage();
             }
         }
     };
@@ -820,8 +887,7 @@
                 mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState);
             }
         } else {
-            showTransientIndication(mContext.getString(R.string.keyguard_unlock),
-                    false /* isError */, true /* hideOnScreenOff */);
+            showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
         }
     }
 
@@ -830,15 +896,15 @@
             // if udfps available, there will always be a tappable affordance to unlock
             // For example, the lock icon
             if (mKeyguardBypassController.getUserHasDeviceEntryIntent()) {
-                showTransientIndication(R.string.keyguard_unlock_press);
+                showBiometricMessage(R.string.keyguard_unlock_press);
             } else if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
                 // since face is locked out, simply show "try fingerprint"
-                showTransientIndication(R.string.keyguard_try_fingerprint);
+                showBiometricMessage(R.string.keyguard_try_fingerprint);
             } else {
-                showTransientIndication(R.string.keyguard_face_failed_use_fp);
+                showBiometricMessage(R.string.keyguard_face_failed_use_fp);
             }
         } else {
-            showTransientIndication(R.string.keyguard_try_fingerprint);
+            showBiometricMessage(R.string.keyguard_try_fingerprint);
         }
 
         // Although we suppress face auth errors visually, we still announce them for a11y
@@ -857,6 +923,8 @@
         pw.println("  mChargingWattage: " + mChargingWattage);
         pw.println("  mMessageToShowOnScreenOn: " + mMessageToShowOnScreenOn);
         pw.println("  mDozing: " + mDozing);
+        pw.println("  mTransientIndication: " + mTransientIndication);
+        pw.println("  mBiometricMessage: " + mBiometricMessage);
         pw.println("  mBatteryLevel: " + mBatteryLevel);
         pw.println("  mBatteryPresent: " + mBatteryPresent);
         pw.println("  mTextView.getText(): " + (
@@ -871,7 +939,7 @@
         @Override
         public void onRefreshBatteryInfo(BatteryStatus status) {
             boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
-                    || status.status == BatteryManager.BATTERY_STATUS_FULL;
+                    || status.isCharged();
             boolean wasPluggedIn = mPowerPluggedIn;
             mPowerPluggedInWired = status.isPluggedInWired() && isChargingOrFull;
             mPowerPluggedInWireless = status.isPluggedInWireless() && isChargingOrFull;
@@ -912,7 +980,6 @@
                     .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)) {
                 return;
             }
-
             boolean showActionToUnlock =
                     msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
             if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
@@ -921,14 +988,10 @@
             } else if (mKeyguardUpdateMonitor.isScreenOn()) {
                 if (biometricSourceType == BiometricSourceType.FACE
                         && shouldSuppressFaceMsgAndShowTryFingerprintMsg()) {
-                    // don't show any help messages, b/c they can come in right before a success
-                    // However, continue to announce help messages for a11y
-                    if (!TextUtils.isEmpty(helpString)) {
-                        mLockScreenIndicationView.announceForAccessibility(helpString);
-                    }
+                    showTryFingerprintMsg(msgId, helpString);
                     return;
                 }
-                showTransientIndication(helpString, false /* isError */, showActionToUnlock);
+                showBiometricMessage(helpString);
             } else if (showActionToUnlock) {
                 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ACTION_TO_UNLOCK),
                         TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
@@ -967,8 +1030,7 @@
             } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
                 mStatusBarKeyguardViewManager.showBouncerMessage(errString, mInitialTextColorState);
             } else if (mKeyguardUpdateMonitor.isScreenOn()) {
-                showTransientIndication(errString, /* isError */ true,
-                    /* hideOnScreenOff */ true);
+                showBiometricMessage(errString);
             } else {
                 mMessageToShowOnScreenOn = errString;
             }
@@ -1014,16 +1076,15 @@
 
         @Override
         public void onTrustAgentErrorMessage(CharSequence message) {
-            showTransientIndication(message, true /* isError */, false /* hideOnScreenOff */);
+            showBiometricMessage(message);
         }
 
         @Override
         public void onScreenTurnedOn() {
             if (mMessageToShowOnScreenOn != null) {
-                showTransientIndication(mMessageToShowOnScreenOn, true /* isError */,
-                        false /* hideOnScreenOff */);
+                showBiometricMessage(mMessageToShowOnScreenOn);
                 // We want to keep this message around in case the screen was off
-                hideTransientIndicationDelayed(HIDE_DELAY_MS);
+                hideBiometricMessageDelayed(HIDE_DELAY_MS);
                 mMessageToShowOnScreenOn = null;
             }
         }
@@ -1034,7 +1095,7 @@
             if (running && biometricSourceType == BiometricSourceType.FACE) {
                 // Let's hide any previous messages when authentication starts, otherwise
                 // multiple auth attempts would overlap.
-                hideTransientIndication();
+                hideBiometricMessage();
                 mMessageToShowOnScreenOn = null;
             }
         }
@@ -1043,11 +1104,11 @@
         public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
                 boolean isStrongBiometric) {
             super.onBiometricAuthenticated(userId, biometricSourceType, isStrongBiometric);
-            mHandler.sendEmptyMessage(MSG_HIDE_TRANSIENT);
+            hideBiometricMessage();
 
             if (biometricSourceType == BiometricSourceType.FACE
                     && !mKeyguardBypassController.canBypass()) {
-                mHandler.sendEmptyMessage(MSG_SHOW_ACTION_TO_UNLOCK);
+                showActionToUnlock();
             }
         }
 
@@ -1074,8 +1135,7 @@
 
         @Override
         public void onRequireUnlockForNfc() {
-            showTransientIndication(mContext.getString(R.string.require_unlock_for_nfc),
-                    false /* isError */, false /* hideOnScreenOff */);
+            showTransientIndication(mContext.getString(R.string.require_unlock_for_nfc));
             hideTransientIndicationDelayed(HIDE_DELAY_MS);
         }
     }
@@ -1094,8 +1154,8 @@
             }
             mDozing = dozing;
 
-            if (mHideTransientMessageOnScreenOff && mDozing) {
-                hideTransientIndication();
+            if (mDozing) {
+                hideBiometricMessage();
             }
             updateIndication(false);
         }
@@ -1112,7 +1172,7 @@
         public void onKeyguardShowingChanged() {
             if (!mKeyguardStateController.isShowing()) {
                 mTopIndicationView.clearMessages();
-                mLockScreenIndicationView.clearMessages();
+                mRotateTextViewController.clearMessages();
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index 3a68b9c..339f371 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -35,23 +35,20 @@
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.keyguard.KeyguardIndication;
 
-import java.util.LinkedList;
-
 /**
  * A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open").
  */
 public class KeyguardIndicationTextView extends TextView {
-    private static final long MSG_MIN_DURATION_MILLIS_DEFAULT = 1500;
-
     @StyleRes
     private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea;
     @StyleRes
     private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button;
 
-    private long mNextAnimationTime = 0;
     private boolean mAnimationsEnabled = true;
-    private LinkedList<CharSequence> mMessages = new LinkedList<>();
-    private LinkedList<KeyguardIndication> mKeyguardIndicationInfo = new LinkedList<>();
+    private CharSequence mMessage;
+    private KeyguardIndication mKeyguardIndicationInfo;
+
+    private Animator mLastAnimator;
 
     public KeyguardIndicationTextView(Context context) {
         super(context);
@@ -71,22 +68,24 @@
     }
 
     /**
-     * Clears message queue.
+     * Clears message queue and currently shown message.
      */
     public void clearMessages() {
-        mMessages.clear();
-        mKeyguardIndicationInfo.clear();
+        if (mLastAnimator != null) {
+            mLastAnimator.cancel();
+        }
+        setText("");
     }
 
     /**
-     * Changes the text with an animation and makes sure a single indication is shown long enough.
+     * Changes the text with an animation.
      */
     public void switchIndication(int textResId) {
         switchIndication(getResources().getText(textResId), null);
     }
 
     /**
-     * Changes the text with an animation and makes sure a single indication is shown long enough.
+     * Changes the text with an animation.
      *
      * @param indication The text to show.
      */
@@ -95,15 +94,14 @@
     }
 
     /**
-     * Changes the text with an animation. Makes sure a single indication is shown long enough.
+     * Changes the text with an animation.
      */
     public void switchIndication(CharSequence text, KeyguardIndication indication) {
         switchIndication(text, indication, true, null);
     }
 
     /**
-     * Changes the text with an optional animation. For animating text, makes sure a single
-     * indication is shown long enough.
+     * Updates the text with an optional animation.
      *
      * @param text The text to show.
      * @param indication optional display information for the text
@@ -112,33 +110,15 @@
      */
     public void switchIndication(CharSequence text, KeyguardIndication indication,
             boolean animate, Runnable onAnimationEndCallback) {
-        if (text == null) text = "";
-
-        CharSequence lastPendingMessage = mMessages.peekLast();
-        if (TextUtils.equals(lastPendingMessage, text)
-                || (lastPendingMessage == null && TextUtils.equals(text, getText()))) {
-            if (onAnimationEndCallback != null) {
-                onAnimationEndCallback.run();
-            }
-            return;
-        }
-        mMessages.add(text);
-        mKeyguardIndicationInfo.add(indication);
+        mMessage = text;
+        mKeyguardIndicationInfo = indication;
 
         if (animate) {
             final boolean hasIcon = indication != null && indication.getIcon() != null;
-            final AnimatorSet animator = new AnimatorSet();
+            AnimatorSet animator = new AnimatorSet();
             // Make sure each animation is visible for a minimum amount of time, while not worrying
             // about fading in blank text
-            long timeInMillis = System.currentTimeMillis();
-            long delay = Math.max(0, mNextAnimationTime - timeInMillis);
-            setNextAnimationTime(timeInMillis + delay + getFadeOutDuration());
-            final long minDurationMillis =
-                    (indication != null && indication.getMinVisibilityMillis() != null)
-                            ? indication.getMinVisibilityMillis()
-                            : MSG_MIN_DURATION_MILLIS_DEFAULT;
-            if (!text.equals("") || hasIcon) {
-                setNextAnimationTime(mNextAnimationTime + minDurationMillis);
+            if (!TextUtils.isEmpty(mMessage) || hasIcon) {
                 Animator inAnimator = getInAnimator();
                 inAnimator.addListener(new AnimatorListenerAdapter() {
                     @Override
@@ -164,7 +144,10 @@
                 animator.play(outAnimator);
             }
 
-            animator.setStartDelay(delay);
+            if (mLastAnimator != null) {
+                mLastAnimator.cancel();
+            }
+            mLastAnimator = animator;
             animator.start();
         } else {
             setAlpha(1f);
@@ -173,6 +156,10 @@
             if (onAnimationEndCallback != null) {
                 onAnimationEndCallback.run();
             }
+            if (mLastAnimator != null) {
+                mLastAnimator.cancel();
+                mLastAnimator = null;
+            }
         }
     }
 
@@ -182,10 +169,20 @@
         fadeOut.setDuration(getFadeOutDuration());
         fadeOut.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
         fadeOut.addListener(new AnimatorListenerAdapter() {
+            private boolean mCancelled = false;
             @Override
             public void onAnimationEnd(Animator animator) {
                 super.onAnimationEnd(animator);
-                setNextIndication();
+                if (!mCancelled) {
+                    setNextIndication();
+                }
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animator) {
+                super.onAnimationCancel(animator);
+                mCancelled = true;
+                setAlpha(0);
             }
         });
 
@@ -198,20 +195,19 @@
     }
 
     private void setNextIndication() {
-        KeyguardIndication info = mKeyguardIndicationInfo.poll();
-        if (info != null) {
+        if (mKeyguardIndicationInfo != null) {
             // First, update the style.
             // If a background is set on the text, we don't want shadow on the text
-            if (info.getBackground() != null) {
+            if (mKeyguardIndicationInfo.getBackground() != null) {
                 setTextAppearance(sButtonStyleId);
             } else {
                 setTextAppearance(sStyleId);
             }
-            setBackground(info.getBackground());
-            setTextColor(info.getTextColor());
-            setOnClickListener(info.getClickListener());
-            setClickable(info.getClickListener() != null);
-            final Drawable icon = info.getIcon();
+            setBackground(mKeyguardIndicationInfo.getBackground());
+            setTextColor(mKeyguardIndicationInfo.getTextColor());
+            setOnClickListener(mKeyguardIndicationInfo.getClickListener());
+            setClickable(mKeyguardIndicationInfo.getClickListener() != null);
+            final Drawable icon = mKeyguardIndicationInfo.getIcon();
             if (icon != null) {
                 icon.setTint(getCurrentTextColor());
                 if (icon instanceof AnimatedVectorDrawable) {
@@ -220,7 +216,7 @@
             }
             setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
         }
-        setText(mMessages.poll());
+        setText(mMessage);
     }
 
     private AnimatorSet getInAnimator() {
@@ -238,6 +234,7 @@
             public void onAnimationCancel(Animator animation) {
                 super.onAnimationCancel(animation);
                 setTranslationY(0);
+                setAlpha(1f);
             }
         });
         animatorSet.playTogether(yTranslate, fadeIn);
@@ -270,14 +267,6 @@
         return 167L;
     }
 
-    private void setNextAnimationTime(long time) {
-        if (mAnimationsEnabled) {
-            mNextAnimationTime = time;
-        } else {
-            mNextAnimationTime = 0L;
-        }
-    }
-
     private int getYTranslationPixels() {
         return mContext.getResources().getDimensionPixelSize(
                 com.android.systemui.R.dimen.keyguard_indication_y_translation);
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/tests/src/com/android/systemui/communal/CommunalConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalConditionTest.java
new file mode 100644
index 0000000..9d7ef0f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalConditionTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.communal;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.communal.conditions.CommunalCondition;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class CommunalConditionTest extends SysuiTestCase {
+    private FakeCommunalCondition mCondition;
+
+    @Before
+    public void setup() {
+        mCondition = spy(new FakeCommunalCondition());
+    }
+
+    @Test
+    public void addCallback_addFirstCallback_triggerStart() {
+        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        mCondition.addCallback(callback);
+        verify(mCondition).start();
+    }
+
+    @Test
+    public void addCallback_addMultipleCallbacks_triggerStartOnlyOnce() {
+        final CommunalCondition.Callback callback1 = mock(CommunalCondition.Callback.class);
+        final CommunalCondition.Callback callback2 = mock(CommunalCondition.Callback.class);
+        final CommunalCondition.Callback callback3 = mock(CommunalCondition.Callback.class);
+
+        mCondition.addCallback(callback1);
+        mCondition.addCallback(callback2);
+        mCondition.addCallback(callback3);
+
+        verify(mCondition, times(1)).start();
+    }
+
+    @Test
+    public void addCallback_alreadyStarted_triggerUpdate() {
+        final CommunalCondition.Callback callback1 = mock(CommunalCondition.Callback.class);
+        mCondition.addCallback(callback1);
+
+        mCondition.fakeUpdateCondition(true);
+
+        final CommunalCondition.Callback callback2 = mock(CommunalCondition.Callback.class);
+        mCondition.addCallback(callback2);
+        verify(callback2).onConditionChanged(mCondition, true);
+    }
+
+    @Test
+    public void removeCallback_removeLastCallback_triggerStop() {
+        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        mCondition.addCallback(callback);
+        verify(mCondition, never()).stop();
+
+        mCondition.removeCallback(callback);
+        verify(mCondition).stop();
+    }
+
+    @Test
+    public void updateCondition_falseToTrue_reportTrue() {
+        mCondition.fakeUpdateCondition(false);
+
+        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        mCondition.addCallback(callback);
+
+        mCondition.fakeUpdateCondition(true);
+        verify(callback).onConditionChanged(eq(mCondition), eq(true));
+    }
+
+    @Test
+    public void updateCondition_trueToFalse_reportFalse() {
+        mCondition.fakeUpdateCondition(true);
+
+        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        mCondition.addCallback(callback);
+
+        mCondition.fakeUpdateCondition(false);
+        verify(callback).onConditionChanged(eq(mCondition), eq(false));
+    }
+
+    @Test
+    public void updateCondition_trueToTrue_reportNothing() {
+        mCondition.fakeUpdateCondition(true);
+
+        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        mCondition.addCallback(callback);
+
+        mCondition.fakeUpdateCondition(true);
+        verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean());
+    }
+
+    @Test
+    public void updateCondition_falseToFalse_reportNothing() {
+        mCondition.fakeUpdateCondition(false);
+
+        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        mCondition.addCallback(callback);
+
+        mCondition.fakeUpdateCondition(false);
+        verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalConditionsMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalConditionsMonitorTest.java
new file mode 100644
index 0000000..59ddba1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalConditionsMonitorTest.java
@@ -0,0 +1,178 @@
+/*
+ * 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.communal;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.communal.conditions.CommunalCondition;
+import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class CommunalConditionsMonitorTest extends SysuiTestCase {
+    private FakeCommunalCondition mCondition1;
+    private FakeCommunalCondition mCondition2;
+    private FakeCommunalCondition mCondition3;
+    private HashSet<CommunalCondition> mConditions;
+
+    private CommunalConditionsMonitor mCommunalConditionsMonitor;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mCondition1 = spy(new FakeCommunalCondition());
+        mCondition2 = spy(new FakeCommunalCondition());
+        mCondition3 = spy(new FakeCommunalCondition());
+        mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3));
+
+        mCommunalConditionsMonitor = new CommunalConditionsMonitor(mConditions);
+    }
+
+    @Test
+    public void addCallback_addFirstCallback_addCallbackToAllConditions() {
+        final CommunalConditionsMonitor.Callback callback1 =
+                mock(CommunalConditionsMonitor.Callback.class);
+        mCommunalConditionsMonitor.addCallback(callback1);
+        mConditions.forEach(condition -> verify(condition).addCallback(any()));
+
+        final CommunalConditionsMonitor.Callback callback2 =
+                mock(CommunalConditionsMonitor.Callback.class);
+        mCommunalConditionsMonitor.addCallback(callback2);
+        mConditions.forEach(condition -> verify(condition, times(1)).addCallback(any()));
+    }
+
+    @Test
+    public void addCallback_addFirstCallback_reportWithDefaultValue() {
+        final CommunalConditionsMonitor.Callback callback =
+                mock(CommunalConditionsMonitor.Callback.class);
+        mCommunalConditionsMonitor.addCallback(callback);
+        verify(callback).onConditionsChanged(false);
+    }
+
+    @Test
+    public void addCallback_addSecondCallback_reportWithExistingValue() {
+        final CommunalConditionsMonitor.Callback callback1 =
+                mock(CommunalConditionsMonitor.Callback.class);
+        mCommunalConditionsMonitor.addCallback(callback1);
+
+        mCommunalConditionsMonitor.overrideAllConditionsMet(true);
+
+        final CommunalConditionsMonitor.Callback callback2 =
+                mock(CommunalConditionsMonitor.Callback.class);
+        mCommunalConditionsMonitor.addCallback(callback2);
+        verify(callback2).onConditionsChanged(true);
+    }
+
+    @Test
+    public void removeCallback_shouldNoLongerReceiveUpdate() {
+        final CommunalConditionsMonitor.Callback callback =
+                mock(CommunalConditionsMonitor.Callback.class);
+        mCommunalConditionsMonitor.addCallback(callback);
+        clearInvocations(callback);
+        mCommunalConditionsMonitor.removeCallback(callback);
+
+        mCommunalConditionsMonitor.overrideAllConditionsMet(true);
+        verify(callback, never()).onConditionsChanged(true);
+
+        mCommunalConditionsMonitor.overrideAllConditionsMet(false);
+        verify(callback, never()).onConditionsChanged(false);
+    }
+
+    @Test
+    public void removeCallback_removeLastCallback_removeCallbackFromAllConditions() {
+        final CommunalConditionsMonitor.Callback callback1 =
+                mock(CommunalConditionsMonitor.Callback.class);
+        final CommunalConditionsMonitor.Callback callback2 =
+                mock(CommunalConditionsMonitor.Callback.class);
+        mCommunalConditionsMonitor.addCallback(callback1);
+        mCommunalConditionsMonitor.addCallback(callback2);
+
+        mCommunalConditionsMonitor.removeCallback(callback1);
+        mConditions.forEach(condition -> verify(condition, never()).removeCallback(any()));
+
+        mCommunalConditionsMonitor.removeCallback(callback2);
+        mConditions.forEach(condition -> verify(condition).removeCallback(any()));
+    }
+
+    @Test
+    public void updateCallbacks_allConditionsMet_reportTrue() {
+        final CommunalConditionsMonitor.Callback callback =
+                mock(CommunalConditionsMonitor.Callback.class);
+        mCommunalConditionsMonitor.addCallback(callback);
+        clearInvocations(callback);
+
+        mCondition1.fakeUpdateCondition(true);
+        mCondition2.fakeUpdateCondition(true);
+        mCondition3.fakeUpdateCondition(true);
+
+        verify(callback).onConditionsChanged(true);
+    }
+
+    @Test
+    public void updateCallbacks_oneConditionStoppedMeeting_reportFalse() {
+        final CommunalConditionsMonitor.Callback callback =
+                mock(CommunalConditionsMonitor.Callback.class);
+        mCommunalConditionsMonitor.addCallback(callback);
+
+        mCondition1.fakeUpdateCondition(true);
+        mCondition2.fakeUpdateCondition(true);
+        mCondition3.fakeUpdateCondition(true);
+        clearInvocations(callback);
+
+        mCondition1.fakeUpdateCondition(false);
+        verify(callback).onConditionsChanged(false);
+    }
+
+    @Test
+    public void updateCallbacks_shouldOnlyUpdateWhenValueChanges() {
+        final CommunalConditionsMonitor.Callback callback =
+                mock(CommunalConditionsMonitor.Callback.class);
+        mCommunalConditionsMonitor.addCallback(callback);
+        verify(callback).onConditionsChanged(false);
+        clearInvocations(callback);
+
+        mCondition1.fakeUpdateCondition(true);
+        verify(callback, never()).onConditionsChanged(anyBoolean());
+
+        mCondition2.fakeUpdateCondition(true);
+        verify(callback, never()).onConditionsChanged(anyBoolean());
+
+        mCondition3.fakeUpdateCondition(true);
+        verify(callback).onConditionsChanged(true);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
new file mode 100644
index 0000000..fb8efa9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal;
+
+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;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class CommunalManagerUpdaterTest extends SysuiTestCase {
+    private CommunalSourceMonitor mMonitor;
+    @Mock
+    private CommunalManager mCommunalManager;
+    @Mock
+    private CommunalConditionsMonitor mCommunalConditionsMonitor;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mContext.addMockSystemService(CommunalManager.class, mCommunalManager);
+
+        mMonitor = new CommunalSourceMonitor(mCommunalConditionsMonitor);
+        final CommunalManagerUpdater updater = new CommunalManagerUpdater(mContext, mMonitor);
+        updater.start();
+    }
+
+    @Test
+    public void testUpdateSystemService_false() {
+        mMonitor.setSource(null);
+        verify(mCommunalManager).setCommunalViewShowing(false);
+    }
+
+    @Test
+    public void testUpdateSystemService_true() {
+        final CommunalSource source = mock(CommunalSource.class);
+        mMonitor.setSource(source);
+        verify(mCommunalManager).setCommunalViewShowing(true);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java
new file mode 100644
index 0000000..cf147f0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.communal;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.communal.conditions.CommunalCondition;
+import com.android.systemui.communal.conditions.CommunalSettingCondition;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.utils.os.FakeHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class CommunalSettingConditionTest extends SysuiTestCase {
+    private FakeSettings mSecureSettings;
+    private CommunalSettingCondition mCondition;
+
+    @Before
+    public void setup() {
+        final FakeHandler handler = new FakeHandler(Looper.getMainLooper());
+        mSecureSettings = new FakeSettings();
+        mCondition = new CommunalSettingCondition(handler, mSecureSettings);
+    }
+
+    @Test
+    public void addCallback_communalSettingEnabled_immediatelyReportsTrue() {
+        updateCommunalSetting(true);
+
+        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        mCondition.addCallback(callback);
+        verify(callback).onConditionChanged(mCondition, true);
+    }
+
+    @Test
+    public void addCallback_communalSettingDisabled_noReport() {
+        updateCommunalSetting(false);
+
+        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        mCondition.addCallback(callback);
+        verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean());
+    }
+
+    @Test
+    public void updateCallback_communalSettingEnabled_reportsTrue() {
+        updateCommunalSetting(false);
+
+        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        mCondition.addCallback(callback);
+        clearInvocations(callback);
+
+        updateCommunalSetting(true);
+        verify(callback).onConditionChanged(mCondition, true);
+    }
+
+    @Test
+    public void updateCallback_communalSettingDisabled_reportsFalse() {
+        updateCommunalSetting(true);
+
+        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        mCondition.addCallback(callback);
+        clearInvocations(callback);
+
+        updateCommunalSetting(false);
+        verify(callback).onConditionChanged(mCondition, false);
+    }
+
+    @Test
+    public void updateCallback_communalSettingDidNotChange_neverReportDup() {
+        updateCommunalSetting(true);
+
+        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        mCondition.addCallback(callback);
+        clearInvocations(callback);
+
+        updateCommunalSetting(true);
+        verify(callback, never()).onConditionChanged(mCondition, true);
+    }
+
+    private void updateCommunalSetting(boolean value) {
+        mSecureSettings.putIntForUser(Settings.Secure.COMMUNAL_MODE_ENABLED, value ? 1 : 0,
+                UserHandle.USER_SYSTEM);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java
index 68600de..df9a2cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java
@@ -20,25 +20,25 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.lang.ref.WeakReference;
@@ -47,15 +47,16 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class CommunalSourceMonitorTest extends SysuiTestCase {
+    @Mock private CommunalConditionsMonitor mCommunalConditionsMonitor;
+
+    @Captor private ArgumentCaptor<CommunalConditionsMonitor.Callback> mConditionsCallbackCaptor;
+
     private CommunalSourceMonitor mCommunalSourceMonitor;
-    private FakeSettings mSecureSettings;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mSecureSettings = new FakeSettings();
-        mCommunalSourceMonitor = new CommunalSourceMonitor(
-                Handler.createAsync(TestableLooper.get(this).getLooper()), mSecureSettings);
+        mCommunalSourceMonitor = new CommunalSourceMonitor(mCommunalConditionsMonitor);
     }
 
     @Test
@@ -65,6 +66,7 @@
 
         mCommunalSourceMonitor.setSource(source);
         mCommunalSourceMonitor.addCallback(callback);
+        setConditionsMet(true);
 
         verifyOnSourceAvailableCalledWith(callback, source);
     }
@@ -82,33 +84,32 @@
     }
 
     @Test
-    public void testAddCallbackWithDefaultSetting() {
+    public void testAddCallbackWithConditionsMet() {
         final CommunalSourceMonitor.Callback callback = mock(CommunalSourceMonitor.Callback.class);
         final CommunalSource source = mock(CommunalSource.class);
 
         mCommunalSourceMonitor.addCallback(callback);
+        setConditionsMet(true);
+        clearInvocations(callback);
         mCommunalSourceMonitor.setSource(source);
 
         verifyOnSourceAvailableCalledWith(callback, source);
     }
 
     @Test
-    public void testAddCallbackWithSettingDisabled() {
-        setCommunalEnabled(false);
-
+    public void testAddCallbackWithConditionsNotMet() {
         final CommunalSourceMonitor.Callback callback = mock(CommunalSourceMonitor.Callback.class);
         final CommunalSource source = mock(CommunalSource.class);
 
         mCommunalSourceMonitor.addCallback(callback);
+        setConditionsMet(false);
         mCommunalSourceMonitor.setSource(source);
 
         verify(callback, never()).onSourceAvailable(any());
     }
 
     @Test
-    public void testSettingEnabledAfterCallbackAdded() {
-        setCommunalEnabled(false);
-
+    public void testConditionsAreMetAfterCallbackAdded() {
         final CommunalSourceMonitor.Callback callback = mock(CommunalSourceMonitor.Callback.class);
         final CommunalSource source = mock(CommunalSource.class);
 
@@ -118,33 +119,27 @@
         // The callback should not have executed since communal is disabled.
         verify(callback, never()).onSourceAvailable(any());
 
-        // The callback should execute when the user changes the setting to enabled.
-        setCommunalEnabled(true);
+        // The callback should execute when all conditions are met.
+        setConditionsMet(true);
         verifyOnSourceAvailableCalledWith(callback, source);
     }
 
     @Test
-    public void testSettingDisabledAfterCallbackAdded() {
+    public void testConditionsNoLongerMetAfterCallbackAdded() {
         final CommunalSourceMonitor.Callback callback = mock(CommunalSourceMonitor.Callback.class);
         final CommunalSource source = mock(CommunalSource.class);
 
         mCommunalSourceMonitor.addCallback(callback);
         mCommunalSourceMonitor.setSource(source);
+        setConditionsMet(true);
         verifyOnSourceAvailableCalledWith(callback, source);
 
-        // The callback should execute again when the setting is disabled, with a value of null.
-        setCommunalEnabled(false);
+        // The callback should execute again when conditions are no longer met, with a value of
+        // null.
+        setConditionsMet(false);
         verify(callback).onSourceAvailable(null);
     }
 
-    private void setCommunalEnabled(boolean enabled) {
-        mSecureSettings.putIntForUser(
-                Settings.Secure.COMMUNAL_MODE_ENABLED,
-                enabled ? 1 : 0,
-                UserHandle.USER_SYSTEM);
-        TestableLooper.get(this).processAllMessages();
-    }
-
     private void verifyOnSourceAvailableCalledWith(CommunalSourceMonitor.Callback callback,
             CommunalSource source) {
         final ArgumentCaptor<WeakReference<CommunalSource>> sourceCapture =
@@ -152,4 +147,13 @@
         verify(callback).onSourceAvailable(sourceCapture.capture());
         assertThat(sourceCapture.getValue().get()).isEqualTo(source);
     }
+
+    // Pushes an update on whether the communal conditions are met, assuming that a callback has
+    // been registered with the communal conditions monitor.
+    private void setConditionsMet(boolean value) {
+        verify(mCommunalConditionsMonitor).addCallback(mConditionsCallbackCaptor.capture());
+        final CommunalConditionsMonitor.Callback conditionsCallback =
+                mConditionsCallbackCaptor.getValue();
+        conditionsCallback.onConditionsChanged(value);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalStateControllerTest.java
index 42abff0..7f85c35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalStateControllerTest.java
@@ -48,13 +48,13 @@
     @Test
     public void testDefaultCommunalViewShowingState() {
         // The state controller should report the communal view as not showing by default.
-        final CommunalStateController stateController = new CommunalStateController(getContext());
+        final CommunalStateController stateController = new CommunalStateController();
         assertThat(stateController.getCommunalViewShowing()).isFalse();
     }
 
     @Test
     public void testNotifyCommunalSurfaceShow() {
-        final CommunalStateController stateController = new CommunalStateController(getContext());
+        final CommunalStateController stateController = new CommunalStateController();
         stateController.addCallback(mCallback);
 
         // Verify setting communal view to showing propagates to callback.
@@ -72,7 +72,7 @@
 
     @Test
     public void testCallbackRegistration() {
-        final CommunalStateController stateController = new CommunalStateController(getContext());
+        final CommunalStateController stateController = new CommunalStateController();
         stateController.addCallback(mCallback);
 
         // Verify setting communal view to showing propagates to callback.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java
new file mode 100644
index 0000000..61a5126
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.communal;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
+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.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.WifiInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.communal.conditions.CommunalTrustedNetworkCondition;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.utils.os.FakeHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class CommunalTrustedNetworkConditionTest extends SysuiTestCase {
+    @Mock private ConnectivityManager mConnectivityManager;
+
+    @Captor private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor;
+
+    private final Handler mHandler = new FakeHandler(Looper.getMainLooper());
+    private CommunalTrustedNetworkCondition mCondition;
+
+    private final String mTrustedWifi1 = "wifi-1";
+    private final String mTrustedWifi2 = "wifi-2";
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        final FakeSettings secureSettings = new FakeSettings();
+        secureSettings.putStringForUser(Settings.Secure.COMMUNAL_MODE_TRUSTED_NETWORKS,
+                mTrustedWifi1 + CommunalTrustedNetworkCondition.SETTINGS_STRING_DELIMINATOR
+                        + mTrustedWifi2, UserHandle.USER_SYSTEM);
+        mCondition = new CommunalTrustedNetworkCondition(mHandler, mConnectivityManager,
+                secureSettings);
+    }
+
+    @Test
+    public void updateCallback_connectedToTrustedNetwork_reportsTrue() {
+        final CommunalTrustedNetworkCondition.Callback callback =
+                mock(CommunalTrustedNetworkCondition.Callback.class);
+        mCondition.addCallback(callback);
+
+        final ConnectivityManager.NetworkCallback networkCallback = captureNetworkCallback();
+
+        // Connected to trusted Wi-Fi network.
+        final Network network = mock(Network.class);
+        networkCallback.onAvailable(network);
+        networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1));
+
+        // Verifies that the callback is triggered.
+        verify(callback).onConditionChanged(mCondition, true);
+    }
+
+    @Test
+    public void updateCallback_switchedToAnotherTrustedNetwork_reportsNothing() {
+        final CommunalTrustedNetworkCondition.Callback callback =
+                mock(CommunalTrustedNetworkCondition.Callback.class);
+        mCondition.addCallback(callback);
+
+        final ConnectivityManager.NetworkCallback networkCallback = captureNetworkCallback();
+
+        // Connected to a trusted Wi-Fi network.
+        final Network network = mock(Network.class);
+        networkCallback.onAvailable(network);
+        networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1));
+        clearInvocations(callback);
+
+        // Connected to another trusted Wi-Fi network.
+        networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi2));
+
+        // Verifies that the callback is not triggered.
+        verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean());
+    }
+
+    @Test
+    public void updateCallback_connectedToNonTrustedNetwork_reportsFalse() {
+        final CommunalTrustedNetworkCondition.Callback callback =
+                mock(CommunalTrustedNetworkCondition.Callback.class);
+        mCondition.addCallback(callback);
+
+        final ConnectivityManager.NetworkCallback networkCallback = captureNetworkCallback();
+
+        // Connected to trusted Wi-Fi network.
+        final Network network = mock(Network.class);
+        networkCallback.onAvailable(network);
+        networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1));
+
+        // Connected to non-trusted Wi-Fi network.
+        networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities("random-wifi"));
+
+        // Verifies that the callback is triggered.
+        verify(callback).onConditionChanged(mCondition, false);
+    }
+
+    @Test
+    public void updateCallback_disconnectedFromNetwork_reportsFalse() {
+        final CommunalTrustedNetworkCondition.Callback callback =
+                mock(CommunalTrustedNetworkCondition.Callback.class);
+        mCondition.addCallback(callback);
+
+        final ConnectivityManager.NetworkCallback networkCallback = captureNetworkCallback();
+
+        // Connected to Wi-Fi.
+        final Network network = mock(Network.class);
+        networkCallback.onAvailable(network);
+        networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1));
+        clearInvocations(callback);
+
+        // Disconnected from Wi-Fi.
+        networkCallback.onLost(network);
+
+        // Verifies that the callback is triggered.
+        verify(callback).onConditionChanged(mCondition, false);
+    }
+
+    // Captures and returns the network callback, assuming it is registered with the connectivity
+    // manager.
+    private ConnectivityManager.NetworkCallback captureNetworkCallback() {
+        verify(mConnectivityManager).registerNetworkCallback(any(NetworkRequest.class),
+                mNetworkCallbackCaptor.capture());
+        return mNetworkCallbackCaptor.getValue();
+    }
+
+    private NetworkCapabilities fakeNetworkCapabilities(String ssid) {
+        final NetworkCapabilities networkCapabilities = mock(NetworkCapabilities.class);
+        final WifiInfo wifiInfo = mock(WifiInfo.class);
+        when(wifiInfo.getSSID()).thenReturn(ssid);
+        when(networkCapabilities.getTransportInfo()).thenReturn(wifiInfo);
+        return networkCapabilities;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/FakeCommunalCondition.java b/packages/SystemUI/tests/src/com/android/systemui/communal/FakeCommunalCondition.java
new file mode 100644
index 0000000..882effd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/FakeCommunalCondition.java
@@ -0,0 +1,35 @@
+/*
+ * 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.communal;
+
+import com.android.systemui.communal.conditions.CommunalCondition;
+
+/**
+ * Fake implementation of {@link CommunalCondition}, and provides a way for tests to update
+ * condition fulfillment.
+ */
+public class FakeCommunalCondition extends CommunalCondition {
+    @Override
+    public void start() {}
+
+    @Override
+    public void stop() {}
+
+    public void fakeUpdateCondition(boolean isConditionMet) {
+        updateCondition(isConditionMet);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
index 61b4041..2290676 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
@@ -18,6 +18,7 @@
 
 
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
 
@@ -56,7 +57,8 @@
 public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCase {
 
     private static final String TEST_MESSAGE = "test message";
-    private static final String TEST_MESSAGE_2 = "test message 2";
+    private static final String TEST_MESSAGE_2 = "test message two";
+    private int mMsgId = 0;
 
     @Mock
     private DelayableExecutor mExecutor;
@@ -201,6 +203,24 @@
     }
 
     @Test
+    public void testSameMessage_noIndicationUpdate() {
+        // GIVEN we are showing and indication with a test message
+        mController.updateIndication(
+                INDICATION_TYPE_OWNER_INFO, createIndication(TEST_MESSAGE), true);
+        reset(mView);
+        reset(mExecutor);
+
+        // WHEN the same type tries to show the same exact message
+        final KeyguardIndication sameIndication = createIndication(TEST_MESSAGE);
+        mController.updateIndication(
+                INDICATION_TYPE_OWNER_INFO, sameIndication, true);
+
+        // THEN
+        // - we don't update the indication b/c there's no reason the animate the same text
+        verify(mView, never()).switchIndication(sameIndication);
+    }
+
+    @Test
     public void testTransientIndication() {
         // GIVEN we already have two indication messages
         mController.updateIndication(
@@ -223,8 +243,11 @@
     @Test
     public void testHideIndicationOneMessage() {
         // GIVEN we have one indication message
+        KeyguardIndication indication = createIndication();
         mController.updateIndication(
-                INDICATION_TYPE_OWNER_INFO, createIndication(), false);
+                INDICATION_TYPE_OWNER_INFO, indication, false);
+        verify(mView).switchIndication(indication);
+        reset(mView);
 
         // WHEN we hide the current indication type
         mController.hideIndication(INDICATION_TYPE_OWNER_INFO);
@@ -254,6 +277,10 @@
 
     @Test
     public void testStartDozing() {
+        // GIVEN a biometric message is showing
+        mController.updateIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                createIndication(), true);
+
         // WHEN the device is dozing
         mStatusBarStateListener.onDozingChanged(true);
 
@@ -293,9 +320,19 @@
         verify(mView, never()).switchIndication(any());
     }
 
+    /**
+     * Create an indication with a unique message.
+     */
     private KeyguardIndication createIndication() {
+        return createIndication(TEST_MESSAGE + " " + mMsgId++);
+    }
+
+    /**
+     * Create an indication with the given message.
+     */
+    private KeyguardIndication createIndication(String msg) {
         return new KeyguardIndication.Builder()
-                .setMessage(TEST_MESSAGE)
+                .setMessage(msg)
                 .setTextColor(ColorStateList.valueOf(Color.WHITE))
                 .build();
     }
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/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/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 8afefde..e427d53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -22,6 +22,7 @@
 
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING;
@@ -112,6 +113,8 @@
     private static final ComponentName DEVICE_OWNER_COMPONENT = new ComponentName("com.android.foo",
             "bar");
 
+    private static final int TEST_STRING_RES = R.string.keyguard_indication_trust_unlocked;
+
     private String mKeyguardTryFingerprintMsg;
     private String mDisclosureWithOrganization;
     private String mDisclosureGeneric;
@@ -419,7 +422,7 @@
 
         // WHEN transient text is shown
         mStatusBarStateListener.onDozingChanged(true);
-        mController.showTransientIndication("Test");
+        mController.showTransientIndication(TEST_STRING_RES);
 
         // THEN wake lock is held while the animation is running
         assertTrue("WakeLock expected: HELD, was: RELEASED", mWakeLock.isHeld());
@@ -434,7 +437,7 @@
 
         // WHEN we show the transient indication
         mStatusBarStateListener.onDozingChanged(true);
-        mController.showTransientIndication("Test");
+        mController.showTransientIndication(TEST_STRING_RES);
 
         // THEN wake lock is RELEASED, not held
         assertFalse("WakeLock expected: RELEASED, was: HELD", mWakeLock.isHeld());
@@ -445,10 +448,11 @@
         createController();
 
         mController.setVisible(true);
-        mController.showTransientIndication("Test");
+        mController.showTransientIndication(TEST_STRING_RES);
         mStatusBarStateListener.onDozingChanged(true);
 
-        assertThat(mTextView.getText()).isEqualTo("Test");
+        assertThat(mTextView.getText()).isEqualTo(
+                mContext.getResources().getString(TEST_STRING_RES));
         assertThat(mTextView.getCurrentTextColor()).isEqualTo(Color.WHITE);
         assertThat(mTextView.getAlpha()).isEqualTo(1f);
     }
@@ -462,11 +466,11 @@
         mController.getKeyguardCallback().onBiometricHelp(
                 KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message,
                 BiometricSourceType.FACE);
-        verifyTransientMessage(message);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message);
         reset(mRotateTextViewController);
         mStatusBarStateListener.onDozingChanged(true);
 
-        verifyHideIndication(INDICATION_TYPE_TRANSIENT);
+        verifyHideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE);
     }
 
     @Test
@@ -478,7 +482,7 @@
         mController.getKeyguardCallback().onBiometricError(FaceManager.FACE_ERROR_TIMEOUT,
                 "A message", BiometricSourceType.FACE);
 
-        verifyTransientMessage(message);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message);
         mStatusBarStateListener.onDozingChanged(true);
 
         assertThat(mTextView.getText()).isNotEqualTo(message);
@@ -497,7 +501,8 @@
                 FingerprintManager.FINGERPRINT_ERROR_CANCELED, "bar",
                 BiometricSourceType.FINGERPRINT);
 
-        verifyNoTransientMessage();
+        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+        verifyNoMessage(INDICATION_TYPE_TRANSIENT);
     }
 
     @Test
@@ -757,7 +762,12 @@
         verify(mRotateTextViewController).showTransient(eq(message));
     }
 
-    private void verifyNoTransientMessage() {
-        verify(mRotateTextViewController, never()).showTransient(any());
+    private void verifyNoMessage(int type) {
+        if (type == INDICATION_TYPE_TRANSIENT) {
+            verify(mRotateTextViewController, never()).showTransient(anyString());
+        } else {
+            verify(mRotateTextViewController, never()).updateIndication(eq(type),
+                    anyObject(), anyBoolean());
+        }
     }
 }
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/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/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..d79f2f3 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();
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/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 2d61773..9b21790 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2232,11 +2232,12 @@
         return false;
     }
 
-    public final boolean filterSharedLibPackage(@Nullable PackageStateInternal ps, int uid,
+    private boolean filterStaticSharedLibPackage(@Nullable PackageStateInternal ps, int uid,
             int userId, @PackageManager.ComponentInfoFlags long flags) {
-        // Callers can access only the libs they depend on, otherwise they need to explicitly
-        // ask for the shared libraries given the caller is allowed to access all static libs.
-        if ((flags & PackageManager.MATCH_STATIC_SHARED_LIBRARIES) != 0) {
+        // Callers can access only the static shared libs they depend on, otherwise they need to
+        // explicitly ask for the static shared libraries given the caller is allowed to access
+        // all static libs.
+        if ((flags & PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES) != 0) {
             // System/shell/root get to see all static libs
             final int appId = UserHandle.getAppId(uid);
             if (appId == Process.SYSTEM_UID || appId == Process.SHELL_UID
@@ -2287,6 +2288,69 @@
         return true;
     }
 
+    private boolean filterSdkLibPackage(@Nullable PackageStateInternal ps, int uid,
+            int userId, @PackageManager.ComponentInfoFlags long flags) {
+        // Callers can access only the SDK libs they depend on, otherwise they need to
+        // explicitly ask for the SDKs given the caller is allowed to access
+        // all shared libs.
+        if ((flags & PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES) != 0) {
+            // System/shell/root get to see all SDK libs.
+            final int appId = UserHandle.getAppId(uid);
+            if (appId == Process.SYSTEM_UID || appId == Process.SHELL_UID
+                    || appId == Process.ROOT_UID) {
+                return false;
+            }
+            // Installer gets to see all SDK libs.
+            if (PackageManager.PERMISSION_GRANTED
+                    == checkUidPermission(Manifest.permission.INSTALL_PACKAGES, uid)) {
+                return false;
+            }
+        }
+
+        // No package means no static lib as it is always on internal storage
+        if (ps == null || ps.getPkg() == null || !ps.getPkg().isSdkLibrary()) {
+            return false;
+        }
+
+        final SharedLibraryInfo libraryInfo = getSharedLibraryInfo(
+                ps.getPkg().getSdkLibName(), ps.getPkg().getSdkLibVersionMajor());
+        if (libraryInfo == null) {
+            return false;
+        }
+
+        final int resolvedUid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
+        final String[] uidPackageNames = getPackagesForUid(resolvedUid);
+        if (uidPackageNames == null) {
+            return true;
+        }
+
+        for (String uidPackageName : uidPackageNames) {
+            if (ps.getPackageName().equals(uidPackageName)) {
+                return false;
+            }
+            PackageStateInternal uidPs = mSettings.getPackage(uidPackageName);
+            if (uidPs != null) {
+                final int index = ArrayUtils.indexOf(uidPs.getUsesSdkLibraries(),
+                        libraryInfo.getName());
+                if (index < 0) {
+                    continue;
+                }
+                if (uidPs.getPkg().getUsesSdkLibrariesVersionsMajor()[index]
+                        == libraryInfo.getLongVersion()) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    @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,
+                userId, flags);
+    }
+
     private boolean hasCrossUserPermission(
             int callingUid, int callingUserId, int userId, boolean requireFullPermission,
             boolean requirePermissionWhenSameUser) {
@@ -3719,7 +3783,7 @@
 
         flags = updateFlagsForPackage(flags, userId);
 
-        final boolean canSeeStaticLibraries =
+        final boolean canSeeStaticAndSdkLibraries =
                 mContext.checkCallingOrSelfPermission(INSTALL_PACKAGES)
                         == PERMISSION_GRANTED
                         || mContext.checkCallingOrSelfPermission(DELETE_PACKAGES)
@@ -3744,7 +3808,7 @@
             final int versionCount = versionedLib.size();
             for (int j = 0; j < versionCount; j++) {
                 SharedLibraryInfo libInfo = versionedLib.valueAt(j);
-                if (!canSeeStaticLibraries && libInfo.isStatic()) {
+                if (!canSeeStaticAndSdkLibraries && (libInfo.isStatic() || libInfo.isSdk())) {
                     break;
                 }
                 final long identity = Binder.clearCallingIdentity();
@@ -3753,7 +3817,7 @@
                     PackageInfo packageInfo = getPackageInfoInternal(
                             declaringPackage.getPackageName(),
                             declaringPackage.getLongVersionCode(),
-                            flags | PackageManager.MATCH_STATIC_SHARED_LIBRARIES,
+                            flags | PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES,
                             Binder.getCallingUid(), userId);
                     if (packageInfo == null) {
                         continue;
@@ -3853,12 +3917,17 @@
             }
 
             final String libName = libInfo.getName();
-            if (libInfo.isStatic()) {
-                final int libIdx = ArrayUtils.indexOf(ps.getUsesStaticLibraries(), libName);
+            if (libInfo.isStatic() || libInfo.isSdk()) {
+                final String[] libs =
+                        libInfo.isStatic() ? ps.getUsesStaticLibraries() : ps.getUsesSdkLibraries();
+                final long[] libsVersions = libInfo.isStatic() ? ps.getUsesStaticLibrariesVersions()
+                        : ps.getUsesSdkLibrariesVersionsMajor();
+
+                final int libIdx = ArrayUtils.indexOf(libs, libName);
                 if (libIdx < 0) {
                     continue;
                 }
-                if (ps.getUsesStaticLibrariesVersions()[libIdx] != libInfo.getLongVersion()) {
+                if (libsVersions[libIdx] != libInfo.getLongVersion()) {
                     continue;
                 }
                 if (shouldFilterApplication(ps, callingUid, userId)) {
@@ -3939,7 +4008,7 @@
                     PackageInfo packageInfo = getPackageInfoInternal(
                             declaringPackage.getPackageName(),
                             declaringPackage.getLongVersionCode(),
-                            flags | PackageManager.MATCH_STATIC_SHARED_LIBRARIES,
+                            flags | PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES,
                             Binder.getCallingUid(), userId);
                     if (packageInfo == null) {
                         continue;
@@ -4034,7 +4103,7 @@
                         getPackageStateInternal(libraryInfo.getPackageName());
                 if (ps != null && !filterSharedLibPackage(ps, Binder.getCallingUid(),
                         UserHandle.getUserId(Binder.getCallingUid()),
-                        PackageManager.MATCH_STATIC_SHARED_LIBRARIES)) {
+                        PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES)) {
                     if (libs == null) {
                         libs = new ArraySet<>();
                     }
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 43d60cc..641f24f 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -164,9 +164,16 @@
 
             allUsers = mUserManagerInternal.getUserIds();
 
-            if (pkg != null && pkg.getStaticSharedLibName() != null) {
-                SharedLibraryInfo libraryInfo = mPm.getSharedLibraryInfo(
-                        pkg.getStaticSharedLibName(), pkg.getStaticSharedLibVersion());
+            if (pkg != null) {
+                SharedLibraryInfo libraryInfo = null;
+                if (pkg.getStaticSharedLibName() != null) {
+                    libraryInfo = mPm.getSharedLibraryInfo(pkg.getStaticSharedLibName(),
+                            pkg.getStaticSharedLibVersion());
+                } else if (pkg.getSdkLibName() != null) {
+                    libraryInfo = mPm.getSharedLibraryInfo(pkg.getSdkLibName(),
+                            pkg.getSdkLibVersionMajor());
+                }
+
                 if (libraryInfo != null) {
                     for (int currUserId : allUsers) {
                         if (removeUser != UserHandle.USER_ALL && removeUser != currUserId) {
@@ -828,9 +835,10 @@
                 continue;
             }
             final String packageName = ps.getPkg().getPackageName();
-            // Skip over if system app or static shared library
+            // Skip over if system app, static shared library or and SDK library.
             if ((ps.getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0
-                    || !TextUtils.isEmpty(ps.getPkg().getStaticSharedLibName())) {
+                    || !TextUtils.isEmpty(ps.getPkg().getStaticSharedLibName())
+                    || !TextUtils.isEmpty(ps.getPkg().getSdkLibName())) {
                 continue;
             }
             if (DEBUG_CLEAN_APKS) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 5ca0618..d2087ee 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -254,12 +254,11 @@
             final List<SharedLibraryInfo> allowedSharedLibInfos =
                     SharedLibraryHelper.getAllowedSharedLibInfos(scanResult,
                             request.mSharedLibrarySource);
-            final SharedLibraryInfo staticLib = scanResult.mStaticSharedLibraryInfo;
             if (allowedSharedLibInfos != null) {
                 for (SharedLibraryInfo info : allowedSharedLibInfos) {
                     if (!SharedLibraryHelper.addSharedLibraryToPackageVersionMap(
                             incomingSharedLibraries, info)) {
-                        throw new ReconcileFailure("Static Shared Library " + staticLib.getName()
+                        throw new ReconcileFailure("Shared Library " + info.getName()
                                 + " is being installed twice in this set!");
                     }
                 }
@@ -1188,7 +1187,8 @@
                     createdAppId.put(packageName, optimisticallyRegisterAppId(result));
                     versionInfos.put(result.mPkgSetting.getPkg().getPackageName(),
                             mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg()));
-                    if (result.mStaticSharedLibraryInfo != null) {
+                    if (result.mStaticSharedLibraryInfo != null
+                            || result.mSdkSharedLibraryInfo != null) {
                         final PackageSetting sharedLibLatestVersionSetting =
                                 mPm.getSharedLibLatestVersionSetting(result);
                         if (sharedLibLatestVersionSetting != null) {
@@ -2695,7 +2695,7 @@
                 }
             }
 
-            if (dataOwnerPkg != null) {
+            if (dataOwnerPkg != null && !dataOwnerPkg.isSdkLibrary()) {
                 if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
                         dataOwnerPkg.isDebuggable())) {
                     try {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 4767d3a..26a5bbb 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1328,7 +1328,7 @@
         PackageInfo packageInfo = null;
         try {
             packageInfo = AppGlobals.getPackageManager().getPackageInfo(
-                    basePackageName, PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId);
+                    basePackageName, PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES, userId);
         } catch (RemoteException ignored) {
         }
         if (packageInfo == null || packageInfo.applicationInfo == null) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index dbbc163..55a0c96 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2901,7 +2901,7 @@
 
         final PackageInfo pkgInfo = mPm.getPackageInfo(
                 params.appPackageName, PackageManager.GET_SIGNATURES
-                        | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
+                        | PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES /*flags*/, userId);
 
         // Partial installs must be consistent with existing install
         if (params.mode == SessionParams.MODE_INHERIT_EXISTING
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9f5adcb..fe5bce13 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4034,7 +4034,13 @@
         // - Package manager is in a state where package isn't scanned yet. This will
         //   get called again after scanning to fix the dependencies.
         if (AndroidPackageUtils.isLibrary(pkg)) {
-            if (pkg.getStaticSharedLibName() != null) {
+            if (pkg.getSdkLibName() != null) {
+                SharedLibraryInfo definedLibrary = getSharedLibraryInfo(
+                        pkg.getSdkLibName(), pkg.getSdkLibVersionMajor());
+                if (definedLibrary != null) {
+                    action.accept(definedLibrary, libInfo);
+                }
+            } else if (pkg.getStaticSharedLibName() != null) {
                 SharedLibraryInfo definedLibrary = getSharedLibraryInfo(
                         pkg.getStaticSharedLibName(), pkg.getStaticSharedLibVersion());
                 if (definedLibrary != null) {
@@ -4186,7 +4192,9 @@
                         && !hasString(pkg.getUsesLibraries(), changingPkg.getLibraryNames())
                         && !hasString(pkg.getUsesOptionalLibraries(), changingPkg.getLibraryNames())
                         && !ArrayUtils.contains(pkg.getUsesStaticLibraries(),
-                        changingPkg.getStaticSharedLibName())) {
+                        changingPkg.getStaticSharedLibName())
+                        && !ArrayUtils.contains(pkg.getUsesSdkLibraries(),
+                        changingPkg.getSdkLibName())) {
                     continue;
                 }
                 if (resultList == null) {
@@ -4477,15 +4485,24 @@
                     Slog.w(TAG, "Cannot hide package: android");
                     return false;
                 }
-                // Cannot hide static shared libs as they are considered
-                // a part of the using app (emulating static linking). Also
-                // static libs are installed always on internal storage.
                 AndroidPackage pkg = mPackages.get(packageName);
-                if (pkg != null && pkg.getStaticSharedLibName() != null) {
-                    Slog.w(TAG, "Cannot hide package: " + packageName
-                            + " providing static shared library: "
-                            + pkg.getStaticSharedLibName());
-                    return false;
+                if (pkg != null) {
+                    // Cannot hide SDK libs as they are controlled by SDK manager.
+                    if (pkg.getSdkLibName() != null) {
+                        Slog.w(TAG, "Cannot hide package: " + packageName
+                                + " providing SDK library: "
+                                + pkg.getSdkLibName());
+                        return false;
+                    }
+                    // Cannot hide static shared libs as they are considered
+                    // a part of the using app (emulating static linking). Also
+                    // static libs are installed always on internal storage.
+                    if (pkg.getStaticSharedLibName() != null) {
+                        Slog.w(TAG, "Cannot hide package: " + packageName
+                                + " providing static shared library: "
+                                + pkg.getStaticSharedLibName());
+                        return false;
+                    }
                 }
                 // Only allow protected packages to hide themselves.
                 if (hidden && !UserHandle.isSameApp(callingUid, pkgSetting.getAppId())
@@ -5154,15 +5171,24 @@
                         continue;
                     }
 
-                    // Cannot suspend static shared libs as they are considered
-                    // a part of the using app (emulating static linking). Also
-                    // static libs are installed always on internal storage.
                     AndroidPackage pkg = mPackages.get(packageName);
-                    if (pkg != null && pkg.isStaticSharedLibrary()) {
-                        Slog.w(TAG, "Cannot suspend package: " + packageName
-                                + " providing static shared library: "
-                                + pkg.getStaticSharedLibName());
-                        continue;
+                    if (pkg != null) {
+                        // Cannot suspend SDK libs as they are controlled by SDK manager.
+                        if (pkg.isSdkLibrary()) {
+                            Slog.w(TAG, "Cannot suspend package: " + packageName
+                                    + " providing SDK library: "
+                                    + pkg.getSdkLibName());
+                            continue;
+                        }
+                        // Cannot suspend static shared libs as they are considered
+                        // a part of the using app (emulating static linking). Also
+                        // static libs are installed always on internal storage.
+                        if (pkg.isStaticSharedLibrary()) {
+                            Slog.w(TAG, "Cannot suspend package: " + packageName
+                                    + " providing static shared library: "
+                                    + pkg.getStaticSharedLibName());
+                            continue;
+                        }
                     }
                 }
                 if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
@@ -5612,14 +5638,22 @@
                 android.Manifest.permission.DELETE_PACKAGES, null);
         // TODO (b/157774108): This should fail on non-existent packages.
         synchronized (mLock) {
-            // Cannot block uninstall of static shared libs as they are
-            // considered a part of the using app (emulating static linking).
-            // Also static libs are installed always on internal storage.
             AndroidPackage pkg = mPackages.get(packageName);
-            if (pkg != null && pkg.getStaticSharedLibName() != null) {
-                Slog.w(TAG, "Cannot block uninstall of package: " + packageName
-                        + " providing static shared library: " + pkg.getStaticSharedLibName());
-                return false;
+            if (pkg != null) {
+                // Cannot block uninstall SDK libs as they are controlled by SDK manager.
+                if (pkg.getSdkLibName() != null) {
+                    Slog.w(TAG, "Cannot block uninstall of package: " + packageName
+                            + " providing SDK library: " + pkg.getSdkLibName());
+                    return false;
+                }
+                // Cannot block uninstall of static shared libs as they are
+                // considered a part of the using app (emulating static linking).
+                // Also static libs are installed always on internal storage.
+                if (pkg.getStaticSharedLibName() != null) {
+                    Slog.w(TAG, "Cannot block uninstall of package: " + packageName
+                            + " providing static shared library: " + pkg.getStaticSharedLibName());
+                    return false;
+                }
             }
             mSettings.setBlockUninstallLPw(userId, packageName, blockUninstall);
             mSettings.writePackageRestrictionsLPr(userId);
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index fb70470..0564e85 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -49,6 +49,7 @@
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
+import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
 import android.content.pm.VersionedPackage;
@@ -667,6 +668,8 @@
                 return runListPermissions();
             case "staged-sessions":
                 return runListStagedSessions();
+            case "sdks":
+                return runListSdks();
             case "users":
                 ServiceManager.getService("user").shellCommand(
                         getInFileDescriptor(), getOutFileDescriptor(), getErrFileDescriptor(),
@@ -792,6 +795,15 @@
     }
 
     private int runListPackages(boolean showSourceDir) throws RemoteException {
+        return runListPackages(showSourceDir, false);
+    }
+
+    private int runListSdks() throws RemoteException {
+        return runListPackages(false, true);
+    }
+
+    private int runListPackages(boolean showSourceDir, boolean showSdks) throws RemoteException {
+        final String prefix = showSdks ? "sdk:" : "package:";
         final PrintWriter pw = getOutPrintWriter();
         int getFlags = 0;
         boolean listDisabled = false, listEnabled = false;
@@ -866,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")
@@ -885,37 +900,61 @@
             }
 
             final boolean isSystem = !isApex &&
-                    (info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0;
+                    (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
             final boolean isEnabled = !isApex && info.applicationInfo.enabled;
-            if ((!listDisabled || !isEnabled) &&
-                    (!listEnabled || isEnabled) &&
-                    (!listSystem || isSystem) &&
-                    (!listThirdParty || !isSystem) &&
-                    (!listApexOnly || isApex)) {
-                pw.print("package:");
-                if (showSourceDir) {
-                    pw.print(info.applicationInfo.sourceDir);
-                    pw.print("=");
+            if ((listDisabled && isEnabled) ||
+                    (listEnabled && !isEnabled) ||
+                    (listSystem && !isSystem) ||
+                    (listThirdParty && isSystem) ||
+                    (listApexOnly && !isApex)) {
+                continue;
+            }
+
+            String name = null;
+            if (showSdks) {
+                final ParceledListSlice<SharedLibraryInfo> libsSlice =
+                        mInterface.getDeclaredSharedLibraries(info.packageName, getFlags, userId);
+                if (libsSlice == null) {
+                    continue;
                 }
-                pw.print(info.packageName);
-                if (showVersionCode) {
-                    pw.print(" versionCode:");
-                    if (info.applicationInfo != null) {
-                        pw.print(info.applicationInfo.longVersionCode);
-                    } else {
-                        pw.print(info.getLongVersionCode());
+                final List<SharedLibraryInfo> libs = libsSlice.getList();
+                for (int l = 0, lsize = libs.size(); l < lsize; ++l) {
+                    SharedLibraryInfo lib = libs.get(l);
+                    if (lib.getType() == SharedLibraryInfo.TYPE_SDK) {
+                        name = lib.getName() + ":" + lib.getLongVersion();
+                        break;
                     }
                 }
-                if (listInstaller) {
-                    pw.print("  installer=");
-                    pw.print(mInterface.getInstallerPackageName(info.packageName));
+                if (name == null) {
+                    continue;
                 }
-                if (showUid && !isApex) {
-                    pw.print(" uid:");
-                    pw.print(info.applicationInfo.uid);
-                }
-                pw.println();
+            } else {
+                name = info.packageName;
             }
+
+            pw.print(prefix);
+            if (showSourceDir) {
+                pw.print(info.applicationInfo.sourceDir);
+                pw.print("=");
+            }
+            pw.print(name);
+            if (showVersionCode) {
+                pw.print(" versionCode:");
+                if (info.applicationInfo != null) {
+                    pw.print(info.applicationInfo.longVersionCode);
+                } else {
+                    pw.print(info.getLongVersionCode());
+                }
+            }
+            if (listInstaller) {
+                pw.print("  installer=");
+                pw.print(mInterface.getInstallerPackageName(info.packageName));
+            }
+            if (showUid && !isApex) {
+                pw.print(" uid:");
+                pw.print(info.applicationInfo.uid);
+            }
+            pw.println();
         }
         return 0;
     }
@@ -2060,7 +2099,7 @@
         } else {
             if ((flags & PackageManager.DELETE_ALL_USERS) == 0) {
                 final PackageInfo info = mInterface.getPackageInfo(packageName,
-                        PackageManager.MATCH_STATIC_SHARED_LIBRARIES, translatedUserId);
+                        PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES, translatedUserId);
                 if (info == null) {
                     pw.println("Failure [not installed for " + translatedUserId + "]");
                     return 1;
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index dc514c1..923a133 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -32,12 +32,6 @@
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
 import android.content.pm.overlay.OverlayPaths;
-
-import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.pm.pkg.PackageUserState;
-import com.android.server.pm.pkg.PackageUserStateImpl;
-import com.android.server.pm.pkg.PackageUserStateInternal;
-import com.android.server.pm.pkg.SuspendParams;
 import android.os.PersistableBundle;
 import android.service.pm.PackageProto;
 import android.util.ArrayMap;
@@ -53,7 +47,12 @@
 import com.android.server.pm.permission.LegacyPermissionState;
 import com.android.server.pm.pkg.AndroidPackageApi;
 import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUnserialized;
+import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateImpl;
+import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.SuspendParams;
 import com.android.server.utils.SnapshotCache;
 
 import libcore.util.EmptyArray;
@@ -94,6 +93,12 @@
     private Set<String> mOldCodePaths;
 
     @Nullable
+    private String[] usesSdkLibraries;
+
+    @Nullable
+    private long[] usesSdkLibrariesVersionsMajor;
+
+    @Nullable
     private String[] usesStaticLibraries;
 
     @Nullable
@@ -208,12 +213,16 @@
             String legacyNativeLibraryPath, String primaryCpuAbi,
             String secondaryCpuAbi, String cpuAbiOverride,
             long longVersionCode, int pkgFlags, int pkgPrivateFlags,
-            int sharedUserId, String[] usesStaticLibraries,
-            long[] usesStaticLibrariesVersions, Map<String, Set<String>> mimeGroups,
+            int sharedUserId,
+            String[] usesSdkLibraries, long[] usesSdkLibrariesVersionsMajor,
+            String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
+            Map<String, Set<String>> mimeGroups,
             @NonNull UUID domainSetId) {
         super(pkgFlags, pkgPrivateFlags);
         this.mName = name;
         this.mRealName = realName;
+        this.usesSdkLibraries = usesSdkLibraries;
+        this.usesSdkLibrariesVersionsMajor = usesSdkLibrariesVersionsMajor;
         this.usesStaticLibraries = usesStaticLibraries;
         this.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
         this.mPath = path;
@@ -617,6 +626,13 @@
         forceQueryableOverride = other.forceQueryableOverride;
         mDomainSetId = other.mDomainSetId;
 
+        usesSdkLibraries = other.usesSdkLibraries != null
+                ? Arrays.copyOf(other.usesSdkLibraries,
+                other.usesSdkLibraries.length) : null;
+        usesSdkLibrariesVersionsMajor = other.usesSdkLibrariesVersionsMajor != null
+                ? Arrays.copyOf(other.usesSdkLibrariesVersionsMajor,
+                other.usesSdkLibrariesVersionsMajor.length) : null;
+
         usesStaticLibraries = other.usesStaticLibraries != null
                 ? Arrays.copyOf(other.usesStaticLibraries,
                 other.usesStaticLibraries.length) : null;
@@ -1225,6 +1241,19 @@
 
     @NonNull
     @Override
+    public String[] getUsesSdkLibraries() {
+        return usesSdkLibraries == null ? EmptyArray.STRING : usesSdkLibraries;
+    }
+
+    @NonNull
+    @Override
+    public long[] getUsesSdkLibrariesVersionsMajor() {
+        return usesSdkLibrariesVersionsMajor == null ? EmptyArray.LONG
+                : usesSdkLibrariesVersionsMajor;
+    }
+
+    @NonNull
+    @Override
     public String[] getUsesStaticLibraries() {
         return usesStaticLibraries == null ? EmptyArray.STRING : usesStaticLibraries;
     }
@@ -1300,6 +1329,18 @@
         return this;
     }
 
+    public PackageSetting setUsesSdkLibraries(String[] usesSdkLibraries) {
+        this.usesSdkLibraries = usesSdkLibraries;
+        onChanged();
+        return this;
+    }
+
+    public PackageSetting setUsesSdkLibrariesVersionsMajor(long[] usesSdkLibrariesVersions) {
+        this.usesSdkLibrariesVersionsMajor = usesSdkLibrariesVersions;
+        onChanged();
+        return this;
+    }
+
     public PackageSetting setUsesStaticLibraries(String[] usesStaticLibraries) {
         this.usesStaticLibraries = usesStaticLibraries;
         onChanged();
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 48b893b..749495c 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -189,7 +189,19 @@
 
         r = null;
 
-        // Any package can hold static shared libraries.
+        // Any package can hold SDK or static shared libraries.
+        if (pkg.getSdkLibName() != null) {
+            if (removeSharedLibraryLPw(pkg.getSdkLibName(), pkg.getSdkLibVersionMajor())) {
+                if (DEBUG_REMOVE && chatty) {
+                    if (r == null) {
+                        r = new StringBuilder(256);
+                    } else {
+                        r.append(' ');
+                    }
+                    r.append(pkg.getSdkLibName());
+                }
+            }
+        }
         if (pkg.getStaticSharedLibName() != null) {
             if (removeSharedLibraryLPw(pkg.getStaticSharedLibName(),
                     pkg.getStaticSharedLibVersion())) {
diff --git a/services/core/java/com/android/server/pm/ScanPackageHelper.java b/services/core/java/com/android/server/pm/ScanPackageHelper.java
index 9b08ef9..eafe0d98 100644
--- a/services/core/java/com/android/server/pm/ScanPackageHelper.java
+++ b/services/core/java/com/android/server/pm/ScanPackageHelper.java
@@ -294,6 +294,12 @@
             }
         }
 
+        String[] usesSdkLibraries = null;
+        if (!parsedPackage.getUsesSdkLibraries().isEmpty()) {
+            usesSdkLibraries = new String[parsedPackage.getUsesSdkLibraries().size()];
+            parsedPackage.getUsesSdkLibraries().toArray(usesSdkLibraries);
+        }
+
         String[] usesStaticLibraries = null;
         if (!parsedPackage.getUsesStaticLibraries().isEmpty()) {
             usesStaticLibraries = new String[parsedPackage.getUsesStaticLibraries().size()];
@@ -324,7 +330,8 @@
                     AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage),
                     parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user,
                     true /*allowInstall*/, instantApp, virtualPreload,
-                    UserManagerService.getInstance(), usesStaticLibraries,
+                    UserManagerService.getInstance(), usesSdkLibraries,
+                    parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
                     parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
                     newDomainSetId);
         } else {
@@ -344,6 +351,7 @@
                     PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting),
                     PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting),
                     UserManagerService.getInstance(),
+                    usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
                     usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
                     parsedPackage.getMimeGroups(), newDomainSetId);
         }
@@ -552,6 +560,10 @@
             pkgSetting.setVolumeUuid(volumeUuid);
         }
 
+        SharedLibraryInfo sdkLibraryInfo = null;
+        if (!TextUtils.isEmpty(parsedPackage.getSdkLibName())) {
+            sdkLibraryInfo = AndroidPackageUtils.createSharedLibraryForSdk(parsedPackage);
+        }
         SharedLibraryInfo staticSharedLibraryInfo = null;
         if (!TextUtils.isEmpty(parsedPackage.getStaticSharedLibName())) {
             staticSharedLibraryInfo =
@@ -568,7 +580,7 @@
 
         return new ScanResult(request, true, pkgSetting, changedAbiCodePath,
                 !createNewPackage /* existingSettingCopied */,
-                previousAppId, staticSharedLibraryInfo,
+                previousAppId, sdkLibraryInfo, staticSharedLibraryInfo,
                 dynamicSharedLibraryInfos);
     }
 
diff --git a/services/core/java/com/android/server/pm/ScanResult.java b/services/core/java/com/android/server/pm/ScanResult.java
index eb44a82..f77be1f 100644
--- a/services/core/java/com/android/server/pm/ScanResult.java
+++ b/services/core/java/com/android/server/pm/ScanResult.java
@@ -51,6 +51,8 @@
     /** ABI code paths that have changed in the package scan */
     @Nullable public final List<String> mChangedAbiCodePath;
 
+    public final SharedLibraryInfo mSdkSharedLibraryInfo;
+
     public final SharedLibraryInfo mStaticSharedLibraryInfo;
 
     public final List<SharedLibraryInfo> mDynamicSharedLibraryInfos;
@@ -60,6 +62,7 @@
             @Nullable PackageSetting pkgSetting,
             @Nullable List<String> changedAbiCodePath, boolean existingSettingCopied,
             int previousAppId,
+            SharedLibraryInfo sdkSharedLibraryInfo,
             SharedLibraryInfo staticSharedLibraryInfo,
             List<SharedLibraryInfo> dynamicSharedLibraryInfos) {
         mRequest = request;
@@ -68,6 +71,7 @@
         mChangedAbiCodePath = changedAbiCodePath;
         mExistingSettingCopied = existingSettingCopied;
         mPreviousAppId = previousAppId;
+        mSdkSharedLibraryInfo = sdkSharedLibraryInfo;
         mStaticSharedLibraryInfo = staticSharedLibraryInfo;
         mDynamicSharedLibraryInfos = dynamicSharedLibraryInfos;
     }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6a163b2..45994f6 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -276,6 +276,7 @@
     private static final String TAG_RUNTIME_PERMISSIONS = "runtime-permissions";
     private static final String TAG_PERMISSIONS = "perms";
     private static final String TAG_CHILD_PACKAGE = "child-package";
+    private static final String TAG_USES_SDK_LIB = "uses-sdk-lib";
     private static final String TAG_USES_STATIC_LIB = "uses-static-lib";
     private static final String TAG_BLOCK_UNINSTALL_PACKAGES = "block-uninstall-packages";
     private static final String TAG_BLOCK_UNINSTALL = "block-uninstall";
@@ -826,6 +827,7 @@
                 p.getLegacyNativeLibraryPath(), p.getPrimaryCpuAbi(),
                 p.getSecondaryCpuAbi(), p.getCpuAbiOverride(),
                 p.getAppId(), p.getVersionCode(), p.getFlags(), p.getPrivateFlags(),
+                p.getUsesSdkLibraries(), p.getUsesSdkLibrariesVersionsMajor(),
                 p.getUsesStaticLibraries(), p.getUsesStaticLibrariesVersions(), p.getMimeGroups(),
                 mDomainVerificationManager.generateNewId());
         if (ret != null) {
@@ -849,9 +851,10 @@
 
     PackageSetting addPackageLPw(String name, String realName, File codePath,
             String legacyNativeLibraryPathString, String primaryCpuAbiString,
-            String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, long vc, int
-            pkgFlags, int pkgPrivateFlags, String[] usesStaticLibraries,
-            long[] usesStaticLibraryNames, Map<String, Set<String>> mimeGroups,
+            String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, long vc,
+            int pkgFlags, int pkgPrivateFlags, String[] usesSdkLibraries,
+            long[] usesSdkLibrariesVersions, String[] usesStaticLibraries,
+            long[] usesStaticLibrariesVersions, Map<String, Set<String>> mimeGroups,
             @NonNull UUID domainSetId) {
         PackageSetting p = mPackages.get(name);
         if (p != null) {
@@ -864,8 +867,8 @@
         }
         p = new PackageSetting(name, realName, codePath, legacyNativeLibraryPathString,
                 primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, vc, pkgFlags,
-                pkgPrivateFlags, 0 /*userId*/, usesStaticLibraries, usesStaticLibraryNames,
-                mimeGroups, domainSetId);
+                pkgPrivateFlags, 0 /*userId*/, usesSdkLibraries, usesSdkLibrariesVersions,
+                usesStaticLibraries, usesStaticLibrariesVersions, mimeGroups, domainSetId);
         p.setAppId(uid);
         if (registerExistingAppIdLPw(uid, p, name)) {
             mPackages.put(name, p);
@@ -925,6 +928,7 @@
             String secondaryCpuAbi, long versionCode, int pkgFlags, int pkgPrivateFlags,
             UserHandle installUser, boolean allowInstall, boolean instantApp,
             boolean virtualPreload, UserManagerService userManager,
+            String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,
             String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
             Set<String> mimeGroupNames, @NonNull UUID domainSetId) {
         final PackageSetting pkgSetting;
@@ -940,6 +944,8 @@
                     // overwrite the signatures in the original package setting.
                     .setSignatures(new PackageSignatures())
                     .setLongVersionCode(versionCode)
+                    .setUsesSdkLibraries(usesSdkLibraries)
+                    .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions)
                     .setUsesStaticLibraries(usesStaticLibraries)
                     .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
                     // Update new package state.
@@ -951,8 +957,9 @@
             pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
                     legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
                     null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
-                    0 /*sharedUserId*/, usesStaticLibraries,
-                    usesStaticLibrariesVersions, createMimeGroups(mimeGroupNames), domainSetId);
+                    0 /*sharedUserId*/, usesSdkLibraries, usesSdkLibrariesVersions,
+                    usesStaticLibraries, usesStaticLibrariesVersions,
+                    createMimeGroups(mimeGroupNames), domainSetId);
             pkgSetting.setLastModifiedTime(codePath.lastModified());
             pkgSetting.setSharedUser(sharedUser);
             // If this is not a system app, it starts out stopped.
@@ -1046,6 +1053,7 @@
             @NonNull File codePath, @Nullable String legacyNativeLibraryPath,
             @Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi, int pkgFlags,
             int pkgPrivateFlags, @NonNull UserManagerService userManager,
+            @Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions,
             @Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
             @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId)
                     throws PackageManagerException {
@@ -1095,7 +1103,17 @@
                 .setSecondaryCpuAbi(secondaryCpuAbi)
                 .updateMimeGroups(mimeGroupNames)
                 .setDomainSetId(domainSetId);
-        // Update static shared library dependencies if needed
+        // Update SDK library dependencies if needed.
+        if (usesSdkLibraries != null && usesSdkLibrariesVersions != null
+                && usesSdkLibraries.length == usesSdkLibrariesVersions.length) {
+            pkgSetting.setUsesSdkLibraries(usesSdkLibraries)
+                    .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions);
+        } else {
+            pkgSetting.setUsesSdkLibraries(null)
+                    .setUsesSdkLibrariesVersionsMajor(null);
+        }
+
+        // Update static shared library dependencies if needed.
         if (usesStaticLibraries != null && usesStaticLibrariesVersions != null
                 && usesStaticLibraries.length == usesStaticLibrariesVersions.length) {
             pkgSetting.setUsesStaticLibraries(usesStaticLibraries)
@@ -2167,6 +2185,21 @@
         }
     }
 
+    void readUsesSdkLibLPw(TypedXmlPullParser parser, PackageSetting outPs)
+            throws IOException, XmlPullParserException {
+        String libName = parser.getAttributeValue(null, ATTR_NAME);
+        long libVersion = parser.getAttributeLong(null, ATTR_VERSION, -1);
+
+        if (libName != null && libVersion >= 0) {
+            outPs.setUsesSdkLibraries(ArrayUtils.appendElement(String.class,
+                    outPs.getUsesSdkLibraries(), libName));
+            outPs.setUsesSdkLibrariesVersionsMajor(ArrayUtils.appendLong(
+                    outPs.getUsesSdkLibrariesVersionsMajor(), libVersion));
+        }
+
+        XmlUtils.skipCurrentTag(parser);
+    }
+
     void readUsesStaticLibLPw(TypedXmlPullParser parser, PackageSetting outPs)
             throws IOException, XmlPullParserException {
         String libName = parser.getAttributeValue(null, ATTR_NAME);
@@ -2182,6 +2215,23 @@
         XmlUtils.skipCurrentTag(parser);
     }
 
+    void writeUsesSdkLibLPw(TypedXmlSerializer serializer, String[] usesSdkLibraries,
+            long[] usesSdkLibraryVersions) throws IOException {
+        if (ArrayUtils.isEmpty(usesSdkLibraries) || ArrayUtils.isEmpty(usesSdkLibraryVersions)
+                || usesSdkLibraries.length != usesSdkLibraryVersions.length) {
+            return;
+        }
+        final int libCount = usesSdkLibraries.length;
+        for (int i = 0; i < libCount; i++) {
+            final String libName = usesSdkLibraries[i];
+            final long libVersion = usesSdkLibraryVersions[i];
+            serializer.startTag(null, TAG_USES_SDK_LIB);
+            serializer.attribute(null, ATTR_NAME, libName);
+            serializer.attributeLong(null, ATTR_VERSION, libVersion);
+            serializer.endTag(null, TAG_USES_SDK_LIB);
+        }
+    }
+
     void writeUsesStaticLibLPw(TypedXmlSerializer serializer, String[] usesStaticLibraries,
             long[] usesStaticLibraryVersions) throws IOException {
         if (ArrayUtils.isEmpty(usesStaticLibraries) || ArrayUtils.isEmpty(usesStaticLibraryVersions)
@@ -2707,6 +2757,9 @@
         }
         serializer.attributeFloat(null, "loadingProgress", pkg.getLoadingProgress());
 
+        writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(),
+                pkg.getUsesSdkLibrariesVersionsMajor());
+
         writeUsesStaticLibLPw(serializer, pkg.getUsesStaticLibraries(),
                 pkg.getUsesStaticLibrariesVersions());
 
@@ -2785,6 +2838,9 @@
 
         serializer.attribute(null, "domainSetId", pkg.getDomainSetId().toString());
 
+        writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(),
+                pkg.getUsesSdkLibrariesVersionsMajor());
+
         writeUsesStaticLibLPw(serializer, pkg.getUsesStaticLibraries(),
                 pkg.getUsesStaticLibrariesVersions());
 
@@ -3455,8 +3511,8 @@
         UUID domainSetId = DomainVerificationManagerInternal.DISABLED_ID;
         PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
                 legacyNativeLibraryPathStr, primaryCpuAbiStr, secondaryCpuAbiStr, cpuAbiOverrideStr,
-                versionCode, pkgFlags, pkgPrivateFlags, 0 /*sharedUserId*/, null, null, null,
-                domainSetId);
+                versionCode, pkgFlags, pkgPrivateFlags, 0 /*sharedUserId*/, null, null, null, null,
+                null, domainSetId);
         long timeStamp = parser.getAttributeLongHex(null, "ft", 0);
         if (timeStamp == 0) {
             timeStamp = parser.getAttributeLong(null, "ts", 0);
@@ -3484,6 +3540,8 @@
                 readInstallPermissionsLPr(parser, ps.getLegacyPermissionState(), users);
             } else if (parser.getName().equals(TAG_USES_STATIC_LIB)) {
                 readUsesStaticLibLPw(parser, ps);
+            } else if (parser.getName().equals(TAG_USES_SDK_LIB)) {
+                readUsesSdkLibLPw(parser, ps);
             } else {
                 PackageManagerService.reportSettingsProblem(Log.WARN,
                         "Unknown element under <updated-package>: " + parser.getName());
@@ -3642,8 +3700,9 @@
                 packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
                         legacyNativeLibraryPathStr, primaryCpuAbiString, secondaryCpuAbiString,
                         cpuAbiOverrideString, userId, versionCode, pkgFlags, pkgPrivateFlags,
-                        null /*usesStaticLibraries*/, null /*usesStaticLibraryVersions*/,
-                        null /*mimeGroups*/, domainSetId);
+                        null /* usesSdkLibraries */, null /* usesSdkLibraryVersions */,
+                        null /* usesStaticLibraries */, null /* usesStaticLibraryVersions */,
+                        null /* mimeGroups */, domainSetId);
                 if (PackageManagerService.DEBUG_SETTINGS)
                     Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId="
                             + userId + " pkg=" + packageSetting);
@@ -3662,9 +3721,11 @@
                             new File(codePathStr), legacyNativeLibraryPathStr,
                             primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
                             versionCode, pkgFlags, pkgPrivateFlags, sharedUserId,
-                            null /*usesStaticLibraries*/,
-                            null /*usesStaticLibraryVersions*/,
-                            null /*mimeGroups*/, domainSetId);
+                            null /* usesSdkLibraries */,
+                            null /* usesSdkLibrariesVersions */,
+                            null /* usesStaticLibraries */,
+                            null /* usesStaticLibraryVersions */,
+                            null /* mimeGroups */, domainSetId);
                     packageSetting.setLastModifiedTime(timeStamp);
                     packageSetting.setFirstInstallTime(firstInstallTime);
                     packageSetting.setLastUpdateTime(lastUpdateTime);
@@ -3793,6 +3854,8 @@
                     }
                 } else if (tagName.equals(TAG_USES_STATIC_LIB)) {
                     readUsesStaticLibLPw(parser, packageSetting);
+                } else if (tagName.equals(TAG_USES_SDK_LIB)) {
+                    readUsesSdkLibLPw(parser, packageSetting);
                 } else {
                     PackageManagerService.reportSettingsProblem(Log.WARN,
                             "Unknown element under <package>: " + parser.getName());
@@ -4581,6 +4644,13 @@
                 pw.print(" version:"); pw.println(pkg.getStaticSharedLibVersion());
             }
 
+            if (pkg.getSdkLibName() != null) {
+                pw.print(prefix); pw.println("  SDK library:");
+                pw.print(prefix); pw.print("    ");
+                pw.print("name:"); pw.print(pkg.getSdkLibName());
+                pw.print(" versionMajor:"); pw.println(pkg.getSdkLibVersionMajor());
+            }
+
             List<String> usesLibraries = pkg.getUsesLibraries();
             if (usesLibraries.size() > 0) {
                 pw.print(prefix); pw.println("  usesLibraries:");
@@ -4600,6 +4670,17 @@
                 }
             }
 
+            List<String> usesSdkLibraries = pkg.getUsesSdkLibraries();
+            long[] usesSdkLibrariesVersionsMajor = pkg.getUsesSdkLibrariesVersionsMajor();
+            if (usesSdkLibraries.size() > 0) {
+                pw.print(prefix); pw.println("  usesSdkLibraries:");
+                for (int i = 0, size = usesSdkLibraries.size(); i < size; ++i) {
+                    pw.print(prefix); pw.print("    ");
+                    pw.print(usesSdkLibraries.get(i)); pw.print(" version:");
+                    pw.println(usesSdkLibrariesVersionsMajor[i]);
+                }
+            }
+
             List<String> usesOptionalLibraries = pkg.getUsesOptionalLibraries();
             if (usesOptionalLibraries.size() > 0) {
                 pw.print(prefix); pw.println("  usesOptionalLibraries:");
diff --git a/services/core/java/com/android/server/pm/SharedLibraryHelper.java b/services/core/java/com/android/server/pm/SharedLibraryHelper.java
index 461fca1..dd8fad0 100644
--- a/services/core/java/com/android/server/pm/SharedLibraryHelper.java
+++ b/services/core/java/com/android/server/pm/SharedLibraryHelper.java
@@ -76,12 +76,15 @@
             Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingSharedLibraries) {
         // Let's used the parsed package as scanResult.pkgSetting may be null
         final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
-        if (scanResult.mStaticSharedLibraryInfo == null
+        if (scanResult.mSdkSharedLibraryInfo == null && scanResult.mStaticSharedLibraryInfo == null
                 && scanResult.mDynamicSharedLibraryInfos == null) {
             return null;
         }
 
-        // Any app can add new static shared libraries
+        // Any app can add new SDKs and static shared libraries.
+        if (scanResult.mSdkSharedLibraryInfo != null) {
+            return Collections.singletonList(scanResult.mSdkSharedLibraryInfo);
+        }
         if (scanResult.mStaticSharedLibraryInfo != null) {
             return Collections.singletonList(scanResult.mStaticSharedLibraryInfo);
         }
@@ -181,41 +184,49 @@
         ArrayList<SharedLibraryInfo> usesLibraryInfos = null;
         if (!pkg.getUsesLibraries().isEmpty()) {
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null,
-                    pkg.getPackageName(), true, pkg.getTargetSdkVersion(), null,
+                    pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null,
                     availablePackages, existingLibraries, newLibraries);
         }
         if (!pkg.getUsesStaticLibraries().isEmpty()) {
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
                     pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
-                    pkg.getPackageName(), true, pkg.getTargetSdkVersion(), usesLibraryInfos,
-                    availablePackages, existingLibraries, newLibraries);
+                    pkg.getPackageName(), "static shared", true, pkg.getTargetSdkVersion(),
+                    usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
         }
         if (!pkg.getUsesOptionalLibraries().isEmpty()) {
-            usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(),
-                    null, null, pkg.getPackageName(), false, pkg.getTargetSdkVersion(),
+            usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null,
+                    pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(),
                     usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
         }
         if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
                 pkg.getPackageName(), pkg.getTargetSdkVersion())) {
             if (!pkg.getUsesNativeLibraries().isEmpty()) {
                 usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
-                        null, pkg.getPackageName(), true, pkg.getTargetSdkVersion(),
-                        usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
+                        null, pkg.getPackageName(), "native shared", true,
+                        pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
+                        existingLibraries, newLibraries);
             }
             if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
                 usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(),
-                        null, null, pkg.getPackageName(), false, pkg.getTargetSdkVersion(),
-                        usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
+                        null, null, pkg.getPackageName(), "native shared", false,
+                        pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
+                        existingLibraries, newLibraries);
             }
         }
+        if (!pkg.getUsesSdkLibraries().isEmpty()) {
+            usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(),
+                    pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
+                    pkg.getPackageName(), "sdk", true, pkg.getTargetSdkVersion(), usesLibraryInfos,
+                    availablePackages, existingLibraries, newLibraries);
+        }
         return usesLibraryInfos;
     }
 
     public static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
             @NonNull List<String> requestedLibraries,
             @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
-            @NonNull String packageName, boolean required, int targetSdk,
-            @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
+            @NonNull String packageName, @NonNull String libraryType, boolean required,
+            int targetSdk, @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
             @NonNull final Map<String, AndroidPackage> availablePackages,
             @NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
             @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
@@ -230,18 +241,17 @@
             if (libraryInfo == null) {
                 if (required) {
                     throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
-                            "Package " + packageName + " requires unavailable shared library "
-                                    + libName + "; failing!");
+                            "Package " + packageName + " requires unavailable " + libraryType
+                                    + " library " + libName + "; failing!");
                 } else if (DEBUG_SHARED_LIBRARIES) {
-                    Slog.i(TAG, "Package " + packageName
-                            + " desires unavailable shared library "
-                            + libName + "; ignoring!");
+                    Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType
+                            + " library " + libName + "; ignoring!");
                 }
             } else {
                 if (requiredVersions != null && requiredCertDigests != null) {
                     if (libraryInfo.getLongVersion() != requiredVersions[i]) {
                         throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
-                                "Package " + packageName + " requires unavailable static shared"
+                                "Package " + packageName + " requires unavailable " + libraryType
                                         + " library " + libName + " version "
                                         + libraryInfo.getLongVersion() + "; failing!");
                     }
@@ -249,7 +259,7 @@
                     SigningDetails libPkg = pkg == null ? null : pkg.getSigningDetails();
                     if (libPkg == null) {
                         throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
-                                "Package " + packageName + " requires unavailable static shared"
+                                "Package " + packageName + " requires unavailable " + libraryType
                                         + " library; failing!");
                     }
                     final String[] expectedCertDigests = requiredCertDigests[i];
@@ -267,8 +277,8 @@
                         // Therefore, the size check is safe to make.
                         if (expectedCertDigests.length != libCertDigests.length) {
                             throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
-                                    "Package " + packageName + " requires differently signed"
-                                            + " static shared library; failing!");
+                                    "Package " + packageName + " requires differently signed "
+                                            + libraryType + " library; failing!");
                         }
 
                         // Use a predictable order as signature order may vary
@@ -280,8 +290,8 @@
                             if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) {
                                 throw new PackageManagerException(
                                         INSTALL_FAILED_MISSING_SHARED_LIBRARY,
-                                        "Package " + packageName + " requires differently signed"
-                                                + " static shared library; failing!");
+                                        "Package " + packageName + " requires differently signed "
+                                                + libraryType + " library; failing!");
                             }
                         }
                     } else {
@@ -290,10 +300,9 @@
                         byte[] digestBytes = HexEncoding.decode(
                                 expectedCertDigests[0], false /* allowSingleChar */);
                         if (!libPkg.hasSha256Certificate(digestBytes)) {
-                            throw new PackageManagerException(
-                                    INSTALL_FAILED_MISSING_SHARED_LIBRARY,
-                                    "Package " + packageName + " requires differently signed"
-                                            + " static shared library; failing!");
+                            throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+                                    "Package " + packageName + " requires differently signed "
+                                            + libraryType + " library; failing!");
                         }
                     }
                 }
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index 32b1e5d..8b2c3a12 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -87,6 +87,17 @@
         return paths;
     }
 
+    public static SharedLibraryInfo createSharedLibraryForSdk(AndroidPackage pkg) {
+        return new SharedLibraryInfo(null, pkg.getPackageName(),
+                AndroidPackageUtils.getAllCodePaths(pkg),
+                pkg.getSdkLibName(),
+                pkg.getSdkLibVersionMajor(),
+                SharedLibraryInfo.TYPE_SDK,
+                new VersionedPackage(pkg.getManifestPackageName(),
+                        pkg.getLongVersionCode()),
+                null, null, false /* isNative */);
+    }
+
     public static SharedLibraryInfo createSharedLibraryForStatic(AndroidPackage pkg) {
         return new SharedLibraryInfo(null, pkg.getPackageName(),
                 AndroidPackageUtils.getAllCodePaths(pkg),
@@ -218,7 +229,8 @@
 
     public static boolean isLibrary(AndroidPackage pkg) {
         // TODO(b/135203078): Can parsing just enforce these always match?
-        return pkg.getStaticSharedLibName() != null || !pkg.getLibraryNames().isEmpty();
+        return pkg.getSdkLibName() != null || pkg.getStaticSharedLibName() != null
+                || !pkg.getLibraryNames().isEmpty();
     }
 
     public static int getHiddenApiEnforcementPolicy(AndroidPackage pkg,
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 82edce6..34575e08 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -27,7 +27,6 @@
 import android.content.pm.SigningInfo;
 import android.util.SparseArray;
 
-import com.android.internal.R;
 import com.android.server.pm.PackageSetting;
 import com.android.server.pm.Settings;
 
@@ -206,6 +205,18 @@
     List<SharedLibraryInfo> getUsesLibraryInfos();
 
     /**
+     * @see R.styleable#AndroidManifestUsesSdkLibrary
+     */
+    @NonNull
+    String[] getUsesSdkLibraries();
+
+    /**
+     * @see R.styleable#AndroidManifestUsesSdkLibrary_versionMajor
+     */
+    @NonNull
+    long[] getUsesSdkLibrariesVersionsMajor();
+
+    /**
      * @see R.styleable#AndroidManifestUsesStaticLibrary
      */
     @NonNull
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index 46d32b9..f5e498d 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -130,6 +130,10 @@
     @Nullable
     private final Integer mSharedUserId;
     @NonNull
+    private final String[] mUsesSdkLibraries;
+    @NonNull
+    private final long[] mUsesSdkLibrariesVersionsMajor;
+    @NonNull
     private final String[] mUsesStaticLibraries;
     @NonNull
     private final long[] mUsesStaticLibrariesVersions;
@@ -171,6 +175,8 @@
         mPrimaryCpuAbi = pkgState.getPrimaryCpuAbi();
         mSecondaryCpuAbi = pkgState.getSecondaryCpuAbi();
         mSharedUserId = pkgState.getSharedUserId();
+        mUsesSdkLibraries = pkgState.getUsesSdkLibraries();
+        mUsesSdkLibrariesVersionsMajor = pkgState.getUsesSdkLibrariesVersionsMajor();
         mUsesStaticLibraries = pkgState.getUsesStaticLibraries();
         mUsesStaticLibrariesVersions = pkgState.getUsesStaticLibrariesVersions();
         mUsesLibraryInfos = pkgState.getUsesLibraryInfos();
@@ -262,6 +268,11 @@
         return getBoolean(Booleans.VENDOR);
     }
 
+    @Override
+    public long getVersionCode() {
+        return mLongVersionCode;
+    }
+
     /**
      * @hide
      */
@@ -500,10 +511,10 @@
         }
 
         @DataClass.Generated(
-                time = 1633375703010L,
+                time = 1637977288540L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
-                inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\npublic static  android.content.pm.pkg.PackageUserState copy(android.content.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [android.content.pm.pkg.PackageUserState]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+                inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\npublic static  com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
         @Deprecated
         private void __metadata() {}
 
@@ -579,7 +590,7 @@
     }
 
     @DataClass.Generated.Member
-    public long getVersionCode() {
+    public long getLongVersionCode() {
         return mLongVersionCode;
     }
 
@@ -609,6 +620,16 @@
     }
 
     @DataClass.Generated.Member
+    public @NonNull String[] getUsesSdkLibraries() {
+        return mUsesSdkLibraries;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull long[] getUsesSdkLibrariesVersionsMajor() {
+        return mUsesSdkLibrariesVersionsMajor;
+    }
+
+    @DataClass.Generated.Member
     public @NonNull String[] getUsesStaticLibraries() {
         return mUsesStaticLibraries;
     }
@@ -650,10 +671,10 @@
     }
 
     @DataClass.Generated(
-            time = 1633375703038L,
+            time = 1637977288579L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
-            inputSignatures = "private  int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackageApi mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final  int mAppId\nprivate final  int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final  long mFirstInstallTime\nprivate final  long mLastModifiedTime\nprivate final  long mLastUpdateTime\nprivate final  long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.Integer mSharedUserId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mUsesLibraryInfos\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<android.content.pm.pkg.PackageUserState> mUserStates\npublic static  com.android.server.pm.pkg.PackageState copy(com.android.server.pm.PackageSetting)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isVendor()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final  int SYSTEM\nprivate static final  int EXTERNAL_STORAGE\nprivate static final  int PRIVILEGED\nprivate static final  int OEM\nprivate static final  int VENDOR\nprivate static final  int PRODUCT\nprivate static final  int SYSTEM_EXT\nprivate static final  int REQUIRED_FOR_SYSTEM_USER\nprivate static final  int ODM\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int HIDDEN_UNTIL_INSTALLED\nprivate static final  int INSTALL_PERMISSIONS_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int UPDATED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+            inputSignatures = "private  int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackageApi mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final  int mAppId\nprivate final  int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final  long mFirstInstallTime\nprivate final  long mLastModifiedTime\nprivate final  long mLastUpdateTime\nprivate final  long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.Integer mSharedUserId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mUsesLibraryInfos\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static  com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final  int SYSTEM\nprivate static final  int EXTERNAL_STORAGE\nprivate static final  int PRIVILEGED\nprivate static final  int OEM\nprivate static final  int VENDOR\nprivate static final  int PRODUCT\nprivate static final  int SYSTEM_EXT\nprivate static final  int REQUIRED_FOR_SYSTEM_USER\nprivate static final  int ODM\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int HIDDEN_UNTIL_INSTALLED\nprivate static final  int INSTALL_PERMISSIONS_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int UPDATED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
     @Deprecated
     private void __metadata() {}
 
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/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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9d8f1fc..2e318bc 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -808,7 +808,7 @@
     };
 
     private final Consumer<WindowState> mPerformLayout = w -> {
-        if (w.mLayoutAttached || w.skipLayout()) {
+        if (w.mLayoutAttached) {
             return;
         }
 
@@ -866,7 +866,7 @@
     };
 
     private final Consumer<WindowState> mPerformLayoutAttached = w -> {
-        if (!w.mLayoutAttached || w.skipLayout()) {
+        if (!w.mLayoutAttached) {
             return;
         }
         if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame
@@ -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));
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index cbb9d5d..7a2a311 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1494,6 +1494,9 @@
      * @param displayFrames The display frames.
      */
     public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
+        if (win.skipLayout()) {
+            return;
+        }
 
         // This window might be in the simulated environment.
         // We invoke this to get the proper DisplayFrames.
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 427bbeb..3200473 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -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/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/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/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/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 9d86081..4b12fd4 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -274,7 +274,7 @@
     private fun makePkgSetting(pkgName: String) = spy(
         PackageSetting(
             pkgName, null, File("/test"),
-            null, null, null, null, 0, 0, 0, 0, null, null, null,
+            null, null, null, null, 0, 0, 0, 0, null, null, null, null, null,
             UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c")
         )
     ) {
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index dc93e53..4a35fdf 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -106,6 +106,16 @@
         "setSplitCodePaths",
         "setSplitClassLoaderName",
         "setSplitHasCode",
+        // Tested through addUsesSdkLibrary
+        "addUsesSdkLibrary",
+        "getUsesSdkLibraries",
+        "getUsesSdkLibrariesVersionsMajor",
+        "getUsesSdkLibrariesCertDigests",
+        // Tested through addUsesStaticLibrary
+        "addUsesStaticLibrary",
+        "getUsesStaticLibraries",
+        "getUsesStaticLibrariesVersions",
+        "getUsesStaticLibrariesCertDigests"
     )
 
     override val baseParams = listOf(
@@ -157,6 +167,8 @@
         AndroidPackage::getSecondaryNativeLibraryDir,
         AndroidPackage::getSharedUserId,
         AndroidPackage::getSharedUserLabel,
+        AndroidPackage::getSdkLibName,
+        AndroidPackage::getSdkLibVersionMajor,
         AndroidPackage::getStaticSharedLibName,
         AndroidPackage::getStaticSharedLibVersion,
         AndroidPackage::getTargetSandboxVersion,
@@ -210,6 +222,7 @@
         AndroidPackage::isResizeableActivityViaSdkVersion,
         AndroidPackage::isRestoreAnyVersion,
         AndroidPackage::isSignedWithPlatformKey,
+        AndroidPackage::isSdkLibrary,
         AndroidPackage::isStaticSharedLibrary,
         AndroidPackage::isStub,
         AndroidPackage::isSupportsRtl,
@@ -228,7 +241,7 @@
         AndroidPackage::getMinAspectRatio,
         AndroidPackage::hasPreserveLegacyExternalStorage,
         AndroidPackage::hasRequestForegroundServiceExemption,
-        AndroidPackage::hasRequestRawExternalStorageAccess,
+        AndroidPackage::hasRequestRawExternalStorageAccess
     )
 
     override fun extraParams() = listOf(
@@ -254,13 +267,6 @@
         adder(AndroidPackage::getUsesNativeLibraries, "testUsesNativeLibrary"),
         adder(AndroidPackage::getUsesOptionalLibraries, "testUsesOptionalLibrary"),
         adder(AndroidPackage::getUsesOptionalNativeLibraries, "testUsesOptionalNativeLibrary"),
-        adder(AndroidPackage::getUsesStaticLibraries, "testUsesStaticLibrary"),
-        getSetByValue(
-            AndroidPackage::getUsesStaticLibrariesVersions,
-            PackageImpl::addUsesStaticLibraryVersion,
-            (testCounter++).toLong(),
-            transformGet = { it?.singleOrNull() }
-        ),
         getSetByValue(
             AndroidPackage::areAttributionsUserVisible,
             ParsingPackage::setAttributionsAreUserVisible,
@@ -290,7 +296,7 @@
             AndroidPackage::getKeySetMapping,
             PackageImpl::addKeySet,
             "testKeySetName" to testKey(),
-            transformGet = { "testKeySetName" to it["testKeySetName"]?.singleOrNull() },
+            transformGet = { "testKeySetName" to it["testKeySetName"]?.singleOrNull() }
         ),
         getSetByValue(
             AndroidPackage::getPermissionGroups,
@@ -315,7 +321,7 @@
                     { it.first },
                     { it.second.intentFilter.schemesIterator().asSequence().singleOrNull() },
                     { it.second.intentFilter.authoritiesIterator().asSequence()
-                        .singleOrNull()?.host },
+                        .singleOrNull()?.host }
                 )
             }
         ),
@@ -324,7 +330,7 @@
             PackageImpl::addQueriesIntent,
             Intent(Intent.ACTION_VIEW, Uri.parse("https://test.pm.server.android.com")),
             transformGet = { it.singleOrNull() },
-            compare = { first, second -> first?.filterEquals(second) },
+            compare = { first, second -> first?.filterEquals(second) }
         ),
         getSetByValue(
             AndroidPackage::getRestrictUpdateHash,
@@ -347,13 +353,6 @@
             }
         ),
         getSetByValue(
-            AndroidPackage::getUsesStaticLibrariesCertDigests,
-            PackageImpl::addUsesStaticLibraryCertDigests,
-            arrayOf("testCertDigest"),
-            transformGet = { it?.singleOrNull() },
-            compare = Array<String?>?::contentEquals
-        ),
-        getSetByValue(
             AndroidPackage::getActivities,
             PackageImpl::addActivity,
             "TestActivityName",
@@ -440,7 +439,7 @@
                     first, second,
                     { it.size() },
                     { it.keyAt(0) },
-                    { it.valueAt(0) },
+                    { it.valueAt(0) }
                 )
             }
         ),
@@ -451,7 +450,7 @@
             compare = { first, second ->
                 equalBy(
                     first, second,
-                    { it["testProcess"]?.name },
+                    { it["testProcess"]?.name }
                 )
             }
         ),
@@ -471,10 +470,10 @@
                     PackageManager.Property::getName,
                     PackageManager.Property::getClassName,
                     PackageManager.Property::getPackageName,
-                    PackageManager.Property::getString,
+                    PackageManager.Property::getString
                 )
             }
-        ),
+        )
     )
 
     override fun initialObject() = PackageImpl.forParsing(
@@ -518,6 +517,9 @@
         .setSplitClassLoaderName(0, "testSplitClassLoaderNameZero")
         .setSplitClassLoaderName(1, "testSplitClassLoaderNameOne")
 
+        .addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1"))
+        .addUsesStaticLibrary("testStatic", 3L, arrayOf("testCertDigest2"))
+
     override fun extraAssertions(before: Parcelable, after: Parcelable) {
         super.extraAssertions(before, after)
         after as PackageImpl
@@ -558,6 +560,18 @@
             expect.that(it.get(0)).asList().containsExactly(-1)
             expect.that(it.get(1)).asList().containsExactly(0)
         }
+
+        expect.that(after.usesSdkLibraries).containsExactly("testSdk")
+        expect.that(after.usesSdkLibrariesVersionsMajor).asList().containsExactly(2L)
+        expect.that(after.usesSdkLibrariesCertDigests!!.size).isEqualTo(1)
+        expect.that(after.usesSdkLibrariesCertDigests!![0]).asList()
+                .containsExactly("testCertDigest1")
+
+        expect.that(after.usesStaticLibraries).containsExactly("testStatic")
+        expect.that(after.usesStaticLibrariesVersions).asList().containsExactly(3L)
+        expect.that(after.usesStaticLibrariesCertDigests!!.size).isEqualTo(1)
+        expect.that(after.usesStaticLibrariesCertDigests!![0]).asList()
+                .containsExactly("testCertDigest2")
     }
 
     private fun testKey() = KeyPairGenerator.getInstance("RSA")
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/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 0e5640a..ae5984a41 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -150,7 +150,7 @@
         }
         whenever(mocks.settings.addPackageLPw(nullable(), nullable(), nullable(), nullable(),
                 nullable(), nullable(), nullable(), nullable(), nullable(), nullable(), nullable(),
-                nullable(), nullable(), nullable(), nullable())) {
+                nullable(), nullable(), nullable(), nullable(), nullable(), nullable())) {
             val name: String = getArgument(0)
             val pendingAdd = mPendingPackageAdds.firstOrNull { it.first == name }
                     ?: return@whenever null
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/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index a9a3469..6c9f8fe 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -498,6 +498,71 @@
     }
 
     @Test
+    public void testWriteReadUsesSdkLibraries() {
+        final Settings settingsUnderTest = makeSettings();
+        final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
+        ps1.setAppId(Process.FIRST_APPLICATION_UID);
+        ps1.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed())
+                .setUid(ps1.getAppId())
+                .setSystem(true)
+                .hideAsFinal());
+        final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
+        ps2.setAppId(Process.FIRST_APPLICATION_UID + 1);
+        ps2.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_2).hideAsParsed())
+                .setUid(ps2.getAppId())
+                .hideAsFinal());
+
+        ps1.setUsesSdkLibraries(new String[] { "com.example.sdk.one" });
+        ps1.setUsesSdkLibrariesVersionsMajor(new long[] { 12 });
+        ps1.setFlags(ps1.getFlags() | ApplicationInfo.FLAG_SYSTEM);
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
+        assertThat(settingsUnderTest.disableSystemPackageLPw(PACKAGE_NAME_1, false), is(true));
+
+        ps2.setUsesSdkLibraries(new String[] { "com.example.sdk.two" });
+        ps2.setUsesSdkLibrariesVersionsMajor(new long[] { 34 });
+        settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
+
+        settingsUnderTest.writeLPr();
+
+        settingsUnderTest.mPackages.clear();
+        settingsUnderTest.mDisabledSysPackages.clear();
+
+        assertThat(settingsUnderTest.readLPw(createFakeUsers()), is(true));
+
+        PackageSetting readPs1 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_1);
+        PackageSetting readPs2 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_2);
+
+        Truth.assertThat(readPs1).isNotNull();
+        Truth.assertThat(readPs1.getUsesSdkLibraries()).isNotNull();
+        Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).isNotNull();
+        Truth.assertThat(readPs2).isNotNull();
+        Truth.assertThat(readPs2.getUsesSdkLibraries()).isNotNull();
+        Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).isNotNull();
+
+        List<Long> ps1VersionsAsList = new ArrayList<>();
+        for (long version : ps1.getUsesSdkLibrariesVersionsMajor()) {
+            ps1VersionsAsList.add(version);
+        }
+
+        List<Long> ps2VersionsAsList = new ArrayList<>();
+        for (long version : ps2.getUsesSdkLibrariesVersionsMajor()) {
+            ps2VersionsAsList.add(version);
+        }
+
+        Truth.assertThat(readPs1.getUsesSdkLibraries()).asList()
+                .containsExactlyElementsIn(ps1.getUsesSdkLibraries()).inOrder();
+
+        Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).asList()
+                .containsExactlyElementsIn(ps1VersionsAsList).inOrder();
+
+        Truth.assertThat(readPs2.getUsesSdkLibraries()).asList()
+                .containsExactlyElementsIn(ps2.getUsesSdkLibraries()).inOrder();
+
+        Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).asList()
+                .containsExactlyElementsIn(ps2VersionsAsList).inOrder();
+    }
+
+    @Test
     public void testPackageRestrictionsDistractionFlagsDefault() {
         final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1);
         assertThat(defaultSetting.getDistractionFlags(0), is(PackageManager.RESTRICTION_NONE));
@@ -571,6 +636,8 @@
                 ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
                 ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
                 0,
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -593,6 +660,8 @@
                 ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
                 ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
                 0,
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -609,6 +678,8 @@
                 0 /*pkgFlags*/,
                 0 /*pkgPrivateFlags*/,
                 0,
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -637,6 +708,8 @@
                 0 /*pkgFlags*/,
                 0 /*pkgPrivateFlags*/,
                 UserManagerService.getInstance(),
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -671,6 +744,8 @@
                 ApplicationInfo.FLAG_SYSTEM /*pkgFlags*/,
                 ApplicationInfo.PRIVATE_FLAG_PRIVILEGED /*pkgPrivateFlags*/,
                 UserManagerService.getInstance(),
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -708,6 +783,8 @@
                     0 /*pkgFlags*/,
                     0 /*pkgPrivateFlags*/,
                     UserManagerService.getInstance(),
+                    null /*usesSdkLibraries*/,
+                    null /*usesSdkLibrariesVersions*/,
                     null /*usesStaticLibraries*/,
                     null /*usesStaticLibrariesVersions*/,
                     null /*mimeGroups*/,
@@ -741,6 +818,8 @@
                 false /*instantApp*/,
                 false /*virtualPreload*/,
                 UserManagerService.getInstance(),
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -780,6 +859,8 @@
                 false /*instantApp*/,
                 false /*virtualPreload*/,
                 UserManagerService.getInstance(),
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -822,6 +903,8 @@
                 false /*instantApp*/,
                 false /*virtualPreload*/,
                 UserManagerService.getInstance(),
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -864,6 +947,8 @@
                 false /*instantApp*/,
                 false /*virtualPreload*/,
                 UserManagerService.getInstance(),
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -971,6 +1056,25 @@
         assertThat(countDownLatch.getCount(), is(1L));
     }
 
+    @Test
+    public void testSetPkgStateLibraryFiles_addNewSdks() {
+        final PackageSetting packageSetting = createPackageSetting("com.foo");
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        packageSetting.registerObserver(new Watcher() {
+            @Override
+            public void onChange(Watchable what) {
+                countDownLatch.countDown();
+            }
+        });
+
+        final List<String> files = new ArrayList<>();
+        files.add("com.sdk1_123");
+        files.add("com.sdk9_876");
+        packageSetting.setUsesSdkLibraries(files.toArray(new String[files.size()]));
+
+        assertThat(countDownLatch.getCount(), is(0L));
+    }
+
     private <T> void assertArrayEquals(T[] a, T[] b) {
         assertTrue("Expected: " + Arrays.toString(a) + ", actual: " + Arrays.toString(b),
                 Arrays.equals(a, b));
@@ -1090,6 +1194,8 @@
                 pkgFlags,
                 0 /*privateFlags*/,
                 sharedUserId,
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
@@ -1109,6 +1215,8 @@
                 0,
                 0 /*privateFlags*/,
                 0,
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
                 null /*mimeGroups*/,
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index fb092d2..11bac45 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -932,11 +932,12 @@
                 .addUsesPermission(new ParsedUsesPermissionImpl("foo7", 0))
                 .addImplicitPermission("foo25")
                 .addProtectedBroadcast("foo8")
+                .setSdkLibName("sdk12")
+                .setSdkLibVersionMajor(42)
+                .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"})
                 .setStaticSharedLibName("foo23")
                 .setStaticSharedLibVersion(100)
-                .addUsesStaticLibrary("foo23")
-                .addUsesStaticLibraryCertDigests(new String[]{"digest"})
-                .addUsesStaticLibraryVersion(100)
+                .addUsesStaticLibrary("foo23", 100, new String[]{"digest"})
                 .addLibraryName("foo10")
                 .addUsesLibrary("foo11")
                 .addUsesOptionalLibrary("foo12")
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index 2146070..94d8358 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -44,8 +44,6 @@
     private SparseArray<PackageUserStateImpl> mUserStates = new SparseArray<>();
     private AndroidPackage mPkg;
     private InstallSource mInstallSource;
-    private String[] mUsesStaticLibraries;
-    private long[] mUsesStaticLibrariesVersions;
     private Map<String, Set<String>> mMimeGroups;
     private SigningDetails mSigningDetails;
     private UUID mDomainSetId = UUID.randomUUID();
@@ -116,17 +114,6 @@
         return this;
     }
 
-    public PackageSettingBuilder setUsesStaticLibraries(String[] usesStaticLibraries) {
-        this.mUsesStaticLibraries = usesStaticLibraries;
-        return this;
-    }
-
-    public PackageSettingBuilder setUsesStaticLibrariesVersions(
-            long[] usesStaticLibrariesVersions) {
-        this.mUsesStaticLibrariesVersions = usesStaticLibrariesVersions;
-        return this;
-    }
-
     public PackageSettingBuilder setMimeGroups(Map<String, Set<String>> mimeGroups) {
         this.mMimeGroups = mimeGroups;
         return this;
@@ -173,8 +160,9 @@
         final PackageSetting packageSetting = new PackageSetting(mName, mRealName,
                 new File(mCodePath), mLegacyNativeLibraryPathString, mPrimaryCpuAbiString,
                 mSecondaryCpuAbiString, mCpuAbiOverrideString, mPVersionCode, mPkgFlags,
-                mPrivateFlags, mSharedUserId, mUsesStaticLibraries, mUsesStaticLibrariesVersions,
-                mMimeGroups, mDomainSetId);
+                mPrivateFlags, mSharedUserId, null /* usesSdkLibraries */,
+                null /* usesSdkLibrariesVersions */, null /* usesStaticLibraries */,
+                null  /* usesStaticLibrariesVersions */, mMimeGroups, mDomainSetId);
         packageSetting.setSignatures(mSigningDetails != null
                 ? new PackageSignatures(mSigningDetails)
                 : new PackageSignatures());
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index cfdbb5b7..71d5b77 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -17,6 +17,7 @@
 package com.android.server.pm;
 
 import static android.content.pm.SharedLibraryInfo.TYPE_DYNAMIC;
+import static android.content.pm.SharedLibraryInfo.TYPE_SDK;
 import static android.content.pm.SharedLibraryInfo.TYPE_STATIC;
 import static android.content.pm.SharedLibraryInfo.VERSION_UNDEFINED;
 
@@ -238,6 +239,37 @@
     }
 
     @Test
+    public void installSdkLibrary() throws Exception {
+        final ParsedPackage pkg = ((ParsedPackage) createBasicPackage("ogl.sdk_123")
+                .setSdkLibName("ogl.sdk")
+                .setSdkLibVersionMajor(123)
+                .hideAsParsed())
+                .setPackageName("ogl.sdk_123")
+                .setVersionCodeMajor(5)
+                .setVersionCode(678)
+                .setBaseApkPath("/some/path.apk")
+                .setSplitCodePaths(new String[] {"/some/other/path.apk"});
+
+        final ScanRequest scanRequest = new ScanRequestBuilder(pkg)
+                .setUser(UserHandle.of(0)).build();
+
+        final ScanResult scanResult = executeScan(scanRequest);
+
+        assertThat(scanResult.mSdkSharedLibraryInfo.getPackageName(), is("ogl.sdk_123"));
+        assertThat(scanResult.mSdkSharedLibraryInfo.getName(), is("ogl.sdk"));
+        assertThat(scanResult.mSdkSharedLibraryInfo.getLongVersion(), is(123L));
+        assertThat(scanResult.mSdkSharedLibraryInfo.getType(), is(TYPE_SDK));
+        assertThat(scanResult.mSdkSharedLibraryInfo.getDeclaringPackage().getPackageName(),
+                is("ogl.sdk_123"));
+        assertThat(scanResult.mSdkSharedLibraryInfo.getDeclaringPackage().getLongVersionCode(),
+                is(pkg.getLongVersionCode()));
+        assertThat(scanResult.mSdkSharedLibraryInfo.getAllCodePaths(),
+                hasItems("/some/path.apk", "/some/other/path.apk"));
+        assertThat(scanResult.mSdkSharedLibraryInfo.getDependencies(), nullValue());
+        assertThat(scanResult.mSdkSharedLibraryInfo.getDependentPackages(), empty());
+    }
+
+    @Test
     public void installStaticSharedLibrary() throws Exception {
         final ParsedPackage pkg = ((ParsedPackage) createBasicPackage("static.lib.pkg")
                 .setStaticSharedLibName("static.lib")
@@ -528,10 +560,10 @@
                 "/data/tmp/randompath/base.apk", createCodePath(packageName),
                 mock(TypedArray.class), false)
                 .setVolumeUuid(UUID_ONE.toString())
-                .addUsesStaticLibrary("some.static.library")
-                .addUsesStaticLibraryVersion(234L)
-                .addUsesStaticLibrary("some.other.static.library")
-                .addUsesStaticLibraryVersion(456L)
+                .addUsesStaticLibrary("some.static.library", 234L, new String[]{"testCert1"})
+                .addUsesStaticLibrary("some.other.static.library", 456L, new String[]{"testCert2"})
+                .addUsesSdkLibrary("some.sdk.library", 123L, new String[]{"testCert3"})
+                .addUsesSdkLibrary("some.other.sdk.library", 789L, new String[]{"testCert4"})
                 .hideAsParsed())
                 .setNativeLibraryRootDir("/data/tmp/randompath/base.apk:/lib")
                 .setVersionCodeMajor(1)
@@ -557,6 +589,9 @@
         assertThat(pkgSetting.getUsesStaticLibraries(),
                 arrayContaining("some.static.library", "some.other.static.library"));
         assertThat(pkgSetting.getUsesStaticLibrariesVersions(), is(new long[]{234L, 456L}));
+        assertThat(pkgSetting.getUsesSdkLibraries(),
+                arrayContaining("some.sdk.library", "some.other.sdk.library"));
+        assertThat(pkgSetting.getUsesSdkLibrariesVersionsMajor(), is(new long[]{123L, 789L}));
         assertThat(pkgSetting.getPkg(), is(scanResult.mRequest.mParsedPackage));
         assertThat(pkgSetting.getPath(), is(new File(createCodePath(packageName))));
         assertThat(pkgSetting.getVersionCode(),
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/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..a7a374be 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/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/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/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()));
+    }
+}
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index 40bbb36..9828b97 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -1461,6 +1461,64 @@
   }
 };
 
+/** Represents <sdk-library> elements. **/
+class SdkLibrary : public ManifestExtractor::Element {
+ public:
+  SdkLibrary() = default;
+  std::string name;
+  int versionMajor;
+
+  void Extract(xml::Element* element) override {
+    auto parent_stack = extractor()->parent_stack();
+    if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) {
+      name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
+      versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0);
+    }
+  }
+
+  void Print(text::Printer* printer) override {
+    printer->Print(
+        StringPrintf("sdk-library: name='%s' versionMajor='%d'\n", name.data(), versionMajor));
+  }
+};
+
+/** Represents <uses-sdk-library> elements. **/
+class UsesSdkLibrary : public ManifestExtractor::Element {
+ public:
+  UsesSdkLibrary() = default;
+  std::string name;
+  int versionMajor;
+  std::vector<std::string> certDigests;
+
+  void Extract(xml::Element* element) override {
+    auto parent_stack = extractor()->parent_stack();
+    if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) {
+      name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
+      versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0);
+      AddCertDigest(element);
+    }
+  }
+
+  void AddCertDigest(xml::Element* element) {
+    std::string digest = GetAttributeStringDefault(FindAttribute(element, CERT_DIGEST_ATTR), "");
+    // We allow ":" delimiters in the SHA declaration as this is the format
+    // emitted by the certtool making it easy for developers to copy/paste.
+    digest.erase(std::remove(digest.begin(), digest.end(), ':'), digest.end());
+    if (!digest.empty()) {
+      certDigests.push_back(digest);
+    }
+  }
+
+  void Print(text::Printer* printer) override {
+    printer->Print(
+        StringPrintf("uses-sdk-library: name='%s' versionMajor='%d'", name.data(), versionMajor));
+    for (size_t i = 0; i < certDigests.size(); i++) {
+      printer->Print(StringPrintf(" certDigest='%s'", certDigests[i].data()));
+    }
+    printer->Print("\n");
+  }
+};
+
 /** Represents <uses-native-library> elements. **/
 class UsesNativeLibrary : public ManifestExtractor::Element {
  public:
@@ -2367,6 +2425,7 @@
       {"required-not-feature", std::is_base_of<RequiredNotFeature, T>::value},
       {"screen", std::is_base_of<Screen, T>::value},
       {"service", std::is_base_of<Service, T>::value},
+      {"sdk-library", std::is_base_of<SdkLibrary, T>::value},
       {"static-library", std::is_base_of<StaticLibrary, T>::value},
       {"supports-gl-texture", std::is_base_of<SupportsGlTexture, T>::value},
       {"supports-input", std::is_base_of<SupportsInput, T>::value},
@@ -2379,6 +2438,7 @@
       {"uses-permission", std::is_base_of<UsesPermission, T>::value},
       {"uses-permission-sdk-23", std::is_base_of<UsesPermissionSdk23, T>::value},
       {"uses-sdk", std::is_base_of<UsesSdkBadging, T>::value},
+      {"uses-sdk-library", std::is_base_of<UsesSdkLibrary, T>::value},
       {"uses-static-library", std::is_base_of<UsesStaticLibrary, T>::value},
   };
 
@@ -2421,6 +2481,7 @@
           {"required-not-feature", &CreateType<RequiredNotFeature>},
           {"screen", &CreateType<Screen>},
           {"service", &CreateType<Service>},
+          {"sdk-library", &CreateType<SdkLibrary>},
           {"static-library", &CreateType<StaticLibrary>},
           {"supports-gl-texture", &CreateType<SupportsGlTexture>},
           {"supports-input", &CreateType<SupportsInput>},
@@ -2433,6 +2494,7 @@
           {"uses-permission", &CreateType<UsesPermission>},
           {"uses-permission-sdk-23", &CreateType<UsesPermissionSdk23>},
           {"uses-sdk", &CreateType<UsesSdkBadging>},
+          {"uses-sdk-library", &CreateType<UsesSdkLibrary>},
           {"uses-static-library", &CreateType<UsesStaticLibrary>},
       };
 
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 63b2fcd..b46a125 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -508,6 +508,16 @@
   uses_static_library_action.Action(RequiredAndroidAttribute("certDigest"));
   uses_static_library_action["additional-certificate"];
 
+  xml::XmlNodeAction& sdk_library_action = application_action["sdk-library"];
+  sdk_library_action.Action(RequiredNameIsJavaPackage);
+  sdk_library_action.Action(RequiredAndroidAttribute("versionMajor"));
+
+  xml::XmlNodeAction& uses_sdk_library_action = application_action["uses-sdk-library"];
+  uses_sdk_library_action.Action(RequiredNameIsJavaPackage);
+  uses_sdk_library_action.Action(RequiredAndroidAttribute("versionMajor"));
+  uses_sdk_library_action.Action(RequiredAndroidAttribute("certDigest"));
+  uses_sdk_library_action["additional-certificate"];
+
   xml::XmlNodeAction& uses_package_action = application_action["uses-package"];
   uses_package_action.Action(RequiredNameIsJavaPackage);
   uses_package_action["additional-certificate"];